Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added std/oserrors for OS error reporting #19390

Merged
merged 3 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
129 changes: 129 additions & 0 deletions lib/std/oserrors.nim
Original file line number Diff line number Diff line change
@@ -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: "<errno.h>".}: cint

proc c_strerror(errnum: cint): cstring {.
importc: "strerror", header: "<string.h>".}

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New code should not support -d:useWinAnsi anymore.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about owned(ref OSError) for newly modules? I didn't touch them because I think they can be cleaned later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the owned would be better, but it's a minor thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks

## 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 <system.html#OSError>`_.
##
## 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 <system.html#OSError>`_.
##
## 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.}
9 changes: 9 additions & 0 deletions tests/stdlib/toserrors.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
discard """
action: compile
"""

import std/oserrors

let x1 = osLastError()
raiseOSError(x1)
echo osErrorMsg(x1)