From 40e90f7b393e34efee3b331743c4a43906bec526 Mon Sep 17 00:00:00 2001 From: flywind Date: Fri, 14 Jan 2022 22:06:31 +0800 Subject: [PATCH 1/3] Added 'std/oserrors' for OS error reporting --- changelog.md | 2 + lib/std/oserrors.nim | 129 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 lib/std/oserrors.nim diff --git a/changelog.md b/changelog.md index 1b97fd99a4f59..6f8e08f3f5d59 100644 --- a/changelog.md +++ b/changelog.md @@ -71,6 +71,8 @@ for the right-hand side of type definitions in type sections. Previously they would error with "invalid indentation". +- Added `std/oserrors` for OS error reporting. + ## Compiler changes - `nim` can now compile version 1.4.0 as follows: `nim c --lib:lib --stylecheck:off compiler/nim`, diff --git a/lib/std/oserrors.nim b/lib/std/oserrors.nim new file mode 100644 index 0000000000000..7a6e69a8526e0 --- /dev/null +++ b/lib/std/oserrors.nim @@ -0,0 +1,129 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2022 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + + +## The `std/oserrors` module implements OS error reporting. + +type + OSErrorCode* = distinct int32 ## Specifies an OS Error Code. + +when not defined(nimscript): + when defined(windows): + import winlean + else: + var errno {.importc, header: "".}: cint + + proc c_strerror(errnum: cint): cstring {. + importc: "strerror", header: "".} + +proc `==`*(err1, err2: OSErrorCode): bool {.borrow.} +proc `$`*(err: OSErrorCode): string {.borrow.} + +proc osErrorMsg*(errorCode: OSErrorCode): string = + ## Converts an OS error code into a human readable string. + ## + ## The error code can be retrieved using the `osLastError proc`_. + ## + ## If conversion fails, or `errorCode` is `0` then `""` will be + ## returned. + ## + ## On Windows, the `-d:useWinAnsi` compilation flag can be used to + ## make this procedure use the non-unicode Win API calls to retrieve the + ## message. + ## + ## See also: + ## * `raiseOSError proc`_ + ## * `osLastError proc`_ + runnableExamples: + when defined(linux): + assert osErrorMsg(OSErrorCode(0)) == "" + assert osErrorMsg(OSErrorCode(1)) == "Operation not permitted" + assert osErrorMsg(OSErrorCode(2)) == "No such file or directory" + + result = "" + when defined(nimscript): + discard + elif defined(windows): + if errorCode != OSErrorCode(0'i32): + when useWinUnicode: + var msgbuf: WideCString + if formatMessageW(0x00000100 or 0x00001000 or 0x00000200, + nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32: + result = $msgbuf + if msgbuf != nil: localFree(cast[pointer](msgbuf)) + else: + var msgbuf: cstring + if formatMessageA(0x00000100 or 0x00001000 or 0x00000200, + nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32: + result = $msgbuf + if msgbuf != nil: localFree(msgbuf) + else: + if errorCode != OSErrorCode(0'i32): + result = $c_strerror(errorCode.int32) + +proc newOSError*( + errorCode: OSErrorCode, additionalInfo = "" +): owned(ref OSError) {.noinline.} = + ## Creates a new `OSError exception `_. + ## + ## The `errorCode` will determine the + ## message, `osErrorMsg proc`_ will be used + ## to get this message. + ## + ## The error code can be retrieved using the `osLastError proc`_. + ## + ## If the error code is `0` or an error message could not be retrieved, + ## the message `unknown OS error` will be used. + ## + ## See also: + ## * `osErrorMsg proc`_ + ## * `osLastError proc`_ + var e: owned(ref OSError); new(e) + e.errorCode = errorCode.int32 + e.msg = osErrorMsg(errorCode) + if additionalInfo.len > 0: + if e.msg.len > 0 and e.msg[^1] != '\n': e.msg.add '\n' + e.msg.add "Additional info: " + e.msg.add additionalInfo + # don't add trailing `.` etc, which negatively impacts "jump to file" in IDEs. + if e.msg == "": + e.msg = "unknown OS error" + return e + +proc raiseOSError*(errorCode: OSErrorCode, additionalInfo = "") {.noinline.} = + ## Raises an `OSError exception `_. + ## + ## Read the description of the `newOSError proc`_ to learn + ## how the exception object is created. + raise newOSError(errorCode, additionalInfo) + +{.push stackTrace:off.} +proc osLastError*(): OSErrorCode {.sideEffect.} = + ## Retrieves the last operating system error code. + ## + ## This procedure is useful in the event when an OS call fails. In that case + ## this procedure will return the error code describing the reason why the + ## OS call failed. The `OSErrorMsg` procedure can then be used to convert + ## this code into a string. + ## + ## .. warning:: The behaviour of this procedure varies between Windows and POSIX systems. + ## On Windows some OS calls can reset the error code to `0` causing this + ## procedure to return `0`. It is therefore advised to call this procedure + ## immediately after an OS call fails. On POSIX systems this is not a problem. + ## + ## See also: + ## * `osErrorMsg proc`_ + ## * `raiseOSError proc`_ + when defined(nimscript): + discard + elif defined(windows): + result = cast[OSErrorCode](getLastError()) + else: + result = OSErrorCode(errno) +{.pop.} From b800cfb69e9027f875ce6e4da4abd701272bcd12 Mon Sep 17 00:00:00 2001 From: flywind Date: Fri, 14 Jan 2022 22:16:48 +0800 Subject: [PATCH 2/3] add a simple test --- tests/stdlib/toserrors.nim | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/stdlib/toserrors.nim diff --git a/tests/stdlib/toserrors.nim b/tests/stdlib/toserrors.nim new file mode 100644 index 0000000000000..e907dfe639356 --- /dev/null +++ b/tests/stdlib/toserrors.nim @@ -0,0 +1,9 @@ +discard """ + action: compile +""" + +import std/oserrors + +let x1 = osLastError() +raiseOSError(x1) +echo osErrorMsg(x1) From fc172f171e0894e5bbc89b7e303e89ac1f4151e2 Mon Sep 17 00:00:00 2001 From: flywind Date: Mon, 17 Jan 2022 10:49:50 +0800 Subject: [PATCH 3/3] New code should not support -d:useWinAnsi anymore thanks to @Araq --- lib/std/oserrors.nim | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/lib/std/oserrors.nim b/lib/std/oserrors.nim index 7a6e69a8526e0..9c2649eab8cc8 100644 --- a/lib/std/oserrors.nim +++ b/lib/std/oserrors.nim @@ -33,10 +33,6 @@ proc osErrorMsg*(errorCode: OSErrorCode): string = ## If conversion fails, or `errorCode` is `0` then `""` will be ## returned. ## - ## On Windows, the `-d:useWinAnsi` compilation flag can be used to - ## make this procedure use the non-unicode Win API calls to retrieve the - ## message. - ## ## See also: ## * `raiseOSError proc`_ ## * `osLastError proc`_ @@ -51,18 +47,11 @@ proc osErrorMsg*(errorCode: OSErrorCode): string = discard elif defined(windows): if errorCode != OSErrorCode(0'i32): - when useWinUnicode: - var msgbuf: WideCString - if formatMessageW(0x00000100 or 0x00001000 or 0x00000200, - nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32: - result = $msgbuf - if msgbuf != nil: localFree(cast[pointer](msgbuf)) - else: - var msgbuf: cstring - if formatMessageA(0x00000100 or 0x00001000 or 0x00000200, - nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32: - result = $msgbuf - if msgbuf != nil: localFree(msgbuf) + var msgbuf: WideCString + if formatMessageW(0x00000100 or 0x00001000 or 0x00000200, + nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32: + result = $msgbuf + if msgbuf != nil: localFree(cast[pointer](msgbuf)) else: if errorCode != OSErrorCode(0'i32): result = $c_strerror(errorCode.int32)