authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-06 05:32:16+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-06 05:32:16+01:00
logc906f7d2e7d66bdc07fe8815d1fb2bb3d6664478
tree13057f5eec03fdb2182cc012e6b1b2941ae30caa
parent6ab1159e815fdc7ce11fa49235509a43da74f899
parent06130c5e61160b20305c9e6a621fa1c4d0df7858

Merge pull request 'std: rework atomic file / temp file API' (#30686) from std.Io.File.Atomic into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30686

13 files changed, 450 insertions(+), 207 deletions(-)

lib/std/Io.zig+2
...@@ -667,6 +667,7 @@ pub const VTable = struct {...@@ -667,6 +667,7 @@ pub const VTable = struct {
667 dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat,667 dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat,
668 dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void,668 dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void,
669 dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File,669 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,
670 dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File,671 dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File,
671 dirClose: *const fn (?*anyopaque, []const Dir) void,672 dirClose: *const fn (?*anyopaque, []const Dir) void,
672 dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize,673 dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize,
...@@ -710,6 +711,7 @@ pub const VTable = struct {...@@ -710,6 +711,7 @@ pub const VTable = struct {
710 fileUnlock: *const fn (?*anyopaque, File) void,711 fileUnlock: *const fn (?*anyopaque, File) void,
711 fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void,712 fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void,
712 fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize,713 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
714 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,716 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
715 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,717 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{...@@ -454,7 +454,6 @@ pub const OpenError = error{
454 SystemFdQuotaExceeded,454 SystemFdQuotaExceeded,
455 NoDevice,455 NoDevice,
456 SystemResources,456 SystemResources,
457 DeviceBusy,
458 /// On Windows, `\\server` or `\\server\share` was not found.457 /// On Windows, `\\server` or `\\server\share` was not found.
459 NetworkNotFound,458 NetworkNotFound,
460} || PathNameError || Io.Cancelable || Io.UnexpectedError;459} || PathNameError || Io.Cancelable || Io.UnexpectedError;
...@@ -598,30 +597,29 @@ pub fn updateFile(...@@ -598,30 +597,29 @@ pub fn updateFile(
598 }597 }
599 }598 }
600599
601 if (path.dirname(dest_path)) |dirname| {600 var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{
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, .{
607 .permissions = actual_permissions,601 .permissions = actual_permissions,
608 .write_buffer = &buffer,602 .make_path = true,
603 .replace = true,
609 });604 });
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
612 var src_reader: File.Reader = .initSize(src_file, io, &.{}, src_stat.size);610 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
615 _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {613 _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
616 error.ReadFailed => return src_reader.err.?,614 error.ReadFailed => return src_reader.err.?,
617 error.WriteFailed => return atomic_file.file_writer.err.?,615 error.WriteFailed => return file_writer.err.?,
618 };616 };
619 try atomic_file.flush();617 try file_writer.flush();
620 try atomic_file.file_writer.file.setTimestamps(io, .{618 try file_writer.file.setTimestamps(io, .{
621 .access_timestamp = .init(src_stat.atime),619 .access_timestamp = .init(src_stat.atime),
622 .modify_timestamp = .init(src_stat.mtime),620 .modify_timestamp = .init(src_stat.mtime),
623 });621 });
624 try atomic_file.renameIntoPlace();622 try atomic_file.replace(io);
625 return .stale;623 return .stale;
626}624}
627625
...@@ -995,27 +993,9 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename...@@ -995,27 +993,9 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename
995 return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);993 return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);
996}994}
997995
998pub const HardLinkOptions = struct {996pub const HardLinkOptions = File.HardLinkOptions;
999 follow_symlinks: bool = true,
1000};
1001997
1002pub const HardLinkError = error{998pub const HardLinkError = File.HardLinkError;
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;
1019999
1020pub fn hardLink(1000pub fn hardLink(
1021 old_dir: Dir,1001 old_dir: Dir,
...@@ -1251,7 +1231,6 @@ pub const DeleteTreeError = error{...@@ -1251,7 +1231,6 @@ pub const DeleteTreeError = error{
1251 ReadOnlyFileSystem,1231 ReadOnlyFileSystem,
1252 FileSystem,1232 FileSystem,
1253 FileBusy,1233 FileBusy,
1254 DeviceBusy,
1255 /// One of the path components was not a directory.1234 /// One of the path components was not a directory.
1256 /// This error is unreachable if `sub_path` does not contain a path separator.1235 /// This error is unreachable if `sub_path` does not contain a path separator.
1257 NotDir,1236 NotDir,
...@@ -1322,7 +1301,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {...@@ -1322,7 +1301,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
1322 error.Unexpected,1301 error.Unexpected,
1323 error.BadPathName,1302 error.BadPathName,
1324 error.NetworkNotFound,1303 error.NetworkNotFound,
1325 error.DeviceBusy,
1326 error.Canceled,1304 error.Canceled,
1327 => |e| return e,1305 => |e| return e,
1328 };1306 };
...@@ -1417,7 +1395,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {...@@ -1417,7 +1395,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
1417 error.Unexpected,1395 error.Unexpected,
1418 error.BadPathName,1396 error.BadPathName,
1419 error.NetworkNotFound,1397 error.NetworkNotFound,
1420 error.DeviceBusy,
1421 error.Canceled,1398 error.Canceled,
1422 => |e| return e,1399 => |e| return e,
1423 };1400 };
...@@ -1522,7 +1499,6 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8,...@@ -1522,7 +1499,6 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8,
1522 error.Unexpected,1499 error.Unexpected,
1523 error.BadPathName,1500 error.BadPathName,
1524 error.NetworkNotFound,1501 error.NetworkNotFound,
1525 error.DeviceBusy,
1526 error.Canceled,1502 error.Canceled,
1527 => |e| return e,1503 => |e| return e,
1528 };1504 };
...@@ -1619,7 +1595,6 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin...@@ -1619,7 +1595,6 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin
1619 error.SystemResources,1595 error.SystemResources,
1620 error.Unexpected,1596 error.Unexpected,
1621 error.BadPathName,1597 error.BadPathName,
1622 error.DeviceBusy,
1623 error.NetworkNotFound,1598 error.NetworkNotFound,
1624 error.Canceled,1599 error.Canceled,
1625 => |e| return e,1600 => |e| return e,
...@@ -1658,15 +1633,18 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin...@@ -1658,15 +1633,18 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin
1658pub const CopyFileOptions = struct {1633pub const CopyFileOptions = struct {
1659 /// When this is `null` the permissions are copied from the source file.1634 /// When this is `null` the permissions are copied from the source file.
1660 permissions: ?File.Permissions = null,1635 permissions: ?File.Permissions = null,
1636 make_path: bool = false,
1637 replace: bool = true,
1661};1638};
16621639
1663pub const CopyFileError = File.OpenError || File.StatError ||1640pub const CopyFileError = File.OpenError || File.StatError ||
1664 File.Atomic.InitError || File.Atomic.FinishError ||1641 CreateFileAtomicError || File.Atomic.ReplaceError || File.Atomic.LinkError ||
1665 File.Reader.Error || File.Writer.Error || error{InvalidFileName};1642 File.Reader.Error || File.Writer.Error || error{InvalidFileName};
16661643
1667/// Atomically creates a new file at `dest_path` within `dest_dir` with the1644/// Atomically creates a new file at `dest_path` within `dest_dir` with the
1668/// same contents as `source_path` within `source_dir`, overwriting any already1645/// same contents as `source_path` within `source_dir`.
1669/// existing file.1646///
1647/// Whether to overwrite the existing file is determined by `options`.
1670///1648///
1671/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and1649/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
1672/// readily available, there is a possibility of power loss or application1650/// readily available, there is a possibility of power loss or application
...@@ -1695,19 +1673,27 @@ pub fn copyFile(...@@ -1695,19 +1673,27 @@ pub fn copyFile(
1695 break :blk st.permissions;1673 break :blk st.permissions;
1696 };1674 };
16971675
1698 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.1676 var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{
1699 var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
1700 .permissions = permissions,1677 .permissions = permissions,
1701 .write_buffer = &buffer,1678 .make_path = options.make_path,
1679 .replace = options.replace,
1702 });1680 });
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) {
1706 error.ReadFailed => return file_reader.err.?,1687 error.ReadFailed => return file_reader.err.?,
1707 error.WriteFailed => return atomic_file.file_writer.err.?,1688 error.WriteFailed => return file_writer.err.?,
1708 };1689 };
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 }
1711}1697}
17121698
1713/// Same as `copyFile`, except asserts that both `source_path` and `dest_path`1699/// Same as `copyFile`, except asserts that both `source_path` and `dest_path`
...@@ -1730,33 +1716,65 @@ pub fn copyFileAbsolute(...@@ -1730,33 +1716,65 @@ pub fn copyFileAbsolute(
17301716
1731test copyFileAbsolute {}1717test copyFileAbsolute {}
17321718
1733pub const AtomicFileOptions = struct {1719pub const CreateFileAtomicOptions = struct {
1734 permissions: File.Permissions = .default_file,1720 permissions: File.Permissions = .default_file,
1735 make_path: bool = false,1721 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,
1737};1728};
17381729
1739/// Directly access the `.file` field, and then call `File.Atomic.finish` to1730pub const CreateFileAtomicError = error{
1740/// atomically replace `dest_path` with contents.1731 NoDevice,
1741///1732 /// On Windows, `\\server` or `\\server\share` was not found.
1742/// Always call `File.Atomic.deinit` to clean up, regardless of whether1733 NetworkNotFound,
1743/// `File.Atomic.finish` succeeded. `dest_path` must remain valid until1734 /// On Windows, antivirus software is enabled by default. It can be
1744/// `File.Atomic.deinit` is called.1735 /// disabled, but Windows Update sometimes ignores the user's preference
1745///1736 /// and re-enables it. When enabled, antivirus software on Windows
1746/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).1737 /// intercepts file system operations and makes them significantly slower
1747/// On WASI, `dest_path` should be encoded as valid UTF-8.1738 /// in addition to possibly failing with this error code.
1748/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.1739 AntivirusInterference,
1749pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic {1740 /// In WASI, this error may occur when the file descriptor does
1750 if (path.dirname(dest_path)) |dirname| {1741 /// not hold the required rights to open a new resource relative to it.
1751 const dir = if (options.make_path)1742 AccessDenied,
1752 try parent.createDirPathOpen(io, dirname, .{})1743 PermissionDenied,
1753 else1744 SymLinkLoop,
1754 try parent.openDir(io, dirname, .{});1745 ProcessFdQuotaExceeded,
17551746 SystemFdQuotaExceeded,
1756 return .init(io, path.basename(dest_path), options.permissions, dir, true, options.write_buffer);1747 /// Either:
1757 } else {1748 /// * One of the path components does not exist.
1758 return .init(io, dest_path, options.permissions, parent, false, options.write_buffer);1749 /// * Cwd was used, but cwd has been deleted.
1759 }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);
1760}1778}
17611779
1762pub const SetPermissionsError = File.SetPermissionsError;1780pub const SetPermissionsError = File.SetPermissionsError;
lib/std/Io/File.zig+33-1
...@@ -278,7 +278,7 @@ pub const OpenError = error{...@@ -278,7 +278,7 @@ pub const OpenError = error{
278 FileBusy,278 FileBusy,
279 /// Non-blocking was requested and the operation cannot return immediately.279 /// Non-blocking was requested and the operation cannot return immediately.
280 WouldBlock,280 WouldBlock,
281} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;281} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
282282
283pub fn close(file: File, io: Io) void {283pub fn close(file: File, io: Io) void {
284 return io.vtable.fileClose(io.userdata, (&file)[0..1]);284 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 {...@@ -708,6 +708,38 @@ pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize {
708 return io.vtable.fileRealPath(io.userdata, file, out_buffer);708 return io.vtable.fileRealPath(io.userdata, file, out_buffer);
709}709}
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
711test {743test {
712 _ = Reader;744 _ = Reader;
713 _ = Writer;745 _ = Writer;
lib/std/Io/File/Atomic.zig+43-62
...@@ -6,97 +6,78 @@ const File = std.Io.File;...@@ -6,97 +6,78 @@ const File = std.Io.File;
6const Dir = std.Io.Dir;6const Dir = std.Io.Dir;
7const assert = std.debug.assert;7const assert = std.debug.assert;
88
9file_writer: File.Writer,9file: File,
10random_integer: u64,10file_basename_hex: u64,
11dest_basename: []const u8,
12file_open: bool,11file_open: bool,
13file_exists: bool,12file_exists: bool,
14close_dir_on_deinit: bool,13
15dir: Dir,14dir: 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.19pub const InitError = File.OpenError;
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;
5320
21/// To release all resources, always call `deinit`, even after a successful
22/// `finish`.
23pub fn deinit(af: *Atomic, io: Io) void {
54 if (af.file_open) {24 if (af.file_open) {
55 af.file_writer.file.close(io);25 af.file.close(io);
56 af.file_open = false;26 af.file_open = false;
57 }27 }
58 if (af.file_exists) {28 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);
60 af.dir.deleteFile(io, &tmp_sub_path) catch {};30 af.dir.deleteFile(io, &tmp_sub_path) catch {};
61 af.file_exists = false;31 af.file_exists = false;
62 }32 }
63 if (af.close_dir_on_deinit) {33 if (af.close_dir_on_deinit) {
64 af.dir.close(io);34 af.dir.close(io);
35 af.close_dir_on_deinit = false;
65 }36 }
66 af.* = undefined;37 af.* = undefined;
67}38}
6839
69pub const FlushError = File.Writer.Error;40pub const LinkError = Dir.HardLinkError;
7041
71pub fn flush(af: *Atomic) FlushError!void {42/// Atomically materializes the file into place, failing with
72 af.file_writer.interface.flush() catch |err| switch (err) {43/// `error.PathAlreadyExists` if something already exists there.
73 error.WriteFailed => return af.file_writer.err.?,44pub fn link(af: *Atomic, io: Io) LinkError!void {
74 };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 }
75}60}
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///
79/// On Windows, this function introduces a period of time where some file70/// On Windows, this function introduces a period of time where some file
80/// system operations on the destination file will result in71/// system operations on the destination file will result in
81/// `error.AccessDenied`, including rename operations (such as the one used in72/// `error.AccessDenied`, including rename operations (such as the one used in
82/// this function).73/// this function).
83pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void {74pub fn replace(af: *Atomic, io: Io) ReplaceError!void {
84 const io = af.file_writer.io;75 assert(af.file_exists); // Wrong value for `CreateFileAtomicOptions.replace`.
85
86 assert(af.file_exists);
87 if (af.file_open) {76 if (af.file_open) {
88 af.file_writer.file.close(io);77 af.file.close(io);
89 af.file_open = false;78 af.file_open = false;
90 }79 }
91 const tmp_sub_path = std.fmt.hex(af.random_integer);80 const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
92 try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io);81 try af.dir.rename(&tmp_sub_path, af.dir, af.dest_sub_path, io);
93 af.file_exists = false;82 af.file_exists = false;
94}83}
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 {...@@ -272,3 +272,11 @@ pub fn end(w: *Writer) EndError!void {
272 => {},272 => {},
273 }273 }
274}274}
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+228-43
...@@ -1403,6 +1403,7 @@ pub fn io(t: *Threaded) Io {...@@ -1403,6 +1403,7 @@ pub fn io(t: *Threaded) Io {
1403 .dirStatFile = dirStatFile,1403 .dirStatFile = dirStatFile,
1404 .dirAccess = dirAccess,1404 .dirAccess = dirAccess,
1405 .dirCreateFile = dirCreateFile,1405 .dirCreateFile = dirCreateFile,
1406 .dirCreateFileAtomic = dirCreateFileAtomic,
1406 .dirOpenFile = dirOpenFile,1407 .dirOpenFile = dirOpenFile,
1407 .dirOpenDir = dirOpenDir,1408 .dirOpenDir = dirOpenDir,
1408 .dirClose = dirClose,1409 .dirClose = dirClose,
...@@ -1445,6 +1446,7 @@ pub fn io(t: *Threaded) Io {...@@ -1445,6 +1446,7 @@ pub fn io(t: *Threaded) Io {
1445 .fileUnlock = fileUnlock,1446 .fileUnlock = fileUnlock,
1446 .fileDowngradeLock = fileDowngradeLock,1447 .fileDowngradeLock = fileDowngradeLock,
1447 .fileRealPath = fileRealPath,1448 .fileRealPath = fileRealPath,
1449 .fileHardLink = fileHardLink,
14481450
1449 .processExecutableOpen = processExecutableOpen,1451 .processExecutableOpen = processExecutableOpen,
1450 .processExecutablePath = processExecutablePath,1452 .processExecutablePath = processExecutablePath,
...@@ -1549,6 +1551,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1549,6 +1551,7 @@ pub fn ioBasic(t: *Threaded) Io {
1549 .dirStatFile = dirStatFile,1551 .dirStatFile = dirStatFile,
1550 .dirAccess = dirAccess,1552 .dirAccess = dirAccess,
1551 .dirCreateFile = dirCreateFile,1553 .dirCreateFile = dirCreateFile,
1554 .dirCreateFileAtomic = dirCreateFileAtomic,
1552 .dirOpenFile = dirOpenFile,1555 .dirOpenFile = dirOpenFile,
1553 .dirOpenDir = dirOpenDir,1556 .dirOpenDir = dirOpenDir,
1554 .dirClose = dirClose,1557 .dirClose = dirClose,
...@@ -1591,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1591,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io {
1591 .fileUnlock = fileUnlock,1594 .fileUnlock = fileUnlock,
1592 .fileDowngradeLock = fileDowngradeLock,1595 .fileDowngradeLock = fileDowngradeLock,
1593 .fileRealPath = fileRealPath,1596 .fileRealPath = fileRealPath,
1597 .fileHardLink = fileHardLink,
15941598
1595 .processExecutableOpen = processExecutableOpen,1599 .processExecutableOpen = processExecutableOpen,
1596 .processExecutablePath = processExecutablePath,1600 .processExecutablePath = processExecutablePath,
...@@ -3413,6 +3417,170 @@ fn dirCreateFileWasi(...@@ -3413,6 +3417,170 @@ fn dirCreateFileWasi(
3413 }3417 }
3414}3418}
34153419
3420fn dirCreateFileAtomic(
3421 userdata: ?*anyopaque,
3422 dir: Dir,
3423 dest_path: []const u8,
3424 options: Dir.CreateFileAtomicOptions,
3425) Dir.CreateFileAtomicError!File.Atomic {
3426 const t: *Threaded = @ptrCast(@alignCast(userdata));
3427 const t_io = ioBasic(t);
3428
3429 // Linux has O_TMPFILE, but linkat() does not support AT_REPLACE, so it's
3430 // useless when we have to make up a bogus path name to do the rename()
3431 // anyway.
3432 if (native_os == .linux and !options.replace) tmpfile: {
3433 const flags: posix.O = if (@hasField(posix.O, "TMPFILE")) .{
3434 .ACCMODE = .RDWR,
3435 .TMPFILE = true,
3436 .DIRECTORY = true,
3437 .CLOEXEC = true,
3438 } else if (@hasField(posix.O, "TMPFILE0") and !@hasField(posix.O, "TMPFILE2")) .{
3439 .ACCMODE = .RDWR,
3440 .TMPFILE0 = true,
3441 .TMPFILE1 = true,
3442 .DIRECTORY = true,
3443 .CLOEXEC = true,
3444 } else break :tmpfile;
3445
3446 const dest_dirname = Dir.path.dirname(dest_path);
3447 if (dest_dirname) |dirname| {
3448 // This has a nice side effect of preemptively triggering EISDIR or
3449 // ENOENT, avoiding the ambiguity below.
3450 dir.createDirPath(t_io, dirname) catch |err| switch (err) {
3451 // None of these make sense in this context.
3452 error.IsDir,
3453 error.Streaming,
3454 error.DiskQuota,
3455 error.PathAlreadyExists,
3456 error.LinkQuotaExceeded,
3457 error.SharingViolation,
3458 error.PipeBusy,
3459 error.FileTooBig,
3460 error.DeviceBusy,
3461 error.FileLocksUnsupported,
3462 error.FileBusy,
3463 => return error.Unexpected,
3464
3465 else => |e| return e,
3466 };
3467 }
3468
3469 var path_buffer: [posix.PATH_MAX]u8 = undefined;
3470 const sub_path_posix = try pathToPosix(dest_dirname orelse ".", &path_buffer);
3471
3472 const syscall: Syscall = try .start();
3473 while (true) {
3474 const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode());
3475 switch (posix.errno(rc)) {
3476 .SUCCESS => {
3477 syscall.finish();
3478 return .{
3479 .file = .{ .handle = @intCast(rc) },
3480 .file_basename_hex = 0,
3481 .dest_sub_path = dest_path,
3482 .file_open = true,
3483 .file_exists = false,
3484 .close_dir_on_deinit = false,
3485 .dir = dir,
3486 };
3487 },
3488 .INTR => {
3489 try syscall.checkCancel();
3490 continue;
3491 },
3492 .ISDIR, .NOENT => {
3493 // Ambiguous error code. It might mean the file system
3494 // does not support O_TMPFILE. Therefore, we must fall
3495 // back to not using O_TMPFILE.
3496 syscall.finish();
3497 break :tmpfile;
3498 },
3499 .INVAL => return syscall.fail(error.BadPathName),
3500 .ACCES => return syscall.fail(error.AccessDenied),
3501 .LOOP => return syscall.fail(error.SymLinkLoop),
3502 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
3503 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
3504 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
3505 .NODEV => return syscall.fail(error.NoDevice),
3506 .NOMEM => return syscall.fail(error.SystemResources),
3507 .NOSPC => return syscall.fail(error.NoSpaceLeft),
3508 .NOTDIR => return syscall.fail(error.NotDir),
3509 .PERM => return syscall.fail(error.PermissionDenied),
3510 .AGAIN => return syscall.fail(error.WouldBlock),
3511 .NXIO => return syscall.fail(error.NoDevice),
3512 .ILSEQ => return syscall.fail(error.BadPathName),
3513 else => |err| return syscall.unexpectedErrno(err),
3514 }
3515 }
3516 }
3517
3518 if (Dir.path.dirname(dest_path)) |dirname| {
3519 const new_dir = if (options.make_path)
3520 dir.createDirPathOpen(t_io, dirname, .{}) catch |err| switch (err) {
3521 // None of these make sense in this context.
3522 error.IsDir,
3523 error.Streaming,
3524 error.DiskQuota,
3525 error.PathAlreadyExists,
3526 error.LinkQuotaExceeded,
3527 error.SharingViolation,
3528 error.PipeBusy,
3529 error.FileTooBig,
3530 error.FileLocksUnsupported,
3531 error.FileBusy,
3532 error.DeviceBusy,
3533 => return error.Unexpected,
3534
3535 else => |e| return e,
3536 }
3537 else
3538 try dir.openDir(t_io, dirname, .{});
3539
3540 return atomicFileInit(t_io, Dir.path.basename(dest_path), options.permissions, new_dir, true);
3541 }
3542
3543 return atomicFileInit(t_io, dest_path, options.permissions, dir, false);
3544}
3545
3546fn atomicFileInit(
3547 t_io: Io,
3548 dest_basename: []const u8,
3549 permissions: File.Permissions,
3550 dir: Dir,
3551 close_dir_on_deinit: bool,
3552) Dir.CreateFileAtomicError!File.Atomic {
3553 while (true) {
3554 const random_integer = std.crypto.random.int(u64);
3555 const tmp_sub_path = std.fmt.hex(random_integer);
3556 const file = dir.createFile(t_io, &tmp_sub_path, .{
3557 .permissions = permissions,
3558 .exclusive = true,
3559 }) catch |err| switch (err) {
3560 error.PathAlreadyExists => continue,
3561 error.DeviceBusy => continue,
3562 error.FileBusy => continue,
3563 error.SharingViolation => continue,
3564
3565 error.IsDir => return error.Unexpected, // No path components.
3566 error.FileTooBig => return error.Unexpected, // Creating, not opening.
3567 error.FileLocksUnsupported => return error.Unexpected, // Not asking for locks.
3568 error.PipeBusy => return error.Unexpected, // Not opening a pipe.
3569
3570 else => |e| return e,
3571 };
3572 return .{
3573 .file = file,
3574 .file_basename_hex = random_integer,
3575 .dest_sub_path = dest_basename,
3576 .file_open = true,
3577 .file_exists = true,
3578 .close_dir_on_deinit = close_dir_on_deinit,
3579 .dir = dir,
3580 };
3581 }
3582}
3583
3416const dirOpenFile = switch (native_os) {3584const dirOpenFile = switch (native_os) {
3417 .windows => dirOpenFileWindows,3585 .windows => dirOpenFileWindows,
3418 .wasi => dirOpenFileWasi,3586 .wasi => dirOpenFileWasi,
...@@ -3925,7 +4093,7 @@ fn dirOpenDirPosix(...@@ -3925,7 +4093,7 @@ fn dirOpenDirPosix(
3925 .NOMEM => return error.SystemResources,4093 .NOMEM => return error.SystemResources,
3926 .NOTDIR => return error.NotDir,4094 .NOTDIR => return error.NotDir,
3927 .PERM => return error.PermissionDenied,4095 .PERM => return error.PermissionDenied,
3928 .BUSY => return error.DeviceBusy,4096 .BUSY => |err| return errnoBug(err), // O_EXCL not passed
3929 .NXIO => return error.NoDevice,4097 .NXIO => return error.NoDevice,
3930 .ILSEQ => return error.BadPathName,4098 .ILSEQ => return error.BadPathName,
3931 else => |err| return posix.unexpectedErrno(err),4099 else => |err| return posix.unexpectedErrno(err),
...@@ -4985,6 +5153,64 @@ fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {...@@ -4985,6 +5153,64 @@ fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
4985 comptime unreachable;5153 comptime unreachable;
4986}5154}
49875155
5156fn fileHardLink(
5157 userdata: ?*anyopaque,
5158 file: File,
5159 new_dir: Dir,
5160 new_sub_path: []const u8,
5161 options: File.HardLinkOptions,
5162) File.HardLinkError!void {
5163 _ = userdata;
5164 if (native_os != .linux) return error.OperationUnsupported;
5165
5166 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
5167 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
5168
5169 const flags: u32 = if (!options.follow_symlinks)
5170 posix.AT.SYMLINK_NOFOLLOW | posix.AT.EMPTY_PATH
5171 else
5172 posix.AT.EMPTY_PATH;
5173
5174 return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags);
5175}
5176
5177fn linkat(
5178 old_dir: posix.fd_t,
5179 old_path: [*:0]const u8,
5180 new_dir: posix.fd_t,
5181 new_path: [*:0]const u8,
5182 flags: u32,
5183) File.HardLinkError!void {
5184 const syscall: Syscall = try .start();
5185 while (true) {
5186 switch (posix.errno(posix.system.linkat(old_dir, old_path, new_dir, new_path, flags))) {
5187 .SUCCESS => return syscall.finish(),
5188 .INTR => {
5189 try syscall.checkCancel();
5190 continue;
5191 },
5192 .ACCES => return syscall.fail(error.AccessDenied),
5193 .DQUOT => return syscall.fail(error.DiskQuota),
5194 .EXIST => return syscall.fail(error.PathAlreadyExists),
5195 .IO => return syscall.fail(error.HardwareFailure),
5196 .LOOP => return syscall.fail(error.SymLinkLoop),
5197 .MLINK => return syscall.fail(error.LinkQuotaExceeded),
5198 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
5199 .NOENT => return syscall.fail(error.FileNotFound),
5200 .NOMEM => return syscall.fail(error.SystemResources),
5201 .NOSPC => return syscall.fail(error.NoSpaceLeft),
5202 .NOTDIR => return syscall.fail(error.NotDir),
5203 .PERM => return syscall.fail(error.PermissionDenied),
5204 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
5205 .XDEV => return syscall.fail(error.NotSameFileSystem),
5206 .ILSEQ => return syscall.fail(error.BadPathName),
5207 .FAULT => |err| return syscall.errnoBug(err),
5208 .INVAL => |err| return syscall.errnoBug(err),
5209 else => |err| return syscall.unexpectedErrno(err),
5210 }
5211 }
5212}
5213
4988const dirDeleteFile = switch (native_os) {5214const dirDeleteFile = switch (native_os) {
4989 .windows => dirDeleteFileWindows,5215 .windows => dirDeleteFileWindows,
4990 .wasi => dirDeleteFileWasi,5216 .wasi => dirDeleteFileWasi,
...@@ -7325,7 +7551,6 @@ fn dirOpenDirWasi(...@@ -7325,7 +7551,6 @@ fn dirOpenDirWasi(
7325 .NOMEM => return error.SystemResources,7551 .NOMEM => return error.SystemResources,
7326 .NOTDIR => return error.NotDir,7552 .NOTDIR => return error.NotDir,
7327 .PERM => return error.PermissionDenied,7553 .PERM => return error.PermissionDenied,
7328 .BUSY => return error.DeviceBusy,
7329 .NOTCAPABLE => return error.AccessDenied,7554 .NOTCAPABLE => return error.AccessDenied,
7330 .ILSEQ => return error.BadPathName,7555 .ILSEQ => return error.BadPathName,
7331 else => |err| return posix.unexpectedErrno(err),7556 else => |err| return posix.unexpectedErrno(err),
...@@ -7401,46 +7626,7 @@ fn dirHardLink(...@@ -7401,46 +7626,7 @@ fn dirHardLink(
7401 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);7626 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
74027627
7403 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;7628 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
74047629 return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags);
7405 const syscall: Syscall = try .start();
7406 while (true) {
7407 switch (posix.errno(posix.system.linkat(
7408 old_dir.handle,
7409 old_sub_path_posix,
7410 new_dir.handle,
7411 new_sub_path_posix,
7412 flags,
7413 ))) {
7414 .SUCCESS => return syscall.finish(),
7415 .INTR => {
7416 try syscall.checkCancel();
7417 continue;
7418 },
7419 else => |e| {
7420 syscall.finish();
7421 switch (e) {
7422 .ACCES => return error.AccessDenied,
7423 .DQUOT => return error.DiskQuota,
7424 .EXIST => return error.PathAlreadyExists,
7425 .FAULT => |err| return errnoBug(err),
7426 .IO => return error.HardwareFailure,
7427 .LOOP => return error.SymLinkLoop,
7428 .MLINK => return error.LinkQuotaExceeded,
7429 .NAMETOOLONG => return error.NameTooLong,
7430 .NOENT => return error.FileNotFound,
7431 .NOMEM => return error.SystemResources,
7432 .NOSPC => return error.NoSpaceLeft,
7433 .NOTDIR => return error.NotDir,
7434 .PERM => return error.PermissionDenied,
7435 .ROFS => return error.ReadOnlyFileSystem,
7436 .XDEV => return error.NotSameFileSystem,
7437 .INVAL => |err| return errnoBug(err),
7438 .ILSEQ => return error.BadPathName,
7439 else => |err| return posix.unexpectedErrno(err),
7440 }
7441 },
7442 }
7443 }
7444}7630}
74457631
7446fn fileClose(userdata: ?*anyopaque, files: []const File) void {7632fn fileClose(userdata: ?*anyopaque, files: []const File) void {
...@@ -13831,7 +14017,6 @@ fn windowsCreateProcessPathExt(...@@ -13831,7 +14017,6 @@ fn windowsCreateProcessPathExt(
13831 error.NetworkNotFound,14017 error.NetworkNotFound,
13832 error.NameTooLong,14018 error.NameTooLong,
13833 error.BadPathName,14019 error.BadPathName,
13834 error.DeviceBusy,
13835 => return error.FileNotFound,14020 => return error.FileNotFound,
13836 };14021 };
13837 };14022 };
lib/std/c.zig+2
...@@ -8427,6 +8427,7 @@ pub const O = switch (native_os) {...@@ -8427,6 +8427,7 @@ pub const O = switch (native_os) {
8427 CLOEXEC: bool = false,8427 CLOEXEC: bool = false,
8428 SYNC: bool = false,8428 SYNC: bool = false,
8429 PATH: bool = false,8429 PATH: bool = false,
8430 /// This is typically invalid without also setting `DIRECTORY`.
8430 TMPFILE: bool = false,8431 TMPFILE: bool = false,
8431 _: u9 = 0,8432 _: u9 = 0,
8432 },8433 },
...@@ -8615,6 +8616,7 @@ pub const O = switch (native_os) {...@@ -8615,6 +8616,7 @@ pub const O = switch (native_os) {
8615 _19: u1 = 0,8616 _19: u1 = 0,
8616 CLOEXEC: bool = false,8617 CLOEXEC: bool = false,
8617 PATH: bool = false,8618 PATH: bool = false,
8619 /// This is typically invalid without also setting `DIRECTORY`.
8618 TMPFILE: bool = false,8620 TMPFILE: bool = false,
8619 _: u9 = 0,8621 _: u9 = 0,
8620 },8622 },
lib/std/fs/test.zig+4-5
...@@ -1652,11 +1652,10 @@ test "AtomicFile" {...@@ -1652,11 +1652,10 @@ test "AtomicFile" {
1652 ;1652 ;
16531653
1654 {1654 {
1655 var buffer: [100]u8 = undefined;1655 var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = true });
1656 var af = try ctx.dir.atomicFile(io, test_out_file, .{ .write_buffer = &buffer });1656 defer af.deinit(io);
1657 defer af.deinit();1657 try af.file.writeStreamingAll(io, test_content);
1658 try af.file_writer.interface.writeAll(test_content);1658 try af.replace(io);
1659 try af.finish();
1660 }1659 }
1661 const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999));1660 const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999));
1662 try expectEqualStrings(test_content, content);1661 try expectEqualStrings(test_content, content);
lib/std/os/linux.zig+12-3
...@@ -324,6 +324,7 @@ pub const O = switch (native_arch) {...@@ -324,6 +324,7 @@ pub const O = switch (native_arch) {
324 CLOEXEC: bool = false,324 CLOEXEC: bool = false,
325 SYNC: bool = false,325 SYNC: bool = false,
326 PATH: bool = false,326 PATH: bool = false,
327 /// This is typically invalid without also setting `DIRECTORY`.
327 TMPFILE: bool = false,328 TMPFILE: bool = false,
328 _23: u9 = 0,329 _23: u9 = 0,
329 },330 },
...@@ -346,6 +347,7 @@ pub const O = switch (native_arch) {...@@ -346,6 +347,7 @@ pub const O = switch (native_arch) {
346 CLOEXEC: bool = false,347 CLOEXEC: bool = false,
347 SYNC: bool = false,348 SYNC: bool = false,
348 PATH: bool = false,349 PATH: bool = false,
350 /// This is typically invalid without also setting `DIRECTORY`.
349 TMPFILE: bool = false,351 TMPFILE: bool = false,
350 _23: u9 = 0,352 _23: u9 = 0,
351 },353 },
...@@ -368,6 +370,7 @@ pub const O = switch (native_arch) {...@@ -368,6 +370,7 @@ pub const O = switch (native_arch) {
368 CLOEXEC: bool = false,370 CLOEXEC: bool = false,
369 SYNC: bool = false,371 SYNC: bool = false,
370 PATH: bool = false,372 PATH: bool = false,
373 /// This is typically invalid without also setting `DIRECTORY`.
371 TMPFILE: bool = false,374 TMPFILE: bool = false,
372 _23: u9 = 0,375 _23: u9 = 0,
373 },376 },
...@@ -393,6 +396,7 @@ pub const O = switch (native_arch) {...@@ -393,6 +396,7 @@ pub const O = switch (native_arch) {
393 CLOEXEC: bool = false,396 CLOEXEC: bool = false,
394 SYNC: bool = false,397 SYNC: bool = false,
395 PATH: bool = false,398 PATH: bool = false,
399 /// This is typically invalid without also setting `DIRECTORY`.
396 TMPFILE: bool = false,400 TMPFILE: bool = false,
397 _27: u6 = 0,401 _27: u6 = 0,
398 },402 },
...@@ -417,6 +421,7 @@ pub const O = switch (native_arch) {...@@ -417,6 +421,7 @@ pub const O = switch (native_arch) {
417 CLOEXEC: bool = false,421 CLOEXEC: bool = false,
418 _20: u1 = 0,422 _20: u1 = 0,
419 PATH: bool = false,423 PATH: bool = false,
424 /// This is typically invalid without also setting `DIRECTORY`.
420 TMPFILE: bool = false,425 TMPFILE: bool = false,
421 _23: u9 = 0,426 _23: u9 = 0,
422 },427 },
...@@ -439,6 +444,7 @@ pub const O = switch (native_arch) {...@@ -439,6 +444,7 @@ pub const O = switch (native_arch) {
439 CLOEXEC: bool = false,444 CLOEXEC: bool = false,
440 SYNC: bool = false,445 SYNC: bool = false,
441 PATH: bool = false,446 PATH: bool = false,
447 /// This is typically invalid without also setting `DIRECTORY`.
442 TMPFILE: bool = false,448 TMPFILE: bool = false,
443 _23: u9 = 0,449 _23: u9 = 0,
444 },450 },
...@@ -459,13 +465,16 @@ pub const O = switch (native_arch) {...@@ -459,13 +465,16 @@ pub const O = switch (native_arch) {
459 NOFOLLOW: bool = false,465 NOFOLLOW: bool = false,
460 NOATIME: bool = false,466 NOATIME: bool = false,
461 CLOEXEC: bool = false,467 CLOEXEC: bool = false,
462 _20: u1 = 0,468 /// This is typically invalid without also setting `TMPFILE1` and `DIRECTORY`.
469 TMPFILE0: bool = false,
463 PATH: bool = false,470 PATH: bool = false,
464 _22: u10 = 0,471 _22: u4 = 0,
472 /// This is typically invalid without also setting `TMPFILE0` and `DIRECTORY`.
473 TMPFILE1: bool = false,
474 _27: u5 = 0,
465475
466 // #define O_RSYNC 04010000476 // #define O_RSYNC 04010000
467 // #define O_SYNC 04010000477 // #define O_SYNC 04010000
468 // #define O_TMPFILE 020200000
469 // #define O_NDELAY O_NONBLOCK478 // #define O_NDELAY O_NONBLOCK
470 },479 },
471 .m68k => packed struct(u32) {480 .m68k => packed struct(u32) {
lib/std/zig/system.zig-1
...@@ -793,7 +793,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {...@@ -793,7 +793,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
793 var dir = cwd.openDir(io, rpath, .{}) catch |err| switch (err) {793 var dir = cwd.openDir(io, rpath, .{}) catch |err| switch (err) {
794 error.NameTooLong => return error.Unexpected,794 error.NameTooLong => return error.Unexpected,
795 error.BadPathName => return error.Unexpected,795 error.BadPathName => return error.Unexpected,
796 error.DeviceBusy => return error.Unexpected,
797 error.NetworkNotFound => return error.Unexpected, // Windows-only796 error.NetworkNotFound => return error.Unexpected, // Windows-only
798797
799 error.FileNotFound => return error.GLibCNotFound,798 error.FileNotFound => return error.GLibCNotFound,
src/Builtin.zig+4-4
...@@ -343,10 +343,10 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {...@@ -343,10 +343,10 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {
343 }343 }
344344
345 // `make_path` matters because the dir hasn't actually been created yet.345 // `make_path` matters because the dir hasn't actually been created yet.
346 var af = try root_dir.atomicFile(io, sub_path, .{ .make_path = true, .write_buffer = &.{} });346 var af = try root_dir.createFileAtomic(io, sub_path, .{ .make_path = true, .replace = true });
347 defer af.deinit();347 defer af.deinit(io);
348 try af.file_writer.interface.writeAll(file.source.?);348 try af.file.writeStreamingAll(io, file.source.?);
349 af.finish() catch |err| switch (err) {349 af.replace(io) catch |err| switch (err) {
350 error.AccessDenied => switch (builtin.os.tag) {350 error.AccessDenied => switch (builtin.os.tag) {
351 .windows => {351 .windows => {
352 // Very likely happened due to another process or thread352 // Very likely happened due to another process or thread
src/Compilation.zig+20-12
...@@ -3916,11 +3916,14 @@ pub fn saveState(comp: *Compilation) !void {...@@ -3916,11 +3916,14 @@ pub fn saveState(comp: *Compilation) !void {
39163916
3917 // Using an atomic file prevents a crash or power failure from corrupting3917 // Using an atomic file prevents a crash or power failure from corrupting
3918 // the previous incremental compilation state.3918 // the previous incremental compilation state.
3919 var af = try lf.emit.root_dir.handle.createFileAtomic(io, basename, .{ .replace = true });
3920 defer af.deinit(io);
3921
3919 var write_buffer: [1024]u8 = undefined;3922 var write_buffer: [1024]u8 = undefined;
3920 var af = try lf.emit.root_dir.handle.atomicFile(io, basename, .{ .write_buffer = &write_buffer });3923 var file_writer = af.file.writer(io, &write_buffer);
3921 defer af.deinit();3924 try file_writer.interface.writeVecAll(bufs.items);
3922 try af.file_writer.interface.writeVecAll(bufs.items);3925 try file_writer.interface.flush();
3923 try af.finish();3926 try af.replace(io);
3924}3927}
39253928
3926fn addBuf(list: *std.array_list.Managed([]const u8), buf: []const u8) void {3929fn addBuf(list: *std.array_list.Managed([]const u8), buf: []const u8) void {
...@@ -5244,26 +5247,31 @@ fn processOneJob(...@@ -5244,26 +5247,31 @@ fn processOneJob(
5244 }5247 }
5245}5248}
52465249
5247fn createDepFile(comp: *Compilation, depfile: []const u8, binfile: Cache.Path) anyerror!void {5250fn createDepFile(comp: *Compilation, dep_file: []const u8, bin_file: Cache.Path) anyerror!void {
5248 const io = comp.io;5251 const io = comp.io;
5249 var buf: [4096]u8 = undefined;
5250 var af = try Io.Dir.cwd().atomicFile(io, depfile, .{ .write_buffer = &buf });
5251 defer af.deinit();
52525252
5253 comp.writeDepFile(binfile, &af.file_writer.interface) catch return af.file_writer.err.?;5253 var af = try Io.Dir.cwd().createFileAtomic(io, dep_file, .{ .replace = true });
5254 defer af.deinit(io);
52545255
5255 try af.finish();5256 var buf: [4096]u8 = undefined;
5257 var file_writer = af.file.writer(io, &buf);
5258
5259 comp.writeDepFile(bin_file, &file_writer.interface) catch |err| switch (err) {
5260 error.WriteFailed => return file_writer.err.?,
5261 };
5262 try file_writer.flush();
5263 try af.replace(io);
5256}5264}
52575265
5258fn writeDepFile(5266fn writeDepFile(
5259 comp: *Compilation,5267 comp: *Compilation,
5260 binfile: Cache.Path,5268 bin_file: Cache.Path,
5261 w: *std.Io.Writer,5269 w: *std.Io.Writer,
5262) std.Io.Writer.Error!void {5270) std.Io.Writer.Error!void {
5263 const prefixes = comp.cache_parent.prefixes();5271 const prefixes = comp.cache_parent.prefixes();
5264 const fsi = comp.file_system_inputs.?.items;5272 const fsi = comp.file_system_inputs.?.items;
52655273
5266 try w.print("{f}:", .{binfile});5274 try w.print("{f}:", .{bin_file});
52675275
5268 {5276 {
5269 var it = std.mem.splitScalar(u8, fsi, 0);5277 var it = std.mem.splitScalar(u8, fsi, 0);
src/fmt.zig+4-4
...@@ -355,11 +355,11 @@ fn fmtPathFile(...@@ -355,11 +355,11 @@ fn fmtPathFile(
355 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});355 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});
356 fmt.any_error = true;356 fmt.any_error = true;
357 } else {357 } else {
358 var af = try dir.atomicFile(io, sub_path, .{ .permissions = stat.permissions, .write_buffer = &.{} });358 var af = try dir.createFileAtomic(io, sub_path, .{ .permissions = stat.permissions, .replace = true });
359 defer af.deinit();359 defer af.deinit(io);
360360
361 try af.file_writer.interface.writeAll(fmt.out_buffer.written());361 try af.file.writeStreamingAll(io, fmt.out_buffer.written());
362 try af.finish();362 try af.replace(io);
363 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});363 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});
364 }364 }
365}365}