[m-rev.] for review: Allow user code to create io.error values with system errors.
Peter Wang
novalazy at gmail.com
Mon Aug 29 14:24:37 AEST 2022
library/io.m:
Move make_io_error_from_system_error to public interface.
Add make_io_error_from_windows_error.
Rename is_maybe_win32_error to is_error_maybe_win32.
Make it take an argument to indicate whether the error is a Win32
error code.
Make make_io_error_from_maybe_win32_error (an internal predicate)
take an argument to indicate whether the error is a Win32
error code.
library/dir.m:
Conform to changes.
NEWS:
Announce recent system error related changes.
---
NEWS | 7 +++
library/dir.m | 102 ++++++++++++++++++++++++++--------------
library/io.m | 128 ++++++++++++++++++++++++++++++--------------------
3 files changed, 149 insertions(+), 88 deletions(-)
diff --git a/NEWS b/NEWS
index d898dadb7..10b6dea53 100644
--- a/NEWS
+++ b/NEWS
@@ -101,6 +101,13 @@ Changes to the Mercury standard library
* The following predicates have been added:
+ - pred `make_io_error_from_system_error/5`
+ - pred `make_io_error_from_windows_error/5`
+ - pred `get_system_error/2`
+ - pred `get_errno_error/2`
+ - pred `get_windows_error/2`
+ - pred `get_exception_object_error/2`
+ - pred `get_system_error_name/2`
- pred `write_binary_string_utf8/3`
- pred `write_binary_string_utf8/4`
diff --git a/library/dir.m b/library/dir.m
index c93c445d9..5a4044e2d 100644
--- a/library/dir.m
+++ b/library/dir.m
@@ -1043,7 +1043,7 @@ make_directory(PathName, Result, !IO) :-
io::di, io::uo) is det.
make_directory_or_check_exists(DirName, Result, !IO) :-
- make_single_directory_2(DirName, MakeDirStatus, MaybeWin32Error, !IO),
+ make_single_directory_2(DirName, MakeDirStatus, Error, IsWin32Error, !IO),
(
MakeDirStatus = ok,
Result = ok
@@ -1053,7 +1053,7 @@ make_directory_or_check_exists(DirName, Result, !IO) :-
( if TypeResult = ok(directory) then
check_dir_accessibility(DirName, Result, !IO)
else
- make_io_error_from_maybe_win32_error(MaybeWin32Error,
+ make_io_error_from_maybe_win32_error(Error, IsWin32Error,
"cannot create directory: ", IOError, !IO),
Result = error(IOError)
)
@@ -1062,7 +1062,7 @@ make_directory_or_check_exists(DirName, Result, !IO) :-
check_dir_accessibility(DirName, Result, !IO)
;
MakeDirStatus = error,
- make_io_error_from_maybe_win32_error(MaybeWin32Error,
+ make_io_error_from_maybe_win32_error(Error, IsWin32Error,
"cannot create directory: ", IOError, !IO),
Result = error(IOError)
).
@@ -1182,7 +1182,7 @@ make_directory_including_parents(DirName, Result, !IO) :-
%---------------------------------------------------------------------------%
make_single_directory(DirName, Result, !IO) :-
- make_single_directory_2(DirName, Status, MaybeWin32Error, !IO),
+ make_single_directory_2(DirName, Status, Error, IsWin32Error, !IO),
(
Status = ok,
Result = ok
@@ -1191,7 +1191,7 @@ make_single_directory(DirName, Result, !IO) :-
; Status = dir_exists
; Status = error
),
- make_io_error_from_maybe_win32_error(MaybeWin32Error,
+ make_io_error_from_maybe_win32_error(Error, IsWin32Error,
"cannot create directory: ", IOError, !IO),
Result = error(IOError)
).
@@ -1210,11 +1210,11 @@ make_single_directory(DirName, Result, !IO) :-
[prefix("ML_MAKE_SINGLE_DIRECTORY_"), uppercase]).
:- pred make_single_directory_2(string::in, make_single_directory_status::out,
- io.system_error::out, io::di, io::uo) is det.
+ io.system_error::out, bool::out, io::di, io::uo) is det.
:- pragma foreign_proc("C",
make_single_directory_2(DirName::in, Status::out, Error::out,
- _IO0::di, _IO::uo),
+ IsWin32Error::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe,
will_not_modify_trail, does_not_affect_liveness, may_not_duplicate],
"
@@ -1230,6 +1230,7 @@ make_single_directory(DirName, Result, !IO) :-
Status = ML_MAKE_SINGLE_DIRECTORY_ERROR;
}
}
+ IsWin32Error = MR_YES;
#elif defined(MR_HAVE_MKDIR)
if (mkdir(DirName, 0777) == 0) {
Status = ML_MAKE_SINGLE_DIRECTORY_OK;
@@ -1243,15 +1244,17 @@ make_single_directory(DirName, Result, !IO) :-
}
#endif // EEXIST
}
+ IsWin32Error = MR_NO
#else // !MR_WIN32 && !MR_HAVE_MKDIR
Status = ML_MAKE_SINGLE_DIRECTORY_ERROR;
Error = ENOSYS;
+ IsWin32Error = MR_NO
#endif
").
:- pragma foreign_proc("C#",
make_single_directory_2(DirName::in, Status::out, Error::out,
- _IO0::di, _IO::uo),
+ IsWin32Error::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe],
"
try {
@@ -1279,11 +1282,12 @@ make_single_directory(DirName, Result, !IO) :-
Status = dir.ML_MAKE_SINGLE_DIRECTORY_ERROR;
Error = e;
}
+ IsWin32Error = mr_bool.NO;
").
:- pragma foreign_proc("Java",
make_single_directory_2(DirName::in, Status::out, Error::out,
- _IO0::di, _IO::uo),
+ IsWin32Error::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe,
may_not_duplicate],
"
@@ -1312,6 +1316,7 @@ make_single_directory(DirName, Result, !IO) :-
Status = dir.ML_MAKE_SINGLE_DIRECTORY_ERROR;
Error = e;
}
+ IsWin32Error = bool.NO;
").
%---------------------------------------------------------------------------%
@@ -1641,6 +1646,7 @@ check_for_symlink_loop(DirName, SymLinkParent, ParentIds0, MaybeLoop, !IO) :-
open(DirName, Result, !IO) :-
( if have_win32 then
+ % XXX This call to check_dir_readable seems to be redundant.
check_dir_readable(DirName, ReadabilityResult, !IO),
(
ReadabilityResult = mfe_ok(_),
@@ -1659,23 +1665,23 @@ open(DirName, Result, !IO) :-
io::di, io::uo) is det.
open_2(DirName, DirPattern, Result, !IO) :-
- open_3(DirName, DirPattern, DirStream, MaybeWin32Error, !IO),
- is_maybe_win32_error(MaybeWin32Error, "cannot open directory: ",
+ open_3(DirName, DirPattern, DirStream, Error, IsWin32Error, !IO),
+ is_error_maybe_win32(Error, IsWin32Error, "cannot open directory: ",
MaybeIOError, !IO),
(
- MaybeIOError = yes(Error),
- Result = mfe_error(file_error(DirName, file_open, Error))
+ MaybeIOError = yes(IOError),
+ Result = mfe_error(file_error(DirName, file_open, IOError))
;
MaybeIOError = no,
Result = mfe_ok(DirStream)
).
-:- pred open_3(string::in, string::in, dir.stream::out,
- io.system_error::out, io::di, io::uo) is det.
+:- pred open_3(string::in, string::in, dir.stream::out, io.system_error::out,
+ bool::out, io::di, io::uo) is det.
:- pragma foreign_proc("C",
open_3(DirName::in, DirPattern::in, DirStream::out, Error::out,
- _IO0::di, _IO::uo),
+ IsWin32Error::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe,
will_not_modify_trail, does_not_affect_liveness, may_not_duplicate],
"
@@ -1695,6 +1701,7 @@ open_2(DirName, DirPattern, Result, !IO) :-
Error = 0;
DirStream->pending_entry = ML_wide_to_utf8(file_data.cFileName, MR_ALLOC_ID);
}
+ IsWin32Error = MR_YES;
#elif defined(MR_HAVE_OPENDIR) && defined(MR_HAVE_READDIR) && \\
defined(MR_HAVE_CLOSEDIR)
@@ -1705,16 +1712,18 @@ open_2(DirName, DirPattern, Result, !IO) :-
} else {
Error = 0;
}
+ IsWin32Error = MR_NO;
#else // !MR_WIN32 && !(MR_HAVE_OPENDIR etc.)
DirStream = NULL;
Error = ENOSYS;
+ IsWin32Error = MR_NO;
#endif
").
:- pragma foreign_proc("C#",
open_3(DirName::in, _DirPattern::in, DirStream::out, Error::out,
- _IO0::di, _IO::uo),
+ IsWin32Error::out, _IO0::di, _IO::uo),
[will_not_modify_trail, promise_pure, tabled_for_io, thread_safe],
"
try {
@@ -1725,11 +1734,12 @@ open_2(DirName, DirPattern, Result, !IO) :-
DirStream = null;
Error = e;
}
+ IsWin32Error = mr_bool.NO;
").
:- pragma foreign_proc("Java",
open_3(DirName::in, _DirPattern::in, DirStream::out, Error::out,
- _IO0::di, _IO::uo),
+ IsWin32Error::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe],
"
try {
@@ -1756,6 +1766,7 @@ open_2(DirName, DirPattern, Result, !IO) :-
DirStream = null;
Error = e;
}
+ IsWin32Error = bool.NO;
").
:- pred check_dir_readable(string::in, maybe_file_error::out,
@@ -1806,11 +1817,11 @@ check_dir_readable(DirName, Result, !IO) :-
io::di, io::uo) is det.
close(DirName, DirStream, Result, !IO) :-
- close_2(DirStream, MaybeWin32Error, !IO),
+ close_2(DirStream, Error, IsWin32Error, !IO),
% XXX The top level caller may not be dir.foldl2.
% XXX The message is too verbose for use in a full file_error.
- is_maybe_win32_error(MaybeWin32Error,
- "closing directory failed: ", MaybeIOError, !IO),
+ is_error_maybe_win32(Error, IsWin32Error, "closing directory failed: ",
+ MaybeIOError, !IO),
(
MaybeIOError = yes(IOError),
Result = mfe_error(file_error(DirName, file_close, IOError))
@@ -1819,11 +1830,11 @@ close(DirName, DirStream, Result, !IO) :-
Result = mfe_ok(unit)
).
-:- pred close_2(dir.stream::in, io.system_error::out, io::di, io::uo)
- is det.
+:- pred close_2(dir.stream::in, io.system_error::out, bool::out,
+ io::di, io::uo) is det.
:- pragma foreign_proc("C",
- close_2(DirStream::in, Error::out, _IO0::di, _IO::uo),
+ close_2(DirStream::in, Error::out, IsWin32Error::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe,
will_not_modify_trail, does_not_affect_liveness, may_not_duplicate],
"
@@ -1836,41 +1847,46 @@ close(DirName, DirStream, Result, !IO) :-
} else {
Error = GetLastError();
}
+ IsWin32Error = MR_YES;
#elif defined(MR_HAVE_CLOSEDIR)
if (closedir(DirStream) == 0) {
Error = 0;
} else {
Error = errno;
}
+ IsWin32Error = MR_NO;
#else
Error = ENOSYS;
+ IsWin32Error = MR_NO;
#endif
").
:- pragma foreign_proc("C#",
- close_2(_DirStream::in, Error::out, _IO0::di, _IO::uo),
+ close_2(_DirStream::in, Error::out, IsWin32Error::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe],
"
// Nothing to do.
Error = null;
+ IsWin32Error = mr_bool.NO;
").
:- pragma foreign_proc("Java",
- close_2(_DirStream::in, Error::out, _IO0::di, _IO::uo),
+ close_2(_DirStream::in, Error::out, IsWin32Error::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe],
"
// Nothing to do.
Error = null;
+ IsWin32Error = bool.NO;
").
:- pred read_entry(dir.stream::in, io.result(string)::out, io::di, io::uo)
is det.
read_entry(DirStream, Result, !IO) :-
- read_entry_2(DirStream, MaybeWin32Error, HaveFileName, FileName, !IO),
+ read_entry_2(DirStream, Error, IsWin32Error, HaveFileName, FileName, !IO),
% XXX The top level caller may not be dir.foldl2.
% XXX The message is too verbose for use in a full file_error.
- is_maybe_win32_error(MaybeWin32Error,
+ is_error_maybe_win32(Error, IsWin32Error,
"reading directory entry failed: ", MaybeIOError, !IO),
(
MaybeIOError = yes(IOError),
@@ -1894,16 +1910,17 @@ read_entry(DirStream, Result, !IO) :-
)
).
- % read_entry_2(DirStream, MaybeWin32Error, HaveFileName, FileName, !IO):
+ % read_entry_2(DirStream, Error, IsWin32Error, HaveFileName, FileName,
+ % !IO):
% If there is no error and HaveFileName = no, then we have reached the
% end-of-stream.
%
:- pred read_entry_2(dir.stream::in, io.system_error::out, bool::out,
- string::out, io::di, io::uo) is det.
+ bool::out, string::out, io::di, io::uo) is det.
:- pragma foreign_proc("C",
- read_entry_2(DirStream::in, Error::out, HaveFileName::out, FileName::out,
- _IO0::di, _IO::uo),
+ read_entry_2(DirStream::in, Error::out, IsWin32Error::out,
+ HaveFileName::out, FileName::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe,
will_not_modify_trail, does_not_affect_liveness, may_not_duplicate],
"
@@ -1913,20 +1930,24 @@ read_entry(DirStream, Result, !IO) :-
if (DirStream->handle == INVALID_HANDLE_VALUE) {
// Directory was empty when opened.
Error = 0;
+ IsWin32Error = MR_YES;
HaveFileName = MR_NO;
FileName = MR_make_string_const("""");
} else if (DirStream->pending_entry != NULL) {
// FindFirstFileW already returned the first entry.
Error = 0;
+ IsWin32Error = MR_YES;
HaveFileName = MR_YES;
FileName = DirStream->pending_entry;
DirStream->pending_entry = NULL;
} else if (FindNextFileW(DirStream->handle, &file_data)) {
Error = 0;
+ IsWin32Error = MR_YES;
HaveFileName = MR_YES;
FileName = ML_wide_to_utf8(file_data.cFileName, MR_ALLOC_ID);
} else {
Error = GetLastError();
+ IsWin32Error = MR_YES;
if (Error == ERROR_NO_MORE_FILES) {
Error = 0;
}
@@ -1941,10 +1962,12 @@ read_entry(DirStream, Result, !IO) :-
dir_entry = readdir(DirStream);
if (dir_entry == NULL) {
Error = errno; // remains zero at end-of-stream
+ IsWin32Error = MR_NO;
HaveFileName = MR_NO;
FileName = MR_make_string_const("""");
} else {
Error = 0;
+ IsWin32Error = MR_NO;
HaveFileName = MR_YES;
MR_make_aligned_string_copy_msg(FileName, dir_entry->d_name,
MR_ALLOC_ID);
@@ -1952,16 +1975,18 @@ read_entry(DirStream, Result, !IO) :-
#else // !MR_WIN32 && !(MR_HAVE_READDIR etc.)
Error = ENOSYS;
+ IsWin32Error = MR_NO;
HaveFileName = MR_NO;
FileName = MR_make_string_const("""");
#endif
").
:- pragma foreign_proc("C#",
- read_entry_2(DirStream::in, Error::out, HaveFileName::out, FileName::out,
- _IO0::di, _IO::uo),
+ read_entry_2(DirStream::in, Error::out, IsWin32Error::out,
+ HaveFileName::out, FileName::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe],
"
+
try {
if (DirStream.MoveNext()) {
// The .NET CLI returns path names qualified with
@@ -1973,18 +1998,21 @@ read_entry(DirStream, Result, !IO) :-
FileName = """";
}
Error = null;
+ IsWin32Error = mr_bool.NO;
} catch (System.Exception e) {
Error = e;
+ IsWin32Error = mr_bool.NO;
HaveFileName = mr_bool.NO;
FileName = """";
}
").
:- pragma foreign_proc("Java",
- read_entry_2(DirStream::in, Error::out, HaveFileName::out, FileName::out,
- _IO0::di, _IO::uo),
+ read_entry_2(DirStream::in, Error::out, IsWin32Error::out,
+ HaveFileName::out, FileName::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io, thread_safe],
"
+
try {
if (DirStream.hasNext()) {
HaveFileName = bool.YES;
@@ -1994,8 +2022,10 @@ read_entry(DirStream, Result, !IO) :-
FileName = """";
}
Error = null;
+ IsWin32Error = bool.NO;
} catch (java.lang.Exception e) {
Error = e;
+ IsWin32Error = bool.NO;
HaveFileName = bool.NO;
FileName = """";
}
diff --git a/library/io.m b/library/io.m
index b9a8d91ef..70fdb5bcc 100644
--- a/library/io.m
+++ b/library/io.m
@@ -2067,6 +2067,28 @@
%
:- func make_io_error(string) = io.error.
+ % make_io_error_from_system_error(SystemError, Prefix, Error, !IO):
+ %
+ % Construct an io.error value given a system error and an error message
+ % prefix, which may be the empty string. The error message will be
+ % constructed by appending Prefix and the error message retrieved from the
+ % system for SystemError.
+ %
+ % On C backends, the io.system_error must be an errno value.
+ % On C# and Java backends, the io.system_error must be an exception object.
+ %
+:- pred make_io_error_from_system_error(io.system_error::in, string::in,
+ io.error::out, io::di, io::uo) is det.
+
+ % make_io_error_from_windows_error(SystemError, Prefix, Error, !IO):
+ %
+ % Construct an io.error value from the given Windows system error and error
+ % message prefix. This predicate may only be called when using a C backend
+ % running on Windows. On other platforms, it throws an exception.
+ %
+:- pred make_io_error_from_windows_error(io.system_error::in, string::in,
+ io.error::out, io::di, io::uo) is det.
+
% Return an error message for the error value.
%
:- func error_message(io.error) = string.
@@ -2275,24 +2297,21 @@
:- pred is_error(system_error::in, string::in, maybe(io.error)::out,
io::di, io::uo) is det.
- % is_maybe_win32_error(Error, MessagePrefix, MaybeIOError, !IO):
- % Same as is_error except that Error is a Win32 error value on Windows.
+ % is_error_maybe_win32(Error, IsWin32Error, MessagePrefix, MaybeIOError,
+ % !IO):
+ % Same as is_error except that IsWin32Error is `yes' if Error originates
+ % from a Win32 system error code, `no' otherwise.
%
-:- pred is_maybe_win32_error(system_error::in, string::in,
+:- pred is_error_maybe_win32(system_error::in, bool::in, string::in,
maybe(io.error)::out, io::di, io::uo) is det.
- % Make an io.error from a system error and message prefix.
- % On Windows, the system error is assumed to be a errno value.
+ % make_io_error_from_maybe_win32_error(Error, IsWin32Error, Prefix,
+ % IOError, !IO):
+ % Helper to call either make_io_error_from_system_error or
+ % make_io_error_from_windows_error.
%
-:- pred make_io_error_from_system_error(io.system_error::in, string::in,
- io.error::out, io::di, io::uo) is det.
-
- % Make an io.error from a system error and message prefix.
- % On Windows, the system error is assumed to be a Windows system error
- % code obtained by calling GetLastError().
- %
-:- pred make_io_error_from_maybe_win32_error(io.system_error::in, string::in,
- io.error::out, io::di, io::uo) is det.
+:- pred make_io_error_from_maybe_win32_error(system_error::in, bool::in,
+ string::in, io.error::out, io::di, io::uo) is det.
% For use by bitmap.m, and other standard library modules
% that want to do I/O.
@@ -4904,6 +4923,40 @@ report_tabling_statistics(!IO) :-
make_io_error(Error) = io_error_string(Error).
+make_io_error_from_system_error(Error, Prefix, IOError, !IO) :-
+ SysErrStyle = native_system_error_style,
+ (
+ ( SysErrStyle = syserr_errno
+ ; SysErrStyle = syserr_errno_or_win32
+ ),
+ make_errno_message(Error, Prefix, Msg, !IO),
+ IOError = io_error_errno(Msg, Error)
+ ;
+ SysErrStyle = syserr_exception_object,
+ get_exception_object_message(Error, Msg0, !IO),
+ ( if Prefix = "" then
+ Msg = Msg0
+ else
+ Msg = Prefix ++ Msg0
+ ),
+ IOError = io_error_exception_object(Msg, Error)
+ ).
+
+make_io_error_from_windows_error(Error, Prefix, IOError, !IO) :-
+ SysErrStyle = native_system_error_style,
+ (
+ SysErrStyle = syserr_errno_or_win32,
+ make_win32_error_message(Error, Prefix, Msg, !IO),
+ IOError = io_error_win32(Msg, Error)
+ ;
+ ( SysErrStyle = syserr_errno
+ ; SysErrStyle = syserr_exception_object
+ ),
+ error("io.make_io_error_from_windows_error: inapplicable platform")
+ ).
+
+%---------------------%
+
error_message(Error) = Msg :-
error_message(Error, Msg).
@@ -5891,52 +5944,23 @@ is_error(Error, Prefix, MaybeError, !IO) :-
MaybeError = yes(IOError)
).
-is_maybe_win32_error(Error, Prefix, MaybeError, !IO) :-
+is_error_maybe_win32(Error, IsWin32Error, Prefix, MaybeError, !IO) :-
( if is_success(Error) then
MaybeError = no
else
- make_io_error_from_maybe_win32_error(Error, Prefix, IOError, !IO),
+ make_io_error_from_maybe_win32_error(Error, IsWin32Error, Prefix,
+ IOError, !IO),
MaybeError = yes(IOError)
).
-make_io_error_from_system_error(Error, Prefix, IOError, !IO) :-
- SysErrStyle = native_system_error_style,
+make_io_error_from_maybe_win32_error(Error, IsWin32Error, Prefix, IOError,
+ !IO) :-
(
- ( SysErrStyle = syserr_errno
- ; SysErrStyle = syserr_errno_or_win32
- ),
- make_errno_message(Error, Prefix, Msg, !IO),
- IOError = io_error_errno(Msg, Error)
+ IsWin32Error = yes,
+ make_io_error_from_windows_error(Error, Prefix, IOError, !IO)
;
- SysErrStyle = syserr_exception_object,
- get_exception_object_message(Error, Msg0, !IO),
- ( if Prefix = "" then
- Msg = Msg0
- else
- Msg = Prefix ++ Msg0
- ),
- IOError = io_error_exception_object(Msg, Error)
- ).
-
-make_io_error_from_maybe_win32_error(Error, Prefix, IOError, !IO) :-
- SysErrStyle = native_system_error_style,
- (
- SysErrStyle = syserr_errno,
- make_errno_message(Error, Prefix, Msg, !IO),
- IOError = io_error_errno(Msg, Error)
- ;
- SysErrStyle = syserr_errno_or_win32,
- make_win32_error_message(Error, Prefix, Msg, !IO),
- IOError = io_error_win32(Msg, Error)
- ;
- SysErrStyle = syserr_exception_object,
- get_exception_object_message(Error, Msg0, !IO),
- ( if Prefix = "" then
- Msg = Msg0
- else
- Msg = Prefix ++ Msg0
- ),
- IOError = io_error_exception_object(Msg, Error)
+ IsWin32Error = no,
+ make_io_error_from_system_error(Error, Prefix, IOError, !IO)
).
:- pred throw_on_error(system_error::in, string::in, io::di, io::uo) is det.
--
2.37.1
More information about the reviews
mailing list