mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-27 18:51:44 +03:00
std.Io.Dir: rework atomic file
This commit is contained in:
@@ -667,6 +667,7 @@ pub const VTable = struct {
|
||||
dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat,
|
||||
dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void,
|
||||
dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File,
|
||||
dirCreateFileAtomic: *const fn (?*anyopaque, Dir, []const u8, Dir.CreateFileAtomicOptions) Dir.CreateFileAtomicError!File.Atomic,
|
||||
dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File,
|
||||
dirClose: *const fn (?*anyopaque, []const Dir) void,
|
||||
dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize,
|
||||
@@ -710,6 +711,7 @@ pub const VTable = struct {
|
||||
fileUnlock: *const fn (?*anyopaque, File) void,
|
||||
fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void,
|
||||
fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize,
|
||||
fileHardLink: *const fn (?*anyopaque, File, Dir, []const u8, File.HardLinkOptions) File.HardLinkError!void,
|
||||
|
||||
processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
|
||||
processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
|
||||
|
||||
+89
-71
@@ -454,7 +454,6 @@ pub const OpenError = error{
|
||||
SystemFdQuotaExceeded,
|
||||
NoDevice,
|
||||
SystemResources,
|
||||
DeviceBusy,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
} || PathNameError || Io.Cancelable || Io.UnexpectedError;
|
||||
@@ -598,30 +597,29 @@ pub fn updateFile(
|
||||
}
|
||||
}
|
||||
|
||||
if (path.dirname(dest_path)) |dirname| {
|
||||
try dest_dir.createDirPath(io, dirname);
|
||||
}
|
||||
|
||||
var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
|
||||
var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{
|
||||
.permissions = actual_permissions,
|
||||
.write_buffer = &buffer,
|
||||
.make_path = true,
|
||||
.replace = true,
|
||||
});
|
||||
defer atomic_file.deinit();
|
||||
defer atomic_file.deinit(io);
|
||||
|
||||
var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var file_writer = atomic_file.file.writer(io, &buffer);
|
||||
|
||||
var src_reader: File.Reader = .initSize(src_file, io, &.{}, src_stat.size);
|
||||
const dest_writer = &atomic_file.file_writer.interface;
|
||||
const dest_writer = &file_writer.interface;
|
||||
|
||||
_ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
|
||||
error.ReadFailed => return src_reader.err.?,
|
||||
error.WriteFailed => return atomic_file.file_writer.err.?,
|
||||
error.WriteFailed => return file_writer.err.?,
|
||||
};
|
||||
try atomic_file.flush();
|
||||
try atomic_file.file_writer.file.setTimestamps(io, .{
|
||||
try file_writer.flush();
|
||||
try file_writer.file.setTimestamps(io, .{
|
||||
.access_timestamp = .init(src_stat.atime),
|
||||
.modify_timestamp = .init(src_stat.mtime),
|
||||
});
|
||||
try atomic_file.renameIntoPlace();
|
||||
try atomic_file.replace(io);
|
||||
return .stale;
|
||||
}
|
||||
|
||||
@@ -995,27 +993,9 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename
|
||||
return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);
|
||||
}
|
||||
|
||||
pub const HardLinkOptions = struct {
|
||||
follow_symlinks: bool = true,
|
||||
};
|
||||
pub const HardLinkOptions = File.HardLinkOptions;
|
||||
|
||||
pub const HardLinkError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
DiskQuota,
|
||||
PathAlreadyExists,
|
||||
HardwareFailure,
|
||||
/// Either the OS or the filesystem does not support hard links.
|
||||
OperationUnsupported,
|
||||
SymLinkLoop,
|
||||
LinkQuotaExceeded,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
NoSpaceLeft,
|
||||
ReadOnlyFileSystem,
|
||||
NotSameFileSystem,
|
||||
NotDir,
|
||||
} || Io.Cancelable || PathNameError || Io.UnexpectedError;
|
||||
pub const HardLinkError = File.HardLinkError;
|
||||
|
||||
pub fn hardLink(
|
||||
old_dir: Dir,
|
||||
@@ -1251,7 +1231,6 @@ pub const DeleteTreeError = error{
|
||||
ReadOnlyFileSystem,
|
||||
FileSystem,
|
||||
FileBusy,
|
||||
DeviceBusy,
|
||||
/// One of the path components was not a directory.
|
||||
/// This error is unreachable if `sub_path` does not contain a path separator.
|
||||
NotDir,
|
||||
@@ -1322,7 +1301,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
@@ -1417,7 +1395,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
@@ -1522,7 +1499,6 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
@@ -1619,7 +1595,6 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin
|
||||
error.SystemResources,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.DeviceBusy,
|
||||
error.NetworkNotFound,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
@@ -1658,15 +1633,18 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin
|
||||
pub const CopyFileOptions = struct {
|
||||
/// When this is `null` the permissions are copied from the source file.
|
||||
permissions: ?File.Permissions = null,
|
||||
make_path: bool = false,
|
||||
replace: bool = true,
|
||||
};
|
||||
|
||||
pub const CopyFileError = File.OpenError || File.StatError ||
|
||||
File.Atomic.InitError || File.Atomic.FinishError ||
|
||||
CreateFileAtomicError || File.Atomic.ReplaceError || File.Atomic.LinkError ||
|
||||
File.Reader.Error || File.Writer.Error || error{InvalidFileName};
|
||||
|
||||
/// Atomically creates a new file at `dest_path` within `dest_dir` with the
|
||||
/// same contents as `source_path` within `source_dir`, overwriting any already
|
||||
/// existing file.
|
||||
/// same contents as `source_path` within `source_dir`.
|
||||
///
|
||||
/// Whether to overwrite the existing file is determined by `options`.
|
||||
///
|
||||
/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
|
||||
/// readily available, there is a possibility of power loss or application
|
||||
@@ -1695,19 +1673,27 @@ pub fn copyFile(
|
||||
break :blk st.permissions;
|
||||
};
|
||||
|
||||
var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
|
||||
var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{
|
||||
.permissions = permissions,
|
||||
.write_buffer = &buffer,
|
||||
.make_path = options.make_path,
|
||||
.replace = options.replace,
|
||||
});
|
||||
defer atomic_file.deinit();
|
||||
defer atomic_file.deinit(io);
|
||||
|
||||
_ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
|
||||
var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var file_writer = atomic_file.file.writer(io, &buffer);
|
||||
|
||||
_ = file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
|
||||
error.ReadFailed => return file_reader.err.?,
|
||||
error.WriteFailed => return atomic_file.file_writer.err.?,
|
||||
error.WriteFailed => return file_writer.err.?,
|
||||
};
|
||||
|
||||
try atomic_file.finish();
|
||||
try file_writer.flush();
|
||||
|
||||
switch (options.replace) {
|
||||
true => try atomic_file.replace(io),
|
||||
false => try atomic_file.link(io),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `copyFile`, except asserts that both `source_path` and `dest_path`
|
||||
@@ -1730,33 +1716,65 @@ pub fn copyFileAbsolute(
|
||||
|
||||
test copyFileAbsolute {}
|
||||
|
||||
pub const AtomicFileOptions = struct {
|
||||
pub const CreateFileAtomicOptions = struct {
|
||||
permissions: File.Permissions = .default_file,
|
||||
make_path: bool = false,
|
||||
write_buffer: []u8,
|
||||
/// Tells whether the unnamed file will be ultimately created with
|
||||
/// `File.Atomic.link` or `File.Atomic.replace`.
|
||||
///
|
||||
/// If this value is incorrect it will cause an assertion failure in
|
||||
/// `File.Atomic.replace`.
|
||||
replace: bool = false,
|
||||
};
|
||||
|
||||
/// Directly access the `.file` field, and then call `File.Atomic.finish` to
|
||||
/// atomically replace `dest_path` with contents.
|
||||
///
|
||||
/// Always call `File.Atomic.deinit` to clean up, regardless of whether
|
||||
/// `File.Atomic.finish` succeeded. `dest_path` must remain valid until
|
||||
/// `File.Atomic.deinit` is called.
|
||||
///
|
||||
/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `dest_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic {
|
||||
if (path.dirname(dest_path)) |dirname| {
|
||||
const dir = if (options.make_path)
|
||||
try parent.createDirPathOpen(io, dirname, .{})
|
||||
else
|
||||
try parent.openDir(io, dirname, .{});
|
||||
pub const CreateFileAtomicError = error{
|
||||
NoDevice,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
/// On Windows, antivirus software is enabled by default. It can be
|
||||
/// disabled, but Windows Update sometimes ignores the user's preference
|
||||
/// and re-enables it. When enabled, antivirus software on Windows
|
||||
/// intercepts file system operations and makes them significantly slower
|
||||
/// in addition to possibly failing with this error code.
|
||||
AntivirusInterference,
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to open a new resource relative to it.
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
SymLinkLoop,
|
||||
ProcessFdQuotaExceeded,
|
||||
SystemFdQuotaExceeded,
|
||||
/// Either:
|
||||
/// * One of the path components does not exist.
|
||||
/// * Cwd was used, but cwd has been deleted.
|
||||
/// * The path associated with the open directory handle has been deleted.
|
||||
FileNotFound,
|
||||
/// Insufficient kernel memory was available.
|
||||
SystemResources,
|
||||
/// A new path cannot be created because the device has no room for the new file.
|
||||
NoSpaceLeft,
|
||||
/// A component used as a directory in the path was not, in fact, a directory.
|
||||
NotDir,
|
||||
WouldBlock,
|
||||
ReadOnlyFileSystem,
|
||||
} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
return .init(io, path.basename(dest_path), options.permissions, dir, true, options.write_buffer);
|
||||
} else {
|
||||
return .init(io, dest_path, options.permissions, parent, false, options.write_buffer);
|
||||
}
|
||||
/// Create an unnamed ephemeral file that can eventually be atomically
|
||||
/// materialized into `sub_path`.
|
||||
///
|
||||
/// The returned `File.Atomic` provides API to emulate the behavior in case it
|
||||
/// is not directly supported by the underlying operating system.
|
||||
///
|
||||
/// * On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// * On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn createFileAtomic(
|
||||
dir: Dir,
|
||||
io: Io,
|
||||
sub_path: []const u8,
|
||||
options: CreateFileAtomicOptions,
|
||||
) CreateFileAtomicError!File.Atomic {
|
||||
return io.vtable.dirCreateFileAtomic(io.userdata, dir, sub_path, options);
|
||||
}
|
||||
|
||||
pub const SetPermissionsError = File.SetPermissionsError;
|
||||
|
||||
+33
-1
@@ -278,7 +278,7 @@ pub const OpenError = error{
|
||||
FileBusy,
|
||||
/// Non-blocking was requested and the operation cannot return immediately.
|
||||
WouldBlock,
|
||||
} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
|
||||
} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
pub fn close(file: File, io: Io) void {
|
||||
return io.vtable.fileClose(io.userdata, (&file)[0..1]);
|
||||
@@ -708,6 +708,38 @@ pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize {
|
||||
return io.vtable.fileRealPath(io.userdata, file, out_buffer);
|
||||
}
|
||||
|
||||
pub const HardLinkOptions = struct {
|
||||
follow_symlinks: bool = true,
|
||||
};
|
||||
|
||||
pub const HardLinkError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
DiskQuota,
|
||||
PathAlreadyExists,
|
||||
HardwareFailure,
|
||||
/// Either the OS or the filesystem does not support hard links.
|
||||
OperationUnsupported,
|
||||
SymLinkLoop,
|
||||
LinkQuotaExceeded,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
NoSpaceLeft,
|
||||
ReadOnlyFileSystem,
|
||||
NotSameFileSystem,
|
||||
NotDir,
|
||||
} || Io.Cancelable || Dir.PathNameError || Io.UnexpectedError;
|
||||
|
||||
pub fn hardLink(
|
||||
file: File,
|
||||
io: Io,
|
||||
new_dir: Dir,
|
||||
new_sub_path: []const u8,
|
||||
options: HardLinkOptions,
|
||||
) HardLinkError!void {
|
||||
return io.vtable.fileHardLink(io.userdata, file, new_dir, new_sub_path, options);
|
||||
}
|
||||
|
||||
test {
|
||||
_ = Reader;
|
||||
_ = Writer;
|
||||
|
||||
+43
-62
@@ -6,97 +6,78 @@ const File = std.Io.File;
|
||||
const Dir = std.Io.Dir;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
file_writer: File.Writer,
|
||||
random_integer: u64,
|
||||
dest_basename: []const u8,
|
||||
file: File,
|
||||
file_basename_hex: u64,
|
||||
file_open: bool,
|
||||
file_exists: bool,
|
||||
close_dir_on_deinit: bool,
|
||||
|
||||
dir: Dir,
|
||||
close_dir_on_deinit: bool,
|
||||
|
||||
dest_sub_path: []const u8,
|
||||
|
||||
pub const InitError = File.OpenError;
|
||||
|
||||
/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
|
||||
pub fn init(
|
||||
io: Io,
|
||||
dest_basename: []const u8,
|
||||
permissions: File.Permissions,
|
||||
dir: Dir,
|
||||
close_dir_on_deinit: bool,
|
||||
write_buffer: []u8,
|
||||
) InitError!Atomic {
|
||||
while (true) {
|
||||
const random_integer = std.crypto.random.int(u64);
|
||||
const tmp_sub_path = std.fmt.hex(random_integer);
|
||||
const file = dir.createFile(io, &tmp_sub_path, .{
|
||||
.permissions = permissions,
|
||||
.exclusive = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
else => |e| return e,
|
||||
};
|
||||
return .{
|
||||
.file_writer = file.writer(io, write_buffer),
|
||||
.random_integer = random_integer,
|
||||
.dest_basename = dest_basename,
|
||||
.file_open = true,
|
||||
.file_exists = true,
|
||||
.close_dir_on_deinit = close_dir_on_deinit,
|
||||
.dir = dir,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Always call deinit, even after a successful finish().
|
||||
pub fn deinit(af: *Atomic) void {
|
||||
const io = af.file_writer.io;
|
||||
|
||||
/// To release all resources, always call `deinit`, even after a successful
|
||||
/// `finish`.
|
||||
pub fn deinit(af: *Atomic, io: Io) void {
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close(io);
|
||||
af.file.close(io);
|
||||
af.file_open = false;
|
||||
}
|
||||
if (af.file_exists) {
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
|
||||
af.dir.deleteFile(io, &tmp_sub_path) catch {};
|
||||
af.file_exists = false;
|
||||
}
|
||||
if (af.close_dir_on_deinit) {
|
||||
af.dir.close(io);
|
||||
af.close_dir_on_deinit = false;
|
||||
}
|
||||
af.* = undefined;
|
||||
}
|
||||
|
||||
pub const FlushError = File.Writer.Error;
|
||||
pub const LinkError = Dir.HardLinkError;
|
||||
|
||||
pub fn flush(af: *Atomic) FlushError!void {
|
||||
af.file_writer.interface.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => return af.file_writer.err.?,
|
||||
};
|
||||
/// Atomically materializes the file into place, failing with
|
||||
/// `error.PathAlreadyExists` if something already exists there.
|
||||
pub fn link(af: *Atomic, io: Io) LinkError!void {
|
||||
if (af.file_exists) {
|
||||
if (af.file_open) {
|
||||
af.file.close(io);
|
||||
af.file_open = false;
|
||||
}
|
||||
const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
|
||||
try af.dir.hardLink(&tmp_sub_path, af.dir, af.dest_sub_path, io, .{});
|
||||
af.dir.deleteFile(io, &tmp_sub_path) catch {};
|
||||
af.file_exists = false;
|
||||
} else {
|
||||
assert(af.file_open);
|
||||
try af.file.hardLink(io, af.dir, af.dest_sub_path, .{});
|
||||
af.file.close(io);
|
||||
af.file_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub const RenameIntoPlaceError = Dir.RenameError;
|
||||
pub const ReplaceError = Dir.RenameError;
|
||||
|
||||
/// Atomically materializes the file into place, replacing any file that
|
||||
/// already exists there.
|
||||
///
|
||||
/// Calling this function requires setting `CreateFileAtomicOptions.replace` to
|
||||
/// `true`.
|
||||
///
|
||||
/// On Windows, this function introduces a period of time where some file
|
||||
/// system operations on the destination file will result in
|
||||
/// `error.AccessDenied`, including rename operations (such as the one used in
|
||||
/// this function).
|
||||
pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void {
|
||||
const io = af.file_writer.io;
|
||||
|
||||
assert(af.file_exists);
|
||||
pub fn replace(af: *Atomic, io: Io) ReplaceError!void {
|
||||
assert(af.file_exists); // Wrong value for `CreateFileAtomicOptions.replace`.
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close(io);
|
||||
af.file.close(io);
|
||||
af.file_open = false;
|
||||
}
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io);
|
||||
const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
|
||||
try af.dir.rename(&tmp_sub_path, af.dir, af.dest_sub_path, io);
|
||||
af.file_exists = false;
|
||||
}
|
||||
|
||||
pub const FinishError = FlushError || RenameIntoPlaceError;
|
||||
|
||||
/// Combination of `flush` followed by `renameIntoPlace`.
|
||||
pub fn finish(af: *Atomic) FinishError!void {
|
||||
try af.flush();
|
||||
try af.renameIntoPlace();
|
||||
}
|
||||
|
||||
@@ -272,3 +272,11 @@ pub fn end(w: *Writer) EndError!void {
|
||||
=> {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience method for calling `Io.Writer.flush` and returning the
|
||||
/// underlying error.
|
||||
pub fn flush(w: *Writer) Error!void {
|
||||
w.interface.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => return w.err.?,
|
||||
};
|
||||
}
|
||||
|
||||
+160
-1
@@ -1403,6 +1403,7 @@ pub fn io(t: *Threaded) Io {
|
||||
.dirStatFile = dirStatFile,
|
||||
.dirAccess = dirAccess,
|
||||
.dirCreateFile = dirCreateFile,
|
||||
.dirCreateFileAtomic = dirCreateFileAtomic,
|
||||
.dirOpenFile = dirOpenFile,
|
||||
.dirOpenDir = dirOpenDir,
|
||||
.dirClose = dirClose,
|
||||
@@ -1549,6 +1550,7 @@ pub fn ioBasic(t: *Threaded) Io {
|
||||
.dirStatFile = dirStatFile,
|
||||
.dirAccess = dirAccess,
|
||||
.dirCreateFile = dirCreateFile,
|
||||
.dirCreateFileAtomic = dirCreateFileAtomic,
|
||||
.dirOpenFile = dirOpenFile,
|
||||
.dirOpenDir = dirOpenDir,
|
||||
.dirClose = dirClose,
|
||||
@@ -3413,6 +3415,163 @@ fn dirCreateFileWasi(
|
||||
}
|
||||
}
|
||||
|
||||
fn dirCreateFileAtomic(
|
||||
userdata: ?*anyopaque,
|
||||
dir: Dir,
|
||||
dest_path: []const u8,
|
||||
options: Dir.CreateFileAtomicOptions,
|
||||
) Dir.CreateFileAtomicError!File.Atomic {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const t_io = ioBasic(t);
|
||||
|
||||
// Linux has O_TMPFILE, but linkat() does not support AT_REPLACE, so it's
|
||||
// useless when we have to make up a bogus path name to do the rename()
|
||||
// anyway.
|
||||
if (native_os == .linux and !options.replace) tmpfile: {
|
||||
const dest_dirname = Dir.path.dirname(dest_path);
|
||||
if (dest_dirname) |dirname| {
|
||||
// This has a nice side effect of preemptively triggering EISDIR or
|
||||
// ENOENT, avoiding the ambiguity below.
|
||||
dir.createDirPath(t_io, dirname) catch |err| switch (err) {
|
||||
// None of these make sense in this context.
|
||||
error.IsDir,
|
||||
error.Streaming,
|
||||
error.DiskQuota,
|
||||
error.PathAlreadyExists,
|
||||
error.LinkQuotaExceeded,
|
||||
error.SharingViolation,
|
||||
error.PipeBusy,
|
||||
error.FileTooBig,
|
||||
error.DeviceBusy,
|
||||
error.FileLocksUnsupported,
|
||||
error.FileBusy,
|
||||
=> return error.Unexpected,
|
||||
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
var path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
const sub_path_posix = try pathToPosix(dest_dirname orelse ".", &path_buffer);
|
||||
|
||||
const flags: posix.O = .{
|
||||
.ACCMODE = .RDWR,
|
||||
.TMPFILE = true,
|
||||
.CLOEXEC = true,
|
||||
};
|
||||
|
||||
const syscall: Syscall = try .start();
|
||||
while (true) {
|
||||
const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode());
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => {
|
||||
syscall.finish();
|
||||
return .{
|
||||
.file = .{ .handle = @intCast(rc) },
|
||||
.file_basename_hex = 0,
|
||||
.dest_sub_path = dest_path,
|
||||
.file_open = true,
|
||||
.file_exists = false,
|
||||
.close_dir_on_deinit = false,
|
||||
.dir = dir,
|
||||
};
|
||||
},
|
||||
.INTR => {
|
||||
try syscall.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.ISDIR, .NOENT => {
|
||||
// Ambiguous error code. It might mean the file system
|
||||
// does not support O_TMPFILE. Therefore, we must fall
|
||||
// back to not using O_TMPFILE.
|
||||
syscall.finish();
|
||||
break :tmpfile;
|
||||
},
|
||||
.INVAL => return syscall.fail(error.BadPathName),
|
||||
.ACCES => return syscall.fail(error.AccessDenied),
|
||||
.LOOP => return syscall.fail(error.SymLinkLoop),
|
||||
.MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
|
||||
.NAMETOOLONG => return syscall.fail(error.NameTooLong),
|
||||
.NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
|
||||
.NODEV => return syscall.fail(error.NoDevice),
|
||||
.NOMEM => return syscall.fail(error.SystemResources),
|
||||
.NOSPC => return syscall.fail(error.NoSpaceLeft),
|
||||
.NOTDIR => return syscall.fail(error.NotDir),
|
||||
.PERM => return syscall.fail(error.PermissionDenied),
|
||||
.AGAIN => return syscall.fail(error.WouldBlock),
|
||||
.NXIO => return syscall.fail(error.NoDevice),
|
||||
.ILSEQ => return syscall.fail(error.BadPathName),
|
||||
else => |err| return syscall.unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Dir.path.dirname(dest_path)) |dirname| {
|
||||
const new_dir = if (options.make_path)
|
||||
dir.createDirPathOpen(t_io, dirname, .{}) catch |err| switch (err) {
|
||||
// None of these make sense in this context.
|
||||
error.IsDir,
|
||||
error.Streaming,
|
||||
error.DiskQuota,
|
||||
error.PathAlreadyExists,
|
||||
error.LinkQuotaExceeded,
|
||||
error.SharingViolation,
|
||||
error.PipeBusy,
|
||||
error.FileTooBig,
|
||||
error.FileLocksUnsupported,
|
||||
error.FileBusy,
|
||||
error.DeviceBusy,
|
||||
=> return error.Unexpected,
|
||||
|
||||
else => |e| return e,
|
||||
}
|
||||
else
|
||||
try dir.openDir(t_io, dirname, .{});
|
||||
|
||||
return atomicFileInit(t_io, Dir.path.basename(dest_path), options.permissions, new_dir, true);
|
||||
}
|
||||
|
||||
return atomicFileInit(t_io, dest_path, options.permissions, dir, false);
|
||||
}
|
||||
|
||||
fn atomicFileInit(
|
||||
t_io: Io,
|
||||
dest_basename: []const u8,
|
||||
permissions: File.Permissions,
|
||||
dir: Dir,
|
||||
close_dir_on_deinit: bool,
|
||||
) Dir.CreateFileAtomicError!File.Atomic {
|
||||
while (true) {
|
||||
const random_integer = std.crypto.random.int(u64);
|
||||
const tmp_sub_path = std.fmt.hex(random_integer);
|
||||
const file = dir.createFile(t_io, &tmp_sub_path, .{
|
||||
.permissions = permissions,
|
||||
.exclusive = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
error.DeviceBusy => continue,
|
||||
error.FileBusy => continue,
|
||||
error.SharingViolation => continue,
|
||||
|
||||
error.IsDir => return error.Unexpected, // No path components.
|
||||
error.FileTooBig => return error.Unexpected, // Creating, not opening.
|
||||
error.FileLocksUnsupported => return error.Unexpected, // Not asking for locks.
|
||||
error.PipeBusy => return error.Unexpected, // Not opening a pipe.
|
||||
|
||||
else => |e| return e,
|
||||
};
|
||||
return .{
|
||||
.file = file,
|
||||
.file_basename_hex = random_integer,
|
||||
.dest_sub_path = dest_basename,
|
||||
.file_open = true,
|
||||
.file_exists = true,
|
||||
.close_dir_on_deinit = close_dir_on_deinit,
|
||||
.dir = dir,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const dirOpenFile = switch (native_os) {
|
||||
.windows => dirOpenFileWindows,
|
||||
.wasi => dirOpenFileWasi,
|
||||
@@ -3925,7 +4084,7 @@ fn dirOpenDirPosix(
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.DeviceBusy,
|
||||
.BUSY => |err| return errnoBug(err), // O_EXCL not passed
|
||||
.NXIO => return error.NoDevice,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
|
||||
@@ -793,7 +793,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
|
||||
var dir = cwd.openDir(io, rpath, .{}) catch |err| switch (err) {
|
||||
error.NameTooLong => return error.Unexpected,
|
||||
error.BadPathName => return error.Unexpected,
|
||||
error.DeviceBusy => return error.Unexpected,
|
||||
error.NetworkNotFound => return error.Unexpected, // Windows-only
|
||||
|
||||
error.FileNotFound => return error.GLibCNotFound,
|
||||
|
||||
Reference in New Issue
Block a user