Skip to content

Commit

Permalink
io, system: disallow if-does-not-exist: #f for file streams
Browse files Browse the repository at this point in the history
  • Loading branch information
cgay committed May 25, 2024
1 parent 7d24b02 commit a7d9dfb
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 31 deletions.
6 changes: 4 additions & 2 deletions documentation/source/library-reference/io/streams.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1233,8 +1233,10 @@ are exported from the *streams* module.
:keyword if-exists: One of :drm:`#f`, ``#"new-version"``,
``#"overwrite"``, ``#"replace"``, ``#"append"``, ``#"truncate"``,
``#"signal"``. Default value: :drm:`#f`.
:keyword if-does-not-exist: One of :drm:`#f`, ``#"signal"``, or
``#"create"``. Default value: depends on the value of ``direction:``.
:keyword if-does-not-exist: Either ``#"signal"`` or ``#"create"``.
The default (and only valid value) for direction ``#"input"`` is
``#"signal"``. The default for direction ``#"output"`` and
``#"input-output"`` is ``#"create"``.
:keyword asynchronous?: If :drm:`#t`, all writes on this stream are
performed asynchronously. Default value::drm:`#f`.

Expand Down
20 changes: 12 additions & 8 deletions documentation/source/library-reference/system/file-system.rst
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ module.
The :drm:`make` method on :class:`<file-stream>` does not create
direct instances of :class:`<file-stream>`, but instead an instance of
a subclass determined by :gf:`type-for-file-stream`. See
`make`_ and `Options when creating file streams`_ below.
`make`_ and :ref:`file-stream-options` below.

.. _file-stream-options:

Options when creating file streams
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -257,9 +259,9 @@ the file named by *filename* already exists. The options are:
The ``if-does-not-exist:`` init-keyword allows you to specify an action to
take if the file named by *filename* does not exist. The options are:

- :drm:`#f` No action.
- ``#"signal"`` Signal a :class:`<file-does-not-exist-error>` condition. This is
the default when the stream's direction is ``#"input"``.
- ``#"signal"`` Signal a :class:`<file-does-not-exist-error>` condition. This
is the default (and the only valid value) when the stream's direction is
``#"input"``.
- ``#"create"`` Create a new zero-length file. This is the default when
the stream's direction is ``#"output"`` or ``#"input-output"``.

Expand Down Expand Up @@ -933,8 +935,10 @@ File-System module.
:parameter #key if-exists: One of :drm:`#f`, ``#"new-version"``,
``#"overwrite"``, ``#"replace"``, ``#"append"``, ``#"truncate"``,
``#"signal"``. Default value: :drm:`#f`.
:parameter #key if-does-not-exist: One of :drm:`#f`, ``#"signal"``, or
``#"create"``. Default value: depends on the value of *direction*.
:parameter #key if-does-not-exist: Either ``#"signal"`` or ``#"create"``.
The default (and only valid value) for direction ``#"input"`` is
``#"signal"``. The default for direction ``#"output"`` and
``#"input-output"`` is ``#"create"``.
:parameter #key buffer-size: An instance of :drm:`<integer>`.
:parameter #key element-type: One of :type:`<byte-character>`,
:type:`<unicode-character>`, or :type:`<byte>`, or :drm:`#f`.
Expand All @@ -960,7 +964,7 @@ File-System module.
The *if-exists* and *if-does-not-exist* init-keywords specify
actions to take if the file named by *filename* does or does not
already exist when the stream is created. These init-keywords are
discussed in more detail in `Options when creating file streams`_.
discussed in more detail in :ref:`file-stream-options`.

The *buffer-size* init-keyword can be used to suggest the size of
a stream's buffer. See :class:`<buffered-stream>`.
Expand All @@ -971,7 +975,7 @@ File-System module.
treated as a single database record. This init-keyword defaults to
something useful, potentially based on the properties of the file;
:type:`<byte-character>` and :type:`<unicode-character>` are likely choices.
See `Options when creating file streams`_.
See :ref:`file-stream-options`.

:seealso:

Expand Down
2 changes: 1 addition & 1 deletion sources/io/streams/file-stream.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ end class <external-file-accessor>;

/// File stream classes

// File streams basically to not inherit any significant methods from
// File streams basically do not inherit any significant methods from
// <single-buffered-stream> or <positionable-stream>, only protocol.
// This is because they use power of two sized buffers which are aligned with
// disk segments. <single-buffered-stream> uses non-aligned buffers
Expand Down
24 changes: 18 additions & 6 deletions sources/system/file-system/unix-file-accessor.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@ define constant $file_create_permissions
define sideways method accessor-open
(accessor :: <native-file-accessor>, locator :: <pathname>,
#rest keywords,
#key direction = #"input", if-exists, if-does-not-exist,
file-position: initial-file-position = #f, // :: false-or(<integer>)?
file-size: initial-file-size = #f, // :: false-or(<integer>)?
#key direction = #"input", if-exists,
if-does-not-exist = unsupplied(),
file-position: initial-file-position, // :: false-or(<integer>)?
file-size: initial-file-size, // :: false-or(<integer>)?
#all-keys) => ()
local
method check-if-does-not-exist (valid-options, default)
if (unsupplied?(if-does-not-exist))
default
elseif (~member?(if-does-not-exist, valid-options))
error("if-does-not-exist: %= is not valid for direction: %=",
if-does-not-exist, direction);
else
if-does-not-exist
end;
end;
block (return)
let locator = expand-pathname(locator);
let pathstring = as(<byte-string>, locator);
Expand All @@ -32,15 +44,15 @@ define sideways method accessor-open
#"input" =>
values($O_RDONLY,
#"overwrite",
(if-does-not-exist | #"signal"));
check-if-does-not-exist(#[#"signal"], #"signal"));
#"output" =>
values(logior($O_WRONLY, $O_SYNC),
(if-exists | #"new-version"),
(if-does-not-exist | #"create"));
check-if-does-not-exist(#[#"signal", #"create"], #"create"));
#"input-output" =>
values(logior($O_RDWR, $O_SYNC),
(if-exists | #"overwrite"),
(if-does-not-exist | #"create"));
check-if-does-not-exist(#[#"signal", #"create"], #"create"));
end;
let mode-code
= if (exists)
Expand Down
37 changes: 25 additions & 12 deletions sources/system/file-system/win32-file-accessor.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,28 @@ end function win32-file-error;
define sideways method accessor-open
(accessor :: <native-file-accessor>, locator :: <pathname>,
#rest keywords,
#key direction = #"input", if-exists, if-does-not-exist,
file-descriptor: initial-file-handle = #f, // :: false-or(<machine-word>)
file-position: initial-file-position = #f, // :: false-or(<integer>)?
file-size: initial-file-size = #f, // :: false-or(<integer>)?
overlapped? :: <boolean> = #f,
share? :: <boolean> = #t, // only shared access allowed in the past
share-mode :: one-of(#"default", #"exclusive", #"share-read",
#"share-write", #"share-read-write") = #"default",
#all-keys) => ();
#key direction = #"input",
if-exists,
if-does-not-exist = unsupplied(),
file-descriptor: initial-file-handle, // :: false-or(<machine-word>)
file-position: initial-file-position, // :: false-or(<integer>)?
file-size: initial-file-size, // :: false-or(<integer>)?
overlapped? :: <boolean>,
share? :: <boolean> = #t, // only shared access allowed in the past
share-mode :: one-of(#"default", #"exclusive", #"share-read",
#"share-write", #"share-read-write") = #"default",
#all-keys) => ()
local
method check-if-does-not-exist (valid-options, default)
if (unsupplied?(if-does-not-exist))
default
elseif (~member?(if-does-not-exist, valid-options))
error("if-does-not-exist: %= is not valid for direction: %=",
if-does-not-exist, direction);
else
if-does-not-exist
end;
end;
block (return)
if (initial-file-position | initial-file-size)
error("Cannot create a file accessor which specifies either"
Expand All @@ -49,13 +62,13 @@ define sideways method accessor-open
select (direction)
#"input" =>
if-exists := #"overwrite";
if-does-not-exist := if-does-not-exist | #"signal";
if-does-not-exist := check-if-does-not-exist(#[#"signal"], #"signal");
#"output" =>
if-exists := if-exists | #"new-version";
if-does-not-exist := if-does-not-exist | #"create";
if-does-not-exist := check-if-does-not-exist(#[#"create", #"signal"], #"create");
#"input-output" =>
if-exists := if-exists | #"overwrite";
if-does-not-exist := if-does-not-exist | #"create";
if-does-not-exist := check-if-does-not-exist(#[#"create", #"signal"], #"create");
end;
let fdwAccess
= select (direction)
Expand Down
58 changes: 56 additions & 2 deletions sources/system/tests/file-system.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,62 @@ end test;

/// Macro tests

define test test-with-open-file-test ()
// ---*** Fill this in.
// For direction: #"input", #"signal" is the only valid option.
define test test-with-open-file/if-does-not-exist/input ()
assert-signals(<file-does-not-exist-error>,
with-open-file (stream = file-locator(test-temp-directory(), "input1"),
direction: #"input",
if-does-not-exist: #"signal")
assert-true(#f, "input1 body should not be executed");
end);
assert-signals(<error>,
with-open-file (stream = file-locator(test-temp-directory(), "input2"),
direction: #"input",
if-does-not-exist: #"create")
assert-true(#f, "input2 body should not be executed");
end);
assert-signals(<error>,
with-open-file (stream = file-locator(test-temp-directory(), "input3"),
direction: #"input",
if-does-not-exist: #f) // not a valid option anymore
assert-true(#f, "input3 body should not be executed");
end);
assert-signals(<error>,
with-open-file (stream = file-locator(test-temp-directory(), "input4"),
direction: #"input",
if-does-not-exist: #"foo") // not a valid option
assert-true(#f, "input4 body should not be executed");
end);
// if-does-not-exist not specified.
assert-signals(<error>,
with-open-file (stream = file-locator(test-temp-directory(), "input5"),
direction: #"input")
assert-true(#f, "input5 body should not be executed");
end);
end test;

// For direction: #"output", #"create" (default) and #"signal" are valid.
define test test-with-open-file/if-does-not-exist/output ()
assert-signals(<file-does-not-exist-error>,
with-open-file (stream = file-locator(test-temp-directory(), "output1"),
direction: #"output",
if-does-not-exist: #"signal")
assert-true(#f, "output1 body should not be executed");
end);
let output2 = file-locator(test-temp-directory(), "output2");
with-open-file (stream = output2 ,
direction: #"output",
if-does-not-exist: #"create")
write(stream, "output2");
end;
assert-equal("output2", with-open-file (stream = output2) read-to-end(stream) end);

// Same as above but if-does-not-exist: is not specified.
let output3 = file-locator(test-temp-directory(), "abc");
with-open-file (stream = output3 , direction: #"output")
write(stream, "output3");
end;
assert-equal("abc", with-open-file (stream = output3) read-to-end(stream) end);
end test;


Expand Down

0 comments on commit a7d9dfb

Please sign in to comment.