| 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 | 667 | dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat, |
| 668 | 668 | dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void, |
| 669 | 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 | 671 | dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File, |
| 671 | 672 | dirClose: *const fn (?*anyopaque, []const Dir) void, |
| 672 | 673 | dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize, |
| ... | ... | @@ -710,6 +711,7 @@ pub const VTable = struct { |
| 710 | 711 | fileUnlock: *const fn (?*anyopaque, File) void, |
| 711 | 712 | fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void, |
| 712 | 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 | 716 | processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File, |
| 715 | 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 | 454 | SystemFdQuotaExceeded, |
| 455 | 455 | NoDevice, |
| 456 | 456 | SystemResources, |
| 457 | DeviceBusy, | |
| 458 | 457 | /// On Windows, `\\server` or `\\server\share` was not found. |
| 459 | 458 | NetworkNotFound, |
| 460 | 459 | } || PathNameError || Io.Cancelable || Io.UnexpectedError; |
| ... | ... | @@ -598,30 +597,29 @@ pub fn updateFile( |
| 598 | 597 | } |
| 599 | 598 | } |
| 600 | 599 | |
| 601 | if (path.dirname(dest_path)) |dirname| { | |
| 602 | try dest_dir.createDirPath(io, dirname); | |
| 603 | } | |
| 604 | ||
| 605 | var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available. | |
| 606 | var atomic_file = try dest_dir.atomicFile(io, dest_path, .{ | |
| 600 | var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{ | |
| 607 | 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 | 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 | 613 | _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) { |
| 616 | 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(); | |
| 620 | try atomic_file.file_writer.file.setTimestamps(io, .{ | |
| 617 | try file_writer.flush(); | |
| 618 | try file_writer.file.setTimestamps(io, .{ | |
| 621 | 619 | .access_timestamp = .init(src_stat.atime), |
| 622 | 620 | .modify_timestamp = .init(src_stat.mtime), |
| 623 | 621 | }); |
| 624 | try atomic_file.renameIntoPlace(); | |
| 622 | try atomic_file.replace(io); | |
| 625 | 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 | 993 | return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path); |
| 996 | 994 | } |
| 997 | 995 | |
| 998 | pub const HardLinkOptions = struct { | |
| 999 | follow_symlinks: bool = true, | |
| 1000 | }; | |
| 996 | pub const HardLinkOptions = File.HardLinkOptions; | |
| 1001 | 997 | |
| 1002 | pub const HardLinkError = error{ | |
| 1003 | AccessDenied, | |
| 1004 | PermissionDenied, | |
| 1005 | DiskQuota, | |
| 1006 | PathAlreadyExists, | |
| 1007 | HardwareFailure, | |
| 1008 | /// Either the OS or the filesystem does not support hard links. | |
| 1009 | OperationUnsupported, | |
| 1010 | SymLinkLoop, | |
| 1011 | LinkQuotaExceeded, | |
| 1012 | FileNotFound, | |
| 1013 | SystemResources, | |
| 1014 | NoSpaceLeft, | |
| 1015 | ReadOnlyFileSystem, | |
| 1016 | NotSameFileSystem, | |
| 1017 | NotDir, | |
| 1018 | } || Io.Cancelable || PathNameError || Io.UnexpectedError; | |
| 998 | pub const HardLinkError = File.HardLinkError; | |
| 1019 | 999 | |
| 1020 | 1000 | pub fn hardLink( |
| 1021 | 1001 | old_dir: Dir, |
| ... | ... | @@ -1251,7 +1231,6 @@ pub const DeleteTreeError = error{ |
| 1251 | 1231 | ReadOnlyFileSystem, |
| 1252 | 1232 | FileSystem, |
| 1253 | 1233 | FileBusy, |
| 1254 | DeviceBusy, | |
| 1255 | 1234 | /// One of the path components was not a directory. |
| 1256 | 1235 | /// This error is unreachable if `sub_path` does not contain a path separator. |
| 1257 | 1236 | NotDir, |
| ... | ... | @@ -1322,7 +1301,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { |
| 1322 | 1301 | error.Unexpected, |
| 1323 | 1302 | error.BadPathName, |
| 1324 | 1303 | error.NetworkNotFound, |
| 1325 | error.DeviceBusy, | |
| 1326 | 1304 | error.Canceled, |
| 1327 | 1305 | => |e| return e, |
| 1328 | 1306 | }; |
| ... | ... | @@ -1417,7 +1395,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { |
| 1417 | 1395 | error.Unexpected, |
| 1418 | 1396 | error.BadPathName, |
| 1419 | 1397 | error.NetworkNotFound, |
| 1420 | error.DeviceBusy, | |
| 1421 | 1398 | error.Canceled, |
| 1422 | 1399 | => |e| return e, |
| 1423 | 1400 | }; |
| ... | ... | @@ -1522,7 +1499,6 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, |
| 1522 | 1499 | error.Unexpected, |
| 1523 | 1500 | error.BadPathName, |
| 1524 | 1501 | error.NetworkNotFound, |
| 1525 | error.DeviceBusy, | |
| 1526 | 1502 | error.Canceled, |
| 1527 | 1503 | => |e| return e, |
| 1528 | 1504 | }; |
| ... | ... | @@ -1619,7 +1595,6 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin |
| 1619 | 1595 | error.SystemResources, |
| 1620 | 1596 | error.Unexpected, |
| 1621 | 1597 | error.BadPathName, |
| 1622 | error.DeviceBusy, | |
| 1623 | 1598 | error.NetworkNotFound, |
| 1624 | 1599 | error.Canceled, |
| 1625 | 1600 | => |e| return e, |
| ... | ... | @@ -1658,15 +1633,18 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin |
| 1658 | 1633 | pub const CopyFileOptions = struct { |
| 1659 | 1634 | /// When this is `null` the permissions are copied from the source file. |
| 1660 | 1635 | permissions: ?File.Permissions = null, |
| 1636 | make_path: bool = false, | |
| 1637 | replace: bool = true, | |
| 1661 | 1638 | }; |
| 1662 | 1639 | |
| 1663 | 1640 | pub const CopyFileError = File.OpenError || File.StatError || |
| 1664 | File.Atomic.InitError || File.Atomic.FinishError || | |
| 1641 | CreateFileAtomicError || File.Atomic.ReplaceError || File.Atomic.LinkError || | |
| 1665 | 1642 | File.Reader.Error || File.Writer.Error || error{InvalidFileName}; |
| 1666 | 1643 | |
| 1667 | 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 | |
| 1669 | /// existing file. | |
| 1645 | /// same contents as `source_path` within `source_dir`. | |
| 1646 | /// | |
| 1647 | /// Whether to overwrite the existing file is determined by `options`. | |
| 1670 | 1648 | /// |
| 1671 | 1649 | /// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and |
| 1672 | 1650 | /// readily available, there is a possibility of power loss or application |
| ... | ... | @@ -1695,19 +1673,27 @@ pub fn copyFile( |
| 1695 | 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. | |
| 1699 | var atomic_file = try dest_dir.atomicFile(io, dest_path, .{ | |
| 1676 | var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{ | |
| 1700 | 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 | 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 | 1699 | /// Same as `copyFile`, except asserts that both `source_path` and `dest_path` |
| ... | ... | @@ -1730,33 +1716,65 @@ pub fn copyFileAbsolute( |
| 1730 | 1716 | |
| 1731 | 1717 | test copyFileAbsolute {} |
| 1732 | 1718 | |
| 1733 | pub const AtomicFileOptions = struct { | |
| 1719 | pub const CreateFileAtomicOptions = struct { | |
| 1734 | 1720 | permissions: File.Permissions = .default_file, |
| 1735 | 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 | |
| 1740 | /// atomically replace `dest_path` with contents. | |
| 1741 | /// | |
| 1742 | /// Always call `File.Atomic.deinit` to clean up, regardless of whether | |
| 1743 | /// `File.Atomic.finish` succeeded. `dest_path` must remain valid until | |
| 1744 | /// `File.Atomic.deinit` is called. | |
| 1745 | /// | |
| 1746 | /// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 1747 | /// On WASI, `dest_path` should be encoded as valid UTF-8. | |
| 1748 | /// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding. | |
| 1749 | pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic { | |
| 1750 | if (path.dirname(dest_path)) |dirname| { | |
| 1751 | const dir = if (options.make_path) | |
| 1752 | try parent.createDirPathOpen(io, dirname, .{}) | |
| 1753 | else | |
| 1754 | try parent.openDir(io, dirname, .{}); | |
| 1755 | ||
| 1756 | return .init(io, path.basename(dest_path), options.permissions, dir, true, options.write_buffer); | |
| 1757 | } else { | |
| 1758 | return .init(io, dest_path, options.permissions, parent, false, options.write_buffer); | |
| 1759 | } | |
| 1730 | pub const CreateFileAtomicError = error{ | |
| 1731 | NoDevice, | |
| 1732 | /// On Windows, `\\server` or `\\server\share` was not found. | |
| 1733 | NetworkNotFound, | |
| 1734 | /// On Windows, antivirus software is enabled by default. It can be | |
| 1735 | /// disabled, but Windows Update sometimes ignores the user's preference | |
| 1736 | /// and re-enables it. When enabled, antivirus software on Windows | |
| 1737 | /// intercepts file system operations and makes them significantly slower | |
| 1738 | /// in addition to possibly failing with this error code. | |
| 1739 | AntivirusInterference, | |
| 1740 | /// In WASI, this error may occur when the file descriptor does | |
| 1741 | /// not hold the required rights to open a new resource relative to it. | |
| 1742 | AccessDenied, | |
| 1743 | PermissionDenied, | |
| 1744 | SymLinkLoop, | |
| 1745 | ProcessFdQuotaExceeded, | |
| 1746 | SystemFdQuotaExceeded, | |
| 1747 | /// Either: | |
| 1748 | /// * One of the path components does not exist. | |
| 1749 | /// * Cwd was used, but cwd has been deleted. | |
| 1750 | /// * The path associated with the open directory handle has been deleted. | |
| 1751 | FileNotFound, | |
| 1752 | /// Insufficient kernel memory was available. | |
| 1753 | SystemResources, | |
| 1754 | /// A new path cannot be created because the device has no room for the new file. | |
| 1755 | NoSpaceLeft, | |
| 1756 | /// A component used as a directory in the path was not, in fact, a directory. | |
| 1757 | NotDir, | |
| 1758 | WouldBlock, | |
| 1759 | ReadOnlyFileSystem, | |
| 1760 | } || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; | |
| 1761 | ||
| 1762 | /// Create an unnamed ephemeral file that can eventually be atomically | |
| 1763 | /// materialized into `sub_path`. | |
| 1764 | /// | |
| 1765 | /// The returned `File.Atomic` provides API to emulate the behavior in case it | |
| 1766 | /// is not directly supported by the underlying operating system. | |
| 1767 | /// | |
| 1768 | /// * On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | |
| 1769 | /// * On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 1770 | /// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 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 | 1780 | pub const SetPermissionsError = File.SetPermissionsError; |
lib/std/Io/File.zig+33-1| ... | ... | @@ -278,7 +278,7 @@ pub const OpenError = error{ |
| 278 | 278 | FileBusy, |
| 279 | 279 | /// Non-blocking was requested and the operation cannot return immediately. |
| 280 | 280 | WouldBlock, |
| 281 | } || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; | |
| 281 | } || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; | |
| 282 | 282 | |
| 283 | 283 | pub fn close(file: File, io: Io) void { |
| 284 | 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 | 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 | 743 | test { |
| 712 | 744 | _ = Reader; |
| 713 | 745 | _ = Writer; |
lib/std/Io/File/Atomic.zig+43-62| ... | ... | @@ -6,97 +6,78 @@ const File = std.Io.File; |
| 6 | 6 | const Dir = std.Io.Dir; |
| 7 | 7 | const assert = std.debug.assert; |
| 8 | 8 | |
| 9 | file_writer: File.Writer, | |
| 10 | random_integer: u64, | |
| 11 | dest_basename: []const u8, | |
| 9 | file: File, | |
| 10 | file_basename_hex: u64, | |
| 12 | 11 | file_open: bool, |
| 13 | 12 | file_exists: bool, |
| 14 | close_dir_on_deinit: bool, | |
| 13 | ||
| 15 | 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. | |
| 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; | |
| 19 | pub const InitError = File.OpenError; | |
| 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 | 24 | if (af.file_open) { |
| 55 | af.file_writer.file.close(io); | |
| 25 | af.file.close(io); | |
| 56 | 26 | af.file_open = false; |
| 57 | 27 | } |
| 58 | 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 | 30 | af.dir.deleteFile(io, &tmp_sub_path) catch {}; |
| 61 | 31 | af.file_exists = false; |
| 62 | 32 | } |
| 63 | 33 | if (af.close_dir_on_deinit) { |
| 64 | 34 | af.dir.close(io); |
| 35 | af.close_dir_on_deinit = false; | |
| 65 | 36 | } |
| 66 | 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 { | |
| 72 | af.file_writer.interface.flush() catch |err| switch (err) { | |
| 73 | error.WriteFailed => return af.file_writer.err.?, | |
| 74 | }; | |
| 42 | /// Atomically materializes the file into place, failing with | |
| 43 | /// `error.PathAlreadyExists` if something already exists there. | |
| 44 | pub fn link(af: *Atomic, io: Io) LinkError!void { | |
| 45 | if (af.file_exists) { | |
| 46 | if (af.file_open) { | |
| 47 | af.file.close(io); | |
| 48 | af.file_open = false; | |
| 49 | } | |
| 50 | const tmp_sub_path = std.fmt.hex(af.file_basename_hex); | |
| 51 | try af.dir.hardLink(&tmp_sub_path, af.dir, af.dest_sub_path, io, .{}); | |
| 52 | af.dir.deleteFile(io, &tmp_sub_path) catch {}; | |
| 53 | af.file_exists = false; | |
| 54 | } else { | |
| 55 | assert(af.file_open); | |
| 56 | try af.file.hardLink(io, af.dir, af.dest_sub_path, .{}); | |
| 57 | af.file.close(io); | |
| 58 | af.file_open = false; | |
| 59 | } | |
| 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 | 70 | /// On Windows, this function introduces a period of time where some file |
| 80 | 71 | /// system operations on the destination file will result in |
| 81 | 72 | /// `error.AccessDenied`, including rename operations (such as the one used in |
| 82 | 73 | /// this function). |
| 83 | pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void { | |
| 84 | const io = af.file_writer.io; | |
| 85 | ||
| 86 | assert(af.file_exists); | |
| 74 | pub fn replace(af: *Atomic, io: Io) ReplaceError!void { | |
| 75 | assert(af.file_exists); // Wrong value for `CreateFileAtomicOptions.replace`. | |
| 87 | 76 | if (af.file_open) { |
| 88 | af.file_writer.file.close(io); | |
| 77 | af.file.close(io); | |
| 89 | 78 | af.file_open = false; |
| 90 | 79 | } |
| 91 | const tmp_sub_path = std.fmt.hex(af.random_integer); | |
| 92 | try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io); | |
| 80 | const tmp_sub_path = std.fmt.hex(af.file_basename_hex); | |
| 81 | try af.dir.rename(&tmp_sub_path, af.dir, af.dest_sub_path, io); | |
| 93 | 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 | 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 | 1403 | .dirStatFile = dirStatFile, |
| 1404 | 1404 | .dirAccess = dirAccess, |
| 1405 | 1405 | .dirCreateFile = dirCreateFile, |
| 1406 | .dirCreateFileAtomic = dirCreateFileAtomic, | |
| 1406 | 1407 | .dirOpenFile = dirOpenFile, |
| 1407 | 1408 | .dirOpenDir = dirOpenDir, |
| 1408 | 1409 | .dirClose = dirClose, |
| ... | ... | @@ -1445,6 +1446,7 @@ pub fn io(t: *Threaded) Io { |
| 1445 | 1446 | .fileUnlock = fileUnlock, |
| 1446 | 1447 | .fileDowngradeLock = fileDowngradeLock, |
| 1447 | 1448 | .fileRealPath = fileRealPath, |
| 1449 | .fileHardLink = fileHardLink, | |
| 1448 | 1450 | |
| 1449 | 1451 | .processExecutableOpen = processExecutableOpen, |
| 1450 | 1452 | .processExecutablePath = processExecutablePath, |
| ... | ... | @@ -1549,6 +1551,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1549 | 1551 | .dirStatFile = dirStatFile, |
| 1550 | 1552 | .dirAccess = dirAccess, |
| 1551 | 1553 | .dirCreateFile = dirCreateFile, |
| 1554 | .dirCreateFileAtomic = dirCreateFileAtomic, | |
| 1552 | 1555 | .dirOpenFile = dirOpenFile, |
| 1553 | 1556 | .dirOpenDir = dirOpenDir, |
| 1554 | 1557 | .dirClose = dirClose, |
| ... | ... | @@ -1591,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1591 | 1594 | .fileUnlock = fileUnlock, |
| 1592 | 1595 | .fileDowngradeLock = fileDowngradeLock, |
| 1593 | 1596 | .fileRealPath = fileRealPath, |
| 1597 | .fileHardLink = fileHardLink, | |
| 1594 | 1598 | |
| 1595 | 1599 | .processExecutableOpen = processExecutableOpen, |
| 1596 | 1600 | .processExecutablePath = processExecutablePath, |
| ... | ... | @@ -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 | 3584 | const dirOpenFile = switch (native_os) { |
| 3417 | 3585 | .windows => dirOpenFileWindows, |
| 3418 | 3586 | .wasi => dirOpenFileWasi, |
| ... | ... | @@ -3925,7 +4093,7 @@ fn dirOpenDirPosix( |
| 3925 | 4093 | .NOMEM => return error.SystemResources, |
| 3926 | 4094 | .NOTDIR => return error.NotDir, |
| 3927 | 4095 | .PERM => return error.PermissionDenied, |
| 3928 | .BUSY => return error.DeviceBusy, | |
| 4096 | .BUSY => |err| return errnoBug(err), // O_EXCL not passed | |
| 3929 | 4097 | .NXIO => return error.NoDevice, |
| 3930 | 4098 | .ILSEQ => return error.BadPathName, |
| 3931 | 4099 | else => |err| return posix.unexpectedErrno(err), |
| ... | ... | @@ -4985,6 +5153,64 @@ fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { |
| 4985 | 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 | 5214 | const dirDeleteFile = switch (native_os) { |
| 4989 | 5215 | .windows => dirDeleteFileWindows, |
| 4990 | 5216 | .wasi => dirDeleteFileWasi, |
| ... | ... | @@ -7325,7 +7551,6 @@ fn dirOpenDirWasi( |
| 7325 | 7551 | .NOMEM => return error.SystemResources, |
| 7326 | 7552 | .NOTDIR => return error.NotDir, |
| 7327 | 7553 | .PERM => return error.PermissionDenied, |
| 7328 | .BUSY => return error.DeviceBusy, | |
| 7329 | 7554 | .NOTCAPABLE => return error.AccessDenied, |
| 7330 | 7555 | .ILSEQ => return error.BadPathName, |
| 7331 | 7556 | else => |err| return posix.unexpectedErrno(err), |
| ... | ... | @@ -7401,46 +7626,7 @@ fn dirHardLink( |
| 7401 | 7626 | const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); |
| 7402 | 7627 | |
| 7403 | 7628 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; |
| 7404 | ||
| 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 | } | |
| 7629 | return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags); | |
| 7444 | 7630 | } |
| 7445 | 7631 | |
| 7446 | 7632 | fn fileClose(userdata: ?*anyopaque, files: []const File) void { |
| ... | ... | @@ -13831,7 +14017,6 @@ fn windowsCreateProcessPathExt( |
| 13831 | 14017 | error.NetworkNotFound, |
| 13832 | 14018 | error.NameTooLong, |
| 13833 | 14019 | error.BadPathName, |
| 13834 | error.DeviceBusy, | |
| 13835 | 14020 | => return error.FileNotFound, |
| 13836 | 14021 | }; |
| 13837 | 14022 | }; |
lib/std/c.zig+2| ... | ... | @@ -8427,6 +8427,7 @@ pub const O = switch (native_os) { |
| 8427 | 8427 | CLOEXEC: bool = false, |
| 8428 | 8428 | SYNC: bool = false, |
| 8429 | 8429 | PATH: bool = false, |
| 8430 | /// This is typically invalid without also setting `DIRECTORY`. | |
| 8430 | 8431 | TMPFILE: bool = false, |
| 8431 | 8432 | _: u9 = 0, |
| 8432 | 8433 | }, |
| ... | ... | @@ -8615,6 +8616,7 @@ pub const O = switch (native_os) { |
| 8615 | 8616 | _19: u1 = 0, |
| 8616 | 8617 | CLOEXEC: bool = false, |
| 8617 | 8618 | PATH: bool = false, |
| 8619 | /// This is typically invalid without also setting `DIRECTORY`. | |
| 8618 | 8620 | TMPFILE: bool = false, |
| 8619 | 8621 | _: u9 = 0, |
| 8620 | 8622 | }, |
lib/std/fs/test.zig+4-5| ... | ... | @@ -1652,11 +1652,10 @@ test "AtomicFile" { |
| 1652 | 1652 | ; |
| 1653 | 1653 | |
| 1654 | 1654 | { |
| 1655 | var buffer: [100]u8 = undefined; | |
| 1656 | var af = try ctx.dir.atomicFile(io, test_out_file, .{ .write_buffer = &buffer }); | |
| 1657 | defer af.deinit(); | |
| 1658 | try af.file_writer.interface.writeAll(test_content); | |
| 1659 | try af.finish(); | |
| 1655 | var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = true }); | |
| 1656 | defer af.deinit(io); | |
| 1657 | try af.file.writeStreamingAll(io, test_content); | |
| 1658 | try af.replace(io); | |
| 1660 | 1659 | } |
| 1661 | 1660 | const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999)); |
| 1662 | 1661 | try expectEqualStrings(test_content, content); |
lib/std/os/linux.zig+12-3| ... | ... | @@ -324,6 +324,7 @@ pub const O = switch (native_arch) { |
| 324 | 324 | CLOEXEC: bool = false, |
| 325 | 325 | SYNC: bool = false, |
| 326 | 326 | PATH: bool = false, |
| 327 | /// This is typically invalid without also setting `DIRECTORY`. | |
| 327 | 328 | TMPFILE: bool = false, |
| 328 | 329 | _23: u9 = 0, |
| 329 | 330 | }, |
| ... | ... | @@ -346,6 +347,7 @@ pub const O = switch (native_arch) { |
| 346 | 347 | CLOEXEC: bool = false, |
| 347 | 348 | SYNC: bool = false, |
| 348 | 349 | PATH: bool = false, |
| 350 | /// This is typically invalid without also setting `DIRECTORY`. | |
| 349 | 351 | TMPFILE: bool = false, |
| 350 | 352 | _23: u9 = 0, |
| 351 | 353 | }, |
| ... | ... | @@ -368,6 +370,7 @@ pub const O = switch (native_arch) { |
| 368 | 370 | CLOEXEC: bool = false, |
| 369 | 371 | SYNC: bool = false, |
| 370 | 372 | PATH: bool = false, |
| 373 | /// This is typically invalid without also setting `DIRECTORY`. | |
| 371 | 374 | TMPFILE: bool = false, |
| 372 | 375 | _23: u9 = 0, |
| 373 | 376 | }, |
| ... | ... | @@ -393,6 +396,7 @@ pub const O = switch (native_arch) { |
| 393 | 396 | CLOEXEC: bool = false, |
| 394 | 397 | SYNC: bool = false, |
| 395 | 398 | PATH: bool = false, |
| 399 | /// This is typically invalid without also setting `DIRECTORY`. | |
| 396 | 400 | TMPFILE: bool = false, |
| 397 | 401 | _27: u6 = 0, |
| 398 | 402 | }, |
| ... | ... | @@ -417,6 +421,7 @@ pub const O = switch (native_arch) { |
| 417 | 421 | CLOEXEC: bool = false, |
| 418 | 422 | _20: u1 = 0, |
| 419 | 423 | PATH: bool = false, |
| 424 | /// This is typically invalid without also setting `DIRECTORY`. | |
| 420 | 425 | TMPFILE: bool = false, |
| 421 | 426 | _23: u9 = 0, |
| 422 | 427 | }, |
| ... | ... | @@ -439,6 +444,7 @@ pub const O = switch (native_arch) { |
| 439 | 444 | CLOEXEC: bool = false, |
| 440 | 445 | SYNC: bool = false, |
| 441 | 446 | PATH: bool = false, |
| 447 | /// This is typically invalid without also setting `DIRECTORY`. | |
| 442 | 448 | TMPFILE: bool = false, |
| 443 | 449 | _23: u9 = 0, |
| 444 | 450 | }, |
| ... | ... | @@ -459,13 +465,16 @@ pub const O = switch (native_arch) { |
| 459 | 465 | NOFOLLOW: bool = false, |
| 460 | 466 | NOATIME: bool = false, |
| 461 | 467 | CLOEXEC: bool = false, |
| 462 | _20: u1 = 0, | |
| 468 | /// This is typically invalid without also setting `TMPFILE1` and `DIRECTORY`. | |
| 469 | TMPFILE0: bool = false, | |
| 463 | 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 | 476 | // #define O_RSYNC 04010000 |
| 467 | 477 | // #define O_SYNC 04010000 |
| 468 | // #define O_TMPFILE 020200000 | |
| 469 | 478 | // #define O_NDELAY O_NONBLOCK |
| 470 | 479 | }, |
| 471 | 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 | 793 | var dir = cwd.openDir(io, rpath, .{}) catch |err| switch (err) { |
| 794 | 794 | error.NameTooLong => return error.Unexpected, |
| 795 | 795 | error.BadPathName => return error.Unexpected, |
| 796 | error.DeviceBusy => return error.Unexpected, | |
| 797 | 796 | error.NetworkNotFound => return error.Unexpected, // Windows-only |
| 798 | 797 | |
| 799 | 798 | error.FileNotFound => return error.GLibCNotFound, |
src/Builtin.zig+4-4| ... | ... | @@ -343,10 +343,10 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void { |
| 343 | 343 | } |
| 344 | 344 | |
| 345 | 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 = &.{} }); | |
| 347 | defer af.deinit(); | |
| 348 | try af.file_writer.interface.writeAll(file.source.?); | |
| 349 | af.finish() catch |err| switch (err) { | |
| 346 | var af = try root_dir.createFileAtomic(io, sub_path, .{ .make_path = true, .replace = true }); | |
| 347 | defer af.deinit(io); | |
| 348 | try af.file.writeStreamingAll(io, file.source.?); | |
| 349 | af.replace(io) catch |err| switch (err) { | |
| 350 | 350 | error.AccessDenied => switch (builtin.os.tag) { |
| 351 | 351 | .windows => { |
| 352 | 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 | 3916 | |
| 3917 | 3917 | // Using an atomic file prevents a crash or power failure from corrupting |
| 3918 | 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 | 3922 | var write_buffer: [1024]u8 = undefined; |
| 3920 | var af = try lf.emit.root_dir.handle.atomicFile(io, basename, .{ .write_buffer = &write_buffer }); | |
| 3921 | defer af.deinit(); | |
| 3922 | try af.file_writer.interface.writeVecAll(bufs.items); | |
| 3923 | try af.finish(); | |
| 3923 | var file_writer = af.file.writer(io, &write_buffer); | |
| 3924 | try file_writer.interface.writeVecAll(bufs.items); | |
| 3925 | try file_writer.interface.flush(); | |
| 3926 | try af.replace(io); | |
| 3924 | 3927 | } |
| 3925 | 3928 | |
| 3926 | 3929 | fn addBuf(list: *std.array_list.Managed([]const u8), buf: []const u8) void { |
| ... | ... | @@ -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 | 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 | 5266 | fn writeDepFile( |
| 5259 | 5267 | comp: *Compilation, |
| 5260 | binfile: Cache.Path, | |
| 5268 | bin_file: Cache.Path, | |
| 5261 | 5269 | w: *std.Io.Writer, |
| 5262 | 5270 | ) std.Io.Writer.Error!void { |
| 5263 | 5271 | const prefixes = comp.cache_parent.prefixes(); |
| 5264 | 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 | 5277 | var it = std.mem.splitScalar(u8, fsi, 0); |
src/fmt.zig+4-4| ... | ... | @@ -355,11 +355,11 @@ fn fmtPathFile( |
| 355 | 355 | try fmt.stdout_writer.interface.print("{s}\n", .{file_path}); |
| 356 | 356 | fmt.any_error = true; |
| 357 | 357 | } else { |
| 358 | var af = try dir.atomicFile(io, sub_path, .{ .permissions = stat.permissions, .write_buffer = &.{} }); | |
| 359 | defer af.deinit(); | |
| 358 | var af = try dir.createFileAtomic(io, sub_path, .{ .permissions = stat.permissions, .replace = true }); | |
| 359 | defer af.deinit(io); | |
| 360 | 360 | |
| 361 | try af.file_writer.interface.writeAll(fmt.out_buffer.written()); | |
| 362 | try af.finish(); | |
| 361 | try af.file.writeStreamingAll(io, fmt.out_buffer.written()); | |
| 362 | try af.replace(io); | |
| 363 | 363 | try fmt.stdout_writer.interface.print("{s}\n", .{file_path}); |
| 364 | 364 | } |
| 365 | 365 | } |