| author | |
| committer | |
| log | 81bfd289745c661ff5b96b42470f47492091c389 |
| tree | ed723e79bc4f7a7c6569e9b757ea972229c0c831 |
| parent | 6ab1159e815fdc7ce11fa49235509a43da74f899 |
7 files changed, 336 insertions(+), 137 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+160-1| ... | ... | @@ -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, |
| ... | ... | @@ -1549,6 +1550,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1549 | 1550 | .dirStatFile = dirStatFile, |
| 1550 | 1551 | .dirAccess = dirAccess, |
| 1551 | 1552 | .dirCreateFile = dirCreateFile, |
| 1553 | .dirCreateFileAtomic = dirCreateFileAtomic, | |
| 1552 | 1554 | .dirOpenFile = dirOpenFile, |
| 1553 | 1555 | .dirOpenDir = dirOpenDir, |
| 1554 | 1556 | .dirClose = dirClose, |
| ... | ... | @@ -3413,6 +3415,163 @@ fn dirCreateFileWasi( |
| 3413 | 3415 | } |
| 3414 | 3416 | } |
| 3415 | 3417 | |
| 3418 | fn dirCreateFileAtomic( | |
| 3419 | userdata: ?*anyopaque, | |
| 3420 | dir: Dir, | |
| 3421 | dest_path: []const u8, | |
| 3422 | options: Dir.CreateFileAtomicOptions, | |
| 3423 | ) Dir.CreateFileAtomicError!File.Atomic { | |
| 3424 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 3425 | const t_io = ioBasic(t); | |
| 3426 | ||
| 3427 | // Linux has O_TMPFILE, but linkat() does not support AT_REPLACE, so it's | |
| 3428 | // useless when we have to make up a bogus path name to do the rename() | |
| 3429 | // anyway. | |
| 3430 | if (native_os == .linux and !options.replace) tmpfile: { | |
| 3431 | const dest_dirname = Dir.path.dirname(dest_path); | |
| 3432 | if (dest_dirname) |dirname| { | |
| 3433 | // This has a nice side effect of preemptively triggering EISDIR or | |
| 3434 | // ENOENT, avoiding the ambiguity below. | |
| 3435 | dir.createDirPath(t_io, dirname) catch |err| switch (err) { | |
| 3436 | // None of these make sense in this context. | |
| 3437 | error.IsDir, | |
| 3438 | error.Streaming, | |
| 3439 | error.DiskQuota, | |
| 3440 | error.PathAlreadyExists, | |
| 3441 | error.LinkQuotaExceeded, | |
| 3442 | error.SharingViolation, | |
| 3443 | error.PipeBusy, | |
| 3444 | error.FileTooBig, | |
| 3445 | error.DeviceBusy, | |
| 3446 | error.FileLocksUnsupported, | |
| 3447 | error.FileBusy, | |
| 3448 | => return error.Unexpected, | |
| 3449 | ||
| 3450 | else => |e| return e, | |
| 3451 | }; | |
| 3452 | } | |
| 3453 | ||
| 3454 | var path_buffer: [posix.PATH_MAX]u8 = undefined; | |
| 3455 | const sub_path_posix = try pathToPosix(dest_dirname orelse ".", &path_buffer); | |
| 3456 | ||
| 3457 | const flags: posix.O = .{ | |
| 3458 | .ACCMODE = .RDWR, | |
| 3459 | .TMPFILE = true, | |
| 3460 | .CLOEXEC = true, | |
| 3461 | }; | |
| 3462 | ||
| 3463 | const syscall: Syscall = try .start(); | |
| 3464 | while (true) { | |
| 3465 | const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode()); | |
| 3466 | switch (posix.errno(rc)) { | |
| 3467 | .SUCCESS => { | |
| 3468 | syscall.finish(); | |
| 3469 | return .{ | |
| 3470 | .file = .{ .handle = @intCast(rc) }, | |
| 3471 | .file_basename_hex = 0, | |
| 3472 | .dest_sub_path = dest_path, | |
| 3473 | .file_open = true, | |
| 3474 | .file_exists = false, | |
| 3475 | .close_dir_on_deinit = false, | |
| 3476 | .dir = dir, | |
| 3477 | }; | |
| 3478 | }, | |
| 3479 | .INTR => { | |
| 3480 | try syscall.checkCancel(); | |
| 3481 | continue; | |
| 3482 | }, | |
| 3483 | .ISDIR, .NOENT => { | |
| 3484 | // Ambiguous error code. It might mean the file system | |
| 3485 | // does not support O_TMPFILE. Therefore, we must fall | |
| 3486 | // back to not using O_TMPFILE. | |
| 3487 | syscall.finish(); | |
| 3488 | break :tmpfile; | |
| 3489 | }, | |
| 3490 | .INVAL => return syscall.fail(error.BadPathName), | |
| 3491 | .ACCES => return syscall.fail(error.AccessDenied), | |
| 3492 | .LOOP => return syscall.fail(error.SymLinkLoop), | |
| 3493 | .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), | |
| 3494 | .NAMETOOLONG => return syscall.fail(error.NameTooLong), | |
| 3495 | .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), | |
| 3496 | .NODEV => return syscall.fail(error.NoDevice), | |
| 3497 | .NOMEM => return syscall.fail(error.SystemResources), | |
| 3498 | .NOSPC => return syscall.fail(error.NoSpaceLeft), | |
| 3499 | .NOTDIR => return syscall.fail(error.NotDir), | |
| 3500 | .PERM => return syscall.fail(error.PermissionDenied), | |
| 3501 | .AGAIN => return syscall.fail(error.WouldBlock), | |
| 3502 | .NXIO => return syscall.fail(error.NoDevice), | |
| 3503 | .ILSEQ => return syscall.fail(error.BadPathName), | |
| 3504 | else => |err| return syscall.unexpectedErrno(err), | |
| 3505 | } | |
| 3506 | } | |
| 3507 | } | |
| 3508 | ||
| 3509 | if (Dir.path.dirname(dest_path)) |dirname| { | |
| 3510 | const new_dir = if (options.make_path) | |
| 3511 | dir.createDirPathOpen(t_io, dirname, .{}) catch |err| switch (err) { | |
| 3512 | // None of these make sense in this context. | |
| 3513 | error.IsDir, | |
| 3514 | error.Streaming, | |
| 3515 | error.DiskQuota, | |
| 3516 | error.PathAlreadyExists, | |
| 3517 | error.LinkQuotaExceeded, | |
| 3518 | error.SharingViolation, | |
| 3519 | error.PipeBusy, | |
| 3520 | error.FileTooBig, | |
| 3521 | error.FileLocksUnsupported, | |
| 3522 | error.FileBusy, | |
| 3523 | error.DeviceBusy, | |
| 3524 | => return error.Unexpected, | |
| 3525 | ||
| 3526 | else => |e| return e, | |
| 3527 | } | |
| 3528 | else | |
| 3529 | try dir.openDir(t_io, dirname, .{}); | |
| 3530 | ||
| 3531 | return atomicFileInit(t_io, Dir.path.basename(dest_path), options.permissions, new_dir, true); | |
| 3532 | } | |
| 3533 | ||
| 3534 | return atomicFileInit(t_io, dest_path, options.permissions, dir, false); | |
| 3535 | } | |
| 3536 | ||
| 3537 | fn atomicFileInit( | |
| 3538 | t_io: Io, | |
| 3539 | dest_basename: []const u8, | |
| 3540 | permissions: File.Permissions, | |
| 3541 | dir: Dir, | |
| 3542 | close_dir_on_deinit: bool, | |
| 3543 | ) Dir.CreateFileAtomicError!File.Atomic { | |
| 3544 | while (true) { | |
| 3545 | const random_integer = std.crypto.random.int(u64); | |
| 3546 | const tmp_sub_path = std.fmt.hex(random_integer); | |
| 3547 | const file = dir.createFile(t_io, &tmp_sub_path, .{ | |
| 3548 | .permissions = permissions, | |
| 3549 | .exclusive = true, | |
| 3550 | }) catch |err| switch (err) { | |
| 3551 | error.PathAlreadyExists => continue, | |
| 3552 | error.DeviceBusy => continue, | |
| 3553 | error.FileBusy => continue, | |
| 3554 | error.SharingViolation => continue, | |
| 3555 | ||
| 3556 | error.IsDir => return error.Unexpected, // No path components. | |
| 3557 | error.FileTooBig => return error.Unexpected, // Creating, not opening. | |
| 3558 | error.FileLocksUnsupported => return error.Unexpected, // Not asking for locks. | |
| 3559 | error.PipeBusy => return error.Unexpected, // Not opening a pipe. | |
| 3560 | ||
| 3561 | else => |e| return e, | |
| 3562 | }; | |
| 3563 | return .{ | |
| 3564 | .file = file, | |
| 3565 | .file_basename_hex = random_integer, | |
| 3566 | .dest_sub_path = dest_basename, | |
| 3567 | .file_open = true, | |
| 3568 | .file_exists = true, | |
| 3569 | .close_dir_on_deinit = close_dir_on_deinit, | |
| 3570 | .dir = dir, | |
| 3571 | }; | |
| 3572 | } | |
| 3573 | } | |
| 3574 | ||
| 3416 | 3575 | const dirOpenFile = switch (native_os) { |
| 3417 | 3576 | .windows => dirOpenFileWindows, |
| 3418 | 3577 | .wasi => dirOpenFileWasi, |
| ... | ... | @@ -3925,7 +4084,7 @@ fn dirOpenDirPosix( |
| 3925 | 4084 | .NOMEM => return error.SystemResources, |
| 3926 | 4085 | .NOTDIR => return error.NotDir, |
| 3927 | 4086 | .PERM => return error.PermissionDenied, |
| 3928 | .BUSY => return error.DeviceBusy, | |
| 4087 | .BUSY => |err| return errnoBug(err), // O_EXCL not passed | |
| 3929 | 4088 | .NXIO => return error.NoDevice, |
| 3930 | 4089 | .ILSEQ => return error.BadPathName, |
| 3931 | 4090 | else => |err| return posix.unexpectedErrno(err), |
lib/std/zig/system.zig-1| ... | ... | @@ -793,7 +793,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { |
| 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, |