| author | |
| committer | |
| log | 814480db7cd0ab1d8441919223a88ee411f54ea8 |
| tree | 2bd58c06839d8af32caa507299abb254b04bc248 |
| parent | d1d2c37af26902f953b2b72335b326c4b01e3bb2 |
17 files changed, 2349 insertions(+), 2305 deletions(-)
CMakeLists.txt-1| ... | ... | @@ -436,7 +436,6 @@ set(ZIG_STAGE2_SOURCES |
| 436 | 436 | lib/std/fmt.zig |
| 437 | 437 | lib/std/fmt/parse_float.zig |
| 438 | 438 | lib/std/fs.zig |
| 439 | lib/std/fs/File.zig | |
| 440 | 439 | lib/std/fs/get_app_data_dir.zig |
| 441 | 440 | lib/std/fs/path.zig |
| 442 | 441 | lib/std/hash.zig |
lib/std/Io.zig+16-1| ... | ... | @@ -679,11 +679,13 @@ pub const VTable = struct { |
| 679 | 679 | dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void, |
| 680 | 680 | dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.RenameError!void, |
| 681 | 681 | dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize, |
| 682 | dirSetMode: *const fn (?*anyopaque, Dir, File.Mode) Dir.SetModeError!void, | |
| 683 | 682 | dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void, |
| 684 | 683 | dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void, |
| 684 | dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, | |
| 685 | dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, | |
| 685 | 686 | |
| 686 | 687 | fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, |
| 688 | fileLength: *const fn (?*anyopaque, File) File.LengthError!u64, | |
| 687 | 689 | fileClose: *const fn (?*anyopaque, File) void, |
| 688 | 690 | fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize, |
| 689 | 691 | fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize, |
| ... | ... | @@ -694,6 +696,19 @@ pub const VTable = struct { |
| 694 | 696 | fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, |
| 695 | 697 | fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void, |
| 696 | 698 | openSelfExe: *const fn (?*anyopaque, File.OpenFlags) File.OpenSelfExeError!File, |
| 699 | fileSync: *const fn (?*anyopaque, File) File.SyncError!void, | |
| 700 | fileIsTty: *const fn (?*anyopaque, File) Cancelable!bool, | |
| 701 | fileEnableAnsiEscapeCodes: *const fn (?*anyopaque, File) File.EnableAnsiEscapeCodesError!void, | |
| 702 | fileSupportsAnsiEscapeCodes: *const fn (?*anyopaque, File) Cancelable!bool, | |
| 703 | fileSetLength: *const fn (?*anyopaque, File, u64) File.SetLengthError!void, | |
| 704 | fileSetOwner: *const fn (?*anyopaque, File, ?File.Uid, ?File.Gid) File.SetOwnerError!void, | |
| 705 | fileSetPermissions: *const fn (?*anyopaque, File, File.Permissions) File.SetPermissionsError!void, | |
| 706 | fileSetTimestamps: *const fn (?*anyopaque, File, last_accessed: Timestamp, last_modified: Timestamp, File.SetTimestampsOptions) File.SetTimestampsError!void, | |
| 707 | fileSetTimestampsNow: *const fn (?*anyopaque, File, File.SetTimestampsOptions) File.SetTimestampsError!void, | |
| 708 | fileLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!void, | |
| 709 | fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool, | |
| 710 | fileUnlock: *const fn (?*anyopaque, File) void, | |
| 711 | fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void, | |
| 697 | 712 | |
| 698 | 713 | now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, |
| 699 | 714 | sleep: *const fn (?*anyopaque, Timeout) SleepError!void, |
lib/std/Io/Dir.zig+52-37| ... | ... | @@ -11,9 +11,6 @@ const Allocator = std.mem.Allocator; |
| 11 | 11 | |
| 12 | 12 | handle: Handle, |
| 13 | 13 | |
| 14 | pub const Mode = Io.File.Mode; | |
| 15 | pub const default_mode: Mode = 0o755; | |
| 16 | ||
| 17 | 14 | pub const Entry = struct { |
| 18 | 15 | name: []const u8, |
| 19 | 16 | kind: File.Kind, |
| ... | ... | @@ -435,10 +432,10 @@ pub const PrevStatus = enum { |
| 435 | 432 | |
| 436 | 433 | pub const UpdateFileError = File.OpenError; |
| 437 | 434 | |
| 438 | /// Check the file size, mtime, and mode of `source_path` and `dest_path`. If | |
| 435 | /// Check the file size, mtime, and permissions of `source_path` and `dest_path`. If | |
| 439 | 436 | /// they are equal, does nothing. Otherwise, atomically copies `source_path` to |
| 440 | 437 | /// `dest_path`, creating the parent directory hierarchy as needed. The |
| 441 | /// destination file gains the mtime, atime, and mode of the source file so | |
| 438 | /// destination file gains the mtime, atime, and permissions of the source file so | |
| 442 | 439 | /// that the next call to `updateFile` will not need a copy. |
| 443 | 440 | /// |
| 444 | 441 | /// Returns the previous status of the file before updating. |
| ... | ... | @@ -459,7 +456,7 @@ pub fn updateFile( |
| 459 | 456 | defer src_file.close(io); |
| 460 | 457 | |
| 461 | 458 | const src_stat = try src_file.stat(io); |
| 462 | const actual_mode = options.override_mode orelse src_stat.mode; | |
| 459 | const actual_permissions = options.override_permissions orelse src_stat.permissions; | |
| 463 | 460 | check_dest_stat: { |
| 464 | 461 | const dest_stat = blk: { |
| 465 | 462 | var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) { |
| ... | ... | @@ -473,19 +470,19 @@ pub fn updateFile( |
| 473 | 470 | |
| 474 | 471 | if (src_stat.size == dest_stat.size and |
| 475 | 472 | src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and |
| 476 | actual_mode == dest_stat.mode) | |
| 473 | actual_permissions == dest_stat.permissions) | |
| 477 | 474 | { |
| 478 | 475 | return .fresh; |
| 479 | 476 | } |
| 480 | 477 | } |
| 481 | 478 | |
| 482 | 479 | if (std.fs.path.dirname(dest_path)) |dirname| { |
| 483 | try dest_dir.makePathMode(io, dirname, default_mode); | |
| 480 | try dest_dir.makePath(io, dirname, .default_dir); | |
| 484 | 481 | } |
| 485 | 482 | |
| 486 | 483 | var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available. |
| 487 | 484 | var atomic_file = try std.fs.Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{ |
| 488 | .mode = actual_mode, | |
| 485 | .permissions = actual_permissions, | |
| 489 | 486 | .write_buffer = &buffer, |
| 490 | 487 | }); |
| 491 | 488 | defer atomic_file.deinit(); |
| ... | ... | @@ -555,17 +552,12 @@ pub const MakeError = error{ |
| 555 | 552 | /// Related: |
| 556 | 553 | /// * `makePath` |
| 557 | 554 | /// * `makeDirAbsolute` |
| 558 | pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void { | |
| 559 | return io.vtable.dirMake(io.userdata, dir, sub_path, default_mode); | |
| 555 | pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakeError!void { | |
| 556 | return io.vtable.dirMake(io.userdata, dir, sub_path, permissions); | |
| 560 | 557 | } |
| 561 | 558 | |
| 562 | 559 | pub const MakePathError = MakeError || StatPathError; |
| 563 | 560 | |
| 564 | /// Same as `makePathMode` but passes `default_mode`. | |
| 565 | pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void { | |
| 566 | _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, default_mode); | |
| 567 | } | |
| 568 | ||
| 569 | 561 | /// Creates parent directories as necessary to ensure `sub_path` exists as a directory. |
| 570 | 562 | /// |
| 571 | 563 | /// Returns success if the path already exists and is a directory. |
| ... | ... | @@ -587,16 +579,16 @@ pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void { |
| 587 | 579 | /// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`, |
| 588 | 580 | /// meaning a `sub_path` like "first/../second" will create both a `./first` |
| 589 | 581 | /// and a `./second` directory. |
| 590 | pub fn makePathMode(dir: Dir, io: Io, sub_path: []const u8, mode: Mode) MakePathError!void { | |
| 591 | _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, mode); | |
| 582 | pub fn makePath(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakePathError!void { | |
| 583 | _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions); | |
| 592 | 584 | } |
| 593 | 585 | |
| 594 | 586 | pub const MakePathStatus = enum { existed, created }; |
| 595 | 587 | |
| 596 | 588 | /// Same as `makePath` except returns whether the path already existed or was |
| 597 | 589 | /// successfully created. |
| 598 | pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus { | |
| 599 | return io.vtable.dirMakePath(io.userdata, dir, sub_path, default_mode); | |
| 590 | pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakePathError!MakePathStatus { | |
| 591 | return io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions); | |
| 600 | 592 | } |
| 601 | 593 | |
| 602 | 594 | pub const MakeOpenPathError = MakeError || OpenError || StatPathError; |
| ... | ... | @@ -609,8 +601,8 @@ pub const MakeOpenPathError = MakeError || OpenError || StatPathError; |
| 609 | 601 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 610 | 602 | /// On WASI, `sub_path` should be encoded as valid UTF-8. |
| 611 | 603 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. |
| 612 | pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) MakeOpenPathError!Dir { | |
| 613 | return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options); | |
| 604 | pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions, options: OpenOptions) MakeOpenPathError!Dir { | |
| 605 | return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, permissions, options); | |
| 614 | 606 | } |
| 615 | 607 | |
| 616 | 608 | pub const Stat = File.Stat; |
| ... | ... | @@ -1453,8 +1445,8 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, sub_path: []const u8, kind_hint: File. |
| 1453 | 1445 | } |
| 1454 | 1446 | |
| 1455 | 1447 | pub const CopyFileOptions = struct { |
| 1456 | /// When this is `null` the mode is copied from the source file. | |
| 1457 | override_mode: ?File.Mode = null, | |
| 1448 | /// When this is `null` the permissions are copied from the source file. | |
| 1449 | override_permissions: ?File.Permissions = null, | |
| 1458 | 1450 | }; |
| 1459 | 1451 | |
| 1460 | 1452 | pub const CopyFileError = File.OpenError || File.StatError || |
| ... | ... | @@ -1486,15 +1478,15 @@ pub fn copyFile( |
| 1486 | 1478 | var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{}); |
| 1487 | 1479 | defer file_reader.file.close(io); |
| 1488 | 1480 | |
| 1489 | const mode = options.override_mode orelse blk: { | |
| 1481 | const permissions = options.override_permissions orelse blk: { | |
| 1490 | 1482 | const st = try file_reader.file.stat(io); |
| 1491 | 1483 | file_reader.size = st.size; |
| 1492 | break :blk st.mode; | |
| 1484 | break :blk st.permissions; | |
| 1493 | 1485 | }; |
| 1494 | 1486 | |
| 1495 | 1487 | var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available. |
| 1496 | 1488 | var atomic_file = try dest_dir.atomicFile(io, dest_path, .{ |
| 1497 | .mode = mode, | |
| 1489 | .permissions = permissions, | |
| 1498 | 1490 | .write_buffer = &buffer, |
| 1499 | 1491 | }); |
| 1500 | 1492 | defer atomic_file.deinit(io); |
| ... | ... | @@ -1508,7 +1500,7 @@ pub fn copyFile( |
| 1508 | 1500 | } |
| 1509 | 1501 | |
| 1510 | 1502 | pub const AtomicFileOptions = struct { |
| 1511 | mode: File.Mode = File.default_mode, | |
| 1503 | permissions: File.Permissions = .default_file, | |
| 1512 | 1504 | make_path: bool = false, |
| 1513 | 1505 | write_buffer: []u8, |
| 1514 | 1506 | }; |
| ... | ... | @@ -1530,13 +1522,14 @@ pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFil |
| 1530 | 1522 | else |
| 1531 | 1523 | try parent.openDir(io, dirname, .{}); |
| 1532 | 1524 | |
| 1533 | return .init(std.fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer); | |
| 1525 | return .init(std.fs.path.basename(dest_path), options.permissions, dir, true, options.write_buffer); | |
| 1534 | 1526 | } else { |
| 1535 | return .init(dest_path, options.mode, parent, false, options.write_buffer); | |
| 1527 | return .init(dest_path, options.permissions, parent, false, options.write_buffer); | |
| 1536 | 1528 | } |
| 1537 | 1529 | } |
| 1538 | 1530 | |
| 1539 | pub const SetModeError = File.SetModeError; | |
| 1531 | pub const SetPermissionsError = File.SetPermissionsError; | |
| 1532 | pub const Permissions = File.Permissions; | |
| 1540 | 1533 | |
| 1541 | 1534 | /// Also known as "chmod". |
| 1542 | 1535 | /// |
| ... | ... | @@ -1544,8 +1537,8 @@ pub const SetModeError = File.SetModeError; |
| 1544 | 1537 | /// successfully, or must have the effective user ID matching the owner |
| 1545 | 1538 | /// of the directory. Additionally, the directory must have been opened |
| 1546 | 1539 | /// with `OpenOptions.iterate` set to `true`. |
| 1547 | pub fn setMode(dir: Dir, io: Io, new_mode: File.Mode) SetModeError!void { | |
| 1548 | return io.vtable.dirSetMode(io.userdata, dir, new_mode); | |
| 1540 | pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPermissionsError!void { | |
| 1541 | return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions); | |
| 1549 | 1542 | } |
| 1550 | 1543 | |
| 1551 | 1544 | pub const SetOwnerError = File.SetOwnerError; |
| ... | ... | @@ -1561,9 +1554,31 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr |
| 1561 | 1554 | return io.vtable.dirSetOwner(io.userdata, dir, owner, group); |
| 1562 | 1555 | } |
| 1563 | 1556 | |
| 1564 | pub const SetPermissionsError = File.SetPermissionsError; | |
| 1565 | pub const Permissions = File.Permissions; | |
| 1557 | pub const SetTimestampsError = File.SetTimestampsError; | |
| 1558 | ||
| 1559 | pub const SetTimestampsOptions = struct { | |
| 1560 | follow_symlinks: bool = true, | |
| 1561 | }; | |
| 1562 | ||
| 1563 | /// The granularity that ultimately is stored depends on the combination of | |
| 1564 | /// operating system and file system. When a value as provided that exceeds | |
| 1565 | /// this range, the value is clamped to the maximum. | |
| 1566 | pub fn setTimestamps( | |
| 1567 | dir: Dir, | |
| 1568 | io: Io, | |
| 1569 | sub_path: []const u8, | |
| 1570 | last_accessed: Io.Timestamp, | |
| 1571 | last_modified: Io.Timestamp, | |
| 1572 | options: SetTimestampsOptions, | |
| 1573 | ) SetTimestampsError!void { | |
| 1574 | return io.vtable.dirSetTimestamps(io.userdata, dir, sub_path, last_accessed, last_modified, options); | |
| 1575 | } | |
| 1566 | 1576 | |
| 1567 | pub fn setPermissions(dir: Dir, io: Io, permissions: Permissions) SetPermissionsError!void { | |
| 1568 | return io.vtable.dirSetPermissions(io.userdata, dir, permissions); | |
| 1577 | /// Sets the accessed and modification timestamps of the provided path to the | |
| 1578 | /// current wall clock time. | |
| 1579 | /// | |
| 1580 | /// The granularity that ultimately is stored depends on the combination of | |
| 1581 | /// operating system and file system. | |
| 1582 | pub fn setTimestampsNow(dir: Dir, io: Io, sub_path: []const u8, options: SetTimestampsOptions) SetTimestampsError!void { | |
| 1583 | return io.vtable.fileSetTimestampsNow(io.userdata, dir, sub_path, options); | |
| 1569 | 1584 | } |
lib/std/Io/File.zig+266-519| ... | ... | @@ -11,20 +11,15 @@ const Dir = std.Io.Dir; |
| 11 | 11 | |
| 12 | 12 | handle: Handle, |
| 13 | 13 | |
| 14 | pub const Reader = @import("File/Reader.zig"); | |
| 15 | pub const Writer = @import("File/Writer.zig"); | |
| 16 | pub const Atomic = @import("File/Atomic.zig"); | |
| 17 | ||
| 14 | 18 | pub const Handle = std.posix.fd_t; |
| 15 | pub const Mode = std.posix.mode_t; | |
| 16 | 19 | pub const INode = std.posix.ino_t; |
| 17 | 20 | pub const Uid = std.posix.uid_t; |
| 18 | 21 | pub const Gid = std.posix.gid_t; |
| 19 | 22 | |
| 20 | /// This is the default mode given to POSIX operating systems for creating | |
| 21 | /// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first, | |
| 22 | /// since most people would expect "-rw-r--r--", for example, when using | |
| 23 | /// the `touch` command, which would correspond to `0o644`. However, POSIX | |
| 24 | /// libc implementations use `0o666` inside `fopen` and then rely on the | |
| 25 | /// process-scoped "umask" setting to adjust this number for file creation. | |
| 26 | pub const default_mode: Mode = if (Mode == u0) 0 else 0o666; | |
| 27 | ||
| 28 | 23 | pub const Kind = enum { |
| 29 | 24 | block_device, |
| 30 | 25 | character_device, |
| ... | ... | @@ -53,8 +48,7 @@ pub const Stat = struct { |
| 53 | 48 | /// is unique to each filesystem. |
| 54 | 49 | inode: INode, |
| 55 | 50 | size: u64, |
| 56 | /// This is available on POSIX systems and is always 0 otherwise. | |
| 57 | mode: Mode, | |
| 51 | permissions: Permissions, | |
| 58 | 52 | kind: Kind, |
| 59 | 53 | /// Last access time in nanoseconds, relative to UTC 1970-01-01. |
| 60 | 54 | atime: Io.Timestamp, |
| ... | ... | @@ -103,11 +97,6 @@ pub const Lock = enum { |
| 103 | 97 | exclusive, |
| 104 | 98 | }; |
| 105 | 99 | |
| 106 | pub const LockError = error{ | |
| 107 | SystemResources, | |
| 108 | FileLocksNotSupported, | |
| 109 | } || Io.UnexpectedError; | |
| 110 | ||
| 111 | 100 | pub const OpenFlags = struct { |
| 112 | 101 | mode: OpenMode = .read_only, |
| 113 | 102 | |
| ... | ... | @@ -200,9 +189,7 @@ pub const CreateFlags = struct { |
| 200 | 189 | /// is available to proceed. |
| 201 | 190 | lock_nonblocking: bool = false, |
| 202 | 191 | |
| 203 | /// For POSIX systems this is the file system mode the file will | |
| 204 | /// be created with. On other systems this is always 0. | |
| 205 | mode: Mode = default_mode, | |
| 192 | permissions: Permissions = .default_file, | |
| 206 | 193 | }; |
| 207 | 194 | |
| 208 | 195 | pub const OpenError = error{ |
| ... | ... | @@ -251,7 +238,7 @@ pub const OpenError = error{ |
| 251 | 238 | /// The path already exists and the `CREAT` and `EXCL` flags were provided. |
| 252 | 239 | PathAlreadyExists, |
| 253 | 240 | DeviceBusy, |
| 254 | FileLocksNotSupported, | |
| 241 | FileLocksUnsupported, | |
| 255 | 242 | /// One of these three things: |
| 256 | 243 | /// * pathname refers to an executable image which is currently being |
| 257 | 244 | /// executed and write access was requested. |
| ... | ... | @@ -269,7 +256,216 @@ pub fn close(file: File, io: Io) void { |
| 269 | 256 | return io.vtable.fileClose(io.userdata, file); |
| 270 | 257 | } |
| 271 | 258 | |
| 272 | pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || std.posix.FlockError; | |
| 259 | pub const SyncError = error{ | |
| 260 | InputOutput, | |
| 261 | NoSpaceLeft, | |
| 262 | DiskQuota, | |
| 263 | AccessDenied, | |
| 264 | } || Io.Cancelable || Io.UnexpectedError; | |
| 265 | ||
| 266 | /// Blocks until all pending file contents and metadata modifications for the | |
| 267 | /// file have been synchronized with the underlying filesystem. | |
| 268 | /// | |
| 269 | /// This does not ensure that metadata for the directory containing the file | |
| 270 | /// has also reached disk. | |
| 271 | pub fn sync(file: File, io: Io) SyncError!void { | |
| 272 | return io.vtable.fileSync(io.userdata, file); | |
| 273 | } | |
| 274 | ||
| 275 | /// Test whether the file refers to a terminal. | |
| 276 | /// | |
| 277 | /// See also: | |
| 278 | /// * `getOrEnableAnsiEscapeSupport` | |
| 279 | /// * `supportsAnsiEscapeCodes`. | |
| 280 | pub fn isTty(file: File, io: Io) bool { | |
| 281 | return io.vtable.fileIsTty(io.userdata, file); | |
| 282 | } | |
| 283 | ||
| 284 | pub const EnableAnsiEscapeCodesError = error{} || Io.Cancelable || Io.UnexpectedError; | |
| 285 | ||
| 286 | pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError { | |
| 287 | return io.vtable.fileEnableAnsiEscapeCodes(io.userdata, file); | |
| 288 | } | |
| 289 | ||
| 290 | /// Test whether ANSI escape codes will be treated as such without | |
| 291 | /// attempting to enable support for ANSI escape codes. | |
| 292 | pub fn supportsAnsiEscapeCodes(file: File, io: Io) Io.Cancelable!bool { | |
| 293 | return io.vtable.fileSupportsAnsiEscapeCodes(io.userdata, file); | |
| 294 | } | |
| 295 | ||
| 296 | pub const SetLengthError = error{ | |
| 297 | FileTooBig, | |
| 298 | InputOutput, | |
| 299 | FileBusy, | |
| 300 | AccessDenied, | |
| 301 | PermissionDenied, | |
| 302 | NonResizable, | |
| 303 | } || Io.Cancelable || Io.UnexpectedError; | |
| 304 | ||
| 305 | /// Truncates or expands the file, populating any new data with zeroes. | |
| 306 | /// | |
| 307 | /// The file offset after this call is left unchanged. | |
| 308 | pub fn setLength(file: File, io: Io, new_length: u64) SetLengthError!void { | |
| 309 | return io.vtable.fileSetLength(io.userdata, file, new_length); | |
| 310 | } | |
| 311 | ||
| 312 | pub const LengthError = StatError; | |
| 313 | ||
| 314 | /// Retrieve the ending byte index of the file. | |
| 315 | /// | |
| 316 | /// Sometimes cheaper than `stat` if only the length is needed. | |
| 317 | pub fn length(file: File, io: Io) LengthError!u64 { | |
| 318 | return io.vtable.fileLength(io.userdata, file); | |
| 319 | } | |
| 320 | ||
| 321 | pub const SetPermissionsError = error{ | |
| 322 | AccessDenied, | |
| 323 | PermissionDenied, | |
| 324 | InputOutput, | |
| 325 | SymLinkLoop, | |
| 326 | FileNotFound, | |
| 327 | SystemResources, | |
| 328 | ReadOnlyFileSystem, | |
| 329 | } || Io.Cancelable || Io.UnexpectedError; | |
| 330 | ||
| 331 | /// Also known as "chmod". | |
| 332 | /// | |
| 333 | /// The process must have the correct privileges in order to do this | |
| 334 | /// successfully, or must have the effective user ID matching the owner of the | |
| 335 | /// file. | |
| 336 | pub fn setPermissions(file: File, io: Io, new_permissions: Permissions) SetPermissionsError!void { | |
| 337 | return io.vtable.fileSetPermissions(io.userdata, file, new_permissions); | |
| 338 | } | |
| 339 | ||
| 340 | pub const SetOwnerError = error{ | |
| 341 | AccessDenied, | |
| 342 | PermissionDenied, | |
| 343 | InputOutput, | |
| 344 | SymLinkLoop, | |
| 345 | FileNotFound, | |
| 346 | SystemResources, | |
| 347 | ReadOnlyFileSystem, | |
| 348 | } || Io.Cancelable || Io.UnexpectedError; | |
| 349 | ||
| 350 | /// Also known as "chown". | |
| 351 | /// | |
| 352 | /// The process must have the correct privileges in order to do this | |
| 353 | /// successfully. The group may be changed by the owner of the file to any | |
| 354 | /// group of which the owner is a member. If the owner or group is specified as | |
| 355 | /// `null`, the ID is not changed. | |
| 356 | pub fn setOwner(file: File, io: Io, owner: ?Uid, group: ?Gid) SetOwnerError!void { | |
| 357 | return io.vtable.fileSetOwner(io.userdata, file, owner, group); | |
| 358 | } | |
| 359 | ||
| 360 | /// Cross-platform representation of permissions on a file. | |
| 361 | /// | |
| 362 | /// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes". | |
| 363 | /// | |
| 364 | /// Overridable via `std.options`. | |
| 365 | pub const Permissions = std.options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) { | |
| 366 | default_file = 0, | |
| 367 | _, | |
| 368 | ||
| 369 | pub const default_dir: @This() = .default_file; | |
| 370 | pub const has_executable_bit = false; | |
| 371 | ||
| 372 | const windows = std.os.windows; | |
| 373 | ||
| 374 | pub fn toAttributes(self: @This()) windows.DWORD { | |
| 375 | return @intFromEnum(self); | |
| 376 | } | |
| 377 | ||
| 378 | pub fn readOnly(self: @This()) bool { | |
| 379 | const attributes = toAttributes(self); | |
| 380 | return attributes & windows.FILE_ATTRIBUTE_READONLY != 0; | |
| 381 | } | |
| 382 | ||
| 383 | pub fn setReadOnly(self: @This(), read_only: bool) @This() { | |
| 384 | const attributes = toAttributes(self); | |
| 385 | return @enumFromInt(if (read_only) | |
| 386 | attributes | windows.FILE_ATTRIBUTE_READONLY | |
| 387 | else | |
| 388 | attributes & ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY)); | |
| 389 | } | |
| 390 | } else if (std.posix.mode_t != u0) enum(std.posix.mode_t) { | |
| 391 | /// This is the default mode given to POSIX operating systems for creating | |
| 392 | /// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first, | |
| 393 | /// since most people would expect "-rw-r--r--", for example, when using | |
| 394 | /// the `touch` command, which would correspond to `0o644`. However, POSIX | |
| 395 | /// libc implementations use `0o666` inside `fopen` and then rely on the | |
| 396 | /// process-scoped "umask" setting to adjust this number for file creation. | |
| 397 | default_file = 0o666, | |
| 398 | default_dir = 0o755, | |
| 399 | _, | |
| 400 | ||
| 401 | pub const has_executable_bit = true; | |
| 402 | ||
| 403 | pub fn toMode(self: @This()) std.posix.mode_t { | |
| 404 | return @intFromEnum(self); | |
| 405 | } | |
| 406 | ||
| 407 | /// Returns `true` if and only if no class has write permissions. | |
| 408 | pub fn readOnly(self: @This()) bool { | |
| 409 | const mode = toMode(self); | |
| 410 | return mode & 0o222 == 0; | |
| 411 | } | |
| 412 | ||
| 413 | /// Enables write permission for all classes. | |
| 414 | pub fn setReadOnly(self: @This(), read_only: bool) @This() { | |
| 415 | const mode = toMode(self); | |
| 416 | const o222 = @as(std.posix.mode_t, 0o222); | |
| 417 | return @enumFromInt(if (read_only) mode & ~o222 else mode | o222); | |
| 418 | } | |
| 419 | } else enum(u0) { | |
| 420 | default_file = 0, | |
| 421 | pub const default_dir: @This() = .default_file; | |
| 422 | pub const has_executable_bit = false; | |
| 423 | }; | |
| 424 | ||
| 425 | pub const SetTimestampsError = error{ | |
| 426 | /// times is NULL, or both nsec values are UTIME_NOW, and either: | |
| 427 | /// * the effective user ID of the caller does not match the owner | |
| 428 | /// of the file, the caller does not have write access to the | |
| 429 | /// file, and the caller is not privileged (Linux: does not have | |
| 430 | /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability); | |
| 431 | /// or, | |
| 432 | /// * the file is marked immutable (see chattr(1)). | |
| 433 | AccessDenied, | |
| 434 | /// The caller attempted to change one or both timestamps to a value | |
| 435 | /// other than the current time, or to change one of the timestamps | |
| 436 | /// to the current time while leaving the other timestamp unchanged, | |
| 437 | /// (i.e., times is not NULL, neither nsec field is UTIME_NOW, | |
| 438 | /// and neither nsec field is UTIME_OMIT) and either: | |
| 439 | /// * the caller's effective user ID does not match the owner of | |
| 440 | /// file, and the caller is not privileged (Linux: does not have | |
| 441 | /// the CAP_FOWNER capability); or, | |
| 442 | /// * the file is marked append-only or immutable (see chattr(1)). | |
| 443 | PermissionDenied, | |
| 444 | ReadOnlyFileSystem, | |
| 445 | } || Io.Cancelable || Io.UnexpectedError; | |
| 446 | ||
| 447 | /// The granularity that ultimately is stored depends on the combination of | |
| 448 | /// operating system and file system. When a value as provided that exceeds | |
| 449 | /// this range, the value is clamped to the maximum. | |
| 450 | pub fn setTimestamps( | |
| 451 | file: File, | |
| 452 | io: Io, | |
| 453 | last_accessed: Io.Timestamp, | |
| 454 | last_modified: Io.Timestamp, | |
| 455 | ) SetTimestampsError!void { | |
| 456 | return io.vtable.fileUpdateTimes(io.userdata, file, last_accessed, last_modified); | |
| 457 | } | |
| 458 | ||
| 459 | /// Sets the accessed and modification timestamps of `file` to the current wall | |
| 460 | /// clock time. | |
| 461 | /// | |
| 462 | /// The granularity that ultimately is stored depends on the combination of | |
| 463 | /// operating system and file system. | |
| 464 | pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { | |
| 465 | return io.vtable.fileSetTimestampsNow(io.userdata, file); | |
| 466 | } | |
| 467 | ||
| 468 | pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || LockError; | |
| 273 | 469 | |
| 274 | 470 | pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File { |
| 275 | 471 | return io.vtable.openSelfExe(io.userdata, flags); |
| ... | ... | @@ -309,6 +505,12 @@ pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenErr |
| 309 | 505 | return Io.Dir.cwd().openFile(io, absolute_path, flags); |
| 310 | 506 | } |
| 311 | 507 | |
| 508 | pub const SeekError = error{ | |
| 509 | Unseekable, | |
| 510 | /// The file descriptor does not hold the required rights to seek on it. | |
| 511 | AccessDenied, | |
| 512 | } || Io.Cancelable || Io.UnexpectedError; | |
| 513 | ||
| 312 | 514 | /// Defaults to positional reading; falls back to streaming. |
| 313 | 515 | /// |
| 314 | 516 | /// Positional is more threadsafe, since the global seek position is not |
| ... | ... | @@ -324,509 +526,54 @@ pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader { |
| 324 | 526 | return .initStreaming(file, io, buffer); |
| 325 | 527 | } |
| 326 | 528 | |
| 327 | pub const SeekError = error{ | |
| 328 | Unseekable, | |
| 329 | /// The file descriptor does not hold the required rights to seek on it. | |
| 330 | AccessDenied, | |
| 331 | } || Io.Cancelable || Io.UnexpectedError; | |
| 332 | ||
| 333 | /// Memoizes key information about a file handle such as: | |
| 334 | /// * The size from calling stat, or the error that occurred therein. | |
| 335 | /// * The current seek position. | |
| 336 | /// * The error that occurred when trying to seek. | |
| 337 | /// * Whether reading should be done positionally or streaming. | |
| 338 | /// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`) | |
| 339 | /// versus plain variants (e.g. `read`). | |
| 529 | /// Defaults to positional reading; falls back to streaming. | |
| 340 | 530 | /// |
| 341 | /// Fulfills the `Io.Reader` interface. | |
| 342 | pub const Reader = struct { | |
| 343 | io: Io, | |
| 344 | file: File, | |
| 345 | err: ?Error = null, | |
| 346 | mode: Reader.Mode = .positional, | |
| 347 | /// Tracks the true seek position in the file. To obtain the logical | |
| 348 | /// position, use `logicalPos`. | |
| 349 | pos: u64 = 0, | |
| 350 | size: ?u64 = null, | |
| 351 | size_err: ?SizeError = null, | |
| 352 | seek_err: ?Reader.SeekError = null, | |
| 353 | interface: Io.Reader, | |
| 354 | ||
| 355 | pub const Error = error{ | |
| 356 | InputOutput, | |
| 357 | SystemResources, | |
| 358 | IsDir, | |
| 359 | BrokenPipe, | |
| 360 | ConnectionResetByPeer, | |
| 361 | Timeout, | |
| 362 | /// In WASI, EBADF is mapped to this error because it is returned when | |
| 363 | /// trying to read a directory file descriptor as if it were a file. | |
| 364 | NotOpenForReading, | |
| 365 | SocketUnconnected, | |
| 366 | /// This error occurs when no global event loop is configured, | |
| 367 | /// and reading from the file descriptor would block. | |
| 368 | WouldBlock, | |
| 369 | /// In WASI, this error occurs when the file descriptor does | |
| 370 | /// not hold the required rights to read from it. | |
| 371 | AccessDenied, | |
| 372 | /// This error occurs in Linux if the process to be read from | |
| 373 | /// no longer exists. | |
| 374 | ProcessNotFound, | |
| 375 | /// Unable to read file due to lock. | |
| 376 | LockViolation, | |
| 377 | } || Io.Cancelable || Io.UnexpectedError; | |
| 378 | ||
| 379 | pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{ | |
| 380 | /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. | |
| 381 | Streaming, | |
| 382 | }; | |
| 383 | ||
| 384 | pub const SeekError = File.SeekError || error{ | |
| 385 | /// Seeking fell back to reading, and reached the end before the requested seek position. | |
| 386 | /// `pos` remains at the end of the file. | |
| 387 | EndOfStream, | |
| 388 | /// Seeking fell back to reading, which failed. | |
| 389 | ReadFailed, | |
| 390 | }; | |
| 391 | ||
| 392 | pub const Mode = enum { | |
| 393 | streaming, | |
| 394 | positional, | |
| 395 | /// Avoid syscalls other than `read` and `readv`. | |
| 396 | streaming_reading, | |
| 397 | /// Avoid syscalls other than `pread` and `preadv`. | |
| 398 | positional_reading, | |
| 399 | /// Indicates reading cannot continue because of a seek failure. | |
| 400 | failure, | |
| 401 | ||
| 402 | pub fn toStreaming(m: @This()) @This() { | |
| 403 | return switch (m) { | |
| 404 | .positional, .streaming => .streaming, | |
| 405 | .positional_reading, .streaming_reading => .streaming_reading, | |
| 406 | .failure => .failure, | |
| 407 | }; | |
| 408 | } | |
| 409 | ||
| 410 | pub fn toReading(m: @This()) @This() { | |
| 411 | return switch (m) { | |
| 412 | .positional, .positional_reading => .positional_reading, | |
| 413 | .streaming, .streaming_reading => .streaming_reading, | |
| 414 | .failure => .failure, | |
| 415 | }; | |
| 416 | } | |
| 417 | }; | |
| 418 | ||
| 419 | pub fn initInterface(buffer: []u8) Io.Reader { | |
| 420 | return .{ | |
| 421 | .vtable = &.{ | |
| 422 | .stream = Reader.stream, | |
| 423 | .discard = Reader.discard, | |
| 424 | .readVec = Reader.readVec, | |
| 425 | }, | |
| 426 | .buffer = buffer, | |
| 427 | .seek = 0, | |
| 428 | .end = 0, | |
| 429 | }; | |
| 430 | } | |
| 431 | ||
| 432 | pub fn init(file: File, io: Io, buffer: []u8) Reader { | |
| 433 | return .{ | |
| 434 | .io = io, | |
| 435 | .file = file, | |
| 436 | .interface = initInterface(buffer), | |
| 437 | }; | |
| 438 | } | |
| 439 | ||
| 440 | pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader { | |
| 441 | return .{ | |
| 442 | .io = io, | |
| 443 | .file = file, | |
| 444 | .interface = initInterface(buffer), | |
| 445 | .size = size, | |
| 446 | }; | |
| 447 | } | |
| 448 | ||
| 449 | /// Positional is more threadsafe, since the global seek position is not | |
| 450 | /// affected, but when such syscalls are not available, preemptively | |
| 451 | /// initializing in streaming mode skips a failed syscall. | |
| 452 | pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader { | |
| 453 | return .{ | |
| 454 | .io = io, | |
| 455 | .file = file, | |
| 456 | .interface = Reader.initInterface(buffer), | |
| 457 | .mode = .streaming, | |
| 458 | .seek_err = error.Unseekable, | |
| 459 | .size_err = error.Streaming, | |
| 460 | }; | |
| 461 | } | |
| 462 | ||
| 463 | pub fn getSize(r: *Reader) SizeError!u64 { | |
| 464 | return r.size orelse { | |
| 465 | if (r.size_err) |err| return err; | |
| 466 | if (stat(r.file, r.io)) |st| { | |
| 467 | if (st.kind == .file) { | |
| 468 | r.size = st.size; | |
| 469 | return st.size; | |
| 470 | } else { | |
| 471 | r.mode = r.mode.toStreaming(); | |
| 472 | r.size_err = error.Streaming; | |
| 473 | return error.Streaming; | |
| 474 | } | |
| 475 | } else |err| { | |
| 476 | r.size_err = err; | |
| 477 | return err; | |
| 478 | } | |
| 479 | }; | |
| 480 | } | |
| 481 | ||
| 482 | pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { | |
| 483 | const io = r.io; | |
| 484 | switch (r.mode) { | |
| 485 | .positional, .positional_reading => { | |
| 486 | setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); | |
| 487 | }, | |
| 488 | .streaming, .streaming_reading => { | |
| 489 | const seek_err = r.seek_err orelse e: { | |
| 490 | if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) { | |
| 491 | setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); | |
| 492 | return; | |
| 493 | } else |err| { | |
| 494 | r.seek_err = err; | |
| 495 | break :e err; | |
| 496 | } | |
| 497 | }; | |
| 498 | var remaining = std.math.cast(u64, offset) orelse return seek_err; | |
| 499 | while (remaining > 0) { | |
| 500 | remaining -= discard(&r.interface, .limited64(remaining)) catch |err| { | |
| 501 | r.seek_err = err; | |
| 502 | return err; | |
| 503 | }; | |
| 504 | } | |
| 505 | r.interface.tossBuffered(); | |
| 506 | }, | |
| 507 | .failure => return r.seek_err.?, | |
| 508 | } | |
| 509 | } | |
| 510 | ||
| 511 | /// Repositions logical read offset relative to the beginning of the file. | |
| 512 | pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { | |
| 513 | const io = r.io; | |
| 514 | switch (r.mode) { | |
| 515 | .positional, .positional_reading => { | |
| 516 | setLogicalPos(r, offset); | |
| 517 | }, | |
| 518 | .streaming, .streaming_reading => { | |
| 519 | const logical_pos = logicalPos(r); | |
| 520 | if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos)); | |
| 521 | if (r.seek_err) |err| return err; | |
| 522 | io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| { | |
| 523 | r.seek_err = err; | |
| 524 | return err; | |
| 525 | }; | |
| 526 | setLogicalPos(r, offset); | |
| 527 | }, | |
| 528 | .failure => return r.seek_err.?, | |
| 529 | } | |
| 530 | } | |
| 531 | ||
| 532 | pub fn logicalPos(r: *const Reader) u64 { | |
| 533 | return r.pos - r.interface.bufferedLen(); | |
| 534 | } | |
| 535 | ||
| 536 | fn setLogicalPos(r: *Reader, offset: u64) void { | |
| 537 | const logical_pos = r.logicalPos(); | |
| 538 | if (offset < logical_pos or offset >= r.pos) { | |
| 539 | r.interface.tossBuffered(); | |
| 540 | r.pos = offset; | |
| 541 | } else r.interface.toss(@intCast(offset - logical_pos)); | |
| 542 | } | |
| 543 | ||
| 544 | /// Number of slices to store on the stack, when trying to send as many byte | |
| 545 | /// vectors through the underlying read calls as possible. | |
| 546 | const max_buffers_len = 16; | |
| 547 | ||
| 548 | fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { | |
| 549 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); | |
| 550 | return streamMode(r, w, limit, r.mode); | |
| 551 | } | |
| 552 | ||
| 553 | pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize { | |
| 554 | switch (mode) { | |
| 555 | .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { | |
| 556 | error.Unimplemented => { | |
| 557 | r.mode = r.mode.toReading(); | |
| 558 | return 0; | |
| 559 | }, | |
| 560 | else => |e| return e, | |
| 561 | }, | |
| 562 | .positional_reading => { | |
| 563 | const dest = limit.slice(try w.writableSliceGreedy(1)); | |
| 564 | var data: [1][]u8 = .{dest}; | |
| 565 | const n = try readVecPositional(r, &data); | |
| 566 | w.advance(n); | |
| 567 | return n; | |
| 568 | }, | |
| 569 | .streaming_reading => { | |
| 570 | const dest = limit.slice(try w.writableSliceGreedy(1)); | |
| 571 | var data: [1][]u8 = .{dest}; | |
| 572 | const n = try readVecStreaming(r, &data); | |
| 573 | w.advance(n); | |
| 574 | return n; | |
| 575 | }, | |
| 576 | .failure => return error.ReadFailed, | |
| 577 | } | |
| 578 | } | |
| 579 | ||
| 580 | fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { | |
| 581 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); | |
| 582 | switch (r.mode) { | |
| 583 | .positional, .positional_reading => return readVecPositional(r, data), | |
| 584 | .streaming, .streaming_reading => return readVecStreaming(r, data), | |
| 585 | .failure => return error.ReadFailed, | |
| 586 | } | |
| 587 | } | |
| 588 | ||
| 589 | fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize { | |
| 590 | const io = r.io; | |
| 591 | var iovecs_buffer: [max_buffers_len][]u8 = undefined; | |
| 592 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); | |
| 593 | const dest = iovecs_buffer[0..dest_n]; | |
| 594 | assert(dest[0].len > 0); | |
| 595 | const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) { | |
| 596 | error.Unseekable => { | |
| 597 | r.mode = r.mode.toStreaming(); | |
| 598 | const pos = r.pos; | |
| 599 | if (pos != 0) { | |
| 600 | r.pos = 0; | |
| 601 | r.seekBy(@intCast(pos)) catch { | |
| 602 | r.mode = .failure; | |
| 603 | return error.ReadFailed; | |
| 604 | }; | |
| 605 | } | |
| 606 | return 0; | |
| 607 | }, | |
| 608 | else => |e| { | |
| 609 | r.err = e; | |
| 610 | return error.ReadFailed; | |
| 611 | }, | |
| 612 | }; | |
| 613 | if (n == 0) { | |
| 614 | r.size = r.pos; | |
| 615 | return error.EndOfStream; | |
| 616 | } | |
| 617 | r.pos += n; | |
| 618 | if (n > data_size) { | |
| 619 | r.interface.end += n - data_size; | |
| 620 | return data_size; | |
| 621 | } | |
| 622 | return n; | |
| 623 | } | |
| 624 | ||
| 625 | fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize { | |
| 626 | const io = r.io; | |
| 627 | var iovecs_buffer: [max_buffers_len][]u8 = undefined; | |
| 628 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); | |
| 629 | const dest = iovecs_buffer[0..dest_n]; | |
| 630 | assert(dest[0].len > 0); | |
| 631 | const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| { | |
| 632 | r.err = err; | |
| 633 | return error.ReadFailed; | |
| 634 | }; | |
| 635 | if (n == 0) { | |
| 636 | r.size = r.pos; | |
| 637 | return error.EndOfStream; | |
| 638 | } | |
| 639 | r.pos += n; | |
| 640 | if (n > data_size) { | |
| 641 | r.interface.end += n - data_size; | |
| 642 | return data_size; | |
| 643 | } | |
| 644 | return n; | |
| 645 | } | |
| 646 | ||
| 647 | fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { | |
| 648 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); | |
| 649 | const io = r.io; | |
| 650 | const file = r.file; | |
| 651 | switch (r.mode) { | |
| 652 | .positional, .positional_reading => { | |
| 653 | const size = r.getSize() catch { | |
| 654 | r.mode = r.mode.toStreaming(); | |
| 655 | return 0; | |
| 656 | }; | |
| 657 | const logical_pos = logicalPos(r); | |
| 658 | const delta = @min(@intFromEnum(limit), size - logical_pos); | |
| 659 | setLogicalPos(r, logical_pos + delta); | |
| 660 | return delta; | |
| 661 | }, | |
| 662 | .streaming, .streaming_reading => { | |
| 663 | // Unfortunately we can't seek forward without knowing the | |
| 664 | // size because the seek syscalls provided to us will not | |
| 665 | // return the true end position if a seek would exceed the | |
| 666 | // end. | |
| 667 | fallback: { | |
| 668 | if (r.size_err == null and r.seek_err == null) break :fallback; | |
| 669 | ||
| 670 | const buffered_len = r.interface.bufferedLen(); | |
| 671 | var remaining = @intFromEnum(limit); | |
| 672 | if (remaining <= buffered_len) { | |
| 673 | r.interface.seek += remaining; | |
| 674 | return remaining; | |
| 675 | } | |
| 676 | remaining -= buffered_len; | |
| 677 | r.interface.seek = 0; | |
| 678 | r.interface.end = 0; | |
| 679 | ||
| 680 | var trash_buffer: [128]u8 = undefined; | |
| 681 | var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]}; | |
| 682 | var iovecs_buffer: [max_buffers_len][]u8 = undefined; | |
| 683 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data); | |
| 684 | const dest = iovecs_buffer[0..dest_n]; | |
| 685 | assert(dest[0].len > 0); | |
| 686 | const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| { | |
| 687 | r.err = err; | |
| 688 | return error.ReadFailed; | |
| 689 | }; | |
| 690 | if (n == 0) { | |
| 691 | r.size = r.pos; | |
| 692 | return error.EndOfStream; | |
| 693 | } | |
| 694 | r.pos += n; | |
| 695 | if (n > data_size) { | |
| 696 | r.interface.end += n - data_size; | |
| 697 | remaining -= data_size; | |
| 698 | } else { | |
| 699 | remaining -= n; | |
| 700 | } | |
| 701 | return @intFromEnum(limit) - remaining; | |
| 702 | } | |
| 703 | const size = r.getSize() catch return 0; | |
| 704 | const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit)); | |
| 705 | io.vtable.fileSeekBy(io.userdata, file, n) catch |err| { | |
| 706 | r.seek_err = err; | |
| 707 | return 0; | |
| 708 | }; | |
| 709 | r.pos += n; | |
| 710 | return n; | |
| 711 | }, | |
| 712 | .failure => return error.ReadFailed, | |
| 713 | } | |
| 714 | } | |
| 715 | ||
| 716 | /// Returns whether the stream is at the logical end. | |
| 717 | pub fn atEnd(r: *Reader) bool { | |
| 718 | // Even if stat fails, size is set when end is encountered. | |
| 719 | const size = r.size orelse return false; | |
| 720 | return size - logicalPos(r) == 0; | |
| 721 | } | |
| 722 | }; | |
| 723 | ||
| 724 | pub const Atomic = struct { | |
| 725 | file_writer: File.Writer, | |
| 726 | random_integer: u64, | |
| 727 | dest_basename: []const u8, | |
| 728 | file_open: bool, | |
| 729 | file_exists: bool, | |
| 730 | close_dir_on_deinit: bool, | |
| 731 | dir: Dir, | |
| 732 | ||
| 733 | pub const InitError = File.OpenError; | |
| 734 | ||
| 735 | /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. | |
| 736 | pub fn init( | |
| 737 | dest_basename: []const u8, | |
| 738 | mode: File.Mode, | |
| 739 | dir: Dir, | |
| 740 | close_dir_on_deinit: bool, | |
| 741 | write_buffer: []u8, | |
| 742 | ) InitError!Atomic { | |
| 743 | while (true) { | |
| 744 | const random_integer = std.crypto.random.int(u64); | |
| 745 | const tmp_sub_path = std.fmt.hex(random_integer); | |
| 746 | const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { | |
| 747 | error.PathAlreadyExists => continue, | |
| 748 | else => |e| return e, | |
| 749 | }; | |
| 750 | return .{ | |
| 751 | .file_writer = file.writer(write_buffer), | |
| 752 | .random_integer = random_integer, | |
| 753 | .dest_basename = dest_basename, | |
| 754 | .file_open = true, | |
| 755 | .file_exists = true, | |
| 756 | .close_dir_on_deinit = close_dir_on_deinit, | |
| 757 | .dir = dir, | |
| 758 | }; | |
| 759 | } | |
| 760 | } | |
| 761 | ||
| 762 | /// Always call deinit, even after a successful finish(). | |
| 763 | pub fn deinit(af: *Atomic) void { | |
| 764 | if (af.file_open) { | |
| 765 | af.file_writer.file.close(); | |
| 766 | af.file_open = false; | |
| 767 | } | |
| 768 | if (af.file_exists) { | |
| 769 | const tmp_sub_path = std.fmt.hex(af.random_integer); | |
| 770 | af.dir.deleteFile(&tmp_sub_path) catch {}; | |
| 771 | af.file_exists = false; | |
| 772 | } | |
| 773 | if (af.close_dir_on_deinit) { | |
| 774 | af.dir.close(); | |
| 775 | } | |
| 776 | af.* = undefined; | |
| 777 | } | |
| 531 | /// Positional is more threadsafe, since the global seek position is not | |
| 532 | /// affected. | |
| 533 | pub fn writer(file: File, io: Io, buffer: []u8) Writer { | |
| 534 | return .init(file, io, buffer); | |
| 535 | } | |
| 778 | 536 | |
| 779 | pub const FlushError = File.WriteError; | |
| 537 | /// Positional is more threadsafe, since the global seek position is not | |
| 538 | /// affected, but when such syscalls are not available, preemptively | |
| 539 | /// initializing in streaming mode will skip a failed syscall. | |
| 540 | pub fn writerStreaming(file: File, io: Io, buffer: []u8) Writer { | |
| 541 | return .initStreaming(file, io, buffer); | |
| 542 | } | |
| 780 | 543 | |
| 781 | pub fn flush(af: *Atomic) FlushError!void { | |
| 782 | af.file_writer.interface.flush() catch |err| switch (err) { | |
| 783 | error.WriteFailed => return af.file_writer.err.?, | |
| 784 | }; | |
| 785 | } | |
| 544 | pub const LockError = error{ | |
| 545 | SystemResources, | |
| 546 | FileLocksUnsupported, | |
| 547 | } || Io.Cancelable || Io.UnexpectedError; | |
| 786 | 548 | |
| 787 | pub const RenameIntoPlaceError = Dir.RenameError; | |
| 788 | ||
| 789 | /// On Windows, this function introduces a period of time where some file | |
| 790 | /// system operations on the destination file will result in | |
| 791 | /// `error.AccessDenied`, including rename operations (such as the one used in | |
| 792 | /// this function). | |
| 793 | pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void { | |
| 794 | const io = af.file_writer.io; | |
| 795 | assert(af.file_exists); | |
| 796 | if (af.file_open) { | |
| 797 | af.file_writer.file.close(); | |
| 798 | af.file_open = false; | |
| 799 | } | |
| 800 | const tmp_sub_path = std.fmt.hex(af.random_integer); | |
| 801 | try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io); | |
| 802 | af.file_exists = false; | |
| 803 | } | |
| 549 | /// Blocks when an incompatible lock is held by another process. A process may | |
| 550 | /// hold only one type of lock (shared or exclusive) on a file. When a process | |
| 551 | /// terminates in any way, the lock is released. | |
| 552 | /// | |
| 553 | /// Assumes the file is unlocked. | |
| 554 | pub fn lock(file: File, io: Io, l: Lock) LockError!void { | |
| 555 | return io.vtable.fileLock(io.userdata, file, l); | |
| 556 | } | |
| 804 | 557 | |
| 805 | pub const FinishError = FlushError || RenameIntoPlaceError; | |
| 558 | /// Assumes the file is locked. | |
| 559 | pub fn unlock(file: File, io: Io) void { | |
| 560 | return io.vtable.fileUnlock(io.userdata, file); | |
| 561 | } | |
| 806 | 562 | |
| 807 | /// Combination of `flush` followed by `renameIntoPlace`. | |
| 808 | pub fn finish(af: *Atomic) FinishError!void { | |
| 809 | try af.flush(); | |
| 810 | try af.renameIntoPlace(); | |
| 811 | } | |
| 812 | }; | |
| 563 | /// Attempts to obtain a lock, returning `true` if the lock is obtained, and | |
| 564 | /// `false` if there was an existing incompatible lock held. A process may hold | |
| 565 | /// only one type of lock (shared or exclusive) on a file. When a process | |
| 566 | /// terminates in any way, the lock is released. | |
| 567 | /// | |
| 568 | /// Assumes the file is unlocked. | |
| 569 | pub fn tryLock(file: File, io: Io, l: Lock) LockError!bool { | |
| 570 | return io.vtable.fileTryLock(io.userdata, file, l); | |
| 571 | } | |
| 813 | 572 | |
| 814 | pub const SetModeError = error{ | |
| 815 | AccessDenied, | |
| 816 | PermissionDenied, | |
| 817 | InputOutput, | |
| 818 | SymLinkLoop, | |
| 819 | FileNotFound, | |
| 820 | SystemResources, | |
| 821 | ReadOnlyFileSystem, | |
| 822 | } || Io.Cancelable || Io.UnexpectedError; | |
| 573 | pub const DowngradeLockError = Io.Cancelable || Io.UnexpectedError; | |
| 823 | 574 | |
| 824 | pub const SetOwnerError = error{ | |
| 825 | AccessDenied, | |
| 826 | PermissionDenied, | |
| 827 | InputOutput, | |
| 828 | SymLinkLoop, | |
| 829 | FileNotFound, | |
| 830 | SystemResources, | |
| 831 | ReadOnlyFileSystem, | |
| 832 | } || Io.Cancelable || Io.UnexpectedError; | |
| 575 | /// Assumes the file is already locked in exclusive mode. | |
| 576 | /// Atomically modifies the lock to be in shared mode, without releasing it. | |
| 577 | pub fn downgradeLock(file: File, io: Io) LockError!void { | |
| 578 | return io.vtable.fileDowngradeLock(io.userdata, file); | |
| 579 | } |
lib/std/Io/File/Atomic.zig created+99| ... | ... | @@ -0,0 +1,99 @@ |
| 1 | const Atomic = @This(); | |
| 2 | ||
| 3 | const std = @import("../../std.zig"); | |
| 4 | const Io = std.Io; | |
| 5 | const File = std.Io.File; | |
| 6 | const Dir = std.Io.Dir; | |
| 7 | const assert = std.debug.assert; | |
| 8 | ||
| 9 | file_writer: File.Writer, | |
| 10 | random_integer: u64, | |
| 11 | dest_basename: []const u8, | |
| 12 | file_open: bool, | |
| 13 | file_exists: bool, | |
| 14 | close_dir_on_deinit: bool, | |
| 15 | dir: Dir, | |
| 16 | ||
| 17 | pub const InitError = File.OpenError; | |
| 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 | mode: File.Mode, | |
| 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, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { | |
| 32 | error.PathAlreadyExists => continue, | |
| 33 | else => |e| return e, | |
| 34 | }; | |
| 35 | return .{ | |
| 36 | .file_writer = file.writer(io, write_buffer), | |
| 37 | .random_integer = random_integer, | |
| 38 | .dest_basename = dest_basename, | |
| 39 | .file_open = true, | |
| 40 | .file_exists = true, | |
| 41 | .close_dir_on_deinit = close_dir_on_deinit, | |
| 42 | .dir = dir, | |
| 43 | }; | |
| 44 | } | |
| 45 | } | |
| 46 | ||
| 47 | /// Always call deinit, even after a successful finish(). | |
| 48 | pub fn deinit(af: *Atomic) void { | |
| 49 | const io = af.file_writer.io; | |
| 50 | ||
| 51 | if (af.file_open) { | |
| 52 | af.file_writer.file.close(io); | |
| 53 | af.file_open = false; | |
| 54 | } | |
| 55 | if (af.file_exists) { | |
| 56 | const tmp_sub_path = std.fmt.hex(af.random_integer); | |
| 57 | af.dir.deleteFile(io, &tmp_sub_path) catch {}; | |
| 58 | af.file_exists = false; | |
| 59 | } | |
| 60 | if (af.close_dir_on_deinit) { | |
| 61 | af.dir.close(io); | |
| 62 | } | |
| 63 | af.* = undefined; | |
| 64 | } | |
| 65 | ||
| 66 | pub const FlushError = File.WriteError; | |
| 67 | ||
| 68 | pub fn flush(af: *Atomic) FlushError!void { | |
| 69 | af.file_writer.interface.flush() catch |err| switch (err) { | |
| 70 | error.WriteFailed => return af.file_writer.err.?, | |
| 71 | }; | |
| 72 | } | |
| 73 | ||
| 74 | pub const RenameIntoPlaceError = Dir.RenameError; | |
| 75 | ||
| 76 | /// On Windows, this function introduces a period of time where some file | |
| 77 | /// system operations on the destination file will result in | |
| 78 | /// `error.AccessDenied`, including rename operations (such as the one used in | |
| 79 | /// this function). | |
| 80 | pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void { | |
| 81 | const io = af.file_writer.io; | |
| 82 | ||
| 83 | assert(af.file_exists); | |
| 84 | if (af.file_open) { | |
| 85 | af.file_writer.file.close(io); | |
| 86 | af.file_open = false; | |
| 87 | } | |
| 88 | const tmp_sub_path = std.fmt.hex(af.random_integer); | |
| 89 | try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io); | |
| 90 | af.file_exists = false; | |
| 91 | } | |
| 92 | ||
| 93 | pub const FinishError = FlushError || RenameIntoPlaceError; | |
| 94 | ||
| 95 | /// Combination of `flush` followed by `renameIntoPlace`. | |
| 96 | pub fn finish(af: *Atomic) FinishError!void { | |
| 97 | try af.flush(); | |
| 98 | try af.renameIntoPlace(); | |
| 99 | } |
lib/std/Io/File/Reader.zig created+395| ... | ... | @@ -0,0 +1,395 @@ |
| 1 | //! Memoizes key information about a file handle such as: | |
| 2 | //! * The size from calling stat, or the error that occurred therein. | |
| 3 | //! * The current seek position. | |
| 4 | //! * The error that occurred when trying to seek. | |
| 5 | //! * Whether reading should be done positionally or streaming. | |
| 6 | //! * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`) | |
| 7 | //! versus plain variants (e.g. `read`). | |
| 8 | //! | |
| 9 | //! Fulfills the `Io.Reader` interface. | |
| 10 | const Reader = @This(); | |
| 11 | ||
| 12 | const std = @import("../../std.zig"); | |
| 13 | const Io = std.Io; | |
| 14 | const File = std.Io.File; | |
| 15 | const assert = std.debug.assert; | |
| 16 | ||
| 17 | io: Io, | |
| 18 | file: File, | |
| 19 | err: ?Error = null, | |
| 20 | mode: Reader.Mode = .positional, | |
| 21 | /// Tracks the true seek position in the file. To obtain the logical | |
| 22 | /// position, use `logicalPos`. | |
| 23 | pos: u64 = 0, | |
| 24 | size: ?u64 = null, | |
| 25 | size_err: ?SizeError = null, | |
| 26 | seek_err: ?Reader.SeekError = null, | |
| 27 | interface: Io.Reader, | |
| 28 | ||
| 29 | pub const Error = error{ | |
| 30 | InputOutput, | |
| 31 | SystemResources, | |
| 32 | IsDir, | |
| 33 | BrokenPipe, | |
| 34 | ConnectionResetByPeer, | |
| 35 | Timeout, | |
| 36 | /// In WASI, EBADF is mapped to this error because it is returned when | |
| 37 | /// trying to read a directory file descriptor as if it were a file. | |
| 38 | NotOpenForReading, | |
| 39 | SocketUnconnected, | |
| 40 | /// This error occurs when no global event loop is configured, | |
| 41 | /// and reading from the file descriptor would block. | |
| 42 | WouldBlock, | |
| 43 | /// In WASI, this error occurs when the file descriptor does | |
| 44 | /// not hold the required rights to read from it. | |
| 45 | AccessDenied, | |
| 46 | /// This error occurs in Linux if the process to be read from | |
| 47 | /// no longer exists. | |
| 48 | ProcessNotFound, | |
| 49 | /// Unable to read file due to lock. | |
| 50 | LockViolation, | |
| 51 | } || Io.Cancelable || Io.UnexpectedError; | |
| 52 | ||
| 53 | pub const SizeError = std.os.windows.GetFileSizeError || File.StatError || error{ | |
| 54 | /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. | |
| 55 | Streaming, | |
| 56 | }; | |
| 57 | ||
| 58 | pub const SeekError = File.SeekError || error{ | |
| 59 | /// Seeking fell back to reading, and reached the end before the requested seek position. | |
| 60 | /// `pos` remains at the end of the file. | |
| 61 | EndOfStream, | |
| 62 | /// Seeking fell back to reading, which failed. | |
| 63 | ReadFailed, | |
| 64 | }; | |
| 65 | ||
| 66 | pub const Mode = enum { | |
| 67 | streaming, | |
| 68 | positional, | |
| 69 | /// Avoid syscalls other than `read` and `readv`. | |
| 70 | streaming_reading, | |
| 71 | /// Avoid syscalls other than `pread` and `preadv`. | |
| 72 | positional_reading, | |
| 73 | /// Indicates reading cannot continue because of a seek failure. | |
| 74 | failure, | |
| 75 | ||
| 76 | pub fn toStreaming(m: @This()) @This() { | |
| 77 | return switch (m) { | |
| 78 | .positional, .streaming => .streaming, | |
| 79 | .positional_reading, .streaming_reading => .streaming_reading, | |
| 80 | .failure => .failure, | |
| 81 | }; | |
| 82 | } | |
| 83 | ||
| 84 | pub fn toReading(m: @This()) @This() { | |
| 85 | return switch (m) { | |
| 86 | .positional, .positional_reading => .positional_reading, | |
| 87 | .streaming, .streaming_reading => .streaming_reading, | |
| 88 | .failure => .failure, | |
| 89 | }; | |
| 90 | } | |
| 91 | }; | |
| 92 | ||
| 93 | pub fn initInterface(buffer: []u8) Io.Reader { | |
| 94 | return .{ | |
| 95 | .vtable = &.{ | |
| 96 | .stream = Reader.stream, | |
| 97 | .discard = Reader.discard, | |
| 98 | .readVec = Reader.readVec, | |
| 99 | }, | |
| 100 | .buffer = buffer, | |
| 101 | .seek = 0, | |
| 102 | .end = 0, | |
| 103 | }; | |
| 104 | } | |
| 105 | ||
| 106 | pub fn init(file: File, io: Io, buffer: []u8) Reader { | |
| 107 | return .{ | |
| 108 | .io = io, | |
| 109 | .file = file, | |
| 110 | .interface = initInterface(buffer), | |
| 111 | }; | |
| 112 | } | |
| 113 | ||
| 114 | pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader { | |
| 115 | return .{ | |
| 116 | .io = io, | |
| 117 | .file = file, | |
| 118 | .interface = initInterface(buffer), | |
| 119 | .size = size, | |
| 120 | }; | |
| 121 | } | |
| 122 | ||
| 123 | /// Positional is more threadsafe, since the global seek position is not | |
| 124 | /// affected, but when such syscalls are not available, preemptively | |
| 125 | /// initializing in streaming mode skips a failed syscall. | |
| 126 | pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader { | |
| 127 | return .{ | |
| 128 | .io = io, | |
| 129 | .file = file, | |
| 130 | .interface = Reader.initInterface(buffer), | |
| 131 | .mode = .streaming, | |
| 132 | .seek_err = error.Unseekable, | |
| 133 | .size_err = error.Streaming, | |
| 134 | }; | |
| 135 | } | |
| 136 | ||
| 137 | pub fn getSize(r: *Reader) SizeError!u64 { | |
| 138 | return r.size orelse { | |
| 139 | if (r.size_err) |err| return err; | |
| 140 | if (r.file.stat(r.io)) |st| { | |
| 141 | if (st.kind == .file) { | |
| 142 | r.size = st.size; | |
| 143 | return st.size; | |
| 144 | } else { | |
| 145 | r.mode = r.mode.toStreaming(); | |
| 146 | r.size_err = error.Streaming; | |
| 147 | return error.Streaming; | |
| 148 | } | |
| 149 | } else |err| { | |
| 150 | r.size_err = err; | |
| 151 | return err; | |
| 152 | } | |
| 153 | }; | |
| 154 | } | |
| 155 | ||
| 156 | pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { | |
| 157 | const io = r.io; | |
| 158 | switch (r.mode) { | |
| 159 | .positional, .positional_reading => { | |
| 160 | setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); | |
| 161 | }, | |
| 162 | .streaming, .streaming_reading => { | |
| 163 | const seek_err = r.seek_err orelse e: { | |
| 164 | if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| { | |
| 165 | setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); | |
| 166 | return; | |
| 167 | } else |err| { | |
| 168 | r.seek_err = err; | |
| 169 | break :e err; | |
| 170 | } | |
| 171 | }; | |
| 172 | var remaining = std.math.cast(u64, offset) orelse return seek_err; | |
| 173 | while (remaining > 0) { | |
| 174 | remaining -= discard(&r.interface, .limited64(remaining)) catch |err| { | |
| 175 | r.seek_err = err; | |
| 176 | return err; | |
| 177 | }; | |
| 178 | } | |
| 179 | r.interface.tossBuffered(); | |
| 180 | }, | |
| 181 | .failure => return r.seek_err.?, | |
| 182 | } | |
| 183 | } | |
| 184 | ||
| 185 | /// Repositions logical read offset relative to the beginning of the file. | |
| 186 | pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { | |
| 187 | const io = r.io; | |
| 188 | switch (r.mode) { | |
| 189 | .positional, .positional_reading => { | |
| 190 | setLogicalPos(r, offset); | |
| 191 | }, | |
| 192 | .streaming, .streaming_reading => { | |
| 193 | const logical_pos = logicalPos(r); | |
| 194 | if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos)); | |
| 195 | if (r.seek_err) |err| return err; | |
| 196 | io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| { | |
| 197 | r.seek_err = err; | |
| 198 | return err; | |
| 199 | }; | |
| 200 | setLogicalPos(r, offset); | |
| 201 | }, | |
| 202 | .failure => return r.seek_err.?, | |
| 203 | } | |
| 204 | } | |
| 205 | ||
| 206 | pub fn logicalPos(r: *const Reader) u64 { | |
| 207 | return r.pos - r.interface.bufferedLen(); | |
| 208 | } | |
| 209 | ||
| 210 | fn setLogicalPos(r: *Reader, offset: u64) void { | |
| 211 | const logical_pos = r.logicalPos(); | |
| 212 | if (offset < logical_pos or offset >= r.pos) { | |
| 213 | r.interface.tossBuffered(); | |
| 214 | r.pos = offset; | |
| 215 | } else r.interface.toss(@intCast(offset - logical_pos)); | |
| 216 | } | |
| 217 | ||
| 218 | /// Number of slices to store on the stack, when trying to send as many byte | |
| 219 | /// vectors through the underlying read calls as possible. | |
| 220 | const max_buffers_len = 16; | |
| 221 | ||
| 222 | fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { | |
| 223 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); | |
| 224 | return streamMode(r, w, limit, r.mode); | |
| 225 | } | |
| 226 | ||
| 227 | pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize { | |
| 228 | switch (mode) { | |
| 229 | .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { | |
| 230 | error.Unimplemented => { | |
| 231 | r.mode = r.mode.toReading(); | |
| 232 | return 0; | |
| 233 | }, | |
| 234 | else => |e| return e, | |
| 235 | }, | |
| 236 | .positional_reading => { | |
| 237 | const dest = limit.slice(try w.writableSliceGreedy(1)); | |
| 238 | var data: [1][]u8 = .{dest}; | |
| 239 | const n = try readVecPositional(r, &data); | |
| 240 | w.advance(n); | |
| 241 | return n; | |
| 242 | }, | |
| 243 | .streaming_reading => { | |
| 244 | const dest = limit.slice(try w.writableSliceGreedy(1)); | |
| 245 | var data: [1][]u8 = .{dest}; | |
| 246 | const n = try readVecStreaming(r, &data); | |
| 247 | w.advance(n); | |
| 248 | return n; | |
| 249 | }, | |
| 250 | .failure => return error.ReadFailed, | |
| 251 | } | |
| 252 | } | |
| 253 | ||
| 254 | fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { | |
| 255 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); | |
| 256 | switch (r.mode) { | |
| 257 | .positional, .positional_reading => return readVecPositional(r, data), | |
| 258 | .streaming, .streaming_reading => return readVecStreaming(r, data), | |
| 259 | .failure => return error.ReadFailed, | |
| 260 | } | |
| 261 | } | |
| 262 | ||
| 263 | fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize { | |
| 264 | const io = r.io; | |
| 265 | var iovecs_buffer: [max_buffers_len][]u8 = undefined; | |
| 266 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); | |
| 267 | const dest = iovecs_buffer[0..dest_n]; | |
| 268 | assert(dest[0].len > 0); | |
| 269 | const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) { | |
| 270 | error.Unseekable => { | |
| 271 | r.mode = r.mode.toStreaming(); | |
| 272 | const pos = r.pos; | |
| 273 | if (pos != 0) { | |
| 274 | r.pos = 0; | |
| 275 | r.seekBy(@intCast(pos)) catch { | |
| 276 | r.mode = .failure; | |
| 277 | return error.ReadFailed; | |
| 278 | }; | |
| 279 | } | |
| 280 | return 0; | |
| 281 | }, | |
| 282 | else => |e| { | |
| 283 | r.err = e; | |
| 284 | return error.ReadFailed; | |
| 285 | }, | |
| 286 | }; | |
| 287 | if (n == 0) { | |
| 288 | r.size = r.pos; | |
| 289 | return error.EndOfStream; | |
| 290 | } | |
| 291 | r.pos += n; | |
| 292 | if (n > data_size) { | |
| 293 | r.interface.end += n - data_size; | |
| 294 | return data_size; | |
| 295 | } | |
| 296 | return n; | |
| 297 | } | |
| 298 | ||
| 299 | fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize { | |
| 300 | const io = r.io; | |
| 301 | var iovecs_buffer: [max_buffers_len][]u8 = undefined; | |
| 302 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); | |
| 303 | const dest = iovecs_buffer[0..dest_n]; | |
| 304 | assert(dest[0].len > 0); | |
| 305 | const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| { | |
| 306 | r.err = err; | |
| 307 | return error.ReadFailed; | |
| 308 | }; | |
| 309 | if (n == 0) { | |
| 310 | r.size = r.pos; | |
| 311 | return error.EndOfStream; | |
| 312 | } | |
| 313 | r.pos += n; | |
| 314 | if (n > data_size) { | |
| 315 | r.interface.end += n - data_size; | |
| 316 | return data_size; | |
| 317 | } | |
| 318 | return n; | |
| 319 | } | |
| 320 | ||
| 321 | fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { | |
| 322 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); | |
| 323 | const io = r.io; | |
| 324 | const file = r.file; | |
| 325 | switch (r.mode) { | |
| 326 | .positional, .positional_reading => { | |
| 327 | const size = r.getSize() catch { | |
| 328 | r.mode = r.mode.toStreaming(); | |
| 329 | return 0; | |
| 330 | }; | |
| 331 | const logical_pos = logicalPos(r); | |
| 332 | const delta = @min(@intFromEnum(limit), size - logical_pos); | |
| 333 | setLogicalPos(r, logical_pos + delta); | |
| 334 | return delta; | |
| 335 | }, | |
| 336 | .streaming, .streaming_reading => { | |
| 337 | // Unfortunately we can't seek forward without knowing the | |
| 338 | // size because the seek syscalls provided to us will not | |
| 339 | // return the true end position if a seek would exceed the | |
| 340 | // end. | |
| 341 | fallback: { | |
| 342 | if (r.size_err == null and r.seek_err == null) break :fallback; | |
| 343 | ||
| 344 | const buffered_len = r.interface.bufferedLen(); | |
| 345 | var remaining = @intFromEnum(limit); | |
| 346 | if (remaining <= buffered_len) { | |
| 347 | r.interface.seek += remaining; | |
| 348 | return remaining; | |
| 349 | } | |
| 350 | remaining -= buffered_len; | |
| 351 | r.interface.seek = 0; | |
| 352 | r.interface.end = 0; | |
| 353 | ||
| 354 | var trash_buffer: [128]u8 = undefined; | |
| 355 | var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]}; | |
| 356 | var iovecs_buffer: [max_buffers_len][]u8 = undefined; | |
| 357 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data); | |
| 358 | const dest = iovecs_buffer[0..dest_n]; | |
| 359 | assert(dest[0].len > 0); | |
| 360 | const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| { | |
| 361 | r.err = err; | |
| 362 | return error.ReadFailed; | |
| 363 | }; | |
| 364 | if (n == 0) { | |
| 365 | r.size = r.pos; | |
| 366 | return error.EndOfStream; | |
| 367 | } | |
| 368 | r.pos += n; | |
| 369 | if (n > data_size) { | |
| 370 | r.interface.end += n - data_size; | |
| 371 | remaining -= data_size; | |
| 372 | } else { | |
| 373 | remaining -= n; | |
| 374 | } | |
| 375 | return @intFromEnum(limit) - remaining; | |
| 376 | } | |
| 377 | const size = r.getSize() catch return 0; | |
| 378 | const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit)); | |
| 379 | io.vtable.fileSeekBy(io.userdata, file, n) catch |err| { | |
| 380 | r.seek_err = err; | |
| 381 | return 0; | |
| 382 | }; | |
| 383 | r.pos += n; | |
| 384 | return n; | |
| 385 | }, | |
| 386 | .failure => return error.ReadFailed, | |
| 387 | } | |
| 388 | } | |
| 389 | ||
| 390 | /// Returns whether the stream is at the logical end. | |
| 391 | pub fn atEnd(r: *Reader) bool { | |
| 392 | // Even if stat fails, size is set when end is encountered. | |
| 393 | const size = r.size orelse return false; | |
| 394 | return size - logicalPos(r) == 0; | |
| 395 | } |
lib/std/Io/File/Writer.zig created+566| ... | ... | @@ -0,0 +1,566 @@ |
| 1 | const Writer = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | const native_os = builtin.os.tag; | |
| 5 | const is_windows = native_os == .windows; | |
| 6 | ||
| 7 | const std = @import("../../std.zig"); | |
| 8 | const Io = std.Io; | |
| 9 | const File = std.Io.File; | |
| 10 | const assert = std.debug.assert; | |
| 11 | const windows = std.os.windows; | |
| 12 | const posix = std.posix; | |
| 13 | ||
| 14 | file: File, | |
| 15 | err: ?File.WriteError = null, | |
| 16 | mode: Writer.Mode = .positional, | |
| 17 | /// Tracks the true seek position in the file. To obtain the logical | |
| 18 | /// position, add the buffer size to this value. | |
| 19 | pos: u64 = 0, | |
| 20 | sendfile_err: ?SendfileError = null, | |
| 21 | copy_file_range_err: ?CopyFileRangeError = null, | |
| 22 | fcopyfile_err: ?FcopyfileError = null, | |
| 23 | seek_err: ?Writer.SeekError = null, | |
| 24 | interface: Io.Writer, | |
| 25 | ||
| 26 | pub const Mode = File.Reader.Mode; | |
| 27 | ||
| 28 | pub const SendfileError = error{ | |
| 29 | UnsupportedOperation, | |
| 30 | SystemResources, | |
| 31 | InputOutput, | |
| 32 | BrokenPipe, | |
| 33 | WouldBlock, | |
| 34 | Unexpected, | |
| 35 | }; | |
| 36 | ||
| 37 | pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError; | |
| 38 | ||
| 39 | pub const FcopyfileError = error{ | |
| 40 | OperationNotSupported, | |
| 41 | OutOfMemory, | |
| 42 | Unexpected, | |
| 43 | }; | |
| 44 | ||
| 45 | pub const SeekError = Io.File.SeekError; | |
| 46 | ||
| 47 | /// Number of slices to store on the stack, when trying to send as many byte | |
| 48 | /// vectors through the underlying write calls as possible. | |
| 49 | const max_buffers_len = 16; | |
| 50 | ||
| 51 | pub fn init(file: File, buffer: []u8) Writer { | |
| 52 | return .{ | |
| 53 | .file = file, | |
| 54 | .interface = initInterface(buffer), | |
| 55 | .mode = .positional, | |
| 56 | }; | |
| 57 | } | |
| 58 | ||
| 59 | /// Positional is more threadsafe, since the global seek position is not | |
| 60 | /// affected, but when such syscalls are not available, preemptively | |
| 61 | /// initializing in streaming mode will skip a failed syscall. | |
| 62 | pub fn initStreaming(file: File, buffer: []u8) Writer { | |
| 63 | return .{ | |
| 64 | .file = file, | |
| 65 | .interface = initInterface(buffer), | |
| 66 | .mode = .streaming, | |
| 67 | }; | |
| 68 | } | |
| 69 | ||
| 70 | pub fn initInterface(buffer: []u8) Io.Writer { | |
| 71 | return .{ | |
| 72 | .vtable = &.{ | |
| 73 | .drain = drain, | |
| 74 | .sendFile = sendFile, | |
| 75 | }, | |
| 76 | .buffer = buffer, | |
| 77 | }; | |
| 78 | } | |
| 79 | ||
| 80 | pub fn moveToReader(w: *Writer) File.Reader { | |
| 81 | defer w.* = undefined; | |
| 82 | return .{ | |
| 83 | .io = w.io, | |
| 84 | .file = .{ .handle = w.file.handle }, | |
| 85 | .mode = w.mode, | |
| 86 | .pos = w.pos, | |
| 87 | .interface = File.Reader.initInterface(w.interface.buffer), | |
| 88 | .seek_err = w.seek_err, | |
| 89 | }; | |
| 90 | } | |
| 91 | ||
| 92 | pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { | |
| 93 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); | |
| 94 | const handle = w.file.handle; | |
| 95 | const buffered = io_w.buffered(); | |
| 96 | if (is_windows) switch (w.mode) { | |
| 97 | .positional, .positional_reading => { | |
| 98 | if (buffered.len != 0) { | |
| 99 | const n = windows.WriteFile(handle, buffered, w.pos) catch |err| { | |
| 100 | w.err = err; | |
| 101 | return error.WriteFailed; | |
| 102 | }; | |
| 103 | w.pos += n; | |
| 104 | return io_w.consume(n); | |
| 105 | } | |
| 106 | for (data[0 .. data.len - 1]) |buf| { | |
| 107 | if (buf.len == 0) continue; | |
| 108 | const n = windows.WriteFile(handle, buf, w.pos) catch |err| { | |
| 109 | w.err = err; | |
| 110 | return error.WriteFailed; | |
| 111 | }; | |
| 112 | w.pos += n; | |
| 113 | return io_w.consume(n); | |
| 114 | } | |
| 115 | const pattern = data[data.len - 1]; | |
| 116 | if (pattern.len == 0 or splat == 0) return 0; | |
| 117 | const n = windows.WriteFile(handle, pattern, w.pos) catch |err| { | |
| 118 | w.err = err; | |
| 119 | return error.WriteFailed; | |
| 120 | }; | |
| 121 | w.pos += n; | |
| 122 | return io_w.consume(n); | |
| 123 | }, | |
| 124 | .streaming, .streaming_reading => { | |
| 125 | if (buffered.len != 0) { | |
| 126 | const n = windows.WriteFile(handle, buffered, null) catch |err| { | |
| 127 | w.err = err; | |
| 128 | return error.WriteFailed; | |
| 129 | }; | |
| 130 | w.pos += n; | |
| 131 | return io_w.consume(n); | |
| 132 | } | |
| 133 | for (data[0 .. data.len - 1]) |buf| { | |
| 134 | if (buf.len == 0) continue; | |
| 135 | const n = windows.WriteFile(handle, buf, null) catch |err| { | |
| 136 | w.err = err; | |
| 137 | return error.WriteFailed; | |
| 138 | }; | |
| 139 | w.pos += n; | |
| 140 | return io_w.consume(n); | |
| 141 | } | |
| 142 | const pattern = data[data.len - 1]; | |
| 143 | if (pattern.len == 0 or splat == 0) return 0; | |
| 144 | const n = windows.WriteFile(handle, pattern, null) catch |err| { | |
| 145 | w.err = err; | |
| 146 | return error.WriteFailed; | |
| 147 | }; | |
| 148 | w.pos += n; | |
| 149 | return io_w.consume(n); | |
| 150 | }, | |
| 151 | .failure => return error.WriteFailed, | |
| 152 | }; | |
| 153 | var iovecs: [max_buffers_len]posix.iovec_const = undefined; | |
| 154 | var len: usize = 0; | |
| 155 | if (buffered.len > 0) { | |
| 156 | iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len }; | |
| 157 | len += 1; | |
| 158 | } | |
| 159 | for (data[0 .. data.len - 1]) |d| { | |
| 160 | if (d.len == 0) continue; | |
| 161 | iovecs[len] = .{ .base = d.ptr, .len = d.len }; | |
| 162 | len += 1; | |
| 163 | if (iovecs.len - len == 0) break; | |
| 164 | } | |
| 165 | const pattern = data[data.len - 1]; | |
| 166 | if (iovecs.len - len != 0) switch (splat) { | |
| 167 | 0 => {}, | |
| 168 | 1 => if (pattern.len != 0) { | |
| 169 | iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; | |
| 170 | len += 1; | |
| 171 | }, | |
| 172 | else => switch (pattern.len) { | |
| 173 | 0 => {}, | |
| 174 | 1 => { | |
| 175 | const splat_buffer_candidate = io_w.buffer[io_w.end..]; | |
| 176 | var backup_buffer: [64]u8 = undefined; | |
| 177 | const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len) | |
| 178 | splat_buffer_candidate | |
| 179 | else | |
| 180 | &backup_buffer; | |
| 181 | const memset_len = @min(splat_buffer.len, splat); | |
| 182 | const buf = splat_buffer[0..memset_len]; | |
| 183 | @memset(buf, pattern[0]); | |
| 184 | iovecs[len] = .{ .base = buf.ptr, .len = buf.len }; | |
| 185 | len += 1; | |
| 186 | var remaining_splat = splat - buf.len; | |
| 187 | while (remaining_splat > splat_buffer.len and iovecs.len - len != 0) { | |
| 188 | assert(buf.len == splat_buffer.len); | |
| 189 | iovecs[len] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len }; | |
| 190 | len += 1; | |
| 191 | remaining_splat -= splat_buffer.len; | |
| 192 | } | |
| 193 | if (remaining_splat > 0 and iovecs.len - len != 0) { | |
| 194 | iovecs[len] = .{ .base = splat_buffer.ptr, .len = remaining_splat }; | |
| 195 | len += 1; | |
| 196 | } | |
| 197 | }, | |
| 198 | else => for (0..splat) |_| { | |
| 199 | iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; | |
| 200 | len += 1; | |
| 201 | if (iovecs.len - len == 0) break; | |
| 202 | }, | |
| 203 | }, | |
| 204 | }; | |
| 205 | if (len == 0) return 0; | |
| 206 | switch (w.mode) { | |
| 207 | .positional, .positional_reading => { | |
| 208 | const n = posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) { | |
| 209 | error.Unseekable => { | |
| 210 | w.mode = w.mode.toStreaming(); | |
| 211 | const pos = w.pos; | |
| 212 | if (pos != 0) { | |
| 213 | w.pos = 0; | |
| 214 | w.seekTo(@intCast(pos)) catch { | |
| 215 | w.mode = .failure; | |
| 216 | return error.WriteFailed; | |
| 217 | }; | |
| 218 | } | |
| 219 | return 0; | |
| 220 | }, | |
| 221 | else => |e| { | |
| 222 | w.err = e; | |
| 223 | return error.WriteFailed; | |
| 224 | }, | |
| 225 | }; | |
| 226 | w.pos += n; | |
| 227 | return io_w.consume(n); | |
| 228 | }, | |
| 229 | .streaming, .streaming_reading => { | |
| 230 | const n = posix.writev(handle, iovecs[0..len]) catch |err| { | |
| 231 | w.err = err; | |
| 232 | return error.WriteFailed; | |
| 233 | }; | |
| 234 | w.pos += n; | |
| 235 | return io_w.consume(n); | |
| 236 | }, | |
| 237 | .failure => return error.WriteFailed, | |
| 238 | } | |
| 239 | } | |
| 240 | ||
| 241 | pub fn sendFile( | |
| 242 | io_w: *Io.Writer, | |
| 243 | file_reader: *Io.File.Reader, | |
| 244 | limit: Io.Limit, | |
| 245 | ) Io.Writer.FileError!usize { | |
| 246 | const reader_buffered = file_reader.interface.buffered(); | |
| 247 | if (reader_buffered.len >= @intFromEnum(limit)) | |
| 248 | return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered)); | |
| 249 | const writer_buffered = io_w.buffered(); | |
| 250 | const file_limit = @intFromEnum(limit) - reader_buffered.len; | |
| 251 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); | |
| 252 | const out_fd = w.file.handle; | |
| 253 | const in_fd = file_reader.file.handle; | |
| 254 | ||
| 255 | if (file_reader.size) |size| { | |
| 256 | if (size - file_reader.pos == 0) { | |
| 257 | if (reader_buffered.len != 0) { | |
| 258 | return sendFileBuffered(io_w, file_reader, reader_buffered); | |
| 259 | } else { | |
| 260 | return error.EndOfStream; | |
| 261 | } | |
| 262 | } | |
| 263 | } | |
| 264 | ||
| 265 | if (native_os == .freebsd and w.mode == .streaming) sf: { | |
| 266 | // Try using sendfile on FreeBSD. | |
| 267 | if (w.sendfile_err != null) break :sf; | |
| 268 | const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; | |
| 269 | var hdtr_data: std.c.sf_hdtr = undefined; | |
| 270 | var headers: [2]posix.iovec_const = undefined; | |
| 271 | var headers_i: u8 = 0; | |
| 272 | if (writer_buffered.len != 0) { | |
| 273 | headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; | |
| 274 | headers_i += 1; | |
| 275 | } | |
| 276 | if (reader_buffered.len != 0) { | |
| 277 | headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; | |
| 278 | headers_i += 1; | |
| 279 | } | |
| 280 | const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { | |
| 281 | hdtr_data = .{ | |
| 282 | .headers = &headers, | |
| 283 | .hdr_cnt = headers_i, | |
| 284 | .trailers = null, | |
| 285 | .trl_cnt = 0, | |
| 286 | }; | |
| 287 | break :b &hdtr_data; | |
| 288 | }; | |
| 289 | var sbytes: std.c.off_t = undefined; | |
| 290 | const nbytes: usize = @min(file_limit, std.math.maxInt(usize)); | |
| 291 | const flags = 0; | |
| 292 | switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { | |
| 293 | .SUCCESS, .INTR => {}, | |
| 294 | .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, | |
| 295 | .BADF => if (builtin.mode == .Debug) @panic("race condition") else { | |
| 296 | w.sendfile_err = error.Unexpected; | |
| 297 | }, | |
| 298 | .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { | |
| 299 | w.sendfile_err = error.Unexpected; | |
| 300 | }, | |
| 301 | .NOTCONN => w.sendfile_err = error.BrokenPipe, | |
| 302 | .AGAIN, .BUSY => if (sbytes == 0) { | |
| 303 | w.sendfile_err = error.WouldBlock; | |
| 304 | }, | |
| 305 | .IO => w.sendfile_err = error.InputOutput, | |
| 306 | .PIPE => w.sendfile_err = error.BrokenPipe, | |
| 307 | .NOBUFS => w.sendfile_err = error.SystemResources, | |
| 308 | else => |err| w.sendfile_err = posix.unexpectedErrno(err), | |
| 309 | } | |
| 310 | if (w.sendfile_err != null) { | |
| 311 | // Give calling code chance to observe the error before trying | |
| 312 | // something else. | |
| 313 | return 0; | |
| 314 | } | |
| 315 | if (sbytes == 0) { | |
| 316 | file_reader.size = file_reader.pos; | |
| 317 | return error.EndOfStream; | |
| 318 | } | |
| 319 | const consumed = io_w.consume(@intCast(sbytes)); | |
| 320 | file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; | |
| 321 | return consumed; | |
| 322 | } | |
| 323 | ||
| 324 | if (native_os.isDarwin() and w.mode == .streaming) sf: { | |
| 325 | // Try using sendfile on macOS. | |
| 326 | if (w.sendfile_err != null) break :sf; | |
| 327 | const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; | |
| 328 | var hdtr_data: std.c.sf_hdtr = undefined; | |
| 329 | var headers: [2]posix.iovec_const = undefined; | |
| 330 | var headers_i: u8 = 0; | |
| 331 | if (writer_buffered.len != 0) { | |
| 332 | headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; | |
| 333 | headers_i += 1; | |
| 334 | } | |
| 335 | if (reader_buffered.len != 0) { | |
| 336 | headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; | |
| 337 | headers_i += 1; | |
| 338 | } | |
| 339 | const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { | |
| 340 | hdtr_data = .{ | |
| 341 | .headers = &headers, | |
| 342 | .hdr_cnt = headers_i, | |
| 343 | .trailers = null, | |
| 344 | .trl_cnt = 0, | |
| 345 | }; | |
| 346 | break :b &hdtr_data; | |
| 347 | }; | |
| 348 | const max_count = std.math.maxInt(i32); // Avoid EINVAL. | |
| 349 | var len: std.c.off_t = @min(file_limit, max_count); | |
| 350 | const flags = 0; | |
| 351 | switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { | |
| 352 | .SUCCESS, .INTR => {}, | |
| 353 | .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, | |
| 354 | .BADF => if (builtin.mode == .Debug) @panic("race condition") else { | |
| 355 | w.sendfile_err = error.Unexpected; | |
| 356 | }, | |
| 357 | .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { | |
| 358 | w.sendfile_err = error.Unexpected; | |
| 359 | }, | |
| 360 | .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { | |
| 361 | w.sendfile_err = error.Unexpected; | |
| 362 | }, | |
| 363 | .NOTCONN => w.sendfile_err = error.BrokenPipe, | |
| 364 | .AGAIN => if (len == 0) { | |
| 365 | w.sendfile_err = error.WouldBlock; | |
| 366 | }, | |
| 367 | .IO => w.sendfile_err = error.InputOutput, | |
| 368 | .PIPE => w.sendfile_err = error.BrokenPipe, | |
| 369 | else => |err| w.sendfile_err = posix.unexpectedErrno(err), | |
| 370 | } | |
| 371 | if (w.sendfile_err != null) { | |
| 372 | // Give calling code chance to observe the error before trying | |
| 373 | // something else. | |
| 374 | return 0; | |
| 375 | } | |
| 376 | if (len == 0) { | |
| 377 | file_reader.size = file_reader.pos; | |
| 378 | return error.EndOfStream; | |
| 379 | } | |
| 380 | const consumed = io_w.consume(@bitCast(len)); | |
| 381 | file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; | |
| 382 | return consumed; | |
| 383 | } | |
| 384 | ||
| 385 | if (native_os == .linux and w.mode == .streaming) sf: { | |
| 386 | // Try using sendfile on Linux. | |
| 387 | if (w.sendfile_err != null) break :sf; | |
| 388 | // Linux sendfile does not support headers. | |
| 389 | if (writer_buffered.len != 0 or reader_buffered.len != 0) | |
| 390 | return sendFileBuffered(io_w, file_reader, reader_buffered); | |
| 391 | const max_count = 0x7ffff000; // Avoid EINVAL. | |
| 392 | var off: std.os.linux.off_t = undefined; | |
| 393 | const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { | |
| 394 | .positional => o: { | |
| 395 | const size = file_reader.getSize() catch return 0; | |
| 396 | off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; | |
| 397 | break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; | |
| 398 | }, | |
| 399 | .streaming => .{ null, limit.minInt(max_count) }, | |
| 400 | .streaming_reading, .positional_reading => break :sf, | |
| 401 | .failure => return error.ReadFailed, | |
| 402 | }; | |
| 403 | const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) { | |
| 404 | error.Unseekable => { | |
| 405 | file_reader.mode = file_reader.mode.toStreaming(); | |
| 406 | const pos = file_reader.pos; | |
| 407 | if (pos != 0) { | |
| 408 | file_reader.pos = 0; | |
| 409 | file_reader.seekBy(@intCast(pos)) catch { | |
| 410 | file_reader.mode = .failure; | |
| 411 | return error.ReadFailed; | |
| 412 | }; | |
| 413 | } | |
| 414 | return 0; | |
| 415 | }, | |
| 416 | else => |e| { | |
| 417 | w.sendfile_err = e; | |
| 418 | return 0; | |
| 419 | }, | |
| 420 | }; | |
| 421 | if (n == 0) { | |
| 422 | file_reader.size = file_reader.pos; | |
| 423 | return error.EndOfStream; | |
| 424 | } | |
| 425 | file_reader.pos += n; | |
| 426 | w.pos += n; | |
| 427 | return n; | |
| 428 | } | |
| 429 | ||
| 430 | const copy_file_range = switch (native_os) { | |
| 431 | .freebsd => std.os.freebsd.copy_file_range, | |
| 432 | .linux => std.os.linux.wrapped.copy_file_range, | |
| 433 | else => {}, | |
| 434 | }; | |
| 435 | if (@TypeOf(copy_file_range) != void) cfr: { | |
| 436 | if (w.copy_file_range_err != null) break :cfr; | |
| 437 | if (writer_buffered.len != 0 or reader_buffered.len != 0) | |
| 438 | return sendFileBuffered(io_w, file_reader, reader_buffered); | |
| 439 | var off_in: i64 = undefined; | |
| 440 | var off_out: i64 = undefined; | |
| 441 | const off_in_ptr: ?*i64 = switch (file_reader.mode) { | |
| 442 | .positional_reading, .streaming_reading => return error.Unimplemented, | |
| 443 | .positional => p: { | |
| 444 | off_in = @intCast(file_reader.pos); | |
| 445 | break :p &off_in; | |
| 446 | }, | |
| 447 | .streaming => null, | |
| 448 | .failure => return error.WriteFailed, | |
| 449 | }; | |
| 450 | const off_out_ptr: ?*i64 = switch (w.mode) { | |
| 451 | .positional_reading, .streaming_reading => return error.Unimplemented, | |
| 452 | .positional => p: { | |
| 453 | off_out = @intCast(w.pos); | |
| 454 | break :p &off_out; | |
| 455 | }, | |
| 456 | .streaming => null, | |
| 457 | .failure => return error.WriteFailed, | |
| 458 | }; | |
| 459 | const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| { | |
| 460 | w.copy_file_range_err = err; | |
| 461 | return 0; | |
| 462 | }; | |
| 463 | if (n == 0) { | |
| 464 | file_reader.size = file_reader.pos; | |
| 465 | return error.EndOfStream; | |
| 466 | } | |
| 467 | file_reader.pos += n; | |
| 468 | w.pos += n; | |
| 469 | return n; | |
| 470 | } | |
| 471 | ||
| 472 | if (builtin.os.tag.isDarwin()) fcf: { | |
| 473 | if (w.fcopyfile_err != null) break :fcf; | |
| 474 | if (file_reader.pos != 0) break :fcf; | |
| 475 | if (w.pos != 0) break :fcf; | |
| 476 | if (limit != .unlimited) break :fcf; | |
| 477 | const size = file_reader.getSize() catch break :fcf; | |
| 478 | if (writer_buffered.len != 0 or reader_buffered.len != 0) | |
| 479 | return sendFileBuffered(io_w, file_reader, reader_buffered); | |
| 480 | const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); | |
| 481 | switch (posix.errno(rc)) { | |
| 482 | .SUCCESS => {}, | |
| 483 | .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { | |
| 484 | w.fcopyfile_err = error.Unexpected; | |
| 485 | return 0; | |
| 486 | }, | |
| 487 | .NOMEM => { | |
| 488 | w.fcopyfile_err = error.OutOfMemory; | |
| 489 | return 0; | |
| 490 | }, | |
| 491 | .OPNOTSUPP => { | |
| 492 | w.fcopyfile_err = error.OperationNotSupported; | |
| 493 | return 0; | |
| 494 | }, | |
| 495 | else => |err| { | |
| 496 | w.fcopyfile_err = posix.unexpectedErrno(err); | |
| 497 | return 0; | |
| 498 | }, | |
| 499 | } | |
| 500 | file_reader.pos = size; | |
| 501 | w.pos = size; | |
| 502 | return size; | |
| 503 | } | |
| 504 | ||
| 505 | return error.Unimplemented; | |
| 506 | } | |
| 507 | ||
| 508 | fn sendFileBuffered( | |
| 509 | io_w: *Io.Writer, | |
| 510 | file_reader: *Io.File.Reader, | |
| 511 | reader_buffered: []const u8, | |
| 512 | ) Io.Writer.FileError!usize { | |
| 513 | const n = try drain(io_w, &.{reader_buffered}, 1); | |
| 514 | file_reader.seekBy(@intCast(n)) catch return error.ReadFailed; | |
| 515 | return n; | |
| 516 | } | |
| 517 | ||
| 518 | pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void { | |
| 519 | try w.interface.flush(); | |
| 520 | try seekToUnbuffered(w, offset); | |
| 521 | } | |
| 522 | ||
| 523 | /// Asserts that no data is currently buffered. | |
| 524 | pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void { | |
| 525 | assert(w.interface.buffered().len == 0); | |
| 526 | switch (w.mode) { | |
| 527 | .positional, .positional_reading => { | |
| 528 | w.pos = offset; | |
| 529 | }, | |
| 530 | .streaming, .streaming_reading => { | |
| 531 | if (w.seek_err) |err| return err; | |
| 532 | posix.lseek_SET(w.file.handle, offset) catch |err| { | |
| 533 | w.seek_err = err; | |
| 534 | return err; | |
| 535 | }; | |
| 536 | w.pos = offset; | |
| 537 | }, | |
| 538 | .failure => return w.seek_err.?, | |
| 539 | } | |
| 540 | } | |
| 541 | ||
| 542 | pub const EndError = File.SetEndPosError || Io.Writer.Error; | |
| 543 | ||
| 544 | /// Flushes any buffered data and sets the end position of the file. | |
| 545 | /// | |
| 546 | /// If not overwriting existing contents, then calling `interface.flush` | |
| 547 | /// directly is sufficient. | |
| 548 | /// | |
| 549 | /// Flush failure is handled by setting `err` so that it can be handled | |
| 550 | /// along with other write failures. | |
| 551 | pub fn end(w: *Writer) EndError!void { | |
| 552 | try w.interface.flush(); | |
| 553 | switch (w.mode) { | |
| 554 | .positional, | |
| 555 | .positional_reading, | |
| 556 | => w.file.setEndPos(w.pos) catch |err| switch (err) { | |
| 557 | error.NonResizable => return, | |
| 558 | else => |e| return e, | |
| 559 | }, | |
| 560 | ||
| 561 | .streaming, | |
| 562 | .streaming_reading, | |
| 563 | .failure, | |
| 564 | => {}, | |
| 565 | } | |
| 566 | } |
lib/std/Io/IoUring.zig+2-2| ... | ... | @@ -1093,7 +1093,7 @@ fn createFile( |
| 1093 | 1093 | .PERM => return error.PermissionDenied, |
| 1094 | 1094 | .EXIST => return error.PathAlreadyExists, |
| 1095 | 1095 | .BUSY => return error.DeviceBusy, |
| 1096 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 1096 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 1097 | 1097 | .AGAIN => return error.WouldBlock, |
| 1098 | 1098 | .TXTBSY => return error.FileBusy, |
| 1099 | 1099 | .NXIO => return error.NoDevice, |
| ... | ... | @@ -1201,7 +1201,7 @@ fn fileOpen( |
| 1201 | 1201 | .PERM => return error.PermissionDenied, |
| 1202 | 1202 | .EXIST => return error.PathAlreadyExists, |
| 1203 | 1203 | .BUSY => return error.DeviceBusy, |
| 1204 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 1204 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 1205 | 1205 | .AGAIN => return error.WouldBlock, |
| 1206 | 1206 | .TXTBSY => return error.FileBusy, |
| 1207 | 1207 | .NXIO => return error.NoDevice, |
lib/std/Io/Threaded.zig+943-53| ... | ... | @@ -668,9 +668,10 @@ pub fn io(t: *Threaded) Io { |
| 668 | 668 | .dirRename = dirRename, |
| 669 | 669 | .dirSymLink = dirSymLink, |
| 670 | 670 | .dirReadLink = dirReadLink, |
| 671 | .dirSetMode = dirSetMode, | |
| 672 | 671 | .dirSetOwner = dirSetOwner, |
| 673 | 672 | .dirSetPermissions = dirSetPermissions, |
| 673 | .dirSetTimestamps = dirSetTimestamps, | |
| 674 | .dirSetTimestampsNow = dirSetTimestampsNow, | |
| 674 | 675 | |
| 675 | 676 | .fileStat = fileStat, |
| 676 | 677 | .fileClose = fileClose, |
| ... | ... | @@ -681,6 +682,19 @@ pub fn io(t: *Threaded) Io { |
| 681 | 682 | .fileSeekBy = fileSeekBy, |
| 682 | 683 | .fileSeekTo = fileSeekTo, |
| 683 | 684 | .openSelfExe = openSelfExe, |
| 685 | .fileSync = fileSync, | |
| 686 | .fileIsTty = fileIsTty, | |
| 687 | .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, | |
| 688 | .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes, | |
| 689 | .fileSetLength = fileSetLength, | |
| 690 | .fileSetOwner = fileSetOwner, | |
| 691 | .fileSetPermissions = fileSetPermissions, | |
| 692 | .fileSetTimestamps = fileSetTimestamps, | |
| 693 | .fileSetTimestampsNow = fileSetTimestampsNow, | |
| 694 | .fileLock = fileLock, | |
| 695 | .fileTryLock = fileTryLock, | |
| 696 | .fileUnlock = fileUnlock, | |
| 697 | .fileDowngradeLock = fileDowngradeLock, | |
| 684 | 698 | |
| 685 | 699 | .now = now, |
| 686 | 700 | .sleep = sleep, |
| ... | ... | @@ -774,9 +788,10 @@ pub fn ioBasic(t: *Threaded) Io { |
| 774 | 788 | .dirRename = dirRename, |
| 775 | 789 | .dirSymLink = dirSymLink, |
| 776 | 790 | .dirReadLink = dirReadLink, |
| 777 | .dirSetMode = dirSetMode, | |
| 778 | 791 | .dirSetOwner = dirSetOwner, |
| 779 | 792 | .dirSetPermissions = dirSetPermissions, |
| 793 | .dirSetTimestamps = dirSetTimestamps, | |
| 794 | .dirSetTimestampsNow = dirSetTimestampsNow, | |
| 780 | 795 | |
| 781 | 796 | .fileStat = fileStat, |
| 782 | 797 | .fileClose = fileClose, |
| ... | ... | @@ -787,6 +802,19 @@ pub fn ioBasic(t: *Threaded) Io { |
| 787 | 802 | .fileSeekBy = fileSeekBy, |
| 788 | 803 | .fileSeekTo = fileSeekTo, |
| 789 | 804 | .openSelfExe = openSelfExe, |
| 805 | .fileSync = fileSync, | |
| 806 | .fileIsTty = fileIsTty, | |
| 807 | .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, | |
| 808 | .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes, | |
| 809 | .fileSetLength = fileSetLength, | |
| 810 | .fileSetOwner = fileSetOwner, | |
| 811 | .fileSetPermissions = fileSetPermissions, | |
| 812 | .fileSetTimestamps = fileSetTimestamps, | |
| 813 | .fileSetTimestampsNow = fileSetTimestampsNow, | |
| 814 | .fileLock = fileLock, | |
| 815 | .fileTryLock = fileTryLock, | |
| 816 | .fileUnlock = fileUnlock, | |
| 817 | .fileDowngradeLock = fileDowngradeLock, | |
| 790 | 818 | |
| 791 | 819 | .now = now, |
| 792 | 820 | .sleep = sleep, |
| ... | ... | @@ -831,6 +859,7 @@ const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fs |
| 831 | 859 | const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; |
| 832 | 860 | const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; |
| 833 | 861 | const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; |
| 862 | const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate; | |
| 834 | 863 | |
| 835 | 864 | /// Trailing data: |
| 836 | 865 | /// 1. context |
| ... | ... | @@ -1956,7 +1985,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi |
| 1956 | 1985 | // size provided. This is treated as success because the type of variable-length information that this would be relevant for |
| 1957 | 1986 | // (name, volume name, etc) we don't care about. |
| 1958 | 1987 | .BUFFER_OVERFLOW => {}, |
| 1959 | .INVALID_PARAMETER => unreachable, | |
| 1988 | .INVALID_PARAMETER => |err| return windows.statusBug(err), | |
| 1960 | 1989 | .ACCESS_DENIED => return error.AccessDenied, |
| 1961 | 1990 | else => return windows.unexpectedStatus(rc), |
| 1962 | 1991 | } |
| ... | ... | @@ -1971,7 +2000,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi |
| 1971 | 2000 | .SUCCESS => {}, |
| 1972 | 2001 | // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors |
| 1973 | 2002 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e |
| 1974 | .INFO_LENGTH_MISMATCH => unreachable, | |
| 2003 | .INFO_LENGTH_MISMATCH => |err| return windows.statusBug(err), | |
| 1975 | 2004 | .ACCESS_DENIED => return error.AccessDenied, |
| 1976 | 2005 | else => return windows.unexpectedStatus(rc), |
| 1977 | 2006 | } |
| ... | ... | @@ -2279,7 +2308,7 @@ fn dirCreateFilePosix( |
| 2279 | 2308 | .PERM => return error.PermissionDenied, |
| 2280 | 2309 | .EXIST => return error.PathAlreadyExists, |
| 2281 | 2310 | .BUSY => return error.DeviceBusy, |
| 2282 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 2311 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 2283 | 2312 | .AGAIN => return error.WouldBlock, |
| 2284 | 2313 | .TXTBSY => return error.FileBusy, |
| 2285 | 2314 | .NXIO => return error.NoDevice, |
| ... | ... | @@ -2318,7 +2347,7 @@ fn dirCreateFilePosix( |
| 2318 | 2347 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 2319 | 2348 | .NOLCK => return error.SystemResources, |
| 2320 | 2349 | .AGAIN => return error.WouldBlock, |
| 2321 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 2350 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 2322 | 2351 | else => |err| return posix.unexpectedErrno(err), |
| 2323 | 2352 | } |
| 2324 | 2353 | }, |
| ... | ... | @@ -2588,7 +2617,7 @@ fn dirOpenFilePosix( |
| 2588 | 2617 | .PERM => return error.PermissionDenied, |
| 2589 | 2618 | .EXIST => return error.PathAlreadyExists, |
| 2590 | 2619 | .BUSY => return error.DeviceBusy, |
| 2591 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 2620 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 2592 | 2621 | .AGAIN => return error.WouldBlock, |
| 2593 | 2622 | .TXTBSY => return error.FileBusy, |
| 2594 | 2623 | .NXIO => return error.NoDevice, |
| ... | ... | @@ -2626,7 +2655,7 @@ fn dirOpenFilePosix( |
| 2626 | 2655 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 2627 | 2656 | .NOLCK => return error.SystemResources, |
| 2628 | 2657 | .AGAIN => return error.WouldBlock, |
| 2629 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 2658 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 2630 | 2659 | else => |err| return posix.unexpectedErrno(err), |
| 2631 | 2660 | } |
| 2632 | 2661 | }, |
| ... | ... | @@ -3544,12 +3573,12 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, re |
| 3544 | 3573 | ); |
| 3545 | 3574 | switch (rc) { |
| 3546 | 3575 | .SUCCESS => {}, |
| 3547 | .OBJECT_NAME_INVALID => unreachable, | |
| 3576 | .OBJECT_NAME_INVALID => |err| return w.statusBug(err), | |
| 3548 | 3577 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 3549 | 3578 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, |
| 3550 | 3579 | .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found |
| 3551 | 3580 | .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't |
| 3552 | .INVALID_PARAMETER => unreachable, | |
| 3581 | .INVALID_PARAMETER => |err| return w.statusBug(err), | |
| 3553 | 3582 | .FILE_IS_A_DIRECTORY => return error.IsDir, |
| 3554 | 3583 | .NOT_A_DIRECTORY => return error.NotDir, |
| 3555 | 3584 | .SHARING_VIOLATION => return error.FileBusy, |
| ... | ... | @@ -3621,7 +3650,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, re |
| 3621 | 3650 | switch (rc) { |
| 3622 | 3651 | .SUCCESS => {}, |
| 3623 | 3652 | .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, |
| 3624 | .INVALID_PARAMETER => unreachable, | |
| 3653 | .INVALID_PARAMETER => |err| return w.statusBug(err), | |
| 3625 | 3654 | .CANNOT_DELETE => return error.AccessDenied, |
| 3626 | 3655 | .MEDIA_WRITE_PROTECTED => return error.AccessDenied, |
| 3627 | 3656 | .ACCESS_DENIED => return error.AccessDenied, |
| ... | ... | @@ -3835,9 +3864,9 @@ fn dirRenameWindows( |
| 3835 | 3864 | |
| 3836 | 3865 | switch (rc) { |
| 3837 | 3866 | .SUCCESS => {}, |
| 3838 | .INVALID_HANDLE => unreachable, | |
| 3839 | .INVALID_PARAMETER => unreachable, | |
| 3840 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | |
| 3867 | .INVALID_HANDLE => |err| return w.statusBug(err), | |
| 3868 | .INVALID_PARAMETER => |err| return w.statusBug(err), | |
| 3869 | .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err), | |
| 3841 | 3870 | .ACCESS_DENIED => return error.AccessDenied, |
| 3842 | 3871 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 3843 | 3872 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, |
| ... | ... | @@ -4320,25 +4349,50 @@ fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, bu |
| 4320 | 4349 | } |
| 4321 | 4350 | } |
| 4322 | 4351 | |
| 4323 | const dirSetMode = switch (native_os) { | |
| 4324 | .windows => dirSetModeUnsupported, | |
| 4325 | else => dirSetModePosix, | |
| 4352 | const dirSetPermissions = switch (native_os) { | |
| 4353 | .windows => dirSetPermissionsWindows, | |
| 4354 | else => dirSetPermissionsPosix, | |
| 4355 | }; | |
| 4356 | ||
| 4357 | fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Io.Dir, permissions: Io.Dir.Permissions) Io.Dir.SetPermissionsError!void { | |
| 4358 | // TODO I think we can actually set permissions on a dir on windows? | |
| 4359 | _ = userdata; | |
| 4360 | _ = dir; | |
| 4361 | _ = permissions; | |
| 4362 | return error.Unexpected; | |
| 4363 | } | |
| 4364 | ||
| 4365 | fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Io.Dir, permissions: Io.Dir.Permissions) Io.Dir.SetPermissionsError!void { | |
| 4366 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4367 | const current_thread = Thread.getCurrent(t); | |
| 4368 | return setPermissionsPosix(current_thread, dir.handle, permissions.toMode()); | |
| 4369 | } | |
| 4370 | ||
| 4371 | const dirSetOwner = switch (native_os) { | |
| 4372 | .windows => dirSetOwnerUnsupported, | |
| 4373 | else => dirSetOwnerPosix, | |
| 4326 | 4374 | }; |
| 4327 | 4375 | |
| 4328 | fn dirSetModeUnsupported(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void { | |
| 4376 | fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void { | |
| 4329 | 4377 | _ = userdata; |
| 4330 | 4378 | _ = dir; |
| 4331 | _ = new_mode; | |
| 4379 | _ = owner; | |
| 4380 | _ = group; | |
| 4332 | 4381 | return error.Unexpected; |
| 4333 | 4382 | } |
| 4334 | 4383 | |
| 4335 | fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void { | |
| 4384 | fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void { | |
| 4336 | 4385 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4337 | 4386 | const current_thread = Thread.getCurrent(t); |
| 4387 | const uid = owner orelse ~@as(posix.uid_t, 0); | |
| 4388 | const gid = group orelse ~@as(posix.gid_t, 0); | |
| 4389 | return setOwnerPosix(current_thread, dir.handle, uid, gid); | |
| 4390 | } | |
| 4338 | 4391 | |
| 4392 | fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) Io.File.SetOwnerError!void { | |
| 4339 | 4393 | try current_thread.beginSyscall(); |
| 4340 | 4394 | while (true) { |
| 4341 | switch (posix.errno(posix.system.fchmod(dir.handle, new_mode))) { | |
| 4395 | switch (posix.errno(posix.system.fchown(fd, uid, gid))) { | |
| 4342 | 4396 | .SUCCESS => return current_thread.endSyscall(), |
| 4343 | 4397 | .CANCELED => return current_thread.endSyscallCanceled(), |
| 4344 | 4398 | .INTR => { |
| ... | ... | @@ -4348,7 +4402,7 @@ fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io |
| 4348 | 4402 | else => |e| { |
| 4349 | 4403 | current_thread.endSyscall(); |
| 4350 | 4404 | switch (e) { |
| 4351 | .BADF => |err| return errnoBug(err), | |
| 4405 | .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Io.Dir.OpenOptions.iterate` | |
| 4352 | 4406 | .FAULT => |err| return errnoBug(err), |
| 4353 | 4407 | .INVAL => |err| return errnoBug(err), |
| 4354 | 4408 | .ACCES => return error.AccessDenied, |
| ... | ... | @@ -4366,28 +4420,386 @@ fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io |
| 4366 | 4420 | } |
| 4367 | 4421 | } |
| 4368 | 4422 | |
| 4369 | const dirSetOwner = switch (native_os) { | |
| 4370 | .windows => dirSetOwnerUnsupported, | |
| 4371 | else => dirSetOwnerPosix, | |
| 4423 | const fileSync = switch (native_os) { | |
| 4424 | .windows => fileSyncWindows, | |
| 4425 | else => fileSyncPosix, | |
| 4372 | 4426 | }; |
| 4373 | 4427 | |
| 4374 | fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void { | |
| 4375 | _ = userdata; | |
| 4376 | _ = dir; | |
| 4377 | _ = owner; | |
| 4378 | _ = group; | |
| 4379 | return error.Unexpected; | |
| 4428 | fn fileSyncWindows(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void { | |
| 4429 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4430 | const current_thread = Thread.getCurrent(t); | |
| 4431 | ||
| 4432 | try current_thread.checkCancel(); | |
| 4433 | ||
| 4434 | if (windows.kernel32.FlushFileBuffers(file.handle) != 0) | |
| 4435 | return; | |
| 4436 | ||
| 4437 | switch (windows.GetLastError()) { | |
| 4438 | .SUCCESS => return, | |
| 4439 | .INVALID_HANDLE => unreachable, | |
| 4440 | .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time | |
| 4441 | .UNEXP_NET_ERR => return error.InputOutput, | |
| 4442 | else => |err| return windows.unexpectedError(err), | |
| 4443 | } | |
| 4380 | 4444 | } |
| 4381 | 4445 | |
| 4382 | fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void { | |
| 4446 | fn fileSyncPosix(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void { | |
| 4447 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4448 | const current_thread = Thread.getCurrent(t); | |
| 4449 | try current_thread.beginSyscall(); | |
| 4450 | while (true) { | |
| 4451 | switch (posix.system.fsync(file.handle)) { | |
| 4452 | .SUCCESS => return current_thread.endSyscall(), | |
| 4453 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4454 | .INTR => { | |
| 4455 | try current_thread.checkCancel(); | |
| 4456 | continue; | |
| 4457 | }, | |
| 4458 | else => |e| { | |
| 4459 | current_thread.endSyscall(); | |
| 4460 | switch (e) { | |
| 4461 | .BADF => |err| return errnoBug(err), | |
| 4462 | .INVAL => |err| return errnoBug(err), | |
| 4463 | .ROFS => |err| return errnoBug(err), | |
| 4464 | .IO => return error.InputOutput, | |
| 4465 | .NOSPC => return error.NoSpaceLeft, | |
| 4466 | .DQUOT => return error.DiskQuota, | |
| 4467 | else => |err| return posix.unexpectedErrno(err), | |
| 4468 | } | |
| 4469 | }, | |
| 4470 | } | |
| 4471 | } | |
| 4472 | } | |
| 4473 | ||
| 4474 | fn fileIsTty(userdata: ?*anyopaque, file: Io.File) Io.Cancelable!bool { | |
| 4475 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4476 | const current_thread = Thread.getCurrent(t); | |
| 4477 | return isTty(current_thread, file); | |
| 4478 | } | |
| 4479 | ||
| 4480 | fn isTty(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { | |
| 4481 | if (is_windows) { | |
| 4482 | if (try isCygwinPty(current_thread, file)) return true; | |
| 4483 | try current_thread.checkCancel(); | |
| 4484 | var out: windows.DWORD = undefined; | |
| 4485 | return windows.kernel32.GetConsoleMode(file.handle, &out) != 0; | |
| 4486 | } | |
| 4487 | ||
| 4488 | if (builtin.link_libc) { | |
| 4489 | try current_thread.beginSyscall(); | |
| 4490 | while (true) { | |
| 4491 | const rc = posix.system.isatty(file.handle); | |
| 4492 | switch (posix.errno(rc - 1)) { | |
| 4493 | .SUCCESS => { | |
| 4494 | current_thread.endSyscall(); | |
| 4495 | return true; | |
| 4496 | }, | |
| 4497 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4498 | .INTR => { | |
| 4499 | try current_thread.checkCancel(); | |
| 4500 | continue; | |
| 4501 | }, | |
| 4502 | else => { | |
| 4503 | current_thread.endSyscall(); | |
| 4504 | return false; | |
| 4505 | }, | |
| 4506 | } | |
| 4507 | } | |
| 4508 | } | |
| 4509 | ||
| 4510 | if (native_os == .wasi) { | |
| 4511 | var statbuf: std.os.wasi.fdstat_t = undefined; | |
| 4512 | const err = std.os.wasi.fd_fdstat_get(file.handle, &statbuf); | |
| 4513 | if (err != .SUCCESS) | |
| 4514 | return false; | |
| 4515 | ||
| 4516 | // A tty is a character device that we can't seek or tell on. | |
| 4517 | if (statbuf.fs_filetype != .CHARACTER_DEVICE) | |
| 4518 | return false; | |
| 4519 | if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL) | |
| 4520 | return false; | |
| 4521 | ||
| 4522 | return true; | |
| 4523 | } | |
| 4524 | ||
| 4525 | if (native_os == .linux) { | |
| 4526 | const linux = std.os.linux; | |
| 4527 | try current_thread.beginSyscall(); | |
| 4528 | while (true) { | |
| 4529 | var wsz: linux.winsize = undefined; | |
| 4530 | const fd: usize = @bitCast(@as(isize, file.handle)); | |
| 4531 | const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); | |
| 4532 | switch (linux.errno(rc)) { | |
| 4533 | .SUCCESS => { | |
| 4534 | current_thread.endSyscall(); | |
| 4535 | return true; | |
| 4536 | }, | |
| 4537 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4538 | .INTR => { | |
| 4539 | try current_thread.checkCancel(); | |
| 4540 | continue; | |
| 4541 | }, | |
| 4542 | else => { | |
| 4543 | current_thread.endSyscall(); | |
| 4544 | return false; | |
| 4545 | }, | |
| 4546 | } | |
| 4547 | } | |
| 4548 | } | |
| 4549 | ||
| 4550 | @compileError("unimplemented"); | |
| 4551 | } | |
| 4552 | ||
| 4553 | fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Io.File.EnableAnsiEscapeCodesError!void { | |
| 4554 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4555 | const current_thread = Thread.getCurrent(t); | |
| 4556 | ||
| 4557 | if (is_windows) { | |
| 4558 | try current_thread.checkCancel(); | |
| 4559 | ||
| 4560 | // For Windows Terminal, VT Sequences processing is enabled by default. | |
| 4561 | var original_console_mode: windows.DWORD = 0; | |
| 4562 | if (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) != 0) { | |
| 4563 | if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; | |
| 4564 | ||
| 4565 | // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. | |
| 4566 | // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ | |
| 4567 | // | |
| 4568 | // Note: In Microsoft's example for enabling virtual terminal processing, it | |
| 4569 | // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well: | |
| 4570 | // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing | |
| 4571 | // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n) | |
| 4572 | // to behave unexpectedly (the cursor moves down 1 row but remains on the same column). | |
| 4573 | // Additionally, the default console mode in Windows Terminal does not have | |
| 4574 | // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` | |
| 4575 | // we end up matching the mode of Windows Terminal. | |
| 4576 | const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; | |
| 4577 | const console_mode = original_console_mode | requested_console_modes; | |
| 4578 | try current_thread.checkCancel(); | |
| 4579 | if (windows.kernel32.SetConsoleMode(file.handle, console_mode) != 0) return; | |
| 4580 | } | |
| 4581 | if (try isCygwinPty(current_thread, file)) return; | |
| 4582 | } else { | |
| 4583 | if (try supportsAnsiEscapeCodes(current_thread, file)) return; | |
| 4584 | } | |
| 4585 | return error.NotTerminalDevice; | |
| 4586 | } | |
| 4587 | ||
| 4588 | fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Io.Cancelable!bool { | |
| 4589 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4590 | const current_thread = Thread.getCurrent(t); | |
| 4591 | return supportsAnsiEscapeCodes(current_thread, file); | |
| 4592 | } | |
| 4593 | ||
| 4594 | fn supportsAnsiEscapeCodes(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { | |
| 4595 | if (is_windows) { | |
| 4596 | try current_thread.checkCancel(); | |
| 4597 | var console_mode: windows.DWORD = 0; | |
| 4598 | if (windows.kernel32.GetConsoleMode(file.handle, &console_mode) != 0) { | |
| 4599 | if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; | |
| 4600 | } | |
| 4601 | return isCygwinPty(current_thread, file); | |
| 4602 | } | |
| 4603 | ||
| 4604 | if (native_os == .wasi) { | |
| 4605 | // WASI sanitizes stdout when fd is a tty so ANSI escape codes will not | |
| 4606 | // be interpreted as actual cursor commands, and stderr is always | |
| 4607 | // sanitized. | |
| 4608 | return false; | |
| 4609 | } | |
| 4610 | ||
| 4611 | if (try isTty(current_thread, file)) return true; | |
| 4612 | ||
| 4613 | return false; | |
| 4614 | } | |
| 4615 | ||
| 4616 | fn isCygwinPty(current_thread: *Thread, file: Io.File) Io.Cancelable!bool { | |
| 4617 | if (!is_windows) return false; | |
| 4618 | ||
| 4619 | const handle = file.handle; | |
| 4620 | ||
| 4621 | // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats: | |
| 4622 | // msys-[...]-ptyN-[...] | |
| 4623 | // cygwin-[...]-ptyN-[...] | |
| 4624 | // | |
| 4625 | // Example: msys-1888ae32e00d56aa-pty0-to-master | |
| 4626 | ||
| 4627 | // First, just check that the handle is a named pipe. | |
| 4628 | // This allows us to avoid the more costly NtQueryInformationFile call | |
| 4629 | // for handles that aren't named pipes. | |
| 4630 | { | |
| 4631 | try current_thread.checkCancel(); | |
| 4632 | var io_status: windows.IO_STATUS_BLOCK = undefined; | |
| 4633 | var device_info: windows.FILE_FS_DEVICE_INFORMATION = undefined; | |
| 4634 | const rc = windows.ntdll.NtQueryVolumeInformationFile(handle, &io_status, &device_info, @sizeOf(windows.FILE_FS_DEVICE_INFORMATION), .FileFsDeviceInformation); | |
| 4635 | switch (rc) { | |
| 4636 | .SUCCESS => {}, | |
| 4637 | else => return false, | |
| 4638 | } | |
| 4639 | if (device_info.DeviceType != windows.FILE_DEVICE_NAMED_PIPE) return false; | |
| 4640 | } | |
| 4641 | ||
| 4642 | const name_bytes_offset = @offsetOf(windows.FILE_NAME_INFO, "FileName"); | |
| 4643 | // `NAME_MAX` UTF-16 code units (2 bytes each) | |
| 4644 | // This buffer may not be long enough to handle *all* possible paths | |
| 4645 | // (PATH_MAX_WIDE would be necessary for that), but because we only care | |
| 4646 | // about certain paths and we know they must be within a reasonable length, | |
| 4647 | // we can use this smaller buffer and just return false on any error from | |
| 4648 | // NtQueryInformationFile. | |
| 4649 | const num_name_bytes = windows.MAX_PATH * 2; | |
| 4650 | var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); | |
| 4651 | ||
| 4652 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 4653 | try current_thread.checkCancel(); | |
| 4654 | const rc = windows.ntdll.NtQueryInformationFile(handle, &io_status_block, &name_info_bytes, @intCast(name_info_bytes.len), .FileNameInformation); | |
| 4655 | switch (rc) { | |
| 4656 | .SUCCESS => {}, | |
| 4657 | .INVALID_PARAMETER => unreachable, | |
| 4658 | else => return false, | |
| 4659 | } | |
| 4660 | ||
| 4661 | const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); | |
| 4662 | const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; | |
| 4663 | const name_wide = std.mem.bytesAsSlice(u16, name_bytes); | |
| 4664 | // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master | |
| 4665 | return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or | |
| 4666 | std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and | |
| 4667 | std.mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; | |
| 4668 | } | |
| 4669 | ||
| 4670 | fn fileSetLength(userdata: ?*anyopaque, file: Io.File, length: u64) Io.File.SetLengthError!void { | |
| 4671 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4672 | const current_thread = Thread.getCurrent(t); | |
| 4673 | ||
| 4674 | const signed_len: i64 = @bitCast(length); | |
| 4675 | if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors. | |
| 4676 | ||
| 4677 | if (is_windows) { | |
| 4678 | try current_thread.checkCancel(); | |
| 4679 | ||
| 4680 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 4681 | var eof_info: windows.FILE_END_OF_FILE_INFORMATION = .{ | |
| 4682 | .EndOfFile = signed_len, | |
| 4683 | }; | |
| 4684 | ||
| 4685 | const status = windows.ntdll.NtSetInformationFile( | |
| 4686 | file.handle, | |
| 4687 | &io_status_block, | |
| 4688 | &eof_info, | |
| 4689 | @sizeOf(windows.FILE_END_OF_FILE_INFORMATION), | |
| 4690 | .FileEndOfFileInformation, | |
| 4691 | ); | |
| 4692 | switch (status) { | |
| 4693 | .SUCCESS => return, | |
| 4694 | .INVALID_HANDLE => |err| return windows.statusBug(err), // Handle not open for writing. | |
| 4695 | .ACCESS_DENIED => return error.AccessDenied, | |
| 4696 | .USER_MAPPED_FILE => return error.AccessDenied, | |
| 4697 | .INVALID_PARAMETER => return error.FileTooBig, | |
| 4698 | else => return windows.unexpectedStatus(status), | |
| 4699 | } | |
| 4700 | } | |
| 4701 | ||
| 4702 | if (native_os == .wasi and !builtin.link_libc) { | |
| 4703 | try current_thread.beginSyscall(); | |
| 4704 | while (true) { | |
| 4705 | switch (std.os.wasi.fd_filestat_set_size(file.handle, length)) { | |
| 4706 | .SUCCESS => return current_thread.endSyscall(), | |
| 4707 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4708 | .INTR => { | |
| 4709 | try current_thread.checkCancel(); | |
| 4710 | continue; | |
| 4711 | }, | |
| 4712 | else => |e| { | |
| 4713 | current_thread.endSyscall(); | |
| 4714 | switch (e) { | |
| 4715 | .FBIG => return error.FileTooBig, | |
| 4716 | .IO => return error.InputOutput, | |
| 4717 | .PERM => return error.PermissionDenied, | |
| 4718 | .TXTBSY => return error.FileBusy, | |
| 4719 | .BADF => |err| return errnoBug(err), // Handle not open for writing | |
| 4720 | .INVAL => return error.NonResizable, | |
| 4721 | .NOTCAPABLE => return error.AccessDenied, | |
| 4722 | else => |err| return posix.unexpectedErrno(err), | |
| 4723 | } | |
| 4724 | }, | |
| 4725 | } | |
| 4726 | } | |
| 4727 | } | |
| 4728 | ||
| 4729 | try current_thread.beginSyscall(); | |
| 4730 | while (true) { | |
| 4731 | switch (posix.errno(ftruncate_sym(file.handle, signed_len))) { | |
| 4732 | .SUCCESS => return current_thread.endSyscall(), | |
| 4733 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4734 | .INTR => { | |
| 4735 | try current_thread.checkCancel(); | |
| 4736 | continue; | |
| 4737 | }, | |
| 4738 | else => |e| { | |
| 4739 | current_thread.endSyscall(); | |
| 4740 | switch (e) { | |
| 4741 | .FBIG => return error.FileTooBig, | |
| 4742 | .IO => return error.InputOutput, | |
| 4743 | .PERM => return error.PermissionDenied, | |
| 4744 | .TXTBSY => return error.FileBusy, | |
| 4745 | .BADF => |err| return errnoBug(err), // Handle not open for writing. | |
| 4746 | .INVAL => return error.NonResizable, // This is returned for /dev/null for example. | |
| 4747 | else => |err| return posix.unexpectedErrno(err), | |
| 4748 | } | |
| 4749 | }, | |
| 4750 | } | |
| 4751 | } | |
| 4752 | } | |
| 4753 | ||
| 4754 | fn fileSetOwner(userdata: ?*anyopaque, file: Io.File, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.File.SetOwnerError!void { | |
| 4755 | switch (native_os) { | |
| 4756 | .windows, .wasi => return error.Unexpected, | |
| 4757 | else => {}, | |
| 4758 | } | |
| 4383 | 4759 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4384 | 4760 | const current_thread = Thread.getCurrent(t); |
| 4385 | 4761 | const uid = owner orelse ~@as(posix.uid_t, 0); |
| 4386 | 4762 | const gid = group orelse ~@as(posix.gid_t, 0); |
| 4763 | return setOwnerPosix(current_thread, file.handle, uid, gid); | |
| 4764 | } | |
| 4765 | ||
| 4766 | fn fileSetPermissions(userdata: ?*anyopaque, file: Io.File, permissions: Io.File.Permissions) Io.File.SetPermissionsError!void { | |
| 4767 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4768 | const current_thread = Thread.getCurrent(t); | |
| 4769 | switch (native_os) { | |
| 4770 | .windows => { | |
| 4771 | try current_thread.checkCancel(); | |
| 4772 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 4773 | var info: windows.FILE_BASIC_INFORMATION = .{ | |
| 4774 | .CreationTime = 0, | |
| 4775 | .LastAccessTime = 0, | |
| 4776 | .LastWriteTime = 0, | |
| 4777 | .ChangeTime = 0, | |
| 4778 | .FileAttributes = permissions.inner.attributes, | |
| 4779 | }; | |
| 4780 | const status = windows.ntdll.NtSetInformationFile( | |
| 4781 | file.handle, | |
| 4782 | &io_status_block, | |
| 4783 | &info, | |
| 4784 | @sizeOf(windows.FILE_BASIC_INFORMATION), | |
| 4785 | .FileBasicInformation, | |
| 4786 | ); | |
| 4787 | switch (status) { | |
| 4788 | .SUCCESS => return, | |
| 4789 | .INVALID_HANDLE => |err| return windows.statusBug(err), | |
| 4790 | .ACCESS_DENIED => return error.AccessDenied, | |
| 4791 | else => return windows.unexpectedStatus(status), | |
| 4792 | } | |
| 4793 | }, | |
| 4794 | .wasi => return error.Unexpected, // Unsupported OS. | |
| 4795 | else => return setPermissionsPosix(current_thread, file.handle, permissions.toMode()), | |
| 4796 | } | |
| 4797 | } | |
| 4387 | 4798 | |
| 4799 | fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode_t) Io.File.SetPermissionsError!void { | |
| 4388 | 4800 | try current_thread.beginSyscall(); |
| 4389 | 4801 | while (true) { |
| 4390 | switch (posix.errno(posix.system.fchown(dir.handle, uid, gid))) { | |
| 4802 | switch (posix.errno(posix.system.fchmod(fd, mode))) { | |
| 4391 | 4803 | .SUCCESS => return current_thread.endSyscall(), |
| 4392 | 4804 | .CANCELED => return current_thread.endSyscallCanceled(), |
| 4393 | 4805 | .INTR => { |
| ... | ... | @@ -4397,7 +4809,7 @@ fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, gro |
| 4397 | 4809 | else => |e| { |
| 4398 | 4810 | current_thread.endSyscall(); |
| 4399 | 4811 | switch (e) { |
| 4400 | .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Io.Dir.OpenOptions.iterate` | |
| 4812 | .BADF => |err| return errnoBug(err), | |
| 4401 | 4813 | .FAULT => |err| return errnoBug(err), |
| 4402 | 4814 | .INVAL => |err| return errnoBug(err), |
| 4403 | 4815 | .ACCES => return error.AccessDenied, |
| ... | ... | @@ -4415,31 +4827,509 @@ fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, gro |
| 4415 | 4827 | } |
| 4416 | 4828 | } |
| 4417 | 4829 | |
| 4418 | const dirSetPermissions = switch (native_os) { | |
| 4419 | .windows => dirSetPermissionsWindows, | |
| 4420 | else => dirSetPermissionsPosix, | |
| 4421 | }; | |
| 4422 | ||
| 4423 | fn dirSetPermissionsWindows( | |
| 4830 | fn dirSetTimestamps( | |
| 4424 | 4831 | userdata: ?*anyopaque, |
| 4425 | 4832 | dir: Io.Dir, |
| 4426 | permissions: Io.Dir.Permissions, | |
| 4427 | ) Io.Dir.SetPermissionsError!void { | |
| 4428 | _ = userdata; | |
| 4429 | _ = dir; | |
| 4430 | _ = permissions; | |
| 4431 | @panic("TODO"); | |
| 4833 | sub_path: []const u8, | |
| 4834 | last_accessed: Io.Timestamp, | |
| 4835 | last_modified: Io.Timestamp, | |
| 4836 | options: Io.File.SetTimestampsOptions, | |
| 4837 | ) Io.File.SetTimestampsError!void { | |
| 4838 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4839 | const current_thread = Thread.getCurrent(t); | |
| 4840 | ||
| 4841 | if (is_windows) { | |
| 4842 | @panic("TODO"); | |
| 4843 | } | |
| 4844 | ||
| 4845 | if (native_os == .wasi and !builtin.link_libc) { | |
| 4846 | @panic("TODO"); | |
| 4847 | } | |
| 4848 | ||
| 4849 | const times: [2]posix.timespec = .{ | |
| 4850 | timestampToPosix(last_accessed), | |
| 4851 | timestampToPosix(last_modified), | |
| 4852 | }; | |
| 4853 | ||
| 4854 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; | |
| 4855 | ||
| 4856 | var path_buffer: [posix.PATH_MAX]u8 = undefined; | |
| 4857 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); | |
| 4858 | ||
| 4859 | try current_thread.beginSyscall(); | |
| 4860 | while (true) { | |
| 4861 | switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, &times, flags))) { | |
| 4862 | .SUCCESS => return current_thread.endSyscall(), | |
| 4863 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4864 | .INTR => { | |
| 4865 | try current_thread.checkCancel(); | |
| 4866 | continue; | |
| 4867 | }, | |
| 4868 | else => |e| { | |
| 4869 | current_thread.endSyscall(); | |
| 4870 | switch (e) { | |
| 4871 | .ACCES => return error.AccessDenied, | |
| 4872 | .PERM => return error.PermissionDenied, | |
| 4873 | .BADF => |err| return errnoBug(err), // always a race condition | |
| 4874 | .FAULT => |err| return errnoBug(err), | |
| 4875 | .INVAL => |err| return errnoBug(err), | |
| 4876 | .ROFS => return error.ReadOnlyFileSystem, | |
| 4877 | else => |err| return posix.unexpectedErrno(err), | |
| 4878 | } | |
| 4879 | }, | |
| 4880 | } | |
| 4881 | } | |
| 4432 | 4882 | } |
| 4433 | 4883 | |
| 4434 | fn dirSetPermissionsPosix( | |
| 4884 | fn dirSetTimestampsNow( | |
| 4435 | 4885 | userdata: ?*anyopaque, |
| 4436 | 4886 | dir: Io.Dir, |
| 4437 | permissions: Io.Dir.Permissions, | |
| 4438 | ) Io.Dir.SetPermissionsError!void { | |
| 4439 | _ = userdata; | |
| 4440 | _ = dir; | |
| 4441 | _ = permissions; | |
| 4442 | @panic("TODO"); | |
| 4887 | sub_path: []const u8, | |
| 4888 | options: Io.File.SetTimestampsOptions, | |
| 4889 | ) Io.File.SetTimestampsError!void { | |
| 4890 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4891 | const current_thread = Thread.getCurrent(t); | |
| 4892 | ||
| 4893 | if (is_windows) { | |
| 4894 | @panic("TODO"); | |
| 4895 | } | |
| 4896 | ||
| 4897 | if (native_os == .wasi and !builtin.link_libc) { | |
| 4898 | @panic("TODO"); | |
| 4899 | } | |
| 4900 | ||
| 4901 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; | |
| 4902 | ||
| 4903 | var path_buffer: [posix.PATH_MAX]u8 = undefined; | |
| 4904 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); | |
| 4905 | ||
| 4906 | try current_thread.beginSyscall(); | |
| 4907 | while (true) { | |
| 4908 | switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, null, flags))) { | |
| 4909 | .SUCCESS => return current_thread.endSyscall(), | |
| 4910 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4911 | .INTR => { | |
| 4912 | try current_thread.checkCancel(); | |
| 4913 | continue; | |
| 4914 | }, | |
| 4915 | else => |e| { | |
| 4916 | current_thread.endSyscall(); | |
| 4917 | switch (e) { | |
| 4918 | .ACCES => return error.AccessDenied, | |
| 4919 | .PERM => return error.PermissionDenied, | |
| 4920 | .BADF => |err| return errnoBug(err), // always a race condition | |
| 4921 | .FAULT => |err| return errnoBug(err), | |
| 4922 | .INVAL => |err| return errnoBug(err), | |
| 4923 | .ROFS => return error.ReadOnlyFileSystem, | |
| 4924 | else => |err| return posix.unexpectedErrno(err), | |
| 4925 | } | |
| 4926 | }, | |
| 4927 | } | |
| 4928 | } | |
| 4929 | } | |
| 4930 | ||
| 4931 | fn fileSetTimestamps( | |
| 4932 | userdata: ?*anyopaque, | |
| 4933 | file: Io.File, | |
| 4934 | last_accessed: Io.Timestamp, | |
| 4935 | last_modified: Io.Timestamp, | |
| 4936 | ) Io.File.SetTimestampsError!void { | |
| 4937 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4938 | const current_thread = Thread.getCurrent(t); | |
| 4939 | ||
| 4940 | if (is_windows) { | |
| 4941 | try current_thread.checkCancel(); | |
| 4942 | ||
| 4943 | const atime_ft = windows.nanoSecondsToFileTime(last_accessed.toNanoseconds()); | |
| 4944 | const mtime_ft = windows.nanoSecondsToFileTime(last_modified.toNanoseconds()); | |
| 4945 | ||
| 4946 | // https://github.com/ziglang/zig/issues/1840 | |
| 4947 | const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft); | |
| 4948 | if (rc == 0) { | |
| 4949 | switch (windows.GetLastError()) { | |
| 4950 | else => |err| return windows.unexpectedError(err), | |
| 4951 | } | |
| 4952 | } | |
| 4953 | return; | |
| 4954 | } | |
| 4955 | ||
| 4956 | const times: [2]posix.timespec = .{ | |
| 4957 | timestampToPosix(last_accessed), | |
| 4958 | timestampToPosix(last_modified), | |
| 4959 | }; | |
| 4960 | ||
| 4961 | if (native_os == .wasi and !builtin.link_libc) { | |
| 4962 | const atim = times[0].toTimestamp(); | |
| 4963 | const mtim = times[1].toTimestamp(); | |
| 4964 | try current_thread.beginSyscall(); | |
| 4965 | while (true) { | |
| 4966 | switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{ | |
| 4967 | .ATIM = true, | |
| 4968 | .MTIM = true, | |
| 4969 | })) { | |
| 4970 | .SUCCESS => return current_thread.endSyscall(), | |
| 4971 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4972 | .INTR => { | |
| 4973 | try current_thread.checkCancel(); | |
| 4974 | continue; | |
| 4975 | }, | |
| 4976 | else => |e| { | |
| 4977 | current_thread.endSyscall(); | |
| 4978 | switch (e) { | |
| 4979 | .ACCES => return error.AccessDenied, | |
| 4980 | .PERM => return error.PermissionDenied, | |
| 4981 | .BADF => |err| return errnoBug(err), // File descriptor use-after-free. | |
| 4982 | .FAULT => |err| return errnoBug(err), | |
| 4983 | .INVAL => |err| return errnoBug(err), | |
| 4984 | .ROFS => return error.ReadOnlyFileSystem, | |
| 4985 | else => |err| return posix.unexpectedErrno(err), | |
| 4986 | } | |
| 4987 | }, | |
| 4988 | } | |
| 4989 | } | |
| 4990 | } | |
| 4991 | ||
| 4992 | try current_thread.beginSyscall(); | |
| 4993 | while (true) { | |
| 4994 | switch (posix.errno(posix.system.futimens(file.handle, &times))) { | |
| 4995 | .SUCCESS => return current_thread.endSyscall(), | |
| 4996 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4997 | .INTR => { | |
| 4998 | try current_thread.checkCancel(); | |
| 4999 | continue; | |
| 5000 | }, | |
| 5001 | else => |e| { | |
| 5002 | current_thread.endSyscall(); | |
| 5003 | switch (e) { | |
| 5004 | .ACCES => return error.AccessDenied, | |
| 5005 | .PERM => return error.PermissionDenied, | |
| 5006 | .BADF => |err| return errnoBug(err), // always a race condition | |
| 5007 | .FAULT => |err| return errnoBug(err), | |
| 5008 | .INVAL => |err| return errnoBug(err), | |
| 5009 | .ROFS => return error.ReadOnlyFileSystem, | |
| 5010 | else => |err| return posix.unexpectedErrno(err), | |
| 5011 | } | |
| 5012 | }, | |
| 5013 | } | |
| 5014 | } | |
| 5015 | } | |
| 5016 | ||
| 5017 | fn fileSetTimestampsNow(userdata: ?*anyopaque, file: Io.File) Io.File.SetTimestampsError!void { | |
| 5018 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 5019 | const current_thread = Thread.getCurrent(t); | |
| 5020 | ||
| 5021 | if (is_windows) { | |
| 5022 | @panic("TODO"); | |
| 5023 | } | |
| 5024 | ||
| 5025 | if (native_os == .wasi and !builtin.link_libc) { | |
| 5026 | try current_thread.beginSyscall(); | |
| 5027 | while (true) { | |
| 5028 | switch (std.os.wasi.fd_filestat_set_times(file.handle, 0, 0, .{ | |
| 5029 | .ATIM_NOW = true, | |
| 5030 | .MTIM_NOW = true, | |
| 5031 | })) { | |
| 5032 | .SUCCESS => return current_thread.endSyscall(), | |
| 5033 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 5034 | .INTR => { | |
| 5035 | try current_thread.checkCancel(); | |
| 5036 | continue; | |
| 5037 | }, | |
| 5038 | else => |e| { | |
| 5039 | current_thread.endSyscall(); | |
| 5040 | switch (e) { | |
| 5041 | .ACCES => return error.AccessDenied, | |
| 5042 | .PERM => return error.PermissionDenied, | |
| 5043 | .BADF => |err| return errnoBug(err), // always a race condition | |
| 5044 | .FAULT => |err| return errnoBug(err), | |
| 5045 | .INVAL => |err| return errnoBug(err), | |
| 5046 | .ROFS => return error.ReadOnlyFileSystem, | |
| 5047 | else => |err| return posix.unexpectedErrno(err), | |
| 5048 | } | |
| 5049 | }, | |
| 5050 | } | |
| 5051 | } | |
| 5052 | } | |
| 5053 | ||
| 5054 | try current_thread.beginSyscall(); | |
| 5055 | while (true) { | |
| 5056 | switch (posix.errno(posix.system.futimens(file.handle, null))) { | |
| 5057 | .SUCCESS => return current_thread.endSyscall(), | |
| 5058 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 5059 | .INTR => { | |
| 5060 | try current_thread.checkCancel(); | |
| 5061 | continue; | |
| 5062 | }, | |
| 5063 | else => |e| { | |
| 5064 | current_thread.endSyscall(); | |
| 5065 | switch (e) { | |
| 5066 | .ACCES => return error.AccessDenied, | |
| 5067 | .PERM => return error.PermissionDenied, | |
| 5068 | .BADF => |err| return errnoBug(err), // always a race condition | |
| 5069 | .FAULT => |err| return errnoBug(err), | |
| 5070 | .INVAL => |err| return errnoBug(err), | |
| 5071 | .ROFS => return error.ReadOnlyFileSystem, | |
| 5072 | else => |err| return posix.unexpectedErrno(err), | |
| 5073 | } | |
| 5074 | }, | |
| 5075 | } | |
| 5076 | } | |
| 5077 | } | |
| 5078 | ||
| 5079 | const windows_lock_range_off: windows.LARGE_INTEGER = 0; | |
| 5080 | const windows_lock_range_len: windows.LARGE_INTEGER = 1; | |
| 5081 | ||
| 5082 | fn fileLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.LockError!void { | |
| 5083 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 5084 | const current_thread = Thread.getCurrent(t); | |
| 5085 | ||
| 5086 | if (is_windows) { | |
| 5087 | const exclusive = switch (lock) { | |
| 5088 | .none => return, | |
| 5089 | .shared => false, | |
| 5090 | .exclusive => true, | |
| 5091 | }; | |
| 5092 | try current_thread.checkCancel(); | |
| 5093 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 5094 | const status = windows.ntdll.NtLockFile( | |
| 5095 | file.handle, | |
| 5096 | null, | |
| 5097 | null, | |
| 5098 | null, | |
| 5099 | &io_status_block, | |
| 5100 | &windows_lock_range_off, | |
| 5101 | &windows_lock_range_len, | |
| 5102 | null, | |
| 5103 | windows.FALSE, | |
| 5104 | @intFromBool(exclusive), | |
| 5105 | ); | |
| 5106 | switch (status) { | |
| 5107 | .SUCCESS => return, | |
| 5108 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | |
| 5109 | .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // passed FailImmediately=false | |
| 5110 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer | |
| 5111 | else => return windows.unexpectedStatus(status), | |
| 5112 | } | |
| 5113 | } | |
| 5114 | ||
| 5115 | const operation = switch (lock) { | |
| 5116 | .none => posix.LOCK.UN, | |
| 5117 | .shared => posix.LOCK.SH, | |
| 5118 | .exclusive => posix.LOCK.EX, | |
| 5119 | }; | |
| 5120 | try current_thread.beginSyscall(); | |
| 5121 | while (true) { | |
| 5122 | switch (posix.errno(posix.system.flock(file.handle, operation))) { | |
| 5123 | .SUCCESS => return current_thread.endSyscall(), | |
| 5124 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 5125 | .INTR => { | |
| 5126 | try current_thread.checkCancel(); | |
| 5127 | continue; | |
| 5128 | }, | |
| 5129 | else => |e| { | |
| 5130 | current_thread.endSyscall(); | |
| 5131 | switch (e) { | |
| 5132 | .BADF => |err| return errnoBug(err), | |
| 5133 | .INVAL => |err| return errnoBug(err), // invalid parameters | |
| 5134 | .NOLCK => return error.SystemResources, | |
| 5135 | .AGAIN => |err| return errnoBug(err), | |
| 5136 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 5137 | else => |err| return posix.unexpectedErrno(err), | |
| 5138 | } | |
| 5139 | }, | |
| 5140 | } | |
| 5141 | } | |
| 5142 | } | |
| 5143 | ||
| 5144 | fn fileTryLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.LockError!bool { | |
| 5145 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 5146 | const current_thread = Thread.getCurrent(t); | |
| 5147 | ||
| 5148 | if (is_windows) { | |
| 5149 | const exclusive = switch (lock) { | |
| 5150 | .none => return, | |
| 5151 | .shared => false, | |
| 5152 | .exclusive => true, | |
| 5153 | }; | |
| 5154 | try current_thread.checkCancel(); | |
| 5155 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 5156 | const status = windows.ntdll.NtLockFile( | |
| 5157 | file.handle, | |
| 5158 | null, | |
| 5159 | null, | |
| 5160 | null, | |
| 5161 | &io_status_block, | |
| 5162 | &windows_lock_range_off, | |
| 5163 | &windows_lock_range_len, | |
| 5164 | null, | |
| 5165 | windows.TRUE, | |
| 5166 | @intFromBool(exclusive), | |
| 5167 | ); | |
| 5168 | switch (status) { | |
| 5169 | .SUCCESS => return true, | |
| 5170 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | |
| 5171 | .LOCK_NOT_GRANTED => return false, | |
| 5172 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer | |
| 5173 | else => return windows.unexpectedStatus(status), | |
| 5174 | } | |
| 5175 | } | |
| 5176 | ||
| 5177 | const operation = switch (lock) { | |
| 5178 | .none => posix.LOCK.UN, | |
| 5179 | .shared => posix.LOCK.SH | posix.LOCK.NB, | |
| 5180 | .exclusive => posix.LOCK.EX | posix.LOCK.NB, | |
| 5181 | }; | |
| 5182 | try current_thread.beginSyscall(); | |
| 5183 | while (true) { | |
| 5184 | switch (posix.errno(posix.system.flock(file.handle, operation))) { | |
| 5185 | .SUCCESS => { | |
| 5186 | current_thread.endSyscall(); | |
| 5187 | return true; | |
| 5188 | }, | |
| 5189 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 5190 | .INTR => { | |
| 5191 | try current_thread.checkCancel(); | |
| 5192 | continue; | |
| 5193 | }, | |
| 5194 | .AGAIN => { | |
| 5195 | current_thread.endSyscall(); | |
| 5196 | return false; | |
| 5197 | }, | |
| 5198 | else => |e| { | |
| 5199 | current_thread.endSyscall(); | |
| 5200 | switch (e) { | |
| 5201 | .BADF => |err| return errnoBug(err), | |
| 5202 | .INVAL => |err| return errnoBug(err), // invalid parameters | |
| 5203 | .NOLCK => return error.SystemResources, | |
| 5204 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 5205 | else => |err| return posix.unexpectedErrno(err), | |
| 5206 | } | |
| 5207 | }, | |
| 5208 | } | |
| 5209 | } | |
| 5210 | } | |
| 5211 | ||
| 5212 | fn fileUnlock(userdata: ?*anyopaque, file: Io.File) void { | |
| 5213 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 5214 | const current_thread = Thread.getCurrent(t); | |
| 5215 | ||
| 5216 | if (is_windows) { | |
| 5217 | try current_thread.checkCancel(); | |
| 5218 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 5219 | const status = windows.ntdll.NtUnlockFile( | |
| 5220 | file.handle, | |
| 5221 | &io_status_block, | |
| 5222 | &windows_lock_range_off, | |
| 5223 | &windows_lock_range_len, | |
| 5224 | null, | |
| 5225 | ); | |
| 5226 | if (is_debug) switch (status) { | |
| 5227 | .SUCCESS => {}, | |
| 5228 | .RANGE_NOT_LOCKED => unreachable, // Function asserts unlocked. | |
| 5229 | .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer | |
| 5230 | else => unreachable, // Resource deallocation must succeed. | |
| 5231 | }; | |
| 5232 | return; | |
| 5233 | } | |
| 5234 | ||
| 5235 | try current_thread.beginSyscall(); | |
| 5236 | while (true) { | |
| 5237 | switch (posix.errno(posix.system.flock(file.handle, posix.LOCK.UN))) { | |
| 5238 | .SUCCESS => return current_thread.endSyscall(), | |
| 5239 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 5240 | .INTR => { | |
| 5241 | try current_thread.checkCancel(); | |
| 5242 | continue; | |
| 5243 | }, | |
| 5244 | else => |e| { | |
| 5245 | current_thread.endSyscall(); | |
| 5246 | if (is_debug) switch (e) { | |
| 5247 | .AGAIN => unreachable, // unlocking can't block | |
| 5248 | .BADF => unreachable, // File descriptor used after closed. | |
| 5249 | .INVAL => unreachable, // invalid parameters | |
| 5250 | .NOLCK => unreachable, // Resource deallocation. | |
| 5251 | .OPNOTSUPP => unreachable, // We already got the lock. | |
| 5252 | else => unreachable, // Resource deallocation must succeed. | |
| 5253 | }; | |
| 5254 | return; | |
| 5255 | }, | |
| 5256 | } | |
| 5257 | } | |
| 5258 | } | |
| 5259 | ||
| 5260 | fn fileDowngradeLock(userdata: ?*anyopaque, file: Io.File) Io.File.DowngradeLockError!void { | |
| 5261 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 5262 | const current_thread = Thread.getCurrent(t); | |
| 5263 | ||
| 5264 | if (is_windows) { | |
| 5265 | try current_thread.checkCancel(); | |
| 5266 | // On Windows it works like a semaphore + exclusivity flag. To | |
| 5267 | // implement this function, we first obtain another lock in shared | |
| 5268 | // mode. This changes the exclusivity flag, but increments the | |
| 5269 | // semaphore to 2. So we follow up with an NtUnlockFile which | |
| 5270 | // decrements the semaphore but does not modify the exclusivity flag. | |
| 5271 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 5272 | switch (windows.ntdll.NtLockFile( | |
| 5273 | file.handle, | |
| 5274 | null, | |
| 5275 | null, | |
| 5276 | null, | |
| 5277 | &io_status_block, | |
| 5278 | &windows_lock_range_off, | |
| 5279 | &windows_lock_range_len, | |
| 5280 | null, | |
| 5281 | windows.TRUE, | |
| 5282 | windows.FALSE, | |
| 5283 | )) { | |
| 5284 | .SUCCESS => {}, | |
| 5285 | .INSUFFICIENT_RESOURCES => |err| return windows.statusBug(err), | |
| 5286 | .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // File was not locked in exclusive mode. | |
| 5287 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer | |
| 5288 | else => |status| return windows.unexpectedStatus(status), | |
| 5289 | } | |
| 5290 | const status = windows.ntdll.NtUnlockFile( | |
| 5291 | file.handle, | |
| 5292 | &io_status_block, | |
| 5293 | &windows_lock_range_off, | |
| 5294 | &windows_lock_range_len, | |
| 5295 | null, | |
| 5296 | ); | |
| 5297 | if (is_debug) switch (status) { | |
| 5298 | .SUCCESS => {}, | |
| 5299 | .RANGE_NOT_LOCKED => unreachable, // File was not locked. | |
| 5300 | .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer | |
| 5301 | else => unreachable, // Resource deallocation must succeed. | |
| 5302 | }; | |
| 5303 | return; | |
| 5304 | } | |
| 5305 | ||
| 5306 | const operation = posix.LOCK.SH | posix.LOCK.NB; | |
| 5307 | ||
| 5308 | try current_thread.beginSyscall(); | |
| 5309 | while (true) { | |
| 5310 | switch (posix.errno(posix.system.flock(file.handle, operation))) { | |
| 5311 | .SUCCESS => { | |
| 5312 | current_thread.endSyscall(); | |
| 5313 | return true; | |
| 5314 | }, | |
| 5315 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 5316 | .INTR => { | |
| 5317 | try current_thread.checkCancel(); | |
| 5318 | continue; | |
| 5319 | }, | |
| 5320 | else => |e| { | |
| 5321 | current_thread.endSyscall(); | |
| 5322 | switch (e) { | |
| 5323 | .AGAIN => |err| return errnoBug(err), // File was not locked in exclusive mode. | |
| 5324 | .BADF => |err| return errnoBug(err), | |
| 5325 | .INVAL => |err| return errnoBug(err), // invalid parameters | |
| 5326 | .NOLCK => |err| return errnoBug(err), // Lock already obtained. | |
| 5327 | .OPNOTSUPP => |err| return errnoBug(err), // Lock already obtained. | |
| 5328 | else => |err| return posix.unexpectedErrno(err), | |
| 5329 | } | |
| 5330 | }, | |
| 5331 | } | |
| 5332 | } | |
| 4443 | 5333 | } |
| 4444 | 5334 | |
| 4445 | 5335 | fn dirOpenDirWasi( |
lib/std/debug/SelfInfo/Windows.zig+1-1| ... | ... | @@ -343,7 +343,7 @@ const Module = struct { |
| 343 | 343 | error.AntivirusInterference, |
| 344 | 344 | error.ProcessFdQuotaExceeded, |
| 345 | 345 | error.SystemFdQuotaExceeded, |
| 346 | error.FileLocksNotSupported, | |
| 346 | error.FileLocksUnsupported, | |
| 347 | 347 | error.FileBusy, |
| 348 | 348 | => return error.ReadFailed, |
| 349 | 349 | }; |
lib/std/fs.zig-6| ... | ... | @@ -23,12 +23,6 @@ pub const Dir = std.Io.Dir; |
| 23 | 23 | pub const File = std.Io.File; |
| 24 | 24 | |
| 25 | 25 | pub const path = @import("fs/path.zig"); |
| 26 | ||
| 27 | pub const has_executable_bit = switch (native_os) { | |
| 28 | .windows, .wasi => false, | |
| 29 | else => true, | |
| 30 | }; | |
| 31 | ||
| 32 | 26 | pub const wasi = @import("fs/wasi.zig"); |
| 33 | 27 | |
| 34 | 28 | // TODO audit these APIs with respect to Dir and absolute paths |
lib/std/fs/File.zig deleted-1356| ... | ... | @@ -1,1356 +0,0 @@ |
| 1 | const File = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | const native_os = builtin.os.tag; | |
| 5 | const is_windows = native_os == .windows; | |
| 6 | ||
| 7 | const std = @import("../std.zig"); | |
| 8 | const Io = std.Io; | |
| 9 | const Os = std.builtin.Os; | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | const posix = std.posix; | |
| 12 | const math = std.math; | |
| 13 | const assert = std.debug.assert; | |
| 14 | const linux = std.os.linux; | |
| 15 | const windows = std.os.windows; | |
| 16 | const maxInt = std.math.maxInt; | |
| 17 | const Alignment = std.mem.Alignment; | |
| 18 | ||
| 19 | /// The OS-specific file descriptor or file handle. | |
| 20 | handle: Handle, | |
| 21 | ||
| 22 | pub const Handle = Io.File.Handle; | |
| 23 | pub const Mode = Io.File.Mode; | |
| 24 | pub const INode = Io.File.INode; | |
| 25 | pub const Uid = Io.File.Uid; | |
| 26 | pub const Gid = Io.File.Gid; | |
| 27 | pub const Kind = Io.File.Kind; | |
| 28 | ||
| 29 | /// Deprecated in favor of `Io.File.OpenError`. | |
| 30 | pub const OpenError = Io.File.OpenError || error{WouldBlock}; | |
| 31 | /// Deprecated in favor of `Io.File.OpenMode`. | |
| 32 | pub const OpenMode = Io.File.OpenMode; | |
| 33 | /// Deprecated in favor of `Io.File.Lock`. | |
| 34 | pub const Lock = Io.File.Lock; | |
| 35 | /// Deprecated in favor of `Io.File.OpenFlags`. | |
| 36 | pub const OpenFlags = Io.File.OpenFlags; | |
| 37 | ||
| 38 | pub const CreateFlags = std.Io.File.CreateFlags; | |
| 39 | ||
| 40 | pub fn stdout() File { | |
| 41 | return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdOutput else posix.STDOUT_FILENO }; | |
| 42 | } | |
| 43 | ||
| 44 | pub fn stderr() File { | |
| 45 | return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdError else posix.STDERR_FILENO }; | |
| 46 | } | |
| 47 | ||
| 48 | pub fn stdin() File { | |
| 49 | return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdInput else posix.STDIN_FILENO }; | |
| 50 | } | |
| 51 | ||
| 52 | /// Upon success, the stream is in an uninitialized state. To continue using it, | |
| 53 | /// you must use the open() function. | |
| 54 | pub fn close(self: File) void { | |
| 55 | if (is_windows) { | |
| 56 | windows.CloseHandle(self.handle); | |
| 57 | } else { | |
| 58 | posix.close(self.handle); | |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | pub const SyncError = posix.SyncError; | |
| 63 | ||
| 64 | /// Blocks until all pending file contents and metadata modifications | |
| 65 | /// for the file have been synchronized with the underlying filesystem. | |
| 66 | /// | |
| 67 | /// Note that this does not ensure that metadata for the | |
| 68 | /// directory containing the file has also reached disk. | |
| 69 | pub fn sync(self: File) SyncError!void { | |
| 70 | return posix.fsync(self.handle); | |
| 71 | } | |
| 72 | ||
| 73 | /// Test whether the file refers to a terminal. | |
| 74 | /// See also `getOrEnableAnsiEscapeSupport` and `supportsAnsiEscapeCodes`. | |
| 75 | pub fn isTty(self: File) bool { | |
| 76 | return posix.isatty(self.handle); | |
| 77 | } | |
| 78 | ||
| 79 | pub fn isCygwinPty(file: File) bool { | |
| 80 | if (builtin.os.tag != .windows) return false; | |
| 81 | ||
| 82 | const handle = file.handle; | |
| 83 | ||
| 84 | // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats: | |
| 85 | // msys-[...]-ptyN-[...] | |
| 86 | // cygwin-[...]-ptyN-[...] | |
| 87 | // | |
| 88 | // Example: msys-1888ae32e00d56aa-pty0-to-master | |
| 89 | ||
| 90 | // First, just check that the handle is a named pipe. | |
| 91 | // This allows us to avoid the more costly NtQueryInformationFile call | |
| 92 | // for handles that aren't named pipes. | |
| 93 | { | |
| 94 | var io_status: windows.IO_STATUS_BLOCK = undefined; | |
| 95 | var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined; | |
| 96 | const rc = windows.ntdll.NtQueryVolumeInformationFile(handle, &io_status, &device_info, @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), .Device); | |
| 97 | switch (rc) { | |
| 98 | .SUCCESS => {}, | |
| 99 | else => return false, | |
| 100 | } | |
| 101 | if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false; | |
| 102 | } | |
| 103 | ||
| 104 | const name_bytes_offset = @offsetOf(windows.FILE_NAME_INFO, "FileName"); | |
| 105 | // `NAME_MAX` UTF-16 code units (2 bytes each) | |
| 106 | // This buffer may not be long enough to handle *all* possible paths | |
| 107 | // (PATH_MAX_WIDE would be necessary for that), but because we only care | |
| 108 | // about certain paths and we know they must be within a reasonable length, | |
| 109 | // we can use this smaller buffer and just return false on any error from | |
| 110 | // NtQueryInformationFile. | |
| 111 | const num_name_bytes = windows.MAX_PATH * 2; | |
| 112 | var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); | |
| 113 | ||
| 114 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 115 | const rc = windows.ntdll.NtQueryInformationFile(handle, &io_status_block, &name_info_bytes, @intCast(name_info_bytes.len), .Name); | |
| 116 | switch (rc) { | |
| 117 | .SUCCESS => {}, | |
| 118 | .INVALID_PARAMETER => unreachable, | |
| 119 | else => return false, | |
| 120 | } | |
| 121 | ||
| 122 | const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); | |
| 123 | const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; | |
| 124 | const name_wide = std.mem.bytesAsSlice(u16, name_bytes); | |
| 125 | // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master | |
| 126 | return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or | |
| 127 | std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and | |
| 128 | std.mem.find(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; | |
| 129 | } | |
| 130 | ||
| 131 | /// Returns whether or not ANSI escape codes will be treated as such, | |
| 132 | /// and attempts to enable support for ANSI escape codes if necessary | |
| 133 | /// (on Windows). | |
| 134 | /// | |
| 135 | /// Returns `true` if ANSI escape codes are supported or support was | |
| 136 | /// successfully enabled. Returns false if ANSI escape codes are not | |
| 137 | /// supported or support was unable to be enabled. | |
| 138 | /// | |
| 139 | /// See also `supportsAnsiEscapeCodes`. | |
| 140 | pub fn getOrEnableAnsiEscapeSupport(self: File) bool { | |
| 141 | if (builtin.os.tag == .windows) { | |
| 142 | var original_console_mode: windows.DWORD = 0; | |
| 143 | ||
| 144 | // For Windows Terminal, VT Sequences processing is enabled by default. | |
| 145 | if (windows.kernel32.GetConsoleMode(self.handle, &original_console_mode) != 0) { | |
| 146 | if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; | |
| 147 | ||
| 148 | // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. | |
| 149 | // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ | |
| 150 | // | |
| 151 | // Note: In Microsoft's example for enabling virtual terminal processing, it | |
| 152 | // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well: | |
| 153 | // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing | |
| 154 | // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n) | |
| 155 | // to behave unexpectedly (the cursor moves down 1 row but remains on the same column). | |
| 156 | // Additionally, the default console mode in Windows Terminal does not have | |
| 157 | // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` | |
| 158 | // we end up matching the mode of Windows Terminal. | |
| 159 | const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; | |
| 160 | const console_mode = original_console_mode | requested_console_modes; | |
| 161 | if (windows.kernel32.SetConsoleMode(self.handle, console_mode) != 0) return true; | |
| 162 | } | |
| 163 | ||
| 164 | return self.isCygwinPty(); | |
| 165 | } | |
| 166 | return self.supportsAnsiEscapeCodes(); | |
| 167 | } | |
| 168 | ||
| 169 | /// Test whether ANSI escape codes will be treated as such without | |
| 170 | /// attempting to enable support for ANSI escape codes. | |
| 171 | /// | |
| 172 | /// See also `getOrEnableAnsiEscapeSupport`. | |
| 173 | pub fn supportsAnsiEscapeCodes(self: File) bool { | |
| 174 | if (builtin.os.tag == .windows) { | |
| 175 | var console_mode: windows.DWORD = 0; | |
| 176 | if (windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) { | |
| 177 | if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; | |
| 178 | } | |
| 179 | ||
| 180 | return self.isCygwinPty(); | |
| 181 | } | |
| 182 | if (builtin.os.tag == .wasi) { | |
| 183 | // WASI sanitizes stdout when fd is a tty so ANSI escape codes | |
| 184 | // will not be interpreted as actual cursor commands, and | |
| 185 | // stderr is always sanitized. | |
| 186 | return false; | |
| 187 | } | |
| 188 | if (self.isTty()) { | |
| 189 | if (self.handle == posix.STDOUT_FILENO or self.handle == posix.STDERR_FILENO) { | |
| 190 | if (posix.getenvZ("TERM")) |term| { | |
| 191 | if (std.mem.eql(u8, term, "dumb")) | |
| 192 | return false; | |
| 193 | } | |
| 194 | } | |
| 195 | return true; | |
| 196 | } | |
| 197 | return false; | |
| 198 | } | |
| 199 | ||
| 200 | pub const SetEndPosError = posix.TruncateError; | |
| 201 | ||
| 202 | /// Shrinks or expands the file. | |
| 203 | /// The file offset after this call is left unchanged. | |
| 204 | pub fn setEndPos(self: File, length: u64) SetEndPosError!void { | |
| 205 | try posix.ftruncate(self.handle, length); | |
| 206 | } | |
| 207 | ||
| 208 | pub const GetEndPosError = std.os.windows.GetFileSizeError || StatError; | |
| 209 | ||
| 210 | /// TODO: integrate with async I/O | |
| 211 | pub fn getEndPos(self: File) GetEndPosError!u64 { | |
| 212 | if (builtin.os.tag == .windows) { | |
| 213 | return windows.GetFileSizeEx(self.handle); | |
| 214 | } | |
| 215 | return (try self.stat()).size; | |
| 216 | } | |
| 217 | ||
| 218 | pub const ModeError = StatError; | |
| 219 | ||
| 220 | /// TODO: integrate with async I/O | |
| 221 | pub fn mode(self: File) ModeError!Mode { | |
| 222 | if (builtin.os.tag == .windows) { | |
| 223 | return 0; | |
| 224 | } | |
| 225 | return (try self.stat()).mode; | |
| 226 | } | |
| 227 | ||
| 228 | /// Deprecated in favor of `Io.File.Stat`. | |
| 229 | pub const Stat = Io.File.Stat; | |
| 230 | ||
| 231 | /// Deprecated in favor of `Io.File.StatError`. | |
| 232 | pub const StatError = posix.FStatError; | |
| 233 | ||
| 234 | /// Deprecated in favor of `Io.File.stat`. | |
| 235 | pub fn stat(self: File) StatError!Stat { | |
| 236 | var threaded: Io.Threaded = .init_single_threaded; | |
| 237 | const io = threaded.ioBasic(); | |
| 238 | return Io.File.stat(.{ .handle = self.handle }, io); | |
| 239 | } | |
| 240 | ||
| 241 | pub const ChmodError = posix.FChmodError; | |
| 242 | ||
| 243 | /// Changes the mode of the file. | |
| 244 | /// The process must have the correct privileges in order to do this | |
| 245 | /// successfully, or must have the effective user ID matching the owner | |
| 246 | /// of the file. | |
| 247 | pub fn chmod(self: File, new_mode: Mode) ChmodError!void { | |
| 248 | try posix.fchmod(self.handle, new_mode); | |
| 249 | } | |
| 250 | ||
| 251 | pub const ChownError = posix.FChownError; | |
| 252 | ||
| 253 | /// Changes the owner and group of the file. | |
| 254 | /// The process must have the correct privileges in order to do this | |
| 255 | /// successfully. The group may be changed by the owner of the file to | |
| 256 | /// any group of which the owner is a member. If the owner or group is | |
| 257 | /// specified as `null`, the ID is not changed. | |
| 258 | pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void { | |
| 259 | try posix.fchown(self.handle, owner, group); | |
| 260 | } | |
| 261 | ||
| 262 | /// Cross-platform representation of permissions on a file. | |
| 263 | /// The `readonly` and `setReadonly` are the only methods available across all platforms. | |
| 264 | /// Platform-specific functionality is available through the `inner` field. | |
| 265 | pub const Permissions = struct { | |
| 266 | /// You may use the `inner` field to use platform-specific functionality | |
| 267 | inner: switch (builtin.os.tag) { | |
| 268 | .windows => PermissionsWindows, | |
| 269 | else => PermissionsUnix, | |
| 270 | }, | |
| 271 | ||
| 272 | const Self = @This(); | |
| 273 | ||
| 274 | /// Returns `true` if permissions represent an unwritable file. | |
| 275 | /// On Unix, `true` is returned only if no class has write permissions. | |
| 276 | pub fn readOnly(self: Self) bool { | |
| 277 | return self.inner.readOnly(); | |
| 278 | } | |
| 279 | ||
| 280 | /// Sets whether write permissions are provided. | |
| 281 | /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`. | |
| 282 | /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` | |
| 283 | pub fn setReadOnly(self: *Self, read_only: bool) void { | |
| 284 | self.inner.setReadOnly(read_only); | |
| 285 | } | |
| 286 | }; | |
| 287 | ||
| 288 | pub const PermissionsWindows = struct { | |
| 289 | attributes: windows.DWORD, | |
| 290 | ||
| 291 | const Self = @This(); | |
| 292 | ||
| 293 | /// Returns `true` if permissions represent an unwritable file. | |
| 294 | pub fn readOnly(self: Self) bool { | |
| 295 | return self.attributes & windows.FILE_ATTRIBUTE_READONLY != 0; | |
| 296 | } | |
| 297 | ||
| 298 | /// Sets whether write permissions are provided. | |
| 299 | /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` | |
| 300 | pub fn setReadOnly(self: *Self, read_only: bool) void { | |
| 301 | if (read_only) { | |
| 302 | self.attributes |= windows.FILE_ATTRIBUTE_READONLY; | |
| 303 | } else { | |
| 304 | self.attributes &= ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY); | |
| 305 | } | |
| 306 | } | |
| 307 | }; | |
| 308 | ||
| 309 | pub const PermissionsUnix = struct { | |
| 310 | mode: Mode, | |
| 311 | ||
| 312 | const Self = @This(); | |
| 313 | ||
| 314 | /// Returns `true` if permissions represent an unwritable file. | |
| 315 | /// `true` is returned only if no class has write permissions. | |
| 316 | pub fn readOnly(self: Self) bool { | |
| 317 | return self.mode & 0o222 == 0; | |
| 318 | } | |
| 319 | ||
| 320 | /// Sets whether write permissions are provided. | |
| 321 | /// This affects *all* classes. If this is undesired, use `unixSet`. | |
| 322 | /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` | |
| 323 | pub fn setReadOnly(self: *Self, read_only: bool) void { | |
| 324 | if (read_only) { | |
| 325 | self.mode &= ~@as(Mode, 0o222); | |
| 326 | } else { | |
| 327 | self.mode |= @as(Mode, 0o222); | |
| 328 | } | |
| 329 | } | |
| 330 | ||
| 331 | pub const Class = enum(u2) { | |
| 332 | user = 2, | |
| 333 | group = 1, | |
| 334 | other = 0, | |
| 335 | }; | |
| 336 | ||
| 337 | pub const Permission = enum(u3) { | |
| 338 | read = 0o4, | |
| 339 | write = 0o2, | |
| 340 | execute = 0o1, | |
| 341 | }; | |
| 342 | ||
| 343 | /// Returns `true` if the chosen class has the selected permission. | |
| 344 | /// This method is only available on Unix platforms. | |
| 345 | pub fn unixHas(self: Self, class: Class, permission: Permission) bool { | |
| 346 | const mask = @as(Mode, @intFromEnum(permission)) << @as(u3, @intFromEnum(class)) * 3; | |
| 347 | return self.mode & mask != 0; | |
| 348 | } | |
| 349 | ||
| 350 | /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged. | |
| 351 | /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` | |
| 352 | pub fn unixSet(self: *Self, class: Class, permissions: struct { | |
| 353 | read: ?bool = null, | |
| 354 | write: ?bool = null, | |
| 355 | execute: ?bool = null, | |
| 356 | }) void { | |
| 357 | const shift = @as(u3, @intFromEnum(class)) * 3; | |
| 358 | if (permissions.read) |r| { | |
| 359 | if (r) { | |
| 360 | self.mode |= @as(Mode, 0o4) << shift; | |
| 361 | } else { | |
| 362 | self.mode &= ~(@as(Mode, 0o4) << shift); | |
| 363 | } | |
| 364 | } | |
| 365 | if (permissions.write) |w| { | |
| 366 | if (w) { | |
| 367 | self.mode |= @as(Mode, 0o2) << shift; | |
| 368 | } else { | |
| 369 | self.mode &= ~(@as(Mode, 0o2) << shift); | |
| 370 | } | |
| 371 | } | |
| 372 | if (permissions.execute) |x| { | |
| 373 | if (x) { | |
| 374 | self.mode |= @as(Mode, 0o1) << shift; | |
| 375 | } else { | |
| 376 | self.mode &= ~(@as(Mode, 0o1) << shift); | |
| 377 | } | |
| 378 | } | |
| 379 | } | |
| 380 | ||
| 381 | /// Returns a `Permissions` struct representing the permissions from the passed mode. | |
| 382 | pub fn unixNew(new_mode: Mode) Self { | |
| 383 | return Self{ | |
| 384 | .mode = new_mode, | |
| 385 | }; | |
| 386 | } | |
| 387 | }; | |
| 388 | ||
| 389 | pub const SetPermissionsError = ChmodError; | |
| 390 | ||
| 391 | /// Sets permissions according to the provided `Permissions` struct. | |
| 392 | /// This method is *NOT* available on WASI | |
| 393 | pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void { | |
| 394 | switch (builtin.os.tag) { | |
| 395 | .windows => { | |
| 396 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 397 | var info = windows.FILE_BASIC_INFORMATION{ | |
| 398 | .CreationTime = 0, | |
| 399 | .LastAccessTime = 0, | |
| 400 | .LastWriteTime = 0, | |
| 401 | .ChangeTime = 0, | |
| 402 | .FileAttributes = permissions.inner.attributes, | |
| 403 | }; | |
| 404 | const rc = windows.ntdll.NtSetInformationFile( | |
| 405 | self.handle, | |
| 406 | &io_status_block, | |
| 407 | &info, | |
| 408 | @sizeOf(windows.FILE_BASIC_INFORMATION), | |
| 409 | .Basic, | |
| 410 | ); | |
| 411 | switch (rc) { | |
| 412 | .SUCCESS => return, | |
| 413 | .INVALID_HANDLE => unreachable, | |
| 414 | .ACCESS_DENIED => return error.AccessDenied, | |
| 415 | else => return windows.unexpectedStatus(rc), | |
| 416 | } | |
| 417 | }, | |
| 418 | .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod | |
| 419 | else => { | |
| 420 | try self.chmod(permissions.inner.mode); | |
| 421 | }, | |
| 422 | } | |
| 423 | } | |
| 424 | ||
| 425 | pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError; | |
| 426 | ||
| 427 | /// The underlying file system may have a different granularity than nanoseconds, | |
| 428 | /// and therefore this function cannot guarantee any precision will be stored. | |
| 429 | /// Further, the maximum value is limited by the system ABI. When a value is provided | |
| 430 | /// that exceeds this range, the value is clamped to the maximum. | |
| 431 | /// TODO: integrate with async I/O | |
| 432 | pub fn updateTimes( | |
| 433 | self: File, | |
| 434 | /// access timestamp in nanoseconds | |
| 435 | atime: Io.Timestamp, | |
| 436 | /// last modification timestamp in nanoseconds | |
| 437 | mtime: Io.Timestamp, | |
| 438 | ) UpdateTimesError!void { | |
| 439 | if (builtin.os.tag == .windows) { | |
| 440 | const atime_ft = windows.nanoSecondsToFileTime(atime); | |
| 441 | const mtime_ft = windows.nanoSecondsToFileTime(mtime); | |
| 442 | return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft); | |
| 443 | } | |
| 444 | const times = [2]posix.timespec{ | |
| 445 | posix.timespec{ | |
| 446 | .sec = math.cast(isize, @divFloor(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), | |
| 447 | .nsec = math.cast(isize, @mod(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), | |
| 448 | }, | |
| 449 | posix.timespec{ | |
| 450 | .sec = math.cast(isize, @divFloor(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), | |
| 451 | .nsec = math.cast(isize, @mod(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), | |
| 452 | }, | |
| 453 | }; | |
| 454 | try posix.futimens(self.handle, &times); | |
| 455 | } | |
| 456 | ||
| 457 | pub const ReadError = posix.ReadError; | |
| 458 | pub const PReadError = posix.PReadError; | |
| 459 | ||
| 460 | pub fn read(self: File, buffer: []u8) ReadError!usize { | |
| 461 | if (is_windows) { | |
| 462 | return windows.ReadFile(self.handle, buffer, null); | |
| 463 | } | |
| 464 | ||
| 465 | return posix.read(self.handle, buffer); | |
| 466 | } | |
| 467 | ||
| 468 | /// On Windows, this function currently does alter the file pointer. | |
| 469 | /// https://github.com/ziglang/zig/issues/12783 | |
| 470 | pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize { | |
| 471 | if (is_windows) { | |
| 472 | return windows.ReadFile(self.handle, buffer, offset); | |
| 473 | } | |
| 474 | ||
| 475 | return posix.pread(self.handle, buffer, offset); | |
| 476 | } | |
| 477 | ||
| 478 | /// Deprecated in favor of `Reader`. | |
| 479 | pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize { | |
| 480 | var index: usize = 0; | |
| 481 | while (index != buffer.len) { | |
| 482 | const amt = try self.pread(buffer[index..], offset + index); | |
| 483 | if (amt == 0) break; | |
| 484 | index += amt; | |
| 485 | } | |
| 486 | return index; | |
| 487 | } | |
| 488 | ||
| 489 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 490 | pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize { | |
| 491 | if (is_windows) { | |
| 492 | if (iovecs.len == 0) return 0; | |
| 493 | const first = iovecs[0]; | |
| 494 | return windows.ReadFile(self.handle, first.base[0..first.len], null); | |
| 495 | } | |
| 496 | ||
| 497 | return posix.readv(self.handle, iovecs); | |
| 498 | } | |
| 499 | ||
| 500 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 501 | /// On Windows, this function currently does alter the file pointer. | |
| 502 | /// https://github.com/ziglang/zig/issues/12783 | |
| 503 | pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!usize { | |
| 504 | if (is_windows) { | |
| 505 | if (iovecs.len == 0) return 0; | |
| 506 | const first = iovecs[0]; | |
| 507 | return windows.ReadFile(self.handle, first.base[0..first.len], offset); | |
| 508 | } | |
| 509 | ||
| 510 | return posix.preadv(self.handle, iovecs, offset); | |
| 511 | } | |
| 512 | ||
| 513 | pub const WriteError = posix.WriteError; | |
| 514 | pub const PWriteError = posix.PWriteError; | |
| 515 | ||
| 516 | pub fn write(self: File, bytes: []const u8) WriteError!usize { | |
| 517 | if (is_windows) { | |
| 518 | return windows.WriteFile(self.handle, bytes, null); | |
| 519 | } | |
| 520 | ||
| 521 | return posix.write(self.handle, bytes); | |
| 522 | } | |
| 523 | ||
| 524 | pub fn writeAll(self: File, bytes: []const u8) WriteError!void { | |
| 525 | var index: usize = 0; | |
| 526 | while (index < bytes.len) { | |
| 527 | index += try self.write(bytes[index..]); | |
| 528 | } | |
| 529 | } | |
| 530 | ||
| 531 | /// Deprecated in favor of `Writer`. | |
| 532 | pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void { | |
| 533 | var index: usize = 0; | |
| 534 | while (index < bytes.len) { | |
| 535 | index += try self.pwrite(bytes[index..], offset + index); | |
| 536 | } | |
| 537 | } | |
| 538 | ||
| 539 | /// On Windows, this function currently does alter the file pointer. | |
| 540 | /// https://github.com/ziglang/zig/issues/12783 | |
| 541 | pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize { | |
| 542 | if (is_windows) { | |
| 543 | return windows.WriteFile(self.handle, bytes, offset); | |
| 544 | } | |
| 545 | ||
| 546 | return posix.pwrite(self.handle, bytes, offset); | |
| 547 | } | |
| 548 | ||
| 549 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 550 | pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize { | |
| 551 | if (is_windows) { | |
| 552 | // TODO improve this to use WriteFileScatter | |
| 553 | if (iovecs.len == 0) return 0; | |
| 554 | const first = iovecs[0]; | |
| 555 | return windows.WriteFile(self.handle, first.base[0..first.len], null); | |
| 556 | } | |
| 557 | ||
| 558 | return posix.writev(self.handle, iovecs); | |
| 559 | } | |
| 560 | ||
| 561 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 562 | /// On Windows, this function currently does alter the file pointer. | |
| 563 | /// https://github.com/ziglang/zig/issues/12783 | |
| 564 | pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!usize { | |
| 565 | if (is_windows) { | |
| 566 | if (iovecs.len == 0) return 0; | |
| 567 | const first = iovecs[0]; | |
| 568 | return windows.WriteFile(self.handle, first.base[0..first.len], offset); | |
| 569 | } | |
| 570 | ||
| 571 | return posix.pwritev(self.handle, iovecs, offset); | |
| 572 | } | |
| 573 | ||
| 574 | /// Deprecated in favor of `Writer`. | |
| 575 | pub const CopyRangeError = posix.CopyFileRangeError; | |
| 576 | ||
| 577 | /// Deprecated in favor of `Writer`. | |
| 578 | pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { | |
| 579 | const adjusted_len = math.cast(usize, len) orelse maxInt(usize); | |
| 580 | const result = try posix.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0); | |
| 581 | return result; | |
| 582 | } | |
| 583 | ||
| 584 | /// Deprecated in favor of `Writer`. | |
| 585 | pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { | |
| 586 | var total_bytes_copied: u64 = 0; | |
| 587 | var in_off = in_offset; | |
| 588 | var out_off = out_offset; | |
| 589 | while (total_bytes_copied < len) { | |
| 590 | const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied); | |
| 591 | if (amt_copied == 0) return total_bytes_copied; | |
| 592 | total_bytes_copied += amt_copied; | |
| 593 | in_off += amt_copied; | |
| 594 | out_off += amt_copied; | |
| 595 | } | |
| 596 | return total_bytes_copied; | |
| 597 | } | |
| 598 | ||
| 599 | /// Deprecated in favor of `Io.File.Reader`. | |
| 600 | pub const Reader = Io.File.Reader; | |
| 601 | ||
| 602 | pub const Writer = struct { | |
| 603 | file: File, | |
| 604 | err: ?WriteError = null, | |
| 605 | mode: Writer.Mode = .positional, | |
| 606 | /// Tracks the true seek position in the file. To obtain the logical | |
| 607 | /// position, add the buffer size to this value. | |
| 608 | pos: u64 = 0, | |
| 609 | sendfile_err: ?SendfileError = null, | |
| 610 | copy_file_range_err: ?CopyFileRangeError = null, | |
| 611 | fcopyfile_err: ?FcopyfileError = null, | |
| 612 | seek_err: ?Writer.SeekError = null, | |
| 613 | interface: Io.Writer, | |
| 614 | ||
| 615 | pub const Mode = Reader.Mode; | |
| 616 | ||
| 617 | pub const SendfileError = error{ | |
| 618 | UnsupportedOperation, | |
| 619 | SystemResources, | |
| 620 | InputOutput, | |
| 621 | BrokenPipe, | |
| 622 | WouldBlock, | |
| 623 | Unexpected, | |
| 624 | }; | |
| 625 | ||
| 626 | pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError; | |
| 627 | ||
| 628 | pub const FcopyfileError = error{ | |
| 629 | OperationNotSupported, | |
| 630 | OutOfMemory, | |
| 631 | Unexpected, | |
| 632 | }; | |
| 633 | ||
| 634 | pub const SeekError = Io.File.SeekError; | |
| 635 | ||
| 636 | /// Number of slices to store on the stack, when trying to send as many byte | |
| 637 | /// vectors through the underlying write calls as possible. | |
| 638 | const max_buffers_len = 16; | |
| 639 | ||
| 640 | pub fn init(file: File, buffer: []u8) Writer { | |
| 641 | return .{ | |
| 642 | .file = file, | |
| 643 | .interface = initInterface(buffer), | |
| 644 | .mode = .positional, | |
| 645 | }; | |
| 646 | } | |
| 647 | ||
| 648 | /// Positional is more threadsafe, since the global seek position is not | |
| 649 | /// affected, but when such syscalls are not available, preemptively | |
| 650 | /// initializing in streaming mode will skip a failed syscall. | |
| 651 | pub fn initStreaming(file: File, buffer: []u8) Writer { | |
| 652 | return .{ | |
| 653 | .file = file, | |
| 654 | .interface = initInterface(buffer), | |
| 655 | .mode = .streaming, | |
| 656 | }; | |
| 657 | } | |
| 658 | ||
| 659 | pub fn initInterface(buffer: []u8) Io.Writer { | |
| 660 | return .{ | |
| 661 | .vtable = &.{ | |
| 662 | .drain = drain, | |
| 663 | .sendFile = sendFile, | |
| 664 | }, | |
| 665 | .buffer = buffer, | |
| 666 | }; | |
| 667 | } | |
| 668 | ||
| 669 | /// TODO when this logic moves from fs.File to Io.File the io parameter should be deleted | |
| 670 | pub fn moveToReader(w: *Writer, io: Io) Reader { | |
| 671 | defer w.* = undefined; | |
| 672 | return .{ | |
| 673 | .io = io, | |
| 674 | .file = .{ .handle = w.file.handle }, | |
| 675 | .mode = w.mode, | |
| 676 | .pos = w.pos, | |
| 677 | .interface = Reader.initInterface(w.interface.buffer), | |
| 678 | .seek_err = w.seek_err, | |
| 679 | }; | |
| 680 | } | |
| 681 | ||
| 682 | pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { | |
| 683 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); | |
| 684 | const handle = w.file.handle; | |
| 685 | const buffered = io_w.buffered(); | |
| 686 | if (is_windows) switch (w.mode) { | |
| 687 | .positional, .positional_reading => { | |
| 688 | if (buffered.len != 0) { | |
| 689 | const n = windows.WriteFile(handle, buffered, w.pos) catch |err| { | |
| 690 | w.err = err; | |
| 691 | return error.WriteFailed; | |
| 692 | }; | |
| 693 | w.pos += n; | |
| 694 | return io_w.consume(n); | |
| 695 | } | |
| 696 | for (data[0 .. data.len - 1]) |buf| { | |
| 697 | if (buf.len == 0) continue; | |
| 698 | const n = windows.WriteFile(handle, buf, w.pos) catch |err| { | |
| 699 | w.err = err; | |
| 700 | return error.WriteFailed; | |
| 701 | }; | |
| 702 | w.pos += n; | |
| 703 | return io_w.consume(n); | |
| 704 | } | |
| 705 | const pattern = data[data.len - 1]; | |
| 706 | if (pattern.len == 0 or splat == 0) return 0; | |
| 707 | const n = windows.WriteFile(handle, pattern, w.pos) catch |err| { | |
| 708 | w.err = err; | |
| 709 | return error.WriteFailed; | |
| 710 | }; | |
| 711 | w.pos += n; | |
| 712 | return io_w.consume(n); | |
| 713 | }, | |
| 714 | .streaming, .streaming_reading => { | |
| 715 | if (buffered.len != 0) { | |
| 716 | const n = windows.WriteFile(handle, buffered, null) catch |err| { | |
| 717 | w.err = err; | |
| 718 | return error.WriteFailed; | |
| 719 | }; | |
| 720 | w.pos += n; | |
| 721 | return io_w.consume(n); | |
| 722 | } | |
| 723 | for (data[0 .. data.len - 1]) |buf| { | |
| 724 | if (buf.len == 0) continue; | |
| 725 | const n = windows.WriteFile(handle, buf, null) catch |err| { | |
| 726 | w.err = err; | |
| 727 | return error.WriteFailed; | |
| 728 | }; | |
| 729 | w.pos += n; | |
| 730 | return io_w.consume(n); | |
| 731 | } | |
| 732 | const pattern = data[data.len - 1]; | |
| 733 | if (pattern.len == 0 or splat == 0) return 0; | |
| 734 | const n = windows.WriteFile(handle, pattern, null) catch |err| { | |
| 735 | w.err = err; | |
| 736 | return error.WriteFailed; | |
| 737 | }; | |
| 738 | w.pos += n; | |
| 739 | return io_w.consume(n); | |
| 740 | }, | |
| 741 | .failure => return error.WriteFailed, | |
| 742 | }; | |
| 743 | var iovecs: [max_buffers_len]std.posix.iovec_const = undefined; | |
| 744 | var len: usize = 0; | |
| 745 | if (buffered.len > 0) { | |
| 746 | iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len }; | |
| 747 | len += 1; | |
| 748 | } | |
| 749 | for (data[0 .. data.len - 1]) |d| { | |
| 750 | if (d.len == 0) continue; | |
| 751 | iovecs[len] = .{ .base = d.ptr, .len = d.len }; | |
| 752 | len += 1; | |
| 753 | if (iovecs.len - len == 0) break; | |
| 754 | } | |
| 755 | const pattern = data[data.len - 1]; | |
| 756 | if (iovecs.len - len != 0) switch (splat) { | |
| 757 | 0 => {}, | |
| 758 | 1 => if (pattern.len != 0) { | |
| 759 | iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; | |
| 760 | len += 1; | |
| 761 | }, | |
| 762 | else => switch (pattern.len) { | |
| 763 | 0 => {}, | |
| 764 | 1 => { | |
| 765 | const splat_buffer_candidate = io_w.buffer[io_w.end..]; | |
| 766 | var backup_buffer: [64]u8 = undefined; | |
| 767 | const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len) | |
| 768 | splat_buffer_candidate | |
| 769 | else | |
| 770 | &backup_buffer; | |
| 771 | const memset_len = @min(splat_buffer.len, splat); | |
| 772 | const buf = splat_buffer[0..memset_len]; | |
| 773 | @memset(buf, pattern[0]); | |
| 774 | iovecs[len] = .{ .base = buf.ptr, .len = buf.len }; | |
| 775 | len += 1; | |
| 776 | var remaining_splat = splat - buf.len; | |
| 777 | while (remaining_splat > splat_buffer.len and iovecs.len - len != 0) { | |
| 778 | assert(buf.len == splat_buffer.len); | |
| 779 | iovecs[len] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len }; | |
| 780 | len += 1; | |
| 781 | remaining_splat -= splat_buffer.len; | |
| 782 | } | |
| 783 | if (remaining_splat > 0 and iovecs.len - len != 0) { | |
| 784 | iovecs[len] = .{ .base = splat_buffer.ptr, .len = remaining_splat }; | |
| 785 | len += 1; | |
| 786 | } | |
| 787 | }, | |
| 788 | else => for (0..splat) |_| { | |
| 789 | iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; | |
| 790 | len += 1; | |
| 791 | if (iovecs.len - len == 0) break; | |
| 792 | }, | |
| 793 | }, | |
| 794 | }; | |
| 795 | if (len == 0) return 0; | |
| 796 | switch (w.mode) { | |
| 797 | .positional, .positional_reading => { | |
| 798 | const n = std.posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) { | |
| 799 | error.Unseekable => { | |
| 800 | w.mode = w.mode.toStreaming(); | |
| 801 | const pos = w.pos; | |
| 802 | if (pos != 0) { | |
| 803 | w.pos = 0; | |
| 804 | w.seekTo(@intCast(pos)) catch { | |
| 805 | w.mode = .failure; | |
| 806 | return error.WriteFailed; | |
| 807 | }; | |
| 808 | } | |
| 809 | return 0; | |
| 810 | }, | |
| 811 | else => |e| { | |
| 812 | w.err = e; | |
| 813 | return error.WriteFailed; | |
| 814 | }, | |
| 815 | }; | |
| 816 | w.pos += n; | |
| 817 | return io_w.consume(n); | |
| 818 | }, | |
| 819 | .streaming, .streaming_reading => { | |
| 820 | const n = std.posix.writev(handle, iovecs[0..len]) catch |err| { | |
| 821 | w.err = err; | |
| 822 | return error.WriteFailed; | |
| 823 | }; | |
| 824 | w.pos += n; | |
| 825 | return io_w.consume(n); | |
| 826 | }, | |
| 827 | .failure => return error.WriteFailed, | |
| 828 | } | |
| 829 | } | |
| 830 | ||
| 831 | pub fn sendFile( | |
| 832 | io_w: *Io.Writer, | |
| 833 | file_reader: *Io.File.Reader, | |
| 834 | limit: Io.Limit, | |
| 835 | ) Io.Writer.FileError!usize { | |
| 836 | const reader_buffered = file_reader.interface.buffered(); | |
| 837 | if (reader_buffered.len >= @intFromEnum(limit)) | |
| 838 | return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered)); | |
| 839 | const writer_buffered = io_w.buffered(); | |
| 840 | const file_limit = @intFromEnum(limit) - reader_buffered.len; | |
| 841 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); | |
| 842 | const out_fd = w.file.handle; | |
| 843 | const in_fd = file_reader.file.handle; | |
| 844 | ||
| 845 | if (file_reader.size) |size| { | |
| 846 | if (size - file_reader.pos == 0) { | |
| 847 | if (reader_buffered.len != 0) { | |
| 848 | return sendFileBuffered(io_w, file_reader, reader_buffered); | |
| 849 | } else { | |
| 850 | return error.EndOfStream; | |
| 851 | } | |
| 852 | } | |
| 853 | } | |
| 854 | ||
| 855 | if (native_os == .freebsd and w.mode == .streaming) sf: { | |
| 856 | // Try using sendfile on FreeBSD. | |
| 857 | if (w.sendfile_err != null) break :sf; | |
| 858 | const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; | |
| 859 | var hdtr_data: std.c.sf_hdtr = undefined; | |
| 860 | var headers: [2]posix.iovec_const = undefined; | |
| 861 | var headers_i: u8 = 0; | |
| 862 | if (writer_buffered.len != 0) { | |
| 863 | headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; | |
| 864 | headers_i += 1; | |
| 865 | } | |
| 866 | if (reader_buffered.len != 0) { | |
| 867 | headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; | |
| 868 | headers_i += 1; | |
| 869 | } | |
| 870 | const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { | |
| 871 | hdtr_data = .{ | |
| 872 | .headers = &headers, | |
| 873 | .hdr_cnt = headers_i, | |
| 874 | .trailers = null, | |
| 875 | .trl_cnt = 0, | |
| 876 | }; | |
| 877 | break :b &hdtr_data; | |
| 878 | }; | |
| 879 | var sbytes: std.c.off_t = undefined; | |
| 880 | const nbytes: usize = @min(file_limit, maxInt(usize)); | |
| 881 | const flags = 0; | |
| 882 | switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { | |
| 883 | .SUCCESS, .INTR => {}, | |
| 884 | .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, | |
| 885 | .BADF => if (builtin.mode == .Debug) @panic("race condition") else { | |
| 886 | w.sendfile_err = error.Unexpected; | |
| 887 | }, | |
| 888 | .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { | |
| 889 | w.sendfile_err = error.Unexpected; | |
| 890 | }, | |
| 891 | .NOTCONN => w.sendfile_err = error.BrokenPipe, | |
| 892 | .AGAIN, .BUSY => if (sbytes == 0) { | |
| 893 | w.sendfile_err = error.WouldBlock; | |
| 894 | }, | |
| 895 | .IO => w.sendfile_err = error.InputOutput, | |
| 896 | .PIPE => w.sendfile_err = error.BrokenPipe, | |
| 897 | .NOBUFS => w.sendfile_err = error.SystemResources, | |
| 898 | else => |err| w.sendfile_err = posix.unexpectedErrno(err), | |
| 899 | } | |
| 900 | if (w.sendfile_err != null) { | |
| 901 | // Give calling code chance to observe the error before trying | |
| 902 | // something else. | |
| 903 | return 0; | |
| 904 | } | |
| 905 | if (sbytes == 0) { | |
| 906 | file_reader.size = file_reader.pos; | |
| 907 | return error.EndOfStream; | |
| 908 | } | |
| 909 | const consumed = io_w.consume(@intCast(sbytes)); | |
| 910 | file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; | |
| 911 | return consumed; | |
| 912 | } | |
| 913 | ||
| 914 | if (native_os.isDarwin() and w.mode == .streaming) sf: { | |
| 915 | // Try using sendfile on macOS. | |
| 916 | if (w.sendfile_err != null) break :sf; | |
| 917 | const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; | |
| 918 | var hdtr_data: std.c.sf_hdtr = undefined; | |
| 919 | var headers: [2]posix.iovec_const = undefined; | |
| 920 | var headers_i: u8 = 0; | |
| 921 | if (writer_buffered.len != 0) { | |
| 922 | headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; | |
| 923 | headers_i += 1; | |
| 924 | } | |
| 925 | if (reader_buffered.len != 0) { | |
| 926 | headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; | |
| 927 | headers_i += 1; | |
| 928 | } | |
| 929 | const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { | |
| 930 | hdtr_data = .{ | |
| 931 | .headers = &headers, | |
| 932 | .hdr_cnt = headers_i, | |
| 933 | .trailers = null, | |
| 934 | .trl_cnt = 0, | |
| 935 | }; | |
| 936 | break :b &hdtr_data; | |
| 937 | }; | |
| 938 | const max_count = maxInt(i32); // Avoid EINVAL. | |
| 939 | var len: std.c.off_t = @min(file_limit, max_count); | |
| 940 | const flags = 0; | |
| 941 | switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { | |
| 942 | .SUCCESS, .INTR => {}, | |
| 943 | .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, | |
| 944 | .BADF => if (builtin.mode == .Debug) @panic("race condition") else { | |
| 945 | w.sendfile_err = error.Unexpected; | |
| 946 | }, | |
| 947 | .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { | |
| 948 | w.sendfile_err = error.Unexpected; | |
| 949 | }, | |
| 950 | .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { | |
| 951 | w.sendfile_err = error.Unexpected; | |
| 952 | }, | |
| 953 | .NOTCONN => w.sendfile_err = error.BrokenPipe, | |
| 954 | .AGAIN => if (len == 0) { | |
| 955 | w.sendfile_err = error.WouldBlock; | |
| 956 | }, | |
| 957 | .IO => w.sendfile_err = error.InputOutput, | |
| 958 | .PIPE => w.sendfile_err = error.BrokenPipe, | |
| 959 | else => |err| w.sendfile_err = posix.unexpectedErrno(err), | |
| 960 | } | |
| 961 | if (w.sendfile_err != null) { | |
| 962 | // Give calling code chance to observe the error before trying | |
| 963 | // something else. | |
| 964 | return 0; | |
| 965 | } | |
| 966 | if (len == 0) { | |
| 967 | file_reader.size = file_reader.pos; | |
| 968 | return error.EndOfStream; | |
| 969 | } | |
| 970 | const consumed = io_w.consume(@bitCast(len)); | |
| 971 | file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; | |
| 972 | return consumed; | |
| 973 | } | |
| 974 | ||
| 975 | if (native_os == .linux and w.mode == .streaming) sf: { | |
| 976 | // Try using sendfile on Linux. | |
| 977 | if (w.sendfile_err != null) break :sf; | |
| 978 | // Linux sendfile does not support headers. | |
| 979 | if (writer_buffered.len != 0 or reader_buffered.len != 0) | |
| 980 | return sendFileBuffered(io_w, file_reader, reader_buffered); | |
| 981 | const max_count = 0x7ffff000; // Avoid EINVAL. | |
| 982 | var off: std.os.linux.off_t = undefined; | |
| 983 | const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { | |
| 984 | .positional => o: { | |
| 985 | const size = file_reader.getSize() catch return 0; | |
| 986 | off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; | |
| 987 | break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; | |
| 988 | }, | |
| 989 | .streaming => .{ null, limit.minInt(max_count) }, | |
| 990 | .streaming_reading, .positional_reading => break :sf, | |
| 991 | .failure => return error.ReadFailed, | |
| 992 | }; | |
| 993 | const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) { | |
| 994 | error.Unseekable => { | |
| 995 | file_reader.mode = file_reader.mode.toStreaming(); | |
| 996 | const pos = file_reader.pos; | |
| 997 | if (pos != 0) { | |
| 998 | file_reader.pos = 0; | |
| 999 | file_reader.seekBy(@intCast(pos)) catch { | |
| 1000 | file_reader.mode = .failure; | |
| 1001 | return error.ReadFailed; | |
| 1002 | }; | |
| 1003 | } | |
| 1004 | return 0; | |
| 1005 | }, | |
| 1006 | else => |e| { | |
| 1007 | w.sendfile_err = e; | |
| 1008 | return 0; | |
| 1009 | }, | |
| 1010 | }; | |
| 1011 | if (n == 0) { | |
| 1012 | file_reader.size = file_reader.pos; | |
| 1013 | return error.EndOfStream; | |
| 1014 | } | |
| 1015 | file_reader.pos += n; | |
| 1016 | w.pos += n; | |
| 1017 | return n; | |
| 1018 | } | |
| 1019 | ||
| 1020 | const copy_file_range = switch (native_os) { | |
| 1021 | .freebsd => std.os.freebsd.copy_file_range, | |
| 1022 | .linux => std.os.linux.wrapped.copy_file_range, | |
| 1023 | else => {}, | |
| 1024 | }; | |
| 1025 | if (@TypeOf(copy_file_range) != void) cfr: { | |
| 1026 | if (w.copy_file_range_err != null) break :cfr; | |
| 1027 | if (writer_buffered.len != 0 or reader_buffered.len != 0) | |
| 1028 | return sendFileBuffered(io_w, file_reader, reader_buffered); | |
| 1029 | var off_in: i64 = undefined; | |
| 1030 | var off_out: i64 = undefined; | |
| 1031 | const off_in_ptr: ?*i64 = switch (file_reader.mode) { | |
| 1032 | .positional_reading, .streaming_reading => return error.Unimplemented, | |
| 1033 | .positional => p: { | |
| 1034 | off_in = @intCast(file_reader.pos); | |
| 1035 | break :p &off_in; | |
| 1036 | }, | |
| 1037 | .streaming => null, | |
| 1038 | .failure => return error.WriteFailed, | |
| 1039 | }; | |
| 1040 | const off_out_ptr: ?*i64 = switch (w.mode) { | |
| 1041 | .positional_reading, .streaming_reading => return error.Unimplemented, | |
| 1042 | .positional => p: { | |
| 1043 | off_out = @intCast(w.pos); | |
| 1044 | break :p &off_out; | |
| 1045 | }, | |
| 1046 | .streaming => null, | |
| 1047 | .failure => return error.WriteFailed, | |
| 1048 | }; | |
| 1049 | const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| { | |
| 1050 | w.copy_file_range_err = err; | |
| 1051 | return 0; | |
| 1052 | }; | |
| 1053 | if (n == 0) { | |
| 1054 | file_reader.size = file_reader.pos; | |
| 1055 | return error.EndOfStream; | |
| 1056 | } | |
| 1057 | file_reader.pos += n; | |
| 1058 | w.pos += n; | |
| 1059 | return n; | |
| 1060 | } | |
| 1061 | ||
| 1062 | if (builtin.os.tag.isDarwin()) fcf: { | |
| 1063 | if (w.fcopyfile_err != null) break :fcf; | |
| 1064 | if (file_reader.pos != 0) break :fcf; | |
| 1065 | if (w.pos != 0) break :fcf; | |
| 1066 | if (limit != .unlimited) break :fcf; | |
| 1067 | const size = file_reader.getSize() catch break :fcf; | |
| 1068 | if (writer_buffered.len != 0 or reader_buffered.len != 0) | |
| 1069 | return sendFileBuffered(io_w, file_reader, reader_buffered); | |
| 1070 | const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); | |
| 1071 | switch (posix.errno(rc)) { | |
| 1072 | .SUCCESS => {}, | |
| 1073 | .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { | |
| 1074 | w.fcopyfile_err = error.Unexpected; | |
| 1075 | return 0; | |
| 1076 | }, | |
| 1077 | .NOMEM => { | |
| 1078 | w.fcopyfile_err = error.OutOfMemory; | |
| 1079 | return 0; | |
| 1080 | }, | |
| 1081 | .OPNOTSUPP => { | |
| 1082 | w.fcopyfile_err = error.OperationNotSupported; | |
| 1083 | return 0; | |
| 1084 | }, | |
| 1085 | else => |err| { | |
| 1086 | w.fcopyfile_err = posix.unexpectedErrno(err); | |
| 1087 | return 0; | |
| 1088 | }, | |
| 1089 | } | |
| 1090 | file_reader.pos = size; | |
| 1091 | w.pos = size; | |
| 1092 | return size; | |
| 1093 | } | |
| 1094 | ||
| 1095 | return error.Unimplemented; | |
| 1096 | } | |
| 1097 | ||
| 1098 | fn sendFileBuffered( | |
| 1099 | io_w: *Io.Writer, | |
| 1100 | file_reader: *Io.File.Reader, | |
| 1101 | reader_buffered: []const u8, | |
| 1102 | ) Io.Writer.FileError!usize { | |
| 1103 | const n = try drain(io_w, &.{reader_buffered}, 1); | |
| 1104 | file_reader.seekBy(@intCast(n)) catch return error.ReadFailed; | |
| 1105 | return n; | |
| 1106 | } | |
| 1107 | ||
| 1108 | pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void { | |
| 1109 | try w.interface.flush(); | |
| 1110 | try seekToUnbuffered(w, offset); | |
| 1111 | } | |
| 1112 | ||
| 1113 | /// Asserts that no data is currently buffered. | |
| 1114 | pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void { | |
| 1115 | assert(w.interface.buffered().len == 0); | |
| 1116 | switch (w.mode) { | |
| 1117 | .positional, .positional_reading => { | |
| 1118 | w.pos = offset; | |
| 1119 | }, | |
| 1120 | .streaming, .streaming_reading => { | |
| 1121 | if (w.seek_err) |err| return err; | |
| 1122 | posix.lseek_SET(w.file.handle, offset) catch |err| { | |
| 1123 | w.seek_err = err; | |
| 1124 | return err; | |
| 1125 | }; | |
| 1126 | w.pos = offset; | |
| 1127 | }, | |
| 1128 | .failure => return w.seek_err.?, | |
| 1129 | } | |
| 1130 | } | |
| 1131 | ||
| 1132 | pub const EndError = SetEndPosError || Io.Writer.Error; | |
| 1133 | ||
| 1134 | /// Flushes any buffered data and sets the end position of the file. | |
| 1135 | /// | |
| 1136 | /// If not overwriting existing contents, then calling `interface.flush` | |
| 1137 | /// directly is sufficient. | |
| 1138 | /// | |
| 1139 | /// Flush failure is handled by setting `err` so that it can be handled | |
| 1140 | /// along with other write failures. | |
| 1141 | pub fn end(w: *Writer) EndError!void { | |
| 1142 | try w.interface.flush(); | |
| 1143 | switch (w.mode) { | |
| 1144 | .positional, | |
| 1145 | .positional_reading, | |
| 1146 | => w.file.setEndPos(w.pos) catch |err| switch (err) { | |
| 1147 | error.NonResizable => return, | |
| 1148 | else => |e| return e, | |
| 1149 | }, | |
| 1150 | ||
| 1151 | .streaming, | |
| 1152 | .streaming_reading, | |
| 1153 | .failure, | |
| 1154 | => {}, | |
| 1155 | } | |
| 1156 | } | |
| 1157 | }; | |
| 1158 | ||
| 1159 | /// Defaults to positional reading; falls back to streaming. | |
| 1160 | /// | |
| 1161 | /// Positional is more threadsafe, since the global seek position is not | |
| 1162 | /// affected. | |
| 1163 | pub fn reader(file: File, io: Io, buffer: []u8) Reader { | |
| 1164 | return .init(.{ .handle = file.handle }, io, buffer); | |
| 1165 | } | |
| 1166 | ||
| 1167 | /// Positional is more threadsafe, since the global seek position is not | |
| 1168 | /// affected, but when such syscalls are not available, preemptively | |
| 1169 | /// initializing in streaming mode skips a failed syscall. | |
| 1170 | pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader { | |
| 1171 | return .initStreaming(.{ .handle = file.handle }, io, buffer); | |
| 1172 | } | |
| 1173 | ||
| 1174 | /// Defaults to positional reading; falls back to streaming. | |
| 1175 | /// | |
| 1176 | /// Positional is more threadsafe, since the global seek position is not | |
| 1177 | /// affected. | |
| 1178 | pub fn writer(file: File, buffer: []u8) Writer { | |
| 1179 | return .init(file, buffer); | |
| 1180 | } | |
| 1181 | ||
| 1182 | /// Positional is more threadsafe, since the global seek position is not | |
| 1183 | /// affected, but when such syscalls are not available, preemptively | |
| 1184 | /// initializing in streaming mode will skip a failed syscall. | |
| 1185 | pub fn writerStreaming(file: File, buffer: []u8) Writer { | |
| 1186 | return .initStreaming(file, buffer); | |
| 1187 | } | |
| 1188 | ||
| 1189 | const range_off: windows.LARGE_INTEGER = 0; | |
| 1190 | const range_len: windows.LARGE_INTEGER = 1; | |
| 1191 | ||
| 1192 | /// Deprecated | |
| 1193 | pub const LockError = Io.File.LockError; | |
| 1194 | ||
| 1195 | /// Blocks when an incompatible lock is held by another process. | |
| 1196 | /// A process may hold only one type of lock (shared or exclusive) on | |
| 1197 | /// a file. When a process terminates in any way, the lock is released. | |
| 1198 | /// | |
| 1199 | /// Assumes the file is unlocked. | |
| 1200 | /// | |
| 1201 | /// TODO: integrate with async I/O | |
| 1202 | pub fn lock(file: File, l: Lock) LockError!void { | |
| 1203 | if (is_windows) { | |
| 1204 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 1205 | const exclusive = switch (l) { | |
| 1206 | .none => return, | |
| 1207 | .shared => false, | |
| 1208 | .exclusive => true, | |
| 1209 | }; | |
| 1210 | return windows.LockFile( | |
| 1211 | file.handle, | |
| 1212 | null, | |
| 1213 | null, | |
| 1214 | null, | |
| 1215 | &io_status_block, | |
| 1216 | &range_off, | |
| 1217 | &range_len, | |
| 1218 | null, | |
| 1219 | windows.FALSE, // non-blocking=false | |
| 1220 | @intFromBool(exclusive), | |
| 1221 | ) catch |err| switch (err) { | |
| 1222 | error.WouldBlock => unreachable, // non-blocking=false | |
| 1223 | else => |e| return e, | |
| 1224 | }; | |
| 1225 | } else { | |
| 1226 | return posix.flock(file.handle, switch (l) { | |
| 1227 | .none => posix.LOCK.UN, | |
| 1228 | .shared => posix.LOCK.SH, | |
| 1229 | .exclusive => posix.LOCK.EX, | |
| 1230 | }) catch |err| switch (err) { | |
| 1231 | error.WouldBlock => unreachable, // non-blocking=false | |
| 1232 | else => |e| return e, | |
| 1233 | }; | |
| 1234 | } | |
| 1235 | } | |
| 1236 | ||
| 1237 | /// Assumes the file is locked. | |
| 1238 | pub fn unlock(file: File) void { | |
| 1239 | if (is_windows) { | |
| 1240 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 1241 | return windows.UnlockFile( | |
| 1242 | file.handle, | |
| 1243 | &io_status_block, | |
| 1244 | &range_off, | |
| 1245 | &range_len, | |
| 1246 | 0, | |
| 1247 | ) catch |err| switch (err) { | |
| 1248 | error.RangeNotLocked => unreachable, // Function assumes unlocked. | |
| 1249 | error.Unexpected => unreachable, // Resource deallocation must succeed. | |
| 1250 | }; | |
| 1251 | } else { | |
| 1252 | return posix.flock(file.handle, posix.LOCK.UN) catch |err| switch (err) { | |
| 1253 | error.WouldBlock => unreachable, // unlocking can't block | |
| 1254 | error.SystemResources => unreachable, // We are deallocating resources. | |
| 1255 | error.FileLocksNotSupported => unreachable, // We already got the lock. | |
| 1256 | error.Unexpected => unreachable, // Resource deallocation must succeed. | |
| 1257 | }; | |
| 1258 | } | |
| 1259 | } | |
| 1260 | ||
| 1261 | /// Attempts to obtain a lock, returning `true` if the lock is | |
| 1262 | /// obtained, and `false` if there was an existing incompatible lock held. | |
| 1263 | /// A process may hold only one type of lock (shared or exclusive) on | |
| 1264 | /// a file. When a process terminates in any way, the lock is released. | |
| 1265 | /// | |
| 1266 | /// Assumes the file is unlocked. | |
| 1267 | /// | |
| 1268 | /// TODO: integrate with async I/O | |
| 1269 | pub fn tryLock(file: File, l: Lock) LockError!bool { | |
| 1270 | if (is_windows) { | |
| 1271 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 1272 | const exclusive = switch (l) { | |
| 1273 | .none => return, | |
| 1274 | .shared => false, | |
| 1275 | .exclusive => true, | |
| 1276 | }; | |
| 1277 | windows.LockFile( | |
| 1278 | file.handle, | |
| 1279 | null, | |
| 1280 | null, | |
| 1281 | null, | |
| 1282 | &io_status_block, | |
| 1283 | &range_off, | |
| 1284 | &range_len, | |
| 1285 | null, | |
| 1286 | windows.TRUE, // non-blocking=true | |
| 1287 | @intFromBool(exclusive), | |
| 1288 | ) catch |err| switch (err) { | |
| 1289 | error.WouldBlock => return false, | |
| 1290 | else => |e| return e, | |
| 1291 | }; | |
| 1292 | } else { | |
| 1293 | posix.flock(file.handle, switch (l) { | |
| 1294 | .none => posix.LOCK.UN, | |
| 1295 | .shared => posix.LOCK.SH | posix.LOCK.NB, | |
| 1296 | .exclusive => posix.LOCK.EX | posix.LOCK.NB, | |
| 1297 | }) catch |err| switch (err) { | |
| 1298 | error.WouldBlock => return false, | |
| 1299 | else => |e| return e, | |
| 1300 | }; | |
| 1301 | } | |
| 1302 | return true; | |
| 1303 | } | |
| 1304 | ||
| 1305 | /// Assumes the file is already locked in exclusive mode. | |
| 1306 | /// Atomically modifies the lock to be in shared mode, without releasing it. | |
| 1307 | /// | |
| 1308 | /// TODO: integrate with async I/O | |
| 1309 | pub fn downgradeLock(file: File) LockError!void { | |
| 1310 | if (is_windows) { | |
| 1311 | // On Windows it works like a semaphore + exclusivity flag. To implement this | |
| 1312 | // function, we first obtain another lock in shared mode. This changes the | |
| 1313 | // exclusivity flag, but increments the semaphore to 2. So we follow up with | |
| 1314 | // an NtUnlockFile which decrements the semaphore but does not modify the | |
| 1315 | // exclusivity flag. | |
| 1316 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 1317 | windows.LockFile( | |
| 1318 | file.handle, | |
| 1319 | null, | |
| 1320 | null, | |
| 1321 | null, | |
| 1322 | &io_status_block, | |
| 1323 | &range_off, | |
| 1324 | &range_len, | |
| 1325 | null, | |
| 1326 | windows.TRUE, // non-blocking=true | |
| 1327 | windows.FALSE, // exclusive=false | |
| 1328 | ) catch |err| switch (err) { | |
| 1329 | error.WouldBlock => unreachable, // File was not locked in exclusive mode. | |
| 1330 | else => |e| return e, | |
| 1331 | }; | |
| 1332 | return windows.UnlockFile( | |
| 1333 | file.handle, | |
| 1334 | &io_status_block, | |
| 1335 | &range_off, | |
| 1336 | &range_len, | |
| 1337 | 0, | |
| 1338 | ) catch |err| switch (err) { | |
| 1339 | error.RangeNotLocked => unreachable, // File was not locked. | |
| 1340 | error.Unexpected => unreachable, // Resource deallocation must succeed. | |
| 1341 | }; | |
| 1342 | } else { | |
| 1343 | return posix.flock(file.handle, posix.LOCK.SH | posix.LOCK.NB) catch |err| switch (err) { | |
| 1344 | error.WouldBlock => unreachable, // File was not locked in exclusive mode. | |
| 1345 | else => |e| return e, | |
| 1346 | }; | |
| 1347 | } | |
| 1348 | } | |
| 1349 | ||
| 1350 | pub fn adaptToNewApi(file: File) Io.File { | |
| 1351 | return .{ .handle = file.handle }; | |
| 1352 | } | |
| 1353 | ||
| 1354 | pub fn adaptFromNewApi(file: Io.File) File { | |
| 1355 | return .{ .handle = file.handle }; | |
| 1356 | } |
lib/std/os/windows.zig-74| ... | ... | @@ -4248,80 +4248,6 @@ pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter |
| 4248 | 4248 | assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0); |
| 4249 | 4249 | } |
| 4250 | 4250 | |
| 4251 | pub const SetFileTimeError = error{Unexpected}; | |
| 4252 | ||
| 4253 | pub fn SetFileTime( | |
| 4254 | hFile: HANDLE, | |
| 4255 | lpCreationTime: ?*const FILETIME, | |
| 4256 | lpLastAccessTime: ?*const FILETIME, | |
| 4257 | lpLastWriteTime: ?*const FILETIME, | |
| 4258 | ) SetFileTimeError!void { | |
| 4259 | const rc = kernel32.SetFileTime(hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime); | |
| 4260 | if (rc == 0) { | |
| 4261 | switch (GetLastError()) { | |
| 4262 | else => |err| return unexpectedError(err), | |
| 4263 | } | |
| 4264 | } | |
| 4265 | } | |
| 4266 | ||
| 4267 | pub const LockFileError = error{ | |
| 4268 | SystemResources, | |
| 4269 | WouldBlock, | |
| 4270 | } || UnexpectedError; | |
| 4271 | ||
| 4272 | pub fn LockFile( | |
| 4273 | FileHandle: HANDLE, | |
| 4274 | Event: ?HANDLE, | |
| 4275 | ApcRoutine: ?*const IO_APC_ROUTINE, | |
| 4276 | ApcContext: ?*anyopaque, | |
| 4277 | IoStatusBlock: *IO_STATUS_BLOCK, | |
| 4278 | ByteOffset: *const LARGE_INTEGER, | |
| 4279 | Length: *const LARGE_INTEGER, | |
| 4280 | Key: ?*ULONG, | |
| 4281 | FailImmediately: BOOLEAN, | |
| 4282 | ExclusiveLock: BOOLEAN, | |
| 4283 | ) !void { | |
| 4284 | const rc = ntdll.NtLockFile( | |
| 4285 | FileHandle, | |
| 4286 | Event, | |
| 4287 | ApcRoutine, | |
| 4288 | ApcContext, | |
| 4289 | IoStatusBlock, | |
| 4290 | ByteOffset, | |
| 4291 | Length, | |
| 4292 | Key, | |
| 4293 | FailImmediately, | |
| 4294 | ExclusiveLock, | |
| 4295 | ); | |
| 4296 | switch (rc) { | |
| 4297 | .SUCCESS => return, | |
| 4298 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | |
| 4299 | .LOCK_NOT_GRANTED => return error.WouldBlock, | |
| 4300 | .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer | |
| 4301 | else => return unexpectedStatus(rc), | |
| 4302 | } | |
| 4303 | } | |
| 4304 | ||
| 4305 | pub const UnlockFileError = error{ | |
| 4306 | RangeNotLocked, | |
| 4307 | } || UnexpectedError; | |
| 4308 | ||
| 4309 | pub fn UnlockFile( | |
| 4310 | FileHandle: HANDLE, | |
| 4311 | IoStatusBlock: *IO_STATUS_BLOCK, | |
| 4312 | ByteOffset: *const LARGE_INTEGER, | |
| 4313 | Length: *const LARGE_INTEGER, | |
| 4314 | Key: ULONG, | |
| 4315 | ) !void { | |
| 4316 | const rc = ntdll.NtUnlockFile(FileHandle, IoStatusBlock, ByteOffset, Length, Key); | |
| 4317 | switch (rc) { | |
| 4318 | .SUCCESS => return, | |
| 4319 | .RANGE_NOT_LOCKED => return error.RangeNotLocked, | |
| 4320 | .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer | |
| 4321 | else => return unexpectedStatus(rc), | |
| 4322 | } | |
| 4323 | } | |
| 4324 | ||
| 4325 | 4251 | /// This is a workaround for the C backend until zig has the ability to put |
| 4326 | 4252 | /// C code in inline assembly. |
| 4327 | 4253 | extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque; |
lib/std/posix.zig+3-252| ... | ... | @@ -309,17 +309,7 @@ pub fn close(fd: fd_t) void { |
| 309 | 309 | } |
| 310 | 310 | } |
| 311 | 311 | |
| 312 | pub const FChmodError = error{ | |
| 313 | AccessDenied, | |
| 314 | PermissionDenied, | |
| 315 | InputOutput, | |
| 316 | SymLinkLoop, | |
| 317 | FileNotFound, | |
| 318 | SystemResources, | |
| 319 | ReadOnlyFileSystem, | |
| 320 | } || UnexpectedError; | |
| 321 | ||
| 322 | pub const FChmodAtError = FChmodError || error{ | |
| 312 | pub const FChmodAtError = std.Io.File.SetPermissionsError || error{ | |
| 323 | 313 | /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded |
| 324 | 314 | /// `PATH_MAX`. |
| 325 | 315 | NameTooLong, |
| ... | ... | @@ -335,7 +325,6 @@ pub const FChmodAtError = FChmodError || error{ |
| 335 | 325 | ProcessFdQuotaExceeded, |
| 336 | 326 | /// The procfs fallback was used but the system exceeded it open file limit. |
| 337 | 327 | SystemFdQuotaExceeded, |
| 338 | Canceled, | |
| 339 | 328 | }; |
| 340 | 329 | |
| 341 | 330 | /// Changes the `mode` of `path` relative to the directory referred to by |
| ... | ... | @@ -969,72 +958,6 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { |
| 969 | 958 | } |
| 970 | 959 | } |
| 971 | 960 | |
| 972 | pub const TruncateError = error{ | |
| 973 | FileTooBig, | |
| 974 | InputOutput, | |
| 975 | FileBusy, | |
| 976 | AccessDenied, | |
| 977 | PermissionDenied, | |
| 978 | NonResizable, | |
| 979 | } || UnexpectedError; | |
| 980 | ||
| 981 | /// Length must be positive when treated as an i64. | |
| 982 | pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { | |
| 983 | const signed_len: i64 = @bitCast(length); | |
| 984 | if (signed_len < 0) return error.FileTooBig; // avoid ambiguous EINVAL errors | |
| 985 | ||
| 986 | if (native_os == .windows) { | |
| 987 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 988 | const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{ | |
| 989 | .EndOfFile = signed_len, | |
| 990 | }; | |
| 991 | const rc = windows.ntdll.NtSetInformationFile( | |
| 992 | fd, | |
| 993 | &io_status_block, | |
| 994 | &eof_info, | |
| 995 | @sizeOf(windows.FILE.END_OF_FILE_INFORMATION), | |
| 996 | .EndOfFile, | |
| 997 | ); | |
| 998 | switch (rc) { | |
| 999 | .SUCCESS => return, | |
| 1000 | .INVALID_HANDLE => unreachable, // Handle not open for writing | |
| 1001 | .ACCESS_DENIED => return error.AccessDenied, | |
| 1002 | .USER_MAPPED_FILE => return error.AccessDenied, | |
| 1003 | .INVALID_PARAMETER => return error.FileTooBig, | |
| 1004 | else => return windows.unexpectedStatus(rc), | |
| 1005 | } | |
| 1006 | } | |
| 1007 | if (native_os == .wasi and !builtin.link_libc) { | |
| 1008 | switch (wasi.fd_filestat_set_size(fd, length)) { | |
| 1009 | .SUCCESS => return, | |
| 1010 | .INTR => unreachable, | |
| 1011 | .FBIG => return error.FileTooBig, | |
| 1012 | .IO => return error.InputOutput, | |
| 1013 | .PERM => return error.PermissionDenied, | |
| 1014 | .TXTBSY => return error.FileBusy, | |
| 1015 | .BADF => unreachable, // Handle not open for writing | |
| 1016 | .INVAL => return error.NonResizable, | |
| 1017 | .NOTCAPABLE => return error.AccessDenied, | |
| 1018 | else => |err| return unexpectedErrno(err), | |
| 1019 | } | |
| 1020 | } | |
| 1021 | ||
| 1022 | const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate; | |
| 1023 | while (true) { | |
| 1024 | switch (errno(ftruncate_sym(fd, signed_len))) { | |
| 1025 | .SUCCESS => return, | |
| 1026 | .INTR => continue, | |
| 1027 | .FBIG => return error.FileTooBig, | |
| 1028 | .IO => return error.InputOutput, | |
| 1029 | .PERM => return error.PermissionDenied, | |
| 1030 | .TXTBSY => return error.FileBusy, | |
| 1031 | .BADF => unreachable, // Handle not open for writing | |
| 1032 | .INVAL => return error.NonResizable, // This is returned for /dev/null for example. | |
| 1033 | else => |err| return unexpectedErrno(err), | |
| 1034 | } | |
| 1035 | } | |
| 1036 | } | |
| 1037 | ||
| 1038 | 961 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. |
| 1039 | 962 | /// |
| 1040 | 963 | /// Retries when interrupted by a signal. |
| ... | ... | @@ -1602,7 +1525,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O |
| 1602 | 1525 | .PERM => return error.PermissionDenied, |
| 1603 | 1526 | .EXIST => return error.PathAlreadyExists, |
| 1604 | 1527 | .BUSY => return error.DeviceBusy, |
| 1605 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 1528 | .OPNOTSUPP => return error.FileLocksUnsupported, | |
| 1606 | 1529 | .AGAIN => return error.WouldBlock, |
| 1607 | 1530 | .TXTBSY => return error.FileBusy, |
| 1608 | 1531 | .NXIO => return error.NoDevice, |
| ... | ... | @@ -2313,47 +2236,6 @@ pub fn getegid() gid_t { |
| 2313 | 2236 | return system.getegid(); |
| 2314 | 2237 | } |
| 2315 | 2238 | |
| 2316 | /// Test whether a file descriptor refers to a terminal. | |
| 2317 | pub fn isatty(handle: fd_t) bool { | |
| 2318 | if (native_os == .windows) { | |
| 2319 | if (fs.File.isCygwinPty(.{ .handle = handle })) | |
| 2320 | return true; | |
| 2321 | ||
| 2322 | var out: windows.DWORD = undefined; | |
| 2323 | return windows.kernel32.GetConsoleMode(handle, &out) != 0; | |
| 2324 | } | |
| 2325 | if (builtin.link_libc) { | |
| 2326 | return system.isatty(handle) != 0; | |
| 2327 | } | |
| 2328 | if (native_os == .wasi) { | |
| 2329 | var statbuf: wasi.fdstat_t = undefined; | |
| 2330 | const err = wasi.fd_fdstat_get(handle, &statbuf); | |
| 2331 | if (err != .SUCCESS) | |
| 2332 | return false; | |
| 2333 | ||
| 2334 | // A tty is a character device that we can't seek or tell on. | |
| 2335 | if (statbuf.fs_filetype != .CHARACTER_DEVICE) | |
| 2336 | return false; | |
| 2337 | if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL) | |
| 2338 | return false; | |
| 2339 | ||
| 2340 | return true; | |
| 2341 | } | |
| 2342 | if (native_os == .linux) { | |
| 2343 | while (true) { | |
| 2344 | var wsz: winsize = undefined; | |
| 2345 | const fd: usize = @bitCast(@as(isize, handle)); | |
| 2346 | const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); | |
| 2347 | switch (linux.errno(rc)) { | |
| 2348 | .SUCCESS => return true, | |
| 2349 | .INTR => continue, | |
| 2350 | else => return false, | |
| 2351 | } | |
| 2352 | } | |
| 2353 | } | |
| 2354 | return system.isatty(handle) != 0; | |
| 2355 | } | |
| 2356 | ||
| 2357 | 2239 | pub const SocketError = error{ |
| 2358 | 2240 | /// Permission to create a socket of the specified type and/or |
| 2359 | 2241 | /// pro‐tocol is denied. |
| ... | ... | @@ -3923,34 +3805,6 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { |
| 3923 | 3805 | } |
| 3924 | 3806 | } |
| 3925 | 3807 | |
| 3926 | pub const FlockError = error{ | |
| 3927 | WouldBlock, | |
| 3928 | ||
| 3929 | /// The kernel ran out of memory for allocating file locks | |
| 3930 | SystemResources, | |
| 3931 | ||
| 3932 | /// The underlying filesystem does not support file locks | |
| 3933 | FileLocksNotSupported, | |
| 3934 | } || UnexpectedError; | |
| 3935 | ||
| 3936 | /// Depending on the operating system `flock` may or may not interact with | |
| 3937 | /// `fcntl` locks made by other processes. | |
| 3938 | pub fn flock(fd: fd_t, operation: i32) FlockError!void { | |
| 3939 | while (true) { | |
| 3940 | const rc = system.flock(fd, operation); | |
| 3941 | switch (errno(rc)) { | |
| 3942 | .SUCCESS => return, | |
| 3943 | .BADF => unreachable, | |
| 3944 | .INTR => continue, | |
| 3945 | .INVAL => unreachable, // invalid parameters | |
| 3946 | .NOLCK => return error.SystemResources, | |
| 3947 | .AGAIN => return error.WouldBlock, // TODO: integrate with async instead of just returning an error | |
| 3948 | .OPNOTSUPP => return error.FileLocksNotSupported, | |
| 3949 | else => |err| return unexpectedErrno(err), | |
| 3950 | } | |
| 3951 | } | |
| 3952 | } | |
| 3953 | ||
| 3954 | 3808 | /// Spurious wakeups are possible and no precision of timing is guaranteed. |
| 3955 | 3809 | pub fn nanosleep(seconds: u64, nanoseconds: u64) void { |
| 3956 | 3810 | var req = timespec{ |
| ... | ... | @@ -4215,74 +4069,6 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?* |
| 4215 | 4069 | } |
| 4216 | 4070 | } |
| 4217 | 4071 | |
| 4218 | pub const FutimensError = error{ | |
| 4219 | /// times is NULL, or both nsec values are UTIME_NOW, and either: | |
| 4220 | /// * the effective user ID of the caller does not match the owner | |
| 4221 | /// of the file, the caller does not have write access to the | |
| 4222 | /// file, and the caller is not privileged (Linux: does not have | |
| 4223 | /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability); | |
| 4224 | /// or, | |
| 4225 | /// * the file is marked immutable (see chattr(1)). | |
| 4226 | AccessDenied, | |
| 4227 | ||
| 4228 | /// The caller attempted to change one or both timestamps to a value | |
| 4229 | /// other than the current time, or to change one of the timestamps | |
| 4230 | /// to the current time while leaving the other timestamp unchanged, | |
| 4231 | /// (i.e., times is not NULL, neither nsec field is UTIME_NOW, | |
| 4232 | /// and neither nsec field is UTIME_OMIT) and either: | |
| 4233 | /// * the caller's effective user ID does not match the owner of | |
| 4234 | /// file, and the caller is not privileged (Linux: does not have | |
| 4235 | /// the CAP_FOWNER capability); or, | |
| 4236 | /// * the file is marked append-only or immutable (see chattr(1)). | |
| 4237 | PermissionDenied, | |
| 4238 | ||
| 4239 | ReadOnlyFileSystem, | |
| 4240 | } || UnexpectedError; | |
| 4241 | ||
| 4242 | pub fn futimens(fd: fd_t, times: ?*const [2]timespec) FutimensError!void { | |
| 4243 | if (native_os == .wasi and !builtin.link_libc) { | |
| 4244 | // TODO WASI encodes `wasi.fstflags` to signify magic values | |
| 4245 | // similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore | |
| 4246 | // this here, but we should really handle it somehow. | |
| 4247 | const error_code = blk: { | |
| 4248 | if (times) |times_arr| { | |
| 4249 | const atim = times_arr[0].toTimestamp(); | |
| 4250 | const mtim = times_arr[1].toTimestamp(); | |
| 4251 | break :blk wasi.fd_filestat_set_times(fd, atim, mtim, .{ | |
| 4252 | .ATIM = true, | |
| 4253 | .MTIM = true, | |
| 4254 | }); | |
| 4255 | } | |
| 4256 | ||
| 4257 | break :blk wasi.fd_filestat_set_times(fd, 0, 0, .{ | |
| 4258 | .ATIM_NOW = true, | |
| 4259 | .MTIM_NOW = true, | |
| 4260 | }); | |
| 4261 | }; | |
| 4262 | switch (error_code) { | |
| 4263 | .SUCCESS => return, | |
| 4264 | .ACCES => return error.AccessDenied, | |
| 4265 | .PERM => return error.PermissionDenied, | |
| 4266 | .BADF => unreachable, // always a race condition | |
| 4267 | .FAULT => unreachable, | |
| 4268 | .INVAL => unreachable, | |
| 4269 | .ROFS => return error.ReadOnlyFileSystem, | |
| 4270 | else => |err| return unexpectedErrno(err), | |
| 4271 | } | |
| 4272 | } | |
| 4273 | ||
| 4274 | switch (errno(system.futimens(fd, times))) { | |
| 4275 | .SUCCESS => return, | |
| 4276 | .ACCES => return error.AccessDenied, | |
| 4277 | .PERM => return error.PermissionDenied, | |
| 4278 | .BADF => unreachable, // always a race condition | |
| 4279 | .FAULT => unreachable, | |
| 4280 | .INVAL => unreachable, | |
| 4281 | .ROFS => return error.ReadOnlyFileSystem, | |
| 4282 | else => |err| return unexpectedErrno(err), | |
| 4283 | } | |
| 4284 | } | |
| 4285 | ||
| 4286 | 4072 | pub const GetHostNameError = error{PermissionDenied} || UnexpectedError; |
| 4287 | 4073 | |
| 4288 | 4074 | pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { |
| ... | ... | @@ -5104,12 +4890,7 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t { |
| 5104 | 4890 | } |
| 5105 | 4891 | } |
| 5106 | 4892 | |
| 5107 | pub const SyncError = error{ | |
| 5108 | InputOutput, | |
| 5109 | NoSpaceLeft, | |
| 5110 | DiskQuota, | |
| 5111 | AccessDenied, | |
| 5112 | } || UnexpectedError; | |
| 4893 | pub const SyncError = std.Io.File.SyncError; | |
| 5113 | 4894 | |
| 5114 | 4895 | /// Write all pending file contents and metadata modifications to all filesystems. |
| 5115 | 4896 | pub fn sync() void { |
| ... | ... | @@ -5129,38 +4910,8 @@ pub fn syncfs(fd: fd_t) SyncError!void { |
| 5129 | 4910 | } |
| 5130 | 4911 | } |
| 5131 | 4912 | |
| 5132 | /// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem. | |
| 5133 | pub fn fsync(fd: fd_t) SyncError!void { | |
| 5134 | if (native_os == .windows) { | |
| 5135 | if (windows.kernel32.FlushFileBuffers(fd) != 0) | |
| 5136 | return; | |
| 5137 | switch (windows.GetLastError()) { | |
| 5138 | .SUCCESS => return, | |
| 5139 | .INVALID_HANDLE => unreachable, | |
| 5140 | .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time | |
| 5141 | .UNEXP_NET_ERR => return error.InputOutput, | |
| 5142 | else => return error.InputOutput, | |
| 5143 | } | |
| 5144 | } | |
| 5145 | const rc = system.fsync(fd); | |
| 5146 | switch (errno(rc)) { | |
| 5147 | .SUCCESS => return, | |
| 5148 | .BADF, .INVAL, .ROFS => unreachable, | |
| 5149 | .IO => return error.InputOutput, | |
| 5150 | .NOSPC => return error.NoSpaceLeft, | |
| 5151 | .DQUOT => return error.DiskQuota, | |
| 5152 | else => |err| return unexpectedErrno(err), | |
| 5153 | } | |
| 5154 | } | |
| 5155 | ||
| 5156 | 4913 | /// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata. |
| 5157 | 4914 | pub fn fdatasync(fd: fd_t) SyncError!void { |
| 5158 | if (native_os == .windows) { | |
| 5159 | return fsync(fd) catch |err| switch (err) { | |
| 5160 | SyncError.AccessDenied => return, // fdatasync doesn't promise that the access time was synced | |
| 5161 | else => return err, | |
| 5162 | }; | |
| 5163 | } | |
| 5164 | 4915 | const rc = system.fdatasync(fd); |
| 5165 | 4916 | switch (errno(rc)) { |
| 5166 | 4917 | .SUCCESS => return, |
lib/std/process/Child.zig+1-1| ... | ... | @@ -596,7 +596,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { |
| 596 | 596 | error.NoSpaceLeft => unreachable, |
| 597 | 597 | error.FileTooBig => unreachable, |
| 598 | 598 | error.DeviceBusy => unreachable, |
| 599 | error.FileLocksNotSupported => unreachable, | |
| 599 | error.FileLocksUnsupported => unreachable, | |
| 600 | 600 | error.BadPathName => unreachable, // Windows-only |
| 601 | 601 | error.WouldBlock => unreachable, |
| 602 | 602 | error.NetworkNotFound => unreachable, // Windows-only |
lib/std/std.zig+3| ... | ... | @@ -173,6 +173,9 @@ pub const Options = struct { |
| 173 | 173 | /// If this is `false`, then captured stack traces will always be empty, and attempts to write |
| 174 | 174 | /// stack traces will just print an error to the relevant `Io.Writer` and return. |
| 175 | 175 | allow_stack_tracing: bool = !@import("builtin").strip_debug_info, |
| 176 | ||
| 177 | /// Overrides `std.Io.File.Permissions`. | |
| 178 | FilePermissions: ?type = null, | |
| 176 | 179 | }; |
| 177 | 180 | |
| 178 | 181 | // This forces the start.zig file to be imported, and the comptime logic inside that |
lib/std/zig/system.zig+2-2| ... | ... | @@ -824,7 +824,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { |
| 824 | 824 | error.SharingViolation => return error.Unexpected, // Windows-only |
| 825 | 825 | error.NetworkNotFound => return error.Unexpected, // Windows-only |
| 826 | 826 | error.AntivirusInterference => return error.Unexpected, // Windows-only |
| 827 | error.FileLocksNotSupported => return error.Unexpected, // No lock requested. | |
| 827 | error.FileLocksUnsupported => return error.Unexpected, // No lock requested. | |
| 828 | 828 | error.NoSpaceLeft => return error.Unexpected, // read-only |
| 829 | 829 | error.PathAlreadyExists => return error.Unexpected, // read-only |
| 830 | 830 | error.DeviceBusy => return error.Unexpected, // read-only |
| ... | ... | @@ -1033,7 +1033,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ |
| 1033 | 1033 | error.SharingViolation => return error.Unexpected, |
| 1034 | 1034 | error.BadPathName => return error.Unexpected, |
| 1035 | 1035 | error.PipeBusy => return error.Unexpected, |
| 1036 | error.FileLocksNotSupported => return error.Unexpected, | |
| 1036 | error.FileLocksUnsupported => return error.Unexpected, | |
| 1037 | 1037 | error.FileBusy => return error.Unexpected, // opened without write permissions |
| 1038 | 1038 | error.AntivirusInterference => return error.Unexpected, // Windows-only error |
| 1039 | 1039 |