| author | |
| committer | |
| log | c906f7d2e7d66bdc07fe8815d1fb2bb3d6664478 |
| tree | 13057f5eec03fdb2182cc012e6b1b2941ae30caa |
| parent | 6ab1159e815fdc7ce11fa49235509a43da74f899 |
| parent | 06130c5e61160b20305c9e6a621fa1c4d0df7858 |
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/3068613 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, | ||
| 713 | 715 | ||
| 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 | } |
| 600 | 599 | ||
| 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); | ||
| 611 | 609 | ||
| 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; |
| 614 | 612 | ||
| 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 | } |
| 627 | 625 | ||
| ... | @@ -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 | } |
| 997 | 995 | ||
| 998 | pub const HardLinkOptions = struct { | 996 | pub const HardLinkOptions = File.HardLinkOptions; |
| 999 | follow_symlinks: bool = true, | ||
| 1000 | }; | ||
| 1001 | 997 | ||
| 1002 | pub const HardLinkError = error{ | 998 | pub 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; | ||
| 1019 | 999 | ||
| 1020 | pub fn hardLink( | 1000 | pub 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 |
| 1658 | pub const CopyFileOptions = struct { | 1633 | pub 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 | }; |
| 1662 | 1639 | ||
| 1663 | pub const CopyFileError = File.OpenError || File.StatError || | 1640 | pub 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}; |
| 1666 | 1643 | ||
| 1667 | /// Atomically creates a new file at `dest_path` within `dest_dir` with the | 1644 | /// 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 | 1645 | /// 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 and | 1649 | /// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and |
| 1672 | /// readily available, there is a possibility of power loss or application | 1650 | /// 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 | }; |
| 1697 | 1675 | ||
| 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); |
| 1704 | 1682 | ||
| 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 | }; |
| 1709 | 1690 | ||
| 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 | } |
| 1712 | 1698 | ||
| 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( |
| 1730 | 1716 | ||
| 1731 | test copyFileAbsolute {} | 1717 | test copyFileAbsolute {} |
| 1732 | 1718 | ||
| 1733 | pub const AtomicFileOptions = struct { | 1719 | pub 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 | }; |
| 1738 | 1729 | ||
| 1739 | /// Directly access the `.file` field, and then call `File.Atomic.finish` to | 1730 | pub 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 whether | 1733 | NetworkNotFound, |
| 1743 | /// `File.Atomic.finish` succeeded. `dest_path` must remain valid until | 1734 | /// 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, |
| 1749 | pub 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 | else | 1744 | SymLinkLoop, |
| 1754 | try parent.openDir(io, dirname, .{}); | 1745 | ProcessFdQuotaExceeded, |
| 1755 | 1746 | 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. | ||
| 1771 | pub 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 | } |
| 1761 | 1779 | ||
| 1762 | pub const SetPermissionsError = File.SetPermissionsError; | 1780 | pub 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; |
| 282 | 282 | ||
| 283 | pub fn close(file: File, io: Io) void { | 283 | pub 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 | } |
| 710 | 710 | ||
| 711 | pub const HardLinkOptions = struct { | ||
| 712 | follow_symlinks: bool = true, | ||
| 713 | }; | ||
| 714 | |||
| 715 | pub 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 | |||
| 733 | pub 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 | |||
| 711 | test { | 743 | test { |
| 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; |
| 6 | const Dir = std.Io.Dir; | 6 | const Dir = std.Io.Dir; |
| 7 | const assert = std.debug.assert; | 7 | const assert = std.debug.assert; |
| 8 | 8 | ||
| 9 | file_writer: File.Writer, | 9 | file: File, |
| 10 | random_integer: u64, | 10 | file_basename_hex: u64, |
| 11 | dest_basename: []const u8, | ||
| 12 | file_open: bool, | 11 | file_open: bool, |
| 13 | file_exists: bool, | 12 | file_exists: bool, |
| 14 | close_dir_on_deinit: bool, | 13 | |
| 15 | dir: Dir, | 14 | dir: Dir, |
| 15 | close_dir_on_deinit: bool, | ||
| 16 | 16 | ||
| 17 | pub const InitError = File.OpenError; | 17 | dest_sub_path: []const u8, |
| 18 | 18 | ||
| 19 | /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. | 19 | pub const InitError = File.OpenError; |
| 20 | pub 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(). | ||
| 51 | pub fn deinit(af: *Atomic) void { | ||
| 52 | const io = af.file_writer.io; | ||
| 53 | 20 | ||
| 21 | /// To release all resources, always call `deinit`, even after a successful | ||
| 22 | /// `finish`. | ||
| 23 | pub 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 | } |
| 68 | 39 | ||
| 69 | pub const FlushError = File.Writer.Error; | 40 | pub const LinkError = Dir.HardLinkError; |
| 70 | 41 | ||
| 71 | pub 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.?, | 44 | pub 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 | } |
| 76 | 61 | ||
| 77 | pub const RenameIntoPlaceError = Dir.RenameError; | 62 | pub const ReplaceError = Dir.RenameError; |
| 78 | 63 | ||
| 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 file | 70 | /// On Windows, this function introduces a period of time where some file |
| 80 | /// system operations on the destination file will result in | 71 | /// system operations on the destination file will result in |
| 81 | /// `error.AccessDenied`, including rename operations (such as the one used in | 72 | /// `error.AccessDenied`, including rename operations (such as the one used in |
| 82 | /// this function). | 73 | /// this function). |
| 83 | pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void { | 74 | pub 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 | |||
| 96 | pub const FinishError = FlushError || RenameIntoPlaceError; | ||
| 97 | |||
| 98 | /// Combination of `flush` followed by `renameIntoPlace`. | ||
| 99 | pub 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. | ||
| 278 | pub 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, | ||
| 1448 | 1450 | ||
| 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, | ||
| 1594 | 1598 | ||
| 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 | } |
| 3415 | 3419 | ||
| 3420 | fn 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 | |||
| 3546 | fn 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 | |||
| 3416 | const dirOpenFile = switch (native_os) { | 3584 | const 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 | } |
| 4987 | 5155 | ||
| 5156 | fn 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 | |||
| 5177 | fn 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 | |||
| 4988 | const dirDeleteFile = switch (native_os) { | 5214 | const 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); |
| 7402 | 7627 | ||
| 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; |
| 7404 | 7629 | 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 | } |
| 7445 | 7631 | ||
| 7446 | fn fileClose(userdata: ?*anyopaque, files: []const File) void { | 7632 | fn 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 | ; |
| 1653 | 1653 | ||
| 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, | ||
| 465 | 475 | ||
| 466 | // #define O_RSYNC 04010000 | 476 | // #define O_RSYNC 04010000 |
| 467 | // #define O_SYNC 04010000 | 477 | // #define O_SYNC 04010000 |
| 468 | // #define O_TMPFILE 020200000 | ||
| 469 | // #define O_NDELAY O_NONBLOCK | 478 | // #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-only | 796 | error.NetworkNotFound => return error.Unexpected, // Windows-only |
| 798 | 797 | ||
| 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 | } |
| 344 | 344 | ||
| 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 thread | 352 | // 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 { |
| 3916 | 3916 | ||
| 3917 | // Using an atomic file prevents a crash or power failure from corrupting | 3917 | // 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 | } |
| 3925 | 3928 | ||
| 3926 | fn addBuf(list: *std.array_list.Managed([]const u8), buf: []const u8) void { | 3929 | fn 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 | } |
| 5246 | 5249 | ||
| 5247 | fn createDepFile(comp: *Compilation, depfile: []const u8, binfile: Cache.Path) anyerror!void { | 5250 | fn 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(); | ||
| 5252 | 5252 | ||
| 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); | ||
| 5254 | 5255 | ||
| 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 | } |
| 5257 | 5265 | ||
| 5258 | fn writeDepFile( | 5266 | fn 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; |
| 5265 | 5273 | ||
| 5266 | try w.print("{f}:", .{binfile}); | 5274 | try w.print("{f}:", .{bin_file}); |
| 5267 | 5275 | ||
| 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); |
| 360 | 360 | ||
| 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 | } |