Skip to content

Add exclusive flag to open() keywords (issue #33947) #33949

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ New library functions
* The new `only(x)` function returns the one-and-only element of a collection `x`, and throws an `ArgumentError` if `x` contains zero or multiple elements. ([#33129])
* `takewhile` and `dropwhile` have been added to the Iterators submodule ([#33437]).
* There is a now an `evalpoly` (generated) function meant to take the role of the `@evalpoly` macro. The function is just as efficient as the macro while giving added flexibility, so it should be preferred over `@evalpoly`. `evalpoly` takes a list of coefficients as a tuple, so where one might write `@evalpoly(x, p1, p2, p3)` one would instead write `evalpoly(x, (p1, p2, p3))`.
* The keyword version of `open` now takes keyword argument `exclusive` to ensure that the call creates the file ([#33949]).

Standard library changes
------------------------
Expand Down
23 changes: 13 additions & 10 deletions base/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,12 @@ Compute the `read`, `write`, `create`, `truncate`, `append` flag value for
a given set of keyword arguments to [`open`](@ref) a [`NamedTuple`](@ref).
"""
function open_flags(;
read :: Union{Bool,Nothing} = nothing,
write :: Union{Bool,Nothing} = nothing,
create :: Union{Bool,Nothing} = nothing,
truncate :: Union{Bool,Nothing} = nothing,
append :: Union{Bool,Nothing} = nothing,
read :: Union{Bool,Nothing} = nothing,
write :: Union{Bool,Nothing} = nothing,
create :: Union{Bool,Nothing} = nothing,
truncate :: Union{Bool,Nothing} = nothing,
append :: Union{Bool,Nothing} = nothing,
exclusive :: Union{Bool,Nothing} = nothing,
)
if write === true && read !== true && append !== true
create === nothing && (create = true)
Expand All @@ -259,18 +260,20 @@ function open_flags(;
create === nothing && (create = true)
end

write === nothing && (write = false)
read === nothing && (read = !write)
create === nothing && (create = false)
truncate === nothing && (truncate = false)
append === nothing && (append = false)
write === nothing && (write = false)
read === nothing && (read = !write)
create === nothing && (create = false)
truncate === nothing && (truncate = false)
append === nothing && (append = false)
exclusive === nothing && (exclusive = false)

return (
read = read,
write = write,
create = create,
truncate = truncate,
append = append,
exclusive = exclusive,
)
end

Expand Down
38 changes: 22 additions & 16 deletions base/iostream.jl
Original file line number Diff line number Diff line change
Expand Up @@ -223,38 +223,44 @@ fdio(fd::Integer, own::Bool=false) = fdio(string("<fd ",fd,">"), fd, own)
"""
open(filename::AbstractString; keywords...) -> IOStream

Open a file in a mode specified by five boolean keyword arguments:
Open a file in a mode specified by six boolean keyword arguments:

| Keyword | Description | Default |
|:-----------|:-----------------------|:----------------------------------------|
| `read` | open for reading | `!write` |
| `write` | open for writing | `truncate \\| append` |
| `create` | create if non-existent | `!read & write \\| truncate \\| append` |
| `truncate` | truncate to zero size | `!read & write` |
| `append` | seek to end | `false` |
| Keyword | Description | Default |
|:------------|:-----------------------|:----------------------------------------|
| `read` | open for reading | `!write` |
| `write` | open for writing | `truncate \\| append` |
| `create` | create if non-existent | `!read & write \\| truncate \\| append` |
| `truncate` | truncate to zero size | `!read & write` |
| `append` | seek to end | `false` |
| `exclusive` | ensure file creation | `false` |

The default when no keywords are passed is to open files for reading only.
Returns a stream for accessing the opened file.

!!! compat "Julia 1.4"
The `exclusive` keyword argument requires Julia 1.4 or later.
"""
function open(fname::AbstractString;
read :: Union{Bool,Nothing} = nothing,
write :: Union{Bool,Nothing} = nothing,
create :: Union{Bool,Nothing} = nothing,
truncate :: Union{Bool,Nothing} = nothing,
append :: Union{Bool,Nothing} = nothing,
read :: Union{Bool,Nothing} = nothing,
write :: Union{Bool,Nothing} = nothing,
create :: Union{Bool,Nothing} = nothing,
truncate :: Union{Bool,Nothing} = nothing,
append :: Union{Bool,Nothing} = nothing,
exclusive :: Union{Bool,Nothing} = nothing,
)
flags = open_flags(
read = read,
write = write,
create = create,
truncate = truncate,
append = append,
exclusive = exclusive,
)
s = IOStream(string("<file ",fname,">"))
systemerror("opening file $(repr(fname))",
ccall(:ios_file, Ptr{Cvoid},
(Ptr{UInt8}, Cstring, Cint, Cint, Cint, Cint),
s.ios, fname, flags.read, flags.write, flags.create, flags.truncate) == C_NULL)
(Ptr{UInt8}, Cstring, Cint, Cint, Cint, Cint, Cint),
s.ios, fname, flags.read, flags.write, flags.create, flags.truncate, flags.exclusive) == C_NULL)
if flags.append
systemerror("seeking to end of file $fname", ccall(:ios_seek_end, Int64, (Ptr{Cvoid},), s.ios) != 0)
end
Expand All @@ -264,7 +270,7 @@ end
"""
open(filename::AbstractString, [mode::AbstractString]) -> IOStream

Alternate syntax for open, where a string-based mode specifier is used instead of the five
Alternate syntax for open, where a string-based mode specifier is used instead of the six
booleans. The values of `mode` correspond to those from `fopen(3)` or Perl `open`, and are
equivalent to setting the following boolean groups:

Expand Down
2 changes: 1 addition & 1 deletion src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1313,7 +1313,7 @@ jl_code_instance_t *jl_compile_linfo(jl_method_instance_t *mi, jl_code_info_t *s
if (!strncmp(t, "stderr", 6))
s_precompile = JL_STDERR;
else {
if (ios_file(&f_precompile, t, 1, 1, 1, 1) == NULL)
if (ios_file(&f_precompile, t, 1, 1, 1, 1, 0) == NULL)
jl_errorf("cannot open precompile statement file \"%s\" for writing", t);
s_precompile = (JL_STREAM*) &f_precompile;
}
Expand Down
4 changes: 2 additions & 2 deletions src/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -2856,7 +2856,7 @@ JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist)
const char *depstr = jl_string_data(dep);
if (!depstr[0])
continue;
ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0);
ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0, 0);
if (!srctp) {
jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n",
jl_string_data(dep));
Expand Down Expand Up @@ -3256,7 +3256,7 @@ JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(const char *buf, size_t
JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *mod_array)
{
ios_t f;
if (ios_file(&f, fname, 1, 0, 0, 0) == NULL) {
if (ios_file(&f, fname, 1, 0, 0, 0, 0) == NULL) {
return jl_get_exceptionf(jl_errorexception_type,
"Cache file \"%s\" not found.\n", fname);
}
Expand Down
2 changes: 1 addition & 1 deletion src/flisp/flisp.h
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ struct _fl_context_t {
int SCR_WIDTH;
int HPOS, VPOS;

value_t iostreamsym, rdsym, wrsym, apsym, crsym, truncsym;
value_t iostreamsym, rdsym, wrsym, apsym, crsym, truncsym, exclsym;
value_t instrsym, outstrsym;
fltype_t *iostreamtype;

Expand Down
6 changes: 4 additions & 2 deletions src/flisp/iostream.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,20 @@ value_t fl_file(fl_context_t *fl_ctx, value_t *args, uint32_t nargs)
{
if (nargs < 1)
argcount(fl_ctx, "file", nargs, 1);
int i, r=0, w=0, c=0, t=0, a=0;
int i, r=0, w=0, c=0, t=0, a=0, e=0;
for(i=1; i < (int)nargs; i++) {
if (args[i] == fl_ctx->wrsym) w = 1;
else if (args[i] == fl_ctx->apsym) { a = 1; w = 1; }
else if (args[i] == fl_ctx->crsym) { c = 1; w = 1; }
else if (args[i] == fl_ctx->truncsym) { t = 1; w = 1; }
else if (args[i] == fl_ctx->rdsym) r = 1;
else if (args[i] == fl_ctx->exclsym) e = 1;
}
if ((r|w|c|t|a) == 0) r = 1; // default to reading
value_t f = cvalue(fl_ctx, fl_ctx->iostreamtype, sizeof(ios_t));
char *fname = tostring(fl_ctx, args[0], "file");
ios_t *s = value2c(ios_t*, f);
if (ios_file(s, fname, r, w, c, t) == NULL)
if (ios_file(s, fname, r, w, c, t, e) == NULL)
lerrorf(fl_ctx, fl_ctx->IOError, "file: could not open \"%s\"", fname);
if (a) ios_seek_end(s);
return f;
Expand Down Expand Up @@ -445,6 +446,7 @@ void iostream_init(fl_context_t *fl_ctx)
fl_ctx->apsym = symbol(fl_ctx, ":append");
fl_ctx->crsym = symbol(fl_ctx, ":create");
fl_ctx->truncsym = symbol(fl_ctx, ":truncate");
fl_ctx->exclsym = symbol(fl_ctx, ":exclusive");
fl_ctx->instrsym = symbol(fl_ctx, "*input-stream*");
fl_ctx->outstrsym = symbol(fl_ctx, "*output-stream*");
fl_ctx->iostreamtype = define_opaque_type(fl_ctx->iostreamsym, sizeof(ios_t),
Expand Down
2 changes: 1 addition & 1 deletion src/precompile.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ void jl_write_compiler_output(void)
}
else {
ios_t f;
if (ios_file(&f, jl_options.outputji, 1, 1, 1, 1) == NULL)
if (ios_file(&f, jl_options.outputji, 1, 1, 1, 1, 0) == NULL)
jl_errorf("cannot open system image file \"%s\" for writing", jl_options.outputji);
ios_write(&f, (const char*)s->buf, (size_t)s->size);
ios_close(&f);
Expand Down
4 changes: 2 additions & 2 deletions src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -1396,7 +1396,7 @@ JL_DLLEXPORT size_t ios_write_direct(ios_t *dest, ios_t *src);
JL_DLLEXPORT void jl_save_system_image(const char *fname)
{
ios_t f;
if (ios_file(&f, fname, 1, 1, 1, 1) == NULL) {
if (ios_file(&f, fname, 1, 1, 1, 1, 0) == NULL) {
jl_errorf("cannot open system image file \"%s\" for writing", fname);
}
JL_SIGATOMIC_BEGIN();
Expand Down Expand Up @@ -1573,7 +1573,7 @@ JL_DLLEXPORT void jl_restore_system_image(const char *fname)
}
else {
ios_t f;
if (ios_file(&f, fname, 1, 0, 0, 0) == NULL)
if (ios_file(&f, fname, 1, 0, 0, 0, 0) == NULL)
jl_errorf("System image file \"%s\" not found.", fname);
ios_bufmode(&f, bm_none);
JL_SIGATOMIC_BEGIN();
Expand Down
3 changes: 2 additions & 1 deletion src/support/ios.c
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,7 @@ static int open_cloexec(const char *path, int flags, mode_t mode)
}
#endif

ios_t *ios_file(ios_t *s, const char *fname, int rd, int wr, int create, int trunc)
ios_t *ios_file(ios_t *s, const char *fname, int rd, int wr, int create, int trunc, int excl)
{
int flags;
int fd;
Expand All @@ -911,6 +911,7 @@ ios_t *ios_file(ios_t *s, const char *fname, int rd, int wr, int create, int tru
goto open_file_err;
flags = wr ? (rd ? O_RDWR : O_WRONLY) : O_RDONLY;
if (create) flags |= O_CREAT;
if (create && excl) flags |= O_EXCL;
if (trunc) flags |= O_TRUNC;
#if defined(_OS_WINDOWS_)
size_t wlen = MultiByteToWideChar(CP_UTF8, 0, fname, -1, NULL, 0);
Expand Down
2 changes: 1 addition & 1 deletion src/support/ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ JL_DLLEXPORT size_t ios_readprep(ios_t *from, size_t n);

/* stream creation */
JL_DLLEXPORT
ios_t *ios_file(ios_t *s, const char *fname, int rd, int wr, int create, int trunc) JL_NOTSAFEPOINT;
ios_t *ios_file(ios_t *s, const char *fname, int rd, int wr, int create, int trunc, int excl) JL_NOTSAFEPOINT;
JL_DLLEXPORT ios_t *ios_mkstemp(ios_t *f, char *fname);
JL_DLLEXPORT ios_t *ios_mem(ios_t *s, size_t initsize) JL_NOTSAFEPOINT;
ios_t *ios_str(ios_t *s, char *str);
Expand Down
10 changes: 8 additions & 2 deletions test/iostream.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ end
end
end

@test Base.open_flags(read=false, write=true, append=false) == (read=false, write=true, create=true, truncate=true, append=false)
@test Base.open_flags(read=false, write=true, append=false) == (read=false, write=true, create=true, truncate=true, append=false, exclusive=false)

@testset "issue #30978" begin
mktemp() do path, io
Expand All @@ -108,4 +108,10 @@ end
open(f -> readbytes!(f, y, 102, all=false), path)
@test y == [x; 0]
end
end
end

@testset "exclusive flag" begin
mktemp() do path, io
@test_throws SystemError open(path, create=true, exclusive=true)
end
end