authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-04 16:03:33-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:07-08:00
log814480db7cd0ab1d8441919223a88ee411f54ea8
tree2bd58c06839d8af32caa507299abb254b04bc248
parentd1d2c37af26902f953b2b72335b326c4b01e3bb2

std: all File functions moved to std.Io


17 files changed, 2349 insertions(+), 2305 deletions(-)

CMakeLists.txt-1
...@@ -436,7 +436,6 @@ set(ZIG_STAGE2_SOURCES...@@ -436,7 +436,6 @@ set(ZIG_STAGE2_SOURCES
436 lib/std/fmt.zig436 lib/std/fmt.zig
437 lib/std/fmt/parse_float.zig437 lib/std/fmt/parse_float.zig
438 lib/std/fs.zig438 lib/std/fs.zig
439 lib/std/fs/File.zig
440 lib/std/fs/get_app_data_dir.zig439 lib/std/fs/get_app_data_dir.zig
441 lib/std/fs/path.zig440 lib/std/fs/path.zig
442 lib/std/hash.zig441 lib/std/hash.zig
lib/std/Io.zig+16-1
...@@ -679,11 +679,13 @@ pub const VTable = struct {...@@ -679,11 +679,13 @@ pub const VTable = struct {
679 dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,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 dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.RenameError!void,680 dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.RenameError!void,
681 dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,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 dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,682 dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,
684 dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void,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,
685686
686 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,687 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
688 fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,
687 fileClose: *const fn (?*anyopaque, File) void,689 fileClose: *const fn (?*anyopaque, File) void,
688 fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize,690 fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize,
689 fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize,691 fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize,
...@@ -694,6 +696,19 @@ pub const VTable = struct {...@@ -694,6 +696,19 @@ pub const VTable = struct {
694 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,696 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
695 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,697 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,
696 openSelfExe: *const fn (?*anyopaque, File.OpenFlags) File.OpenSelfExeError!File,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,
697712
698 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,713 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
699 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,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,9 +11,6 @@ const Allocator = std.mem.Allocator;
1111
12handle: Handle,12handle: Handle,
1313
14pub const Mode = Io.File.Mode;
15pub const default_mode: Mode = 0o755;
16
17pub const Entry = struct {14pub const Entry = struct {
18 name: []const u8,15 name: []const u8,
19 kind: File.Kind,16 kind: File.Kind,
...@@ -435,10 +432,10 @@ pub const PrevStatus = enum {...@@ -435,10 +432,10 @@ pub const PrevStatus = enum {
435432
436pub const UpdateFileError = File.OpenError;433pub const UpdateFileError = File.OpenError;
437434
438/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If435/// Check the file size, mtime, and permissions of `source_path` and `dest_path`. If
439/// they are equal, does nothing. Otherwise, atomically copies `source_path` to436/// they are equal, does nothing. Otherwise, atomically copies `source_path` to
440/// `dest_path`, creating the parent directory hierarchy as needed. The437/// `dest_path`, creating the parent directory hierarchy as needed. The
441/// destination file gains the mtime, atime, and mode of the source file so438/// destination file gains the mtime, atime, and permissions of the source file so
442/// that the next call to `updateFile` will not need a copy.439/// that the next call to `updateFile` will not need a copy.
443///440///
444/// Returns the previous status of the file before updating.441/// Returns the previous status of the file before updating.
...@@ -459,7 +456,7 @@ pub fn updateFile(...@@ -459,7 +456,7 @@ pub fn updateFile(
459 defer src_file.close(io);456 defer src_file.close(io);
460457
461 const src_stat = try src_file.stat(io);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 check_dest_stat: {460 check_dest_stat: {
464 const dest_stat = blk: {461 const dest_stat = blk: {
465 var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {462 var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {
...@@ -473,19 +470,19 @@ pub fn updateFile(...@@ -473,19 +470,19 @@ pub fn updateFile(
473470
474 if (src_stat.size == dest_stat.size and471 if (src_stat.size == dest_stat.size and
475 src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and472 src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and
476 actual_mode == dest_stat.mode)473 actual_permissions == dest_stat.permissions)
477 {474 {
478 return .fresh;475 return .fresh;
479 }476 }
480 }477 }
481478
482 if (std.fs.path.dirname(dest_path)) |dirname| {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 }
485482
486 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.483 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
487 var atomic_file = try std.fs.Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{484 var atomic_file = try std.fs.Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{
488 .mode = actual_mode,485 .permissions = actual_permissions,
489 .write_buffer = &buffer,486 .write_buffer = &buffer,
490 });487 });
491 defer atomic_file.deinit();488 defer atomic_file.deinit();
...@@ -555,17 +552,12 @@ pub const MakeError = error{...@@ -555,17 +552,12 @@ pub const MakeError = error{
555/// Related:552/// Related:
556/// * `makePath`553/// * `makePath`
557/// * `makeDirAbsolute`554/// * `makeDirAbsolute`
558pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void {555pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakeError!void {
559 return io.vtable.dirMake(io.userdata, dir, sub_path, default_mode);556 return io.vtable.dirMake(io.userdata, dir, sub_path, permissions);
560}557}
561558
562pub const MakePathError = MakeError || StatPathError;559pub const MakePathError = MakeError || StatPathError;
563560
564/// Same as `makePathMode` but passes `default_mode`.
565pub 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/// Creates parent directories as necessary to ensure `sub_path` exists as a directory.561/// Creates parent directories as necessary to ensure `sub_path` exists as a directory.
570///562///
571/// Returns success if the path already exists and is a directory.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,16 +579,16 @@ pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void {
587/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,579/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
588/// meaning a `sub_path` like "first/../second" will create both a `./first`580/// meaning a `sub_path` like "first/../second" will create both a `./first`
589/// and a `./second` directory.581/// and a `./second` directory.
590pub fn makePathMode(dir: Dir, io: Io, sub_path: []const u8, mode: Mode) MakePathError!void {582pub fn makePath(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakePathError!void {
591 _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, mode);583 _ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions);
592}584}
593585
594pub const MakePathStatus = enum { existed, created };586pub const MakePathStatus = enum { existed, created };
595587
596/// Same as `makePath` except returns whether the path already existed or was588/// Same as `makePath` except returns whether the path already existed or was
597/// successfully created.589/// successfully created.
598pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus {590pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakePathError!MakePathStatus {
599 return io.vtable.dirMakePath(io.userdata, dir, sub_path, default_mode);591 return io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions);
600}592}
601593
602pub const MakeOpenPathError = MakeError || OpenError || StatPathError;594pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
...@@ -609,8 +601,8 @@ pub const MakeOpenPathError = MakeError || OpenError || StatPathError;...@@ -609,8 +601,8 @@ pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
609/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).601/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
610/// On WASI, `sub_path` should be encoded as valid UTF-8.602/// On WASI, `sub_path` should be encoded as valid UTF-8.
611/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.603/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
612pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) MakeOpenPathError!Dir {604pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions, options: OpenOptions) MakeOpenPathError!Dir {
613 return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options);605 return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, permissions, options);
614}606}
615607
616pub const Stat = File.Stat;608pub const Stat = File.Stat;
...@@ -1453,8 +1445,8 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, sub_path: []const u8, kind_hint: File....@@ -1453,8 +1445,8 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, sub_path: []const u8, kind_hint: File.
1453}1445}
14541446
1455pub const CopyFileOptions = struct {1447pub const CopyFileOptions = struct {
1456 /// When this is `null` the mode is copied from the source file.1448 /// When this is `null` the permissions are copied from the source file.
1457 override_mode: ?File.Mode = null,1449 override_permissions: ?File.Permissions = null,
1458};1450};
14591451
1460pub const CopyFileError = File.OpenError || File.StatError ||1452pub const CopyFileError = File.OpenError || File.StatError ||
...@@ -1486,15 +1478,15 @@ pub fn copyFile(...@@ -1486,15 +1478,15 @@ pub fn copyFile(
1486 var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});1478 var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
1487 defer file_reader.file.close(io);1479 defer file_reader.file.close(io);
14881480
1489 const mode = options.override_mode orelse blk: {1481 const permissions = options.override_permissions orelse blk: {
1490 const st = try file_reader.file.stat(io);1482 const st = try file_reader.file.stat(io);
1491 file_reader.size = st.size;1483 file_reader.size = st.size;
1492 break :blk st.mode;1484 break :blk st.permissions;
1493 };1485 };
14941486
1495 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.1487 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
1496 var atomic_file = try dest_dir.atomicFile(io, dest_path, .{1488 var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
1497 .mode = mode,1489 .permissions = permissions,
1498 .write_buffer = &buffer,1490 .write_buffer = &buffer,
1499 });1491 });
1500 defer atomic_file.deinit(io);1492 defer atomic_file.deinit(io);
...@@ -1508,7 +1500,7 @@ pub fn copyFile(...@@ -1508,7 +1500,7 @@ pub fn copyFile(
1508}1500}
15091501
1510pub const AtomicFileOptions = struct {1502pub const AtomicFileOptions = struct {
1511 mode: File.Mode = File.default_mode,1503 permissions: File.Permissions = .default_file,
1512 make_path: bool = false,1504 make_path: bool = false,
1513 write_buffer: []u8,1505 write_buffer: []u8,
1514};1506};
...@@ -1530,13 +1522,14 @@ pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFil...@@ -1530,13 +1522,14 @@ pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFil
1530 else1522 else
1531 try parent.openDir(io, dirname, .{});1523 try parent.openDir(io, dirname, .{});
15321524
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 } else {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}
15381530
1539pub const SetModeError = File.SetModeError;1531pub const SetPermissionsError = File.SetPermissionsError;
1532pub const Permissions = File.Permissions;
15401533
1541/// Also known as "chmod".1534/// Also known as "chmod".
1542///1535///
...@@ -1544,8 +1537,8 @@ pub const SetModeError = File.SetModeError;...@@ -1544,8 +1537,8 @@ pub const SetModeError = File.SetModeError;
1544/// successfully, or must have the effective user ID matching the owner1537/// successfully, or must have the effective user ID matching the owner
1545/// of the directory. Additionally, the directory must have been opened1538/// of the directory. Additionally, the directory must have been opened
1546/// with `OpenOptions.iterate` set to `true`.1539/// with `OpenOptions.iterate` set to `true`.
1547pub fn setMode(dir: Dir, io: Io, new_mode: File.Mode) SetModeError!void {1540pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPermissionsError!void {
1548 return io.vtable.dirSetMode(io.userdata, dir, new_mode);1541 return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions);
1549}1542}
15501543
1551pub const SetOwnerError = File.SetOwnerError;1544pub const SetOwnerError = File.SetOwnerError;
...@@ -1561,9 +1554,31 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr...@@ -1561,9 +1554,31 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr
1561 return io.vtable.dirSetOwner(io.userdata, dir, owner, group);1554 return io.vtable.dirSetOwner(io.userdata, dir, owner, group);
1562}1555}
15631556
1564pub const SetPermissionsError = File.SetPermissionsError;1557pub const SetTimestampsError = File.SetTimestampsError;
1565pub const Permissions = File.Permissions;1558
1559pub 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.
1566pub 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}
15661576
1567pub fn setPermissions(dir: Dir, io: Io, permissions: Permissions) SetPermissionsError!void {1577/// Sets the accessed and modification timestamps of the provided path to the
1568 return io.vtable.dirSetPermissions(io.userdata, dir, permissions);1578/// current wall clock time.
1579///
1580/// The granularity that ultimately is stored depends on the combination of
1581/// operating system and file system.
1582pub 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,20 +11,15 @@ const Dir = std.Io.Dir;
1111
12handle: Handle,12handle: Handle,
1313
14pub const Reader = @import("File/Reader.zig");
15pub const Writer = @import("File/Writer.zig");
16pub const Atomic = @import("File/Atomic.zig");
17
14pub const Handle = std.posix.fd_t;18pub const Handle = std.posix.fd_t;
15pub const Mode = std.posix.mode_t;
16pub const INode = std.posix.ino_t;19pub const INode = std.posix.ino_t;
17pub const Uid = std.posix.uid_t;20pub const Uid = std.posix.uid_t;
18pub const Gid = std.posix.gid_t;21pub const Gid = std.posix.gid_t;
1922
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.
26pub const default_mode: Mode = if (Mode == u0) 0 else 0o666;
27
28pub const Kind = enum {23pub const Kind = enum {
29 block_device,24 block_device,
30 character_device,25 character_device,
...@@ -53,8 +48,7 @@ pub const Stat = struct {...@@ -53,8 +48,7 @@ pub const Stat = struct {
53 /// is unique to each filesystem.48 /// is unique to each filesystem.
54 inode: INode,49 inode: INode,
55 size: u64,50 size: u64,
56 /// This is available on POSIX systems and is always 0 otherwise.51 permissions: Permissions,
57 mode: Mode,
58 kind: Kind,52 kind: Kind,
59 /// Last access time in nanoseconds, relative to UTC 1970-01-01.53 /// Last access time in nanoseconds, relative to UTC 1970-01-01.
60 atime: Io.Timestamp,54 atime: Io.Timestamp,
...@@ -103,11 +97,6 @@ pub const Lock = enum {...@@ -103,11 +97,6 @@ pub const Lock = enum {
103 exclusive,97 exclusive,
104};98};
10599
106pub const LockError = error{
107 SystemResources,
108 FileLocksNotSupported,
109} || Io.UnexpectedError;
110
111pub const OpenFlags = struct {100pub const OpenFlags = struct {
112 mode: OpenMode = .read_only,101 mode: OpenMode = .read_only,
113102
...@@ -200,9 +189,7 @@ pub const CreateFlags = struct {...@@ -200,9 +189,7 @@ pub const CreateFlags = struct {
200 /// is available to proceed.189 /// is available to proceed.
201 lock_nonblocking: bool = false,190 lock_nonblocking: bool = false,
202191
203 /// For POSIX systems this is the file system mode the file will192 permissions: Permissions = .default_file,
204 /// be created with. On other systems this is always 0.
205 mode: Mode = default_mode,
206};193};
207194
208pub const OpenError = error{195pub const OpenError = error{
...@@ -251,7 +238,7 @@ pub const OpenError = error{...@@ -251,7 +238,7 @@ pub const OpenError = error{
251 /// The path already exists and the `CREAT` and `EXCL` flags were provided.238 /// The path already exists and the `CREAT` and `EXCL` flags were provided.
252 PathAlreadyExists,239 PathAlreadyExists,
253 DeviceBusy,240 DeviceBusy,
254 FileLocksNotSupported,241 FileLocksUnsupported,
255 /// One of these three things:242 /// One of these three things:
256 /// * pathname refers to an executable image which is currently being243 /// * pathname refers to an executable image which is currently being
257 /// executed and write access was requested.244 /// executed and write access was requested.
...@@ -269,7 +256,216 @@ pub fn close(file: File, io: Io) void {...@@ -269,7 +256,216 @@ pub fn close(file: File, io: Io) void {
269 return io.vtable.fileClose(io.userdata, file);256 return io.vtable.fileClose(io.userdata, file);
270}257}
271258
272pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || std.posix.FlockError;259pub 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.
271pub 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`.
280pub fn isTty(file: File, io: Io) bool {
281 return io.vtable.fileIsTty(io.userdata, file);
282}
283
284pub const EnableAnsiEscapeCodesError = error{} || Io.Cancelable || Io.UnexpectedError;
285
286pub 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.
292pub fn supportsAnsiEscapeCodes(file: File, io: Io) Io.Cancelable!bool {
293 return io.vtable.fileSupportsAnsiEscapeCodes(io.userdata, file);
294}
295
296pub 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.
308pub fn setLength(file: File, io: Io, new_length: u64) SetLengthError!void {
309 return io.vtable.fileSetLength(io.userdata, file, new_length);
310}
311
312pub 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.
317pub fn length(file: File, io: Io) LengthError!u64 {
318 return io.vtable.fileLength(io.userdata, file);
319}
320
321pub 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.
336pub fn setPermissions(file: File, io: Io, new_permissions: Permissions) SetPermissionsError!void {
337 return io.vtable.fileSetPermissions(io.userdata, file, new_permissions);
338}
339
340pub 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.
356pub 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`.
365pub 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
425pub 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.
450pub 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.
464pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {
465 return io.vtable.fileSetTimestampsNow(io.userdata, file);
466}
467
468pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || LockError;
273469
274pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File {470pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File {
275 return io.vtable.openSelfExe(io.userdata, flags);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,6 +505,12 @@ pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenErr
309 return Io.Dir.cwd().openFile(io, absolute_path, flags);505 return Io.Dir.cwd().openFile(io, absolute_path, flags);
310}506}
311507
508pub 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/// Defaults to positional reading; falls back to streaming.514/// Defaults to positional reading; falls back to streaming.
313///515///
314/// Positional is more threadsafe, since the global seek position is not516/// 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,509 +526,54 @@ pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
324 return .initStreaming(file, io, buffer);526 return .initStreaming(file, io, buffer);
325}527}
326528
327pub const SeekError = error{529/// Defaults to positional reading; falls back to streaming.
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`).
340///530///
341/// Fulfills the `Io.Reader` interface.531/// Positional is more threadsafe, since the global seek position is not
342pub const Reader = struct {532/// affected.
343 io: Io,533pub fn writer(file: File, io: Io, buffer: []u8) Writer {
344 file: File,534 return .init(file, io, buffer);
345 err: ?Error = null,535}
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
724pub 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 }
778536
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.
540pub fn writerStreaming(file: File, io: Io, buffer: []u8) Writer {
541 return .initStreaming(file, io, buffer);
542}
780543
781 pub fn flush(af: *Atomic) FlushError!void {544pub const LockError = error{
782 af.file_writer.interface.flush() catch |err| switch (err) {545 SystemResources,
783 error.WriteFailed => return af.file_writer.err.?,546 FileLocksUnsupported,
784 };547} || Io.Cancelable || Io.UnexpectedError;
785 }
786548
787 pub const RenameIntoPlaceError = Dir.RenameError;549/// Blocks when an incompatible lock is held by another process. A process may
788550/// hold only one type of lock (shared or exclusive) on a file. When a process
789 /// On Windows, this function introduces a period of time where some file551/// terminates in any way, the lock is released.
790 /// system operations on the destination file will result in552///
791 /// `error.AccessDenied`, including rename operations (such as the one used in553/// Assumes the file is unlocked.
792 /// this function).554pub fn lock(file: File, io: Io, l: Lock) LockError!void {
793 pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void {555 return io.vtable.fileLock(io.userdata, file, l);
794 const io = af.file_writer.io;556}
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 }
804557
805 pub const FinishError = FlushError || RenameIntoPlaceError;558/// Assumes the file is locked.
559pub fn unlock(file: File, io: Io) void {
560 return io.vtable.fileUnlock(io.userdata, file);
561}
806562
807 /// Combination of `flush` followed by `renameIntoPlace`.563/// Attempts to obtain a lock, returning `true` if the lock is obtained, and
808 pub fn finish(af: *Atomic) FinishError!void {564/// `false` if there was an existing incompatible lock held. A process may hold
809 try af.flush();565/// only one type of lock (shared or exclusive) on a file. When a process
810 try af.renameIntoPlace();566/// terminates in any way, the lock is released.
811 }567///
812};568/// Assumes the file is unlocked.
569pub fn tryLock(file: File, io: Io, l: Lock) LockError!bool {
570 return io.vtable.fileTryLock(io.userdata, file, l);
571}
813572
814pub const SetModeError = error{573pub const DowngradeLockError = Io.Cancelable || Io.UnexpectedError;
815 AccessDenied,
816 PermissionDenied,
817 InputOutput,
818 SymLinkLoop,
819 FileNotFound,
820 SystemResources,
821 ReadOnlyFileSystem,
822} || Io.Cancelable || Io.UnexpectedError;
823574
824pub const SetOwnerError = error{575/// Assumes the file is already locked in exclusive mode.
825 AccessDenied,576/// Atomically modifies the lock to be in shared mode, without releasing it.
826 PermissionDenied,577pub fn downgradeLock(file: File, io: Io) LockError!void {
827 InputOutput,578 return io.vtable.fileDowngradeLock(io.userdata, file);
828 SymLinkLoop,579}
829 FileNotFound,
830 SystemResources,
831 ReadOnlyFileSystem,
832} || Io.Cancelable || Io.UnexpectedError;
lib/std/Io/File/Atomic.zig created+99
...@@ -0,0 +1,99 @@
1const Atomic = @This();
2
3const std = @import("../../std.zig");
4const Io = std.Io;
5const File = std.Io.File;
6const Dir = std.Io.Dir;
7const assert = std.debug.assert;
8
9file_writer: File.Writer,
10random_integer: u64,
11dest_basename: []const u8,
12file_open: bool,
13file_exists: bool,
14close_dir_on_deinit: bool,
15dir: Dir,
16
17pub const InitError = File.OpenError;
18
19/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
20pub 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().
48pub 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
66pub const FlushError = File.WriteError;
67
68pub 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
74pub 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).
80pub 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
93pub const FinishError = FlushError || RenameIntoPlaceError;
94
95/// Combination of `flush` followed by `renameIntoPlace`.
96pub 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.
10const Reader = @This();
11
12const std = @import("../../std.zig");
13const Io = std.Io;
14const File = std.Io.File;
15const assert = std.debug.assert;
16
17io: Io,
18file: File,
19err: ?Error = null,
20mode: Reader.Mode = .positional,
21/// Tracks the true seek position in the file. To obtain the logical
22/// position, use `logicalPos`.
23pos: u64 = 0,
24size: ?u64 = null,
25size_err: ?SizeError = null,
26seek_err: ?Reader.SeekError = null,
27interface: Io.Reader,
28
29pub 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
53pub 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
58pub 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
66pub 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
93pub 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
106pub fn init(file: File, io: Io, buffer: []u8) Reader {
107 return .{
108 .io = io,
109 .file = file,
110 .interface = initInterface(buffer),
111 };
112}
113
114pub 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.
126pub 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
137pub 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
156pub 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.
186pub 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
206pub fn logicalPos(r: *const Reader) u64 {
207 return r.pos - r.interface.bufferedLen();
208}
209
210fn 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.
220const max_buffers_len = 16;
221
222fn 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
227pub 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
254fn 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
263fn 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
299fn 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
321fn 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.
391pub 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 @@
1const Writer = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6
7const std = @import("../../std.zig");
8const Io = std.Io;
9const File = std.Io.File;
10const assert = std.debug.assert;
11const windows = std.os.windows;
12const posix = std.posix;
13
14file: File,
15err: ?File.WriteError = null,
16mode: 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.
19pos: u64 = 0,
20sendfile_err: ?SendfileError = null,
21copy_file_range_err: ?CopyFileRangeError = null,
22fcopyfile_err: ?FcopyfileError = null,
23seek_err: ?Writer.SeekError = null,
24interface: Io.Writer,
25
26pub const Mode = File.Reader.Mode;
27
28pub const SendfileError = error{
29 UnsupportedOperation,
30 SystemResources,
31 InputOutput,
32 BrokenPipe,
33 WouldBlock,
34 Unexpected,
35};
36
37pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError;
38
39pub const FcopyfileError = error{
40 OperationNotSupported,
41 OutOfMemory,
42 Unexpected,
43};
44
45pub 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.
49const max_buffers_len = 16;
50
51pub 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.
62pub fn initStreaming(file: File, buffer: []u8) Writer {
63 return .{
64 .file = file,
65 .interface = initInterface(buffer),
66 .mode = .streaming,
67 };
68}
69
70pub fn initInterface(buffer: []u8) Io.Writer {
71 return .{
72 .vtable = &.{
73 .drain = drain,
74 .sendFile = sendFile,
75 },
76 .buffer = buffer,
77 };
78}
79
80pub 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
92pub 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
241pub 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
508fn 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
518pub 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.
524pub 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
542pub 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.
551pub 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,7 +1093,7 @@ fn createFile(
1093 .PERM => return error.PermissionDenied,1093 .PERM => return error.PermissionDenied,
1094 .EXIST => return error.PathAlreadyExists,1094 .EXIST => return error.PathAlreadyExists,
1095 .BUSY => return error.DeviceBusy,1095 .BUSY => return error.DeviceBusy,
1096 .OPNOTSUPP => return error.FileLocksNotSupported,1096 .OPNOTSUPP => return error.FileLocksUnsupported,
1097 .AGAIN => return error.WouldBlock,1097 .AGAIN => return error.WouldBlock,
1098 .TXTBSY => return error.FileBusy,1098 .TXTBSY => return error.FileBusy,
1099 .NXIO => return error.NoDevice,1099 .NXIO => return error.NoDevice,
...@@ -1201,7 +1201,7 @@ fn fileOpen(...@@ -1201,7 +1201,7 @@ fn fileOpen(
1201 .PERM => return error.PermissionDenied,1201 .PERM => return error.PermissionDenied,
1202 .EXIST => return error.PathAlreadyExists,1202 .EXIST => return error.PathAlreadyExists,
1203 .BUSY => return error.DeviceBusy,1203 .BUSY => return error.DeviceBusy,
1204 .OPNOTSUPP => return error.FileLocksNotSupported,1204 .OPNOTSUPP => return error.FileLocksUnsupported,
1205 .AGAIN => return error.WouldBlock,1205 .AGAIN => return error.WouldBlock,
1206 .TXTBSY => return error.FileBusy,1206 .TXTBSY => return error.FileBusy,
1207 .NXIO => return error.NoDevice,1207 .NXIO => return error.NoDevice,
lib/std/Io/Threaded.zig+943-53
...@@ -668,9 +668,10 @@ pub fn io(t: *Threaded) Io {...@@ -668,9 +668,10 @@ pub fn io(t: *Threaded) Io {
668 .dirRename = dirRename,668 .dirRename = dirRename,
669 .dirSymLink = dirSymLink,669 .dirSymLink = dirSymLink,
670 .dirReadLink = dirReadLink,670 .dirReadLink = dirReadLink,
671 .dirSetMode = dirSetMode,
672 .dirSetOwner = dirSetOwner,671 .dirSetOwner = dirSetOwner,
673 .dirSetPermissions = dirSetPermissions,672 .dirSetPermissions = dirSetPermissions,
673 .dirSetTimestamps = dirSetTimestamps,
674 .dirSetTimestampsNow = dirSetTimestampsNow,
674675
675 .fileStat = fileStat,676 .fileStat = fileStat,
676 .fileClose = fileClose,677 .fileClose = fileClose,
...@@ -681,6 +682,19 @@ pub fn io(t: *Threaded) Io {...@@ -681,6 +682,19 @@ pub fn io(t: *Threaded) Io {
681 .fileSeekBy = fileSeekBy,682 .fileSeekBy = fileSeekBy,
682 .fileSeekTo = fileSeekTo,683 .fileSeekTo = fileSeekTo,
683 .openSelfExe = openSelfExe,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,
684698
685 .now = now,699 .now = now,
686 .sleep = sleep,700 .sleep = sleep,
...@@ -774,9 +788,10 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -774,9 +788,10 @@ pub fn ioBasic(t: *Threaded) Io {
774 .dirRename = dirRename,788 .dirRename = dirRename,
775 .dirSymLink = dirSymLink,789 .dirSymLink = dirSymLink,
776 .dirReadLink = dirReadLink,790 .dirReadLink = dirReadLink,
777 .dirSetMode = dirSetMode,
778 .dirSetOwner = dirSetOwner,791 .dirSetOwner = dirSetOwner,
779 .dirSetPermissions = dirSetPermissions,792 .dirSetPermissions = dirSetPermissions,
793 .dirSetTimestamps = dirSetTimestamps,
794 .dirSetTimestampsNow = dirSetTimestampsNow,
780795
781 .fileStat = fileStat,796 .fileStat = fileStat,
782 .fileClose = fileClose,797 .fileClose = fileClose,
...@@ -787,6 +802,19 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -787,6 +802,19 @@ pub fn ioBasic(t: *Threaded) Io {
787 .fileSeekBy = fileSeekBy,802 .fileSeekBy = fileSeekBy,
788 .fileSeekTo = fileSeekTo,803 .fileSeekTo = fileSeekTo,
789 .openSelfExe = openSelfExe,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,
790818
791 .now = now,819 .now = now,
792 .sleep = sleep,820 .sleep = sleep,
...@@ -831,6 +859,7 @@ const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fs...@@ -831,6 +859,7 @@ const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fs
831const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;859const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
832const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;860const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
833const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;861const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
862const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate;
834863
835/// Trailing data:864/// Trailing data:
836/// 1. context865/// 1. context
...@@ -1956,7 +1985,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi...@@ -1956,7 +1985,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi
1956 // size provided. This is treated as success because the type of variable-length information that this would be relevant for1985 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
1957 // (name, volume name, etc) we don't care about.1986 // (name, volume name, etc) we don't care about.
1958 .BUFFER_OVERFLOW => {},1987 .BUFFER_OVERFLOW => {},
1959 .INVALID_PARAMETER => unreachable,1988 .INVALID_PARAMETER => |err| return windows.statusBug(err),
1960 .ACCESS_DENIED => return error.AccessDenied,1989 .ACCESS_DENIED => return error.AccessDenied,
1961 else => return windows.unexpectedStatus(rc),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,7 +2000,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi
1971 .SUCCESS => {},2000 .SUCCESS => {},
1972 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors2001 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
1973 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e2002 // 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 .ACCESS_DENIED => return error.AccessDenied,2004 .ACCESS_DENIED => return error.AccessDenied,
1976 else => return windows.unexpectedStatus(rc),2005 else => return windows.unexpectedStatus(rc),
1977 }2006 }
...@@ -2279,7 +2308,7 @@ fn dirCreateFilePosix(...@@ -2279,7 +2308,7 @@ fn dirCreateFilePosix(
2279 .PERM => return error.PermissionDenied,2308 .PERM => return error.PermissionDenied,
2280 .EXIST => return error.PathAlreadyExists,2309 .EXIST => return error.PathAlreadyExists,
2281 .BUSY => return error.DeviceBusy,2310 .BUSY => return error.DeviceBusy,
2282 .OPNOTSUPP => return error.FileLocksNotSupported,2311 .OPNOTSUPP => return error.FileLocksUnsupported,
2283 .AGAIN => return error.WouldBlock,2312 .AGAIN => return error.WouldBlock,
2284 .TXTBSY => return error.FileBusy,2313 .TXTBSY => return error.FileBusy,
2285 .NXIO => return error.NoDevice,2314 .NXIO => return error.NoDevice,
...@@ -2318,7 +2347,7 @@ fn dirCreateFilePosix(...@@ -2318,7 +2347,7 @@ fn dirCreateFilePosix(
2318 .INVAL => |err| return errnoBug(err), // invalid parameters2347 .INVAL => |err| return errnoBug(err), // invalid parameters
2319 .NOLCK => return error.SystemResources,2348 .NOLCK => return error.SystemResources,
2320 .AGAIN => return error.WouldBlock,2349 .AGAIN => return error.WouldBlock,
2321 .OPNOTSUPP => return error.FileLocksNotSupported,2350 .OPNOTSUPP => return error.FileLocksUnsupported,
2322 else => |err| return posix.unexpectedErrno(err),2351 else => |err| return posix.unexpectedErrno(err),
2323 }2352 }
2324 },2353 },
...@@ -2588,7 +2617,7 @@ fn dirOpenFilePosix(...@@ -2588,7 +2617,7 @@ fn dirOpenFilePosix(
2588 .PERM => return error.PermissionDenied,2617 .PERM => return error.PermissionDenied,
2589 .EXIST => return error.PathAlreadyExists,2618 .EXIST => return error.PathAlreadyExists,
2590 .BUSY => return error.DeviceBusy,2619 .BUSY => return error.DeviceBusy,
2591 .OPNOTSUPP => return error.FileLocksNotSupported,2620 .OPNOTSUPP => return error.FileLocksUnsupported,
2592 .AGAIN => return error.WouldBlock,2621 .AGAIN => return error.WouldBlock,
2593 .TXTBSY => return error.FileBusy,2622 .TXTBSY => return error.FileBusy,
2594 .NXIO => return error.NoDevice,2623 .NXIO => return error.NoDevice,
...@@ -2626,7 +2655,7 @@ fn dirOpenFilePosix(...@@ -2626,7 +2655,7 @@ fn dirOpenFilePosix(
2626 .INVAL => |err| return errnoBug(err), // invalid parameters2655 .INVAL => |err| return errnoBug(err), // invalid parameters
2627 .NOLCK => return error.SystemResources,2656 .NOLCK => return error.SystemResources,
2628 .AGAIN => return error.WouldBlock,2657 .AGAIN => return error.WouldBlock,
2629 .OPNOTSUPP => return error.FileLocksNotSupported,2658 .OPNOTSUPP => return error.FileLocksUnsupported,
2630 else => |err| return posix.unexpectedErrno(err),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,12 +3573,12 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, re
3544 );3573 );
3545 switch (rc) {3574 switch (rc) {
3546 .SUCCESS => {},3575 .SUCCESS => {},
3547 .OBJECT_NAME_INVALID => unreachable,3576 .OBJECT_NAME_INVALID => |err| return w.statusBug(err),
3548 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,3577 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3549 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,3578 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
3550 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found3579 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
3551 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't3580 .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 .FILE_IS_A_DIRECTORY => return error.IsDir,3582 .FILE_IS_A_DIRECTORY => return error.IsDir,
3554 .NOT_A_DIRECTORY => return error.NotDir,3583 .NOT_A_DIRECTORY => return error.NotDir,
3555 .SHARING_VIOLATION => return error.FileBusy,3584 .SHARING_VIOLATION => return error.FileBusy,
...@@ -3621,7 +3650,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, re...@@ -3621,7 +3650,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, re
3621 switch (rc) {3650 switch (rc) {
3622 .SUCCESS => {},3651 .SUCCESS => {},
3623 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,3652 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
3624 .INVALID_PARAMETER => unreachable,3653 .INVALID_PARAMETER => |err| return w.statusBug(err),
3625 .CANNOT_DELETE => return error.AccessDenied,3654 .CANNOT_DELETE => return error.AccessDenied,
3626 .MEDIA_WRITE_PROTECTED => return error.AccessDenied,3655 .MEDIA_WRITE_PROTECTED => return error.AccessDenied,
3627 .ACCESS_DENIED => return error.AccessDenied,3656 .ACCESS_DENIED => return error.AccessDenied,
...@@ -3835,9 +3864,9 @@ fn dirRenameWindows(...@@ -3835,9 +3864,9 @@ fn dirRenameWindows(
38353864
3836 switch (rc) {3865 switch (rc) {
3837 .SUCCESS => {},3866 .SUCCESS => {},
3838 .INVALID_HANDLE => unreachable,3867 .INVALID_HANDLE => |err| return w.statusBug(err),
3839 .INVALID_PARAMETER => unreachable,3868 .INVALID_PARAMETER => |err| return w.statusBug(err),
3840 .OBJECT_PATH_SYNTAX_BAD => unreachable,3869 .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err),
3841 .ACCESS_DENIED => return error.AccessDenied,3870 .ACCESS_DENIED => return error.AccessDenied,
3842 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,3871 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3843 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,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,25 +4349,50 @@ fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, bu
4320 }4349 }
4321}4350}
43224351
4323const dirSetMode = switch (native_os) {4352const dirSetPermissions = switch (native_os) {
4324 .windows => dirSetModeUnsupported,4353 .windows => dirSetPermissionsWindows,
4325 else => dirSetModePosix,4354 else => dirSetPermissionsPosix,
4355};
4356
4357fn 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
4365fn 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
4371const dirSetOwner = switch (native_os) {
4372 .windows => dirSetOwnerUnsupported,
4373 else => dirSetOwnerPosix,
4326};4374};
43274375
4328fn dirSetModeUnsupported(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void {4376fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void {
4329 _ = userdata;4377 _ = userdata;
4330 _ = dir;4378 _ = dir;
4331 _ = new_mode;4379 _ = owner;
4380 _ = group;
4332 return error.Unexpected;4381 return error.Unexpected;
4333}4382}
43344383
4335fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void {4384fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void {
4336 const t: *Threaded = @ptrCast(@alignCast(userdata));4385 const t: *Threaded = @ptrCast(@alignCast(userdata));
4337 const current_thread = Thread.getCurrent(t);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}
43384391
4392fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) Io.File.SetOwnerError!void {
4339 try current_thread.beginSyscall();4393 try current_thread.beginSyscall();
4340 while (true) {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 .SUCCESS => return current_thread.endSyscall(),4396 .SUCCESS => return current_thread.endSyscall(),
4343 .CANCELED => return current_thread.endSyscallCanceled(),4397 .CANCELED => return current_thread.endSyscallCanceled(),
4344 .INTR => {4398 .INTR => {
...@@ -4348,7 +4402,7 @@ fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io...@@ -4348,7 +4402,7 @@ fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io
4348 else => |e| {4402 else => |e| {
4349 current_thread.endSyscall();4403 current_thread.endSyscall();
4350 switch (e) {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 .FAULT => |err| return errnoBug(err),4406 .FAULT => |err| return errnoBug(err),
4353 .INVAL => |err| return errnoBug(err),4407 .INVAL => |err| return errnoBug(err),
4354 .ACCES => return error.AccessDenied,4408 .ACCES => return error.AccessDenied,
...@@ -4366,28 +4420,386 @@ fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io...@@ -4366,28 +4420,386 @@ fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io
4366 }4420 }
4367}4421}
43684422
4369const dirSetOwner = switch (native_os) {4423const fileSync = switch (native_os) {
4370 .windows => dirSetOwnerUnsupported,4424 .windows => fileSyncWindows,
4371 else => dirSetOwnerPosix,4425 else => fileSyncPosix,
4372};4426};
43734427
4374fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void {4428fn fileSyncWindows(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void {
4375 _ = userdata;4429 const t: *Threaded = @ptrCast(@alignCast(userdata));
4376 _ = dir;4430 const current_thread = Thread.getCurrent(t);
4377 _ = owner;4431
4378 _ = group;4432 try current_thread.checkCancel();
4379 return error.Unexpected;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}
43814445
4382fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void {4446fn 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
4474fn 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
4480fn 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
4553fn 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
4588fn 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
4594fn 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
4616fn 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
4670fn 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
4754fn 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 const t: *Threaded = @ptrCast(@alignCast(userdata));4759 const t: *Threaded = @ptrCast(@alignCast(userdata));
4384 const current_thread = Thread.getCurrent(t);4760 const current_thread = Thread.getCurrent(t);
4385 const uid = owner orelse ~@as(posix.uid_t, 0);4761 const uid = owner orelse ~@as(posix.uid_t, 0);
4386 const gid = group orelse ~@as(posix.gid_t, 0);4762 const gid = group orelse ~@as(posix.gid_t, 0);
4763 return setOwnerPosix(current_thread, file.handle, uid, gid);
4764}
4765
4766fn 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}
43874798
4799fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode_t) Io.File.SetPermissionsError!void {
4388 try current_thread.beginSyscall();4800 try current_thread.beginSyscall();
4389 while (true) {4801 while (true) {
4390 switch (posix.errno(posix.system.fchown(dir.handle, uid, gid))) {4802 switch (posix.errno(posix.system.fchmod(fd, mode))) {
4391 .SUCCESS => return current_thread.endSyscall(),4803 .SUCCESS => return current_thread.endSyscall(),
4392 .CANCELED => return current_thread.endSyscallCanceled(),4804 .CANCELED => return current_thread.endSyscallCanceled(),
4393 .INTR => {4805 .INTR => {
...@@ -4397,7 +4809,7 @@ fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, gro...@@ -4397,7 +4809,7 @@ fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, gro
4397 else => |e| {4809 else => |e| {
4398 current_thread.endSyscall();4810 current_thread.endSyscall();
4399 switch (e) {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 .FAULT => |err| return errnoBug(err),4813 .FAULT => |err| return errnoBug(err),
4402 .INVAL => |err| return errnoBug(err),4814 .INVAL => |err| return errnoBug(err),
4403 .ACCES => return error.AccessDenied,4815 .ACCES => return error.AccessDenied,
...@@ -4415,31 +4827,509 @@ fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, gro...@@ -4415,31 +4827,509 @@ fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, gro
4415 }4827 }
4416}4828}
44174829
4418const dirSetPermissions = switch (native_os) {4830fn dirSetTimestamps(
4419 .windows => dirSetPermissionsWindows,
4420 else => dirSetPermissionsPosix,
4421};
4422
4423fn dirSetPermissionsWindows(
4424 userdata: ?*anyopaque,4831 userdata: ?*anyopaque,
4425 dir: Io.Dir,4832 dir: Io.Dir,
4426 permissions: Io.Dir.Permissions,4833 sub_path: []const u8,
4427) Io.Dir.SetPermissionsError!void {4834 last_accessed: Io.Timestamp,
4428 _ = userdata;4835 last_modified: Io.Timestamp,
4429 _ = dir;4836 options: Io.File.SetTimestampsOptions,
4430 _ = permissions;4837) Io.File.SetTimestampsError!void {
4431 @panic("TODO");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}
44334883
4434fn dirSetPermissionsPosix(4884fn dirSetTimestampsNow(
4435 userdata: ?*anyopaque,4885 userdata: ?*anyopaque,
4436 dir: Io.Dir,4886 dir: Io.Dir,
4437 permissions: Io.Dir.Permissions,4887 sub_path: []const u8,
4438) Io.Dir.SetPermissionsError!void {4888 options: Io.File.SetTimestampsOptions,
4439 _ = userdata;4889) Io.File.SetTimestampsError!void {
4440 _ = dir;4890 const t: *Threaded = @ptrCast(@alignCast(userdata));
4441 _ = permissions;4891 const current_thread = Thread.getCurrent(t);
4442 @panic("TODO");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
4931fn 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
5017fn 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
5079const windows_lock_range_off: windows.LARGE_INTEGER = 0;
5080const windows_lock_range_len: windows.LARGE_INTEGER = 1;
5081
5082fn 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
5144fn 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
5212fn 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
5260fn 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}
44445334
4445fn dirOpenDirWasi(5335fn dirOpenDirWasi(
lib/std/debug/SelfInfo/Windows.zig+1-1
...@@ -343,7 +343,7 @@ const Module = struct {...@@ -343,7 +343,7 @@ const Module = struct {
343 error.AntivirusInterference,343 error.AntivirusInterference,
344 error.ProcessFdQuotaExceeded,344 error.ProcessFdQuotaExceeded,
345 error.SystemFdQuotaExceeded,345 error.SystemFdQuotaExceeded,
346 error.FileLocksNotSupported,346 error.FileLocksUnsupported,
347 error.FileBusy,347 error.FileBusy,
348 => return error.ReadFailed,348 => return error.ReadFailed,
349 };349 };
lib/std/fs.zig-6
...@@ -23,12 +23,6 @@ pub const Dir = std.Io.Dir;...@@ -23,12 +23,6 @@ pub const Dir = std.Io.Dir;
23pub const File = std.Io.File;23pub const File = std.Io.File;
2424
25pub const path = @import("fs/path.zig");25pub const path = @import("fs/path.zig");
26
27pub const has_executable_bit = switch (native_os) {
28 .windows, .wasi => false,
29 else => true,
30};
31
32pub const wasi = @import("fs/wasi.zig");26pub const wasi = @import("fs/wasi.zig");
3327
34// TODO audit these APIs with respect to Dir and absolute paths28// TODO audit these APIs with respect to Dir and absolute paths
lib/std/fs/File.zig deleted-1356
...@@ -1,1356 +0,0 @@
1const File = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6
7const std = @import("../std.zig");
8const Io = std.Io;
9const Os = std.builtin.Os;
10const Allocator = std.mem.Allocator;
11const posix = std.posix;
12const math = std.math;
13const assert = std.debug.assert;
14const linux = std.os.linux;
15const windows = std.os.windows;
16const maxInt = std.math.maxInt;
17const Alignment = std.mem.Alignment;
18
19/// The OS-specific file descriptor or file handle.
20handle: Handle,
21
22pub const Handle = Io.File.Handle;
23pub const Mode = Io.File.Mode;
24pub const INode = Io.File.INode;
25pub const Uid = Io.File.Uid;
26pub const Gid = Io.File.Gid;
27pub const Kind = Io.File.Kind;
28
29/// Deprecated in favor of `Io.File.OpenError`.
30pub const OpenError = Io.File.OpenError || error{WouldBlock};
31/// Deprecated in favor of `Io.File.OpenMode`.
32pub const OpenMode = Io.File.OpenMode;
33/// Deprecated in favor of `Io.File.Lock`.
34pub const Lock = Io.File.Lock;
35/// Deprecated in favor of `Io.File.OpenFlags`.
36pub const OpenFlags = Io.File.OpenFlags;
37
38pub const CreateFlags = std.Io.File.CreateFlags;
39
40pub fn stdout() File {
41 return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdOutput else posix.STDOUT_FILENO };
42}
43
44pub fn stderr() File {
45 return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdError else posix.STDERR_FILENO };
46}
47
48pub 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.
54pub fn close(self: File) void {
55 if (is_windows) {
56 windows.CloseHandle(self.handle);
57 } else {
58 posix.close(self.handle);
59 }
60}
61
62pub 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.
69pub 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`.
75pub fn isTty(self: File) bool {
76 return posix.isatty(self.handle);
77}
78
79pub 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`.
140pub 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`.
173pub 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
200pub const SetEndPosError = posix.TruncateError;
201
202/// Shrinks or expands the file.
203/// The file offset after this call is left unchanged.
204pub fn setEndPos(self: File, length: u64) SetEndPosError!void {
205 try posix.ftruncate(self.handle, length);
206}
207
208pub const GetEndPosError = std.os.windows.GetFileSizeError || StatError;
209
210/// TODO: integrate with async I/O
211pub 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
218pub const ModeError = StatError;
219
220/// TODO: integrate with async I/O
221pub 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`.
229pub const Stat = Io.File.Stat;
230
231/// Deprecated in favor of `Io.File.StatError`.
232pub const StatError = posix.FStatError;
233
234/// Deprecated in favor of `Io.File.stat`.
235pub 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
241pub 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.
247pub fn chmod(self: File, new_mode: Mode) ChmodError!void {
248 try posix.fchmod(self.handle, new_mode);
249}
250
251pub 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.
258pub 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.
265pub 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
288pub 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
309pub 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
389pub const SetPermissionsError = ChmodError;
390
391/// Sets permissions according to the provided `Permissions` struct.
392/// This method is *NOT* available on WASI
393pub 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
425pub 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
432pub 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
457pub const ReadError = posix.ReadError;
458pub const PReadError = posix.PReadError;
459
460pub 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
470pub 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`.
479pub 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
490pub 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
503pub 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
513pub const WriteError = posix.WriteError;
514pub const PWriteError = posix.PWriteError;
515
516pub 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
524pub 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`.
532pub 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
541pub 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
550pub 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
564pub 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`.
575pub const CopyRangeError = posix.CopyFileRangeError;
576
577/// Deprecated in favor of `Writer`.
578pub 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`.
585pub 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`.
600pub const Reader = Io.File.Reader;
601
602pub 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.
1163pub 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.
1170pub 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.
1178pub 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.
1185pub fn writerStreaming(file: File, buffer: []u8) Writer {
1186 return .initStreaming(file, buffer);
1187}
1188
1189const range_off: windows.LARGE_INTEGER = 0;
1190const range_len: windows.LARGE_INTEGER = 1;
1191
1192/// Deprecated
1193pub 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
1202pub 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.
1238pub 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
1269pub 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
1309pub 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
1350pub fn adaptToNewApi(file: File) Io.File {
1351 return .{ .handle = file.handle };
1352}
1353
1354pub 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,80 +4248,6 @@ pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter
4248 assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0);4248 assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0);
4249}4249}
42504250
4251pub const SetFileTimeError = error{Unexpected};
4252
4253pub 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
4267pub const LockFileError = error{
4268 SystemResources,
4269 WouldBlock,
4270} || UnexpectedError;
4271
4272pub 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
4305pub const UnlockFileError = error{
4306 RangeNotLocked,
4307} || UnexpectedError;
4308
4309pub 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/// This is a workaround for the C backend until zig has the ability to put4251/// This is a workaround for the C backend until zig has the ability to put
4326/// C code in inline assembly.4252/// C code in inline assembly.
4327extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque;4253extern 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,17 +309,7 @@ pub fn close(fd: fd_t) void {
309 }309 }
310}310}
311311
312pub const FChmodError = error{312pub const FChmodAtError = std.Io.File.SetPermissionsError || error{
313 AccessDenied,
314 PermissionDenied,
315 InputOutput,
316 SymLinkLoop,
317 FileNotFound,
318 SystemResources,
319 ReadOnlyFileSystem,
320} || UnexpectedError;
321
322pub const FChmodAtError = FChmodError || error{
323 /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded313 /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded
324 /// `PATH_MAX`.314 /// `PATH_MAX`.
325 NameTooLong,315 NameTooLong,
...@@ -335,7 +325,6 @@ pub const FChmodAtError = FChmodError || error{...@@ -335,7 +325,6 @@ pub const FChmodAtError = FChmodError || error{
335 ProcessFdQuotaExceeded,325 ProcessFdQuotaExceeded,
336 /// The procfs fallback was used but the system exceeded it open file limit.326 /// The procfs fallback was used but the system exceeded it open file limit.
337 SystemFdQuotaExceeded,327 SystemFdQuotaExceeded,
338 Canceled,
339};328};
340329
341/// Changes the `mode` of `path` relative to the directory referred to by330/// 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,72 +958,6 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
969 }958 }
970}959}
971960
972pub 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.
982pub 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/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.961/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
1039///962///
1040/// Retries when interrupted by a signal.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,7 +1525,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O
1602 .PERM => return error.PermissionDenied,1525 .PERM => return error.PermissionDenied,
1603 .EXIST => return error.PathAlreadyExists,1526 .EXIST => return error.PathAlreadyExists,
1604 .BUSY => return error.DeviceBusy,1527 .BUSY => return error.DeviceBusy,
1605 .OPNOTSUPP => return error.FileLocksNotSupported,1528 .OPNOTSUPP => return error.FileLocksUnsupported,
1606 .AGAIN => return error.WouldBlock,1529 .AGAIN => return error.WouldBlock,
1607 .TXTBSY => return error.FileBusy,1530 .TXTBSY => return error.FileBusy,
1608 .NXIO => return error.NoDevice,1531 .NXIO => return error.NoDevice,
...@@ -2313,47 +2236,6 @@ pub fn getegid() gid_t {...@@ -2313,47 +2236,6 @@ pub fn getegid() gid_t {
2313 return system.getegid();2236 return system.getegid();
2314}2237}
23152238
2316/// Test whether a file descriptor refers to a terminal.
2317pub 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
2357pub const SocketError = error{2239pub const SocketError = error{
2358 /// Permission to create a socket of the specified type and/or2240 /// Permission to create a socket of the specified type and/or
2359 /// pro‐tocol is denied.2241 /// pro‐tocol is denied.
...@@ -3923,34 +3805,6 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {...@@ -3923,34 +3805,6 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {
3923 }3805 }
3924}3806}
39253807
3926pub 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.
3938pub 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/// Spurious wakeups are possible and no precision of timing is guaranteed.3808/// Spurious wakeups are possible and no precision of timing is guaranteed.
3955pub fn nanosleep(seconds: u64, nanoseconds: u64) void {3809pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
3956 var req = timespec{3810 var req = timespec{
...@@ -4215,74 +4069,6 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*...@@ -4215,74 +4069,6 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*
4215 }4069 }
4216}4070}
42174071
4218pub 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
4242pub 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
4286pub const GetHostNameError = error{PermissionDenied} || UnexpectedError;4072pub const GetHostNameError = error{PermissionDenied} || UnexpectedError;
42874073
4288pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {4074pub 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,12 +4890,7 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t {
5104 }4890 }
5105}4891}
51064892
5107pub const SyncError = error{4893pub const SyncError = std.Io.File.SyncError;
5108 InputOutput,
5109 NoSpaceLeft,
5110 DiskQuota,
5111 AccessDenied,
5112} || UnexpectedError;
51134894
5114/// Write all pending file contents and metadata modifications to all filesystems.4895/// Write all pending file contents and metadata modifications to all filesystems.
5115pub fn sync() void {4896pub fn sync() void {
...@@ -5129,38 +4910,8 @@ pub fn syncfs(fd: fd_t) SyncError!void {...@@ -5129,38 +4910,8 @@ pub fn syncfs(fd: fd_t) SyncError!void {
5129 }4910 }
5130}4911}
51314912
5132/// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem.
5133pub 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/// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata.4913/// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata.
5157pub fn fdatasync(fd: fd_t) SyncError!void {4914pub 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 const rc = system.fdatasync(fd);4915 const rc = system.fdatasync(fd);
5165 switch (errno(rc)) {4916 switch (errno(rc)) {
5166 .SUCCESS => return,4917 .SUCCESS => return,
lib/std/process/Child.zig+1-1
...@@ -596,7 +596,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -596,7 +596,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
596 error.NoSpaceLeft => unreachable,596 error.NoSpaceLeft => unreachable,
597 error.FileTooBig => unreachable,597 error.FileTooBig => unreachable,
598 error.DeviceBusy => unreachable,598 error.DeviceBusy => unreachable,
599 error.FileLocksNotSupported => unreachable,599 error.FileLocksUnsupported => unreachable,
600 error.BadPathName => unreachable, // Windows-only600 error.BadPathName => unreachable, // Windows-only
601 error.WouldBlock => unreachable,601 error.WouldBlock => unreachable,
602 error.NetworkNotFound => unreachable, // Windows-only602 error.NetworkNotFound => unreachable, // Windows-only
lib/std/std.zig+3
...@@ -173,6 +173,9 @@ pub const Options = struct {...@@ -173,6 +173,9 @@ pub const Options = struct {
173 /// If this is `false`, then captured stack traces will always be empty, and attempts to write173 /// If this is `false`, then captured stack traces will always be empty, and attempts to write
174 /// stack traces will just print an error to the relevant `Io.Writer` and return.174 /// stack traces will just print an error to the relevant `Io.Writer` and return.
175 allow_stack_tracing: bool = !@import("builtin").strip_debug_info,175 allow_stack_tracing: bool = !@import("builtin").strip_debug_info,
176
177 /// Overrides `std.Io.File.Permissions`.
178 FilePermissions: ?type = null,
176};179};
177180
178// This forces the start.zig file to be imported, and the comptime logic inside that181// 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,7 +824,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
824 error.SharingViolation => return error.Unexpected, // Windows-only824 error.SharingViolation => return error.Unexpected, // Windows-only
825 error.NetworkNotFound => return error.Unexpected, // Windows-only825 error.NetworkNotFound => return error.Unexpected, // Windows-only
826 error.AntivirusInterference => return error.Unexpected, // Windows-only826 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 error.NoSpaceLeft => return error.Unexpected, // read-only828 error.NoSpaceLeft => return error.Unexpected, // read-only
829 error.PathAlreadyExists => return error.Unexpected, // read-only829 error.PathAlreadyExists => return error.Unexpected, // read-only
830 error.DeviceBusy => return error.Unexpected, // read-only830 error.DeviceBusy => return error.Unexpected, // read-only
...@@ -1033,7 +1033,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ...@@ -1033,7 +1033,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
1033 error.SharingViolation => return error.Unexpected,1033 error.SharingViolation => return error.Unexpected,
1034 error.BadPathName => return error.Unexpected,1034 error.BadPathName => return error.Unexpected,
1035 error.PipeBusy => return error.Unexpected,1035 error.PipeBusy => return error.Unexpected,
1036 error.FileLocksNotSupported => return error.Unexpected,1036 error.FileLocksUnsupported => return error.Unexpected,
1037 error.FileBusy => return error.Unexpected, // opened without write permissions1037 error.FileBusy => return error.Unexpected, // opened without write permissions
1038 error.AntivirusInterference => return error.Unexpected, // Windows-only error1038 error.AntivirusInterference => return error.Unexpected, // Windows-only error
10391039