authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 21:26:27-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-05 20:28:58-08:00
log81bfd289745c661ff5b96b42470f47492091c389
treeed723e79bc4f7a7c6569e9b757ea972229c0c831
parent6ab1159e815fdc7ce11fa49235509a43da74f899

std.Io.Dir: rework atomic file


7 files changed, 336 insertions(+), 137 deletions(-)

lib/std/Io.zig+2
......@@ -667,6 +667,7 @@ pub const VTable = struct {
667667 dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat,
668668 dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void,
669669 dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File,
670 dirCreateFileAtomic: *const fn (?*anyopaque, Dir, []const u8, Dir.CreateFileAtomicOptions) Dir.CreateFileAtomicError!File.Atomic,
670671 dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File,
671672 dirClose: *const fn (?*anyopaque, []const Dir) void,
672673 dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize,
......@@ -710,6 +711,7 @@ pub const VTable = struct {
710711 fileUnlock: *const fn (?*anyopaque, File) void,
711712 fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void,
712713 fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize,
714 fileHardLink: *const fn (?*anyopaque, File, Dir, []const u8, File.HardLinkOptions) File.HardLinkError!void,
713715
714716 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
715717 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
lib/std/Io/Dir.zig+90-72
......@@ -454,7 +454,6 @@ pub const OpenError = error{
454454 SystemFdQuotaExceeded,
455455 NoDevice,
456456 SystemResources,
457 DeviceBusy,
458457 /// On Windows, `\\server` or `\\server\share` was not found.
459458 NetworkNotFound,
460459} || PathNameError || Io.Cancelable || Io.UnexpectedError;
......@@ -598,30 +597,29 @@ pub fn updateFile(
598597 }
599598 }
600599
601 if (path.dirname(dest_path)) |dirname| {
602 try dest_dir.createDirPath(io, dirname);
603 }
604
605 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
606 var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
600 var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{
607601 .permissions = actual_permissions,
608 .write_buffer = &buffer,
602 .make_path = true,
603 .replace = true,
609604 });
610 defer atomic_file.deinit();
605 defer atomic_file.deinit(io);
606
607 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
608 var file_writer = atomic_file.file.writer(io, &buffer);
611609
612610 var src_reader: File.Reader = .initSize(src_file, io, &.{}, src_stat.size);
613 const dest_writer = &atomic_file.file_writer.interface;
611 const dest_writer = &file_writer.interface;
614612
615613 _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
616614 error.ReadFailed => return src_reader.err.?,
617 error.WriteFailed => return atomic_file.file_writer.err.?,
615 error.WriteFailed => return file_writer.err.?,
618616 };
619 try atomic_file.flush();
620 try atomic_file.file_writer.file.setTimestamps(io, .{
617 try file_writer.flush();
618 try file_writer.file.setTimestamps(io, .{
621619 .access_timestamp = .init(src_stat.atime),
622620 .modify_timestamp = .init(src_stat.mtime),
623621 });
624 try atomic_file.renameIntoPlace();
622 try atomic_file.replace(io);
625623 return .stale;
626624}
627625
......@@ -995,27 +993,9 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename
995993 return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);
996994}
997995
998pub const HardLinkOptions = struct {
999 follow_symlinks: bool = true,
1000};
996pub const HardLinkOptions = File.HardLinkOptions;
1001997
1002pub const HardLinkError = error{
1003 AccessDenied,
1004 PermissionDenied,
1005 DiskQuota,
1006 PathAlreadyExists,
1007 HardwareFailure,
1008 /// Either the OS or the filesystem does not support hard links.
1009 OperationUnsupported,
1010 SymLinkLoop,
1011 LinkQuotaExceeded,
1012 FileNotFound,
1013 SystemResources,
1014 NoSpaceLeft,
1015 ReadOnlyFileSystem,
1016 NotSameFileSystem,
1017 NotDir,
1018} || Io.Cancelable || PathNameError || Io.UnexpectedError;
998pub const HardLinkError = File.HardLinkError;
1019999
10201000pub fn hardLink(
10211001 old_dir: Dir,
......@@ -1251,7 +1231,6 @@ pub const DeleteTreeError = error{
12511231 ReadOnlyFileSystem,
12521232 FileSystem,
12531233 FileBusy,
1254 DeviceBusy,
12551234 /// One of the path components was not a directory.
12561235 /// This error is unreachable if `sub_path` does not contain a path separator.
12571236 NotDir,
......@@ -1322,7 +1301,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
13221301 error.Unexpected,
13231302 error.BadPathName,
13241303 error.NetworkNotFound,
1325 error.DeviceBusy,
13261304 error.Canceled,
13271305 => |e| return e,
13281306 };
......@@ -1417,7 +1395,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
14171395 error.Unexpected,
14181396 error.BadPathName,
14191397 error.NetworkNotFound,
1420 error.DeviceBusy,
14211398 error.Canceled,
14221399 => |e| return e,
14231400 };
......@@ -1522,7 +1499,6 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8,
15221499 error.Unexpected,
15231500 error.BadPathName,
15241501 error.NetworkNotFound,
1525 error.DeviceBusy,
15261502 error.Canceled,
15271503 => |e| return e,
15281504 };
......@@ -1619,7 +1595,6 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin
16191595 error.SystemResources,
16201596 error.Unexpected,
16211597 error.BadPathName,
1622 error.DeviceBusy,
16231598 error.NetworkNotFound,
16241599 error.Canceled,
16251600 => |e| return e,
......@@ -1658,15 +1633,18 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin
16581633pub const CopyFileOptions = struct {
16591634 /// When this is `null` the permissions are copied from the source file.
16601635 permissions: ?File.Permissions = null,
1636 make_path: bool = false,
1637 replace: bool = true,
16611638};
16621639
16631640pub const CopyFileError = File.OpenError || File.StatError ||
1664 File.Atomic.InitError || File.Atomic.FinishError ||
1641 CreateFileAtomicError || File.Atomic.ReplaceError || File.Atomic.LinkError ||
16651642 File.Reader.Error || File.Writer.Error || error{InvalidFileName};
16661643
16671644/// Atomically creates a new file at `dest_path` within `dest_dir` with the
1668/// same contents as `source_path` within `source_dir`, overwriting any already
1669/// existing file.
1645/// same contents as `source_path` within `source_dir`.
1646///
1647/// Whether to overwrite the existing file is determined by `options`.
16701648///
16711649/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
16721650/// readily available, there is a possibility of power loss or application
......@@ -1695,19 +1673,27 @@ pub fn copyFile(
16951673 break :blk st.permissions;
16961674 };
16971675
1698 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
1699 var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
1676 var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{
17001677 .permissions = permissions,
1701 .write_buffer = &buffer,
1678 .make_path = options.make_path,
1679 .replace = options.replace,
17021680 });
1703 defer atomic_file.deinit();
1681 defer atomic_file.deinit(io);
17041682
1705 _ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
1683 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
1684 var file_writer = atomic_file.file.writer(io, &buffer);
1685
1686 _ = file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
17061687 error.ReadFailed => return file_reader.err.?,
1707 error.WriteFailed => return atomic_file.file_writer.err.?,
1688 error.WriteFailed => return file_writer.err.?,
17081689 };
17091690
1710 try atomic_file.finish();
1691 try file_writer.flush();
1692
1693 switch (options.replace) {
1694 true => try atomic_file.replace(io),
1695 false => try atomic_file.link(io),
1696 }
17111697}
17121698
17131699/// Same as `copyFile`, except asserts that both `source_path` and `dest_path`
......@@ -1730,33 +1716,65 @@ pub fn copyFileAbsolute(
17301716
17311717test copyFileAbsolute {}
17321718
1733pub const AtomicFileOptions = struct {
1719pub const CreateFileAtomicOptions = struct {
17341720 permissions: File.Permissions = .default_file,
17351721 make_path: bool = false,
1736 write_buffer: []u8,
1722 /// Tells whether the unnamed file will be ultimately created with
1723 /// `File.Atomic.link` or `File.Atomic.replace`.
1724 ///
1725 /// If this value is incorrect it will cause an assertion failure in
1726 /// `File.Atomic.replace`.
1727 replace: bool = false,
17371728};
17381729
1739/// Directly access the `.file` field, and then call `File.Atomic.finish` to
1740/// atomically replace `dest_path` with contents.
1741///
1742/// Always call `File.Atomic.deinit` to clean up, regardless of whether
1743/// `File.Atomic.finish` succeeded. `dest_path` must remain valid until
1744/// `File.Atomic.deinit` is called.
1745///
1746/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1747/// On WASI, `dest_path` should be encoded as valid UTF-8.
1748/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
1749pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic {
1750 if (path.dirname(dest_path)) |dirname| {
1751 const dir = if (options.make_path)
1752 try parent.createDirPathOpen(io, dirname, .{})
1753 else
1754 try parent.openDir(io, dirname, .{});
1755
1756 return .init(io, path.basename(dest_path), options.permissions, dir, true, options.write_buffer);
1757 } else {
1758 return .init(io, dest_path, options.permissions, parent, false, options.write_buffer);
1759 }
1730pub const CreateFileAtomicError = error{
1731 NoDevice,
1732 /// On Windows, `\\server` or `\\server\share` was not found.
1733 NetworkNotFound,
1734 /// On Windows, antivirus software is enabled by default. It can be
1735 /// disabled, but Windows Update sometimes ignores the user's preference
1736 /// and re-enables it. When enabled, antivirus software on Windows
1737 /// intercepts file system operations and makes them significantly slower
1738 /// in addition to possibly failing with this error code.
1739 AntivirusInterference,
1740 /// In WASI, this error may occur when the file descriptor does
1741 /// not hold the required rights to open a new resource relative to it.
1742 AccessDenied,
1743 PermissionDenied,
1744 SymLinkLoop,
1745 ProcessFdQuotaExceeded,
1746 SystemFdQuotaExceeded,
1747 /// Either:
1748 /// * One of the path components does not exist.
1749 /// * Cwd was used, but cwd has been deleted.
1750 /// * The path associated with the open directory handle has been deleted.
1751 FileNotFound,
1752 /// Insufficient kernel memory was available.
1753 SystemResources,
1754 /// A new path cannot be created because the device has no room for the new file.
1755 NoSpaceLeft,
1756 /// A component used as a directory in the path was not, in fact, a directory.
1757 NotDir,
1758 WouldBlock,
1759 ReadOnlyFileSystem,
1760} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
1761
1762/// Create an unnamed ephemeral file that can eventually be atomically
1763/// materialized into `sub_path`.
1764///
1765/// The returned `File.Atomic` provides API to emulate the behavior in case it
1766/// is not directly supported by the underlying operating system.
1767///
1768/// * On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1769/// * On WASI, `sub_path` should be encoded as valid UTF-8.
1770/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1771pub fn createFileAtomic(
1772 dir: Dir,
1773 io: Io,
1774 sub_path: []const u8,
1775 options: CreateFileAtomicOptions,
1776) CreateFileAtomicError!File.Atomic {
1777 return io.vtable.dirCreateFileAtomic(io.userdata, dir, sub_path, options);
17601778}
17611779
17621780pub const SetPermissionsError = File.SetPermissionsError;
lib/std/Io/File.zig+33-1
......@@ -278,7 +278,7 @@ pub const OpenError = error{
278278 FileBusy,
279279 /// Non-blocking was requested and the operation cannot return immediately.
280280 WouldBlock,
281} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
281} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
282282
283283pub fn close(file: File, io: Io) void {
284284 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 {
708708 return io.vtable.fileRealPath(io.userdata, file, out_buffer);
709709}
710710
711pub const HardLinkOptions = struct {
712 follow_symlinks: bool = true,
713};
714
715pub const HardLinkError = error{
716 AccessDenied,
717 PermissionDenied,
718 DiskQuota,
719 PathAlreadyExists,
720 HardwareFailure,
721 /// Either the OS or the filesystem does not support hard links.
722 OperationUnsupported,
723 SymLinkLoop,
724 LinkQuotaExceeded,
725 FileNotFound,
726 SystemResources,
727 NoSpaceLeft,
728 ReadOnlyFileSystem,
729 NotSameFileSystem,
730 NotDir,
731} || Io.Cancelable || Dir.PathNameError || Io.UnexpectedError;
732
733pub fn hardLink(
734 file: File,
735 io: Io,
736 new_dir: Dir,
737 new_sub_path: []const u8,
738 options: HardLinkOptions,
739) HardLinkError!void {
740 return io.vtable.fileHardLink(io.userdata, file, new_dir, new_sub_path, options);
741}
742
711743test {
712744 _ = Reader;
713745 _ = Writer;
lib/std/Io/File/Atomic.zig+43-62
......@@ -6,97 +6,78 @@ const File = std.Io.File;
66const Dir = std.Io.Dir;
77const assert = std.debug.assert;
88
9file_writer: File.Writer,
10random_integer: u64,
11dest_basename: []const u8,
9file: File,
10file_basename_hex: u64,
1211file_open: bool,
1312file_exists: bool,
14close_dir_on_deinit: bool,
13
1514dir: Dir,
15close_dir_on_deinit: bool,
1616
17pub const InitError = File.OpenError;
17dest_sub_path: []const u8,
1818
19/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
20pub fn init(
21 io: Io,
22 dest_basename: []const u8,
23 permissions: File.Permissions,
24 dir: Dir,
25 close_dir_on_deinit: bool,
26 write_buffer: []u8,
27) InitError!Atomic {
28 while (true) {
29 const random_integer = std.crypto.random.int(u64);
30 const tmp_sub_path = std.fmt.hex(random_integer);
31 const file = dir.createFile(io, &tmp_sub_path, .{
32 .permissions = permissions,
33 .exclusive = true,
34 }) catch |err| switch (err) {
35 error.PathAlreadyExists => continue,
36 else => |e| return e,
37 };
38 return .{
39 .file_writer = file.writer(io, write_buffer),
40 .random_integer = random_integer,
41 .dest_basename = dest_basename,
42 .file_open = true,
43 .file_exists = true,
44 .close_dir_on_deinit = close_dir_on_deinit,
45 .dir = dir,
46 };
47 }
48}
49
50/// Always call deinit, even after a successful finish().
51pub fn deinit(af: *Atomic) void {
52 const io = af.file_writer.io;
19pub const InitError = File.OpenError;
5320
21/// To release all resources, always call `deinit`, even after a successful
22/// `finish`.
23pub fn deinit(af: *Atomic, io: Io) void {
5424 if (af.file_open) {
55 af.file_writer.file.close(io);
25 af.file.close(io);
5626 af.file_open = false;
5727 }
5828 if (af.file_exists) {
59 const tmp_sub_path = std.fmt.hex(af.random_integer);
29 const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
6030 af.dir.deleteFile(io, &tmp_sub_path) catch {};
6131 af.file_exists = false;
6232 }
6333 if (af.close_dir_on_deinit) {
6434 af.dir.close(io);
35 af.close_dir_on_deinit = false;
6536 }
6637 af.* = undefined;
6738}
6839
69pub const FlushError = File.Writer.Error;
40pub const LinkError = Dir.HardLinkError;
7041
71pub fn flush(af: *Atomic) FlushError!void {
72 af.file_writer.interface.flush() catch |err| switch (err) {
73 error.WriteFailed => return af.file_writer.err.?,
74 };
42/// Atomically materializes the file into place, failing with
43/// `error.PathAlreadyExists` if something already exists there.
44pub fn link(af: *Atomic, io: Io) LinkError!void {
45 if (af.file_exists) {
46 if (af.file_open) {
47 af.file.close(io);
48 af.file_open = false;
49 }
50 const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
51 try af.dir.hardLink(&tmp_sub_path, af.dir, af.dest_sub_path, io, .{});
52 af.dir.deleteFile(io, &tmp_sub_path) catch {};
53 af.file_exists = false;
54 } else {
55 assert(af.file_open);
56 try af.file.hardLink(io, af.dir, af.dest_sub_path, .{});
57 af.file.close(io);
58 af.file_open = false;
59 }
7560}
7661
77pub const RenameIntoPlaceError = Dir.RenameError;
62pub const ReplaceError = Dir.RenameError;
7863
64/// Atomically materializes the file into place, replacing any file that
65/// already exists there.
66///
67/// Calling this function requires setting `CreateFileAtomicOptions.replace` to
68/// `true`.
69///
7970/// On Windows, this function introduces a period of time where some file
8071/// system operations on the destination file will result in
8172/// `error.AccessDenied`, including rename operations (such as the one used in
8273/// this function).
83pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void {
84 const io = af.file_writer.io;
85
86 assert(af.file_exists);
74pub fn replace(af: *Atomic, io: Io) ReplaceError!void {
75 assert(af.file_exists); // Wrong value for `CreateFileAtomicOptions.replace`.
8776 if (af.file_open) {
88 af.file_writer.file.close(io);
77 af.file.close(io);
8978 af.file_open = false;
9079 }
91 const tmp_sub_path = std.fmt.hex(af.random_integer);
92 try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io);
80 const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
81 try af.dir.rename(&tmp_sub_path, af.dir, af.dest_sub_path, io);
9382 af.file_exists = false;
9483}
95
96pub const FinishError = FlushError || RenameIntoPlaceError;
97
98/// Combination of `flush` followed by `renameIntoPlace`.
99pub fn finish(af: *Atomic) FinishError!void {
100 try af.flush();
101 try af.renameIntoPlace();
102}
lib/std/Io/File/Writer.zig+8
......@@ -272,3 +272,11 @@ pub fn end(w: *Writer) EndError!void {
272272 => {},
273273 }
274274}
275
276/// Convenience method for calling `Io.Writer.flush` and returning the
277/// underlying error.
278pub fn flush(w: *Writer) Error!void {
279 w.interface.flush() catch |err| switch (err) {
280 error.WriteFailed => return w.err.?,
281 };
282}
lib/std/Io/Threaded.zig+160-1
......@@ -1403,6 +1403,7 @@ pub fn io(t: *Threaded) Io {
14031403 .dirStatFile = dirStatFile,
14041404 .dirAccess = dirAccess,
14051405 .dirCreateFile = dirCreateFile,
1406 .dirCreateFileAtomic = dirCreateFileAtomic,
14061407 .dirOpenFile = dirOpenFile,
14071408 .dirOpenDir = dirOpenDir,
14081409 .dirClose = dirClose,
......@@ -1549,6 +1550,7 @@ pub fn ioBasic(t: *Threaded) Io {
15491550 .dirStatFile = dirStatFile,
15501551 .dirAccess = dirAccess,
15511552 .dirCreateFile = dirCreateFile,
1553 .dirCreateFileAtomic = dirCreateFileAtomic,
15521554 .dirOpenFile = dirOpenFile,
15531555 .dirOpenDir = dirOpenDir,
15541556 .dirClose = dirClose,
......@@ -3413,6 +3415,163 @@ fn dirCreateFileWasi(
34133415 }
34143416}
34153417
3418fn dirCreateFileAtomic(
3419 userdata: ?*anyopaque,
3420 dir: Dir,
3421 dest_path: []const u8,
3422 options: Dir.CreateFileAtomicOptions,
3423) Dir.CreateFileAtomicError!File.Atomic {
3424 const t: *Threaded = @ptrCast(@alignCast(userdata));
3425 const t_io = ioBasic(t);
3426
3427 // Linux has O_TMPFILE, but linkat() does not support AT_REPLACE, so it's
3428 // useless when we have to make up a bogus path name to do the rename()
3429 // anyway.
3430 if (native_os == .linux and !options.replace) tmpfile: {
3431 const dest_dirname = Dir.path.dirname(dest_path);
3432 if (dest_dirname) |dirname| {
3433 // This has a nice side effect of preemptively triggering EISDIR or
3434 // ENOENT, avoiding the ambiguity below.
3435 dir.createDirPath(t_io, dirname) catch |err| switch (err) {
3436 // None of these make sense in this context.
3437 error.IsDir,
3438 error.Streaming,
3439 error.DiskQuota,
3440 error.PathAlreadyExists,
3441 error.LinkQuotaExceeded,
3442 error.SharingViolation,
3443 error.PipeBusy,
3444 error.FileTooBig,
3445 error.DeviceBusy,
3446 error.FileLocksUnsupported,
3447 error.FileBusy,
3448 => return error.Unexpected,
3449
3450 else => |e| return e,
3451 };
3452 }
3453
3454 var path_buffer: [posix.PATH_MAX]u8 = undefined;
3455 const sub_path_posix = try pathToPosix(dest_dirname orelse ".", &path_buffer);
3456
3457 const flags: posix.O = .{
3458 .ACCMODE = .RDWR,
3459 .TMPFILE = true,
3460 .CLOEXEC = true,
3461 };
3462
3463 const syscall: Syscall = try .start();
3464 while (true) {
3465 const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode());
3466 switch (posix.errno(rc)) {
3467 .SUCCESS => {
3468 syscall.finish();
3469 return .{
3470 .file = .{ .handle = @intCast(rc) },
3471 .file_basename_hex = 0,
3472 .dest_sub_path = dest_path,
3473 .file_open = true,
3474 .file_exists = false,
3475 .close_dir_on_deinit = false,
3476 .dir = dir,
3477 };
3478 },
3479 .INTR => {
3480 try syscall.checkCancel();
3481 continue;
3482 },
3483 .ISDIR, .NOENT => {
3484 // Ambiguous error code. It might mean the file system
3485 // does not support O_TMPFILE. Therefore, we must fall
3486 // back to not using O_TMPFILE.
3487 syscall.finish();
3488 break :tmpfile;
3489 },
3490 .INVAL => return syscall.fail(error.BadPathName),
3491 .ACCES => return syscall.fail(error.AccessDenied),
3492 .LOOP => return syscall.fail(error.SymLinkLoop),
3493 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
3494 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
3495 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
3496 .NODEV => return syscall.fail(error.NoDevice),
3497 .NOMEM => return syscall.fail(error.SystemResources),
3498 .NOSPC => return syscall.fail(error.NoSpaceLeft),
3499 .NOTDIR => return syscall.fail(error.NotDir),
3500 .PERM => return syscall.fail(error.PermissionDenied),
3501 .AGAIN => return syscall.fail(error.WouldBlock),
3502 .NXIO => return syscall.fail(error.NoDevice),
3503 .ILSEQ => return syscall.fail(error.BadPathName),
3504 else => |err| return syscall.unexpectedErrno(err),
3505 }
3506 }
3507 }
3508
3509 if (Dir.path.dirname(dest_path)) |dirname| {
3510 const new_dir = if (options.make_path)
3511 dir.createDirPathOpen(t_io, dirname, .{}) catch |err| switch (err) {
3512 // None of these make sense in this context.
3513 error.IsDir,
3514 error.Streaming,
3515 error.DiskQuota,
3516 error.PathAlreadyExists,
3517 error.LinkQuotaExceeded,
3518 error.SharingViolation,
3519 error.PipeBusy,
3520 error.FileTooBig,
3521 error.FileLocksUnsupported,
3522 error.FileBusy,
3523 error.DeviceBusy,
3524 => return error.Unexpected,
3525
3526 else => |e| return e,
3527 }
3528 else
3529 try dir.openDir(t_io, dirname, .{});
3530
3531 return atomicFileInit(t_io, Dir.path.basename(dest_path), options.permissions, new_dir, true);
3532 }
3533
3534 return atomicFileInit(t_io, dest_path, options.permissions, dir, false);
3535}
3536
3537fn atomicFileInit(
3538 t_io: Io,
3539 dest_basename: []const u8,
3540 permissions: File.Permissions,
3541 dir: Dir,
3542 close_dir_on_deinit: bool,
3543) Dir.CreateFileAtomicError!File.Atomic {
3544 while (true) {
3545 const random_integer = std.crypto.random.int(u64);
3546 const tmp_sub_path = std.fmt.hex(random_integer);
3547 const file = dir.createFile(t_io, &tmp_sub_path, .{
3548 .permissions = permissions,
3549 .exclusive = true,
3550 }) catch |err| switch (err) {
3551 error.PathAlreadyExists => continue,
3552 error.DeviceBusy => continue,
3553 error.FileBusy => continue,
3554 error.SharingViolation => continue,
3555
3556 error.IsDir => return error.Unexpected, // No path components.
3557 error.FileTooBig => return error.Unexpected, // Creating, not opening.
3558 error.FileLocksUnsupported => return error.Unexpected, // Not asking for locks.
3559 error.PipeBusy => return error.Unexpected, // Not opening a pipe.
3560
3561 else => |e| return e,
3562 };
3563 return .{
3564 .file = file,
3565 .file_basename_hex = random_integer,
3566 .dest_sub_path = dest_basename,
3567 .file_open = true,
3568 .file_exists = true,
3569 .close_dir_on_deinit = close_dir_on_deinit,
3570 .dir = dir,
3571 };
3572 }
3573}
3574
34163575const dirOpenFile = switch (native_os) {
34173576 .windows => dirOpenFileWindows,
34183577 .wasi => dirOpenFileWasi,
......@@ -3925,7 +4084,7 @@ fn dirOpenDirPosix(
39254084 .NOMEM => return error.SystemResources,
39264085 .NOTDIR => return error.NotDir,
39274086 .PERM => return error.PermissionDenied,
3928 .BUSY => return error.DeviceBusy,
4087 .BUSY => |err| return errnoBug(err), // O_EXCL not passed
39294088 .NXIO => return error.NoDevice,
39304089 .ILSEQ => return error.BadPathName,
39314090 else => |err| return posix.unexpectedErrno(err),
lib/std/zig/system.zig-1
......@@ -793,7 +793,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
793793 var dir = cwd.openDir(io, rpath, .{}) catch |err| switch (err) {
794794 error.NameTooLong => return error.Unexpected,
795795 error.BadPathName => return error.Unexpected,
796 error.DeviceBusy => return error.Unexpected,
797796 error.NetworkNotFound => return error.Unexpected, // Windows-only
798797
799798 error.FileNotFound => return error.GLibCNotFound,