From bed0900c8c4b9eeb40dcb7fa39123f4f2427e9c7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 4 Jan 2026 21:26:27 -0800 Subject: [PATCH] std.Io.Dir: rework atomic file --- lib/std/Io.zig | 2 + lib/std/Io/Dir.zig | 160 ++++++++++++++++++++---------------- lib/std/Io/File.zig | 34 +++++++- lib/std/Io/File/Atomic.zig | 105 ++++++++++-------------- lib/std/Io/File/Writer.zig | 8 ++ lib/std/Io/Threaded.zig | 161 ++++++++++++++++++++++++++++++++++++- lib/std/zig/system.zig | 1 - 7 files changed, 335 insertions(+), 136 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index a1e38135bd..7ef4b85142 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -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, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index cc1dec8efe..9ec1abe32e 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -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; diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index bbe550a9cc..389c8fb8b1 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -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; diff --git a/lib/std/Io/File/Atomic.zig b/lib/std/Io/File/Atomic.zig index 340303ca39..440430e04c 100644 --- a/lib/std/Io/File/Atomic.zig +++ b/lib/std/Io/File/Atomic.zig @@ -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(); -} diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index bf8c0bf289..52bbe83513 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -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.?, + }; +} diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ce41c8ca75..507c64d268 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -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), diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index dfa10b84b6..d94ae1180c 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -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,