diff options
| author | Andrew Kelley <andrewrk@noreply.codeberg.org> | 2025-12-27 14:10:46 +0100 |
|---|---|---|
| committer | Andrew Kelley <andrewrk@noreply.codeberg.org> | 2025-12-27 14:10:46 +0100 |
| commit | e55e6b5528bb2f01de242fcf32b172e244e98e74 (patch) | |
| tree | 3a5eb3193d3d192c54ab0c2b7295a7f21861c27e /lib/std/fs/File.zig | |
| parent | c3f2de5e519926eb0029062fe8e782a6f9df9c05 (diff) | |
| parent | 60a1ba0a8f3517356fa2941462f002a7f580545b (diff) | |
| download | zig-e55e6b5528bb2f01de242fcf32b172e244e98e74.tar.gz zig-e55e6b5528bb2f01de242fcf32b172e244e98e74.zip | |
Merge pull request 'std: migrate all `fs` APIs to `Io`' (#30232) from std.Io-fs into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30232
Diffstat (limited to 'lib/std/fs/File.zig')
| -rw-r--r-- | lib/std/fs/File.zig | 1437 |
1 files changed, 0 insertions, 1437 deletions
diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig deleted file mode 100644 index 5e54ba5f7a..0000000000 --- a/lib/std/fs/File.zig +++ /dev/null @@ -1,1437 +0,0 @@ -const File = @This(); - -const builtin = @import("builtin"); -const native_os = builtin.os.tag; -const is_windows = native_os == .windows; - -const std = @import("../std.zig"); -const Io = std.Io; -const Os = std.builtin.Os; -const Allocator = std.mem.Allocator; -const posix = std.posix; -const math = std.math; -const assert = std.debug.assert; -const linux = std.os.linux; -const windows = std.os.windows; -const maxInt = std.math.maxInt; -const Alignment = std.mem.Alignment; - -/// The OS-specific file descriptor or file handle. -handle: Handle, - -pub const Handle = Io.File.Handle; -pub const Mode = Io.File.Mode; -pub const INode = Io.File.INode; -pub const Uid = posix.uid_t; -pub const Gid = posix.gid_t; -pub const Kind = Io.File.Kind; - -/// This is the default mode given to POSIX operating systems for creating -/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first, -/// since most people would expect "-rw-r--r--", for example, when using -/// the `touch` command, which would correspond to `0o644`. However, POSIX -/// libc implementations use `0o666` inside `fopen` and then rely on the -/// process-scoped "umask" setting to adjust this number for file creation. -pub const default_mode: Mode = if (Mode == u0) 0 else 0o666; - -/// Deprecated in favor of `Io.File.OpenError`. -pub const OpenError = Io.File.OpenError || error{WouldBlock}; -/// Deprecated in favor of `Io.File.OpenMode`. -pub const OpenMode = Io.File.OpenMode; -/// Deprecated in favor of `Io.File.Lock`. -pub const Lock = Io.File.Lock; -/// Deprecated in favor of `Io.File.OpenFlags`. -pub const OpenFlags = Io.File.OpenFlags; - -pub const CreateFlags = struct { - /// Whether the file will be created with read access. - read: bool = false, - - /// If the file already exists, and is a regular file, and the access - /// mode allows writing, it will be truncated to length 0. - truncate: bool = true, - - /// Ensures that this open call creates the file, otherwise causes - /// `error.PathAlreadyExists` to be returned. - exclusive: bool = false, - - /// Open the file with an advisory lock to coordinate with other processes - /// accessing it at the same time. An exclusive lock will prevent other - /// processes from acquiring a lock. A shared lock will prevent other - /// processes from acquiring a exclusive lock, but does not prevent - /// other process from getting their own shared locks. - /// - /// The lock is advisory, except on Linux in very specific circumstances[1]. - /// This means that a process that does not respect the locking API can still get access - /// to the file, despite the lock. - /// - /// On these operating systems, the lock is acquired atomically with - /// opening the file: - /// * Darwin - /// * DragonFlyBSD - /// * FreeBSD - /// * Haiku - /// * NetBSD - /// * OpenBSD - /// On these operating systems, the lock is acquired via a separate syscall - /// after opening the file: - /// * Linux - /// * Windows - /// - /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt - lock: Lock = .none, - - /// Sets whether or not to wait until the file is locked to return. If set to true, - /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file - /// is available to proceed. - lock_nonblocking: bool = false, - - /// For POSIX systems this is the file system mode the file will - /// be created with. On other systems this is always 0. - mode: Mode = default_mode, -}; - -pub fn stdout() File { - return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdOutput else posix.STDOUT_FILENO }; -} - -pub fn stderr() File { - return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdError else posix.STDERR_FILENO }; -} - -pub fn stdin() File { - return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdInput else posix.STDIN_FILENO }; -} - -/// Upon success, the stream is in an uninitialized state. To continue using it, -/// you must use the open() function. -pub fn close(self: File) void { - if (is_windows) { - windows.CloseHandle(self.handle); - } else { - posix.close(self.handle); - } -} - -pub const SyncError = posix.SyncError; - -/// Blocks until all pending file contents and metadata modifications -/// for the file have been synchronized with the underlying filesystem. -/// -/// Note that this does not ensure that metadata for the -/// directory containing the file has also reached disk. -pub fn sync(self: File) SyncError!void { - return posix.fsync(self.handle); -} - -/// Test whether the file refers to a terminal. -/// See also `getOrEnableAnsiEscapeSupport` and `supportsAnsiEscapeCodes`. -pub fn isTty(self: File) bool { - return posix.isatty(self.handle); -} - -pub fn isCygwinPty(file: File) bool { - if (builtin.os.tag != .windows) return false; - - const handle = file.handle; - - // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats: - // msys-[...]-ptyN-[...] - // cygwin-[...]-ptyN-[...] - // - // Example: msys-1888ae32e00d56aa-pty0-to-master - - // First, just check that the handle is a named pipe. - // This allows us to avoid the more costly NtQueryInformationFile call - // for handles that aren't named pipes. - { - var io_status: windows.IO_STATUS_BLOCK = undefined; - var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined; - const rc = windows.ntdll.NtQueryVolumeInformationFile(handle, &io_status, &device_info, @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), .Device); - switch (rc) { - .SUCCESS => {}, - else => return false, - } - if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false; - } - - const name_bytes_offset = @offsetOf(windows.FILE_NAME_INFO, "FileName"); - // `NAME_MAX` UTF-16 code units (2 bytes each) - // This buffer may not be long enough to handle *all* possible paths - // (PATH_MAX_WIDE would be necessary for that), but because we only care - // about certain paths and we know they must be within a reasonable length, - // we can use this smaller buffer and just return false on any error from - // NtQueryInformationFile. - const num_name_bytes = windows.MAX_PATH * 2; - var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); - - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const rc = windows.ntdll.NtQueryInformationFile(handle, &io_status_block, &name_info_bytes, @intCast(name_info_bytes.len), .Name); - switch (rc) { - .SUCCESS => {}, - .INVALID_PARAMETER => unreachable, - else => return false, - } - - const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); - const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; - const name_wide = std.mem.bytesAsSlice(u16, name_bytes); - // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master - return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or - std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and - std.mem.find(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; -} - -/// Returns whether or not ANSI escape codes will be treated as such, -/// and attempts to enable support for ANSI escape codes if necessary -/// (on Windows). -/// -/// Returns `true` if ANSI escape codes are supported or support was -/// successfully enabled. Returns false if ANSI escape codes are not -/// supported or support was unable to be enabled. -/// -/// See also `supportsAnsiEscapeCodes`. -pub fn getOrEnableAnsiEscapeSupport(self: File) bool { - if (builtin.os.tag == .windows) { - var original_console_mode: windows.DWORD = 0; - - // For Windows Terminal, VT Sequences processing is enabled by default. - if (windows.kernel32.GetConsoleMode(self.handle, &original_console_mode) != 0) { - if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; - - // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. - // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ - // - // Note: In Microsoft's example for enabling virtual terminal processing, it - // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well: - // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing - // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n) - // to behave unexpectedly (the cursor moves down 1 row but remains on the same column). - // Additionally, the default console mode in Windows Terminal does not have - // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` - // we end up matching the mode of Windows Terminal. - const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; - const console_mode = original_console_mode | requested_console_modes; - if (windows.kernel32.SetConsoleMode(self.handle, console_mode) != 0) return true; - } - - return self.isCygwinPty(); - } - return self.supportsAnsiEscapeCodes(); -} - -/// Test whether ANSI escape codes will be treated as such without -/// attempting to enable support for ANSI escape codes. -/// -/// See also `getOrEnableAnsiEscapeSupport`. -pub fn supportsAnsiEscapeCodes(self: File) bool { - if (builtin.os.tag == .windows) { - var console_mode: windows.DWORD = 0; - if (windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) { - if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; - } - - return self.isCygwinPty(); - } - if (builtin.os.tag == .wasi) { - // WASI sanitizes stdout when fd is a tty so ANSI escape codes - // will not be interpreted as actual cursor commands, and - // stderr is always sanitized. - return false; - } - if (self.isTty()) { - if (self.handle == posix.STDOUT_FILENO or self.handle == posix.STDERR_FILENO) { - if (posix.getenvZ("TERM")) |term| { - if (std.mem.eql(u8, term, "dumb")) - return false; - } - } - return true; - } - return false; -} - -pub const SetEndPosError = posix.TruncateError; - -/// Shrinks or expands the file. -/// The file offset after this call is left unchanged. -pub fn setEndPos(self: File, length: u64) SetEndPosError!void { - try posix.ftruncate(self.handle, length); -} - -pub const SeekError = posix.SeekError; - -/// Repositions read/write file offset relative to the current offset. -/// TODO: integrate with async I/O -pub fn seekBy(self: File, offset: i64) SeekError!void { - return posix.lseek_CUR(self.handle, offset); -} - -/// Repositions read/write file offset relative to the end. -/// TODO: integrate with async I/O -pub fn seekFromEnd(self: File, offset: i64) SeekError!void { - return posix.lseek_END(self.handle, offset); -} - -/// Repositions read/write file offset relative to the beginning. -/// TODO: integrate with async I/O -pub fn seekTo(self: File, offset: u64) SeekError!void { - return posix.lseek_SET(self.handle, offset); -} - -pub const GetSeekPosError = posix.SeekError || StatError; - -/// TODO: integrate with async I/O -pub fn getPos(self: File) GetSeekPosError!u64 { - return posix.lseek_CUR_get(self.handle); -} - -pub const GetEndPosError = std.os.windows.GetFileSizeError || StatError; - -/// TODO: integrate with async I/O -pub fn getEndPos(self: File) GetEndPosError!u64 { - if (builtin.os.tag == .windows) { - return windows.GetFileSizeEx(self.handle); - } - return (try self.stat()).size; -} - -pub const ModeError = StatError; - -/// TODO: integrate with async I/O -pub fn mode(self: File) ModeError!Mode { - if (builtin.os.tag == .windows) { - return 0; - } - return (try self.stat()).mode; -} - -pub const Stat = Io.File.Stat; - -pub const StatError = posix.FStatError; - -/// Returns `Stat` containing basic information about the `File`. -pub fn stat(self: File) StatError!Stat { - var threaded: Io.Threaded = .init_single_threaded; - const io = threaded.ioBasic(); - return Io.File.stat(.{ .handle = self.handle }, io); -} - -pub const ChmodError = posix.FChmodError; - -/// Changes the mode of the file. -/// The process must have the correct privileges in order to do this -/// successfully, or must have the effective user ID matching the owner -/// of the file. -pub fn chmod(self: File, new_mode: Mode) ChmodError!void { - try posix.fchmod(self.handle, new_mode); -} - -pub const ChownError = posix.FChownError; - -/// Changes the owner and group of the file. -/// The process must have the correct privileges in order to do this -/// successfully. The group may be changed by the owner of the file to -/// any group of which the owner is a member. If the owner or group is -/// specified as `null`, the ID is not changed. -pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void { - try posix.fchown(self.handle, owner, group); -} - -/// Cross-platform representation of permissions on a file. -/// The `readonly` and `setReadonly` are the only methods available across all platforms. -/// Platform-specific functionality is available through the `inner` field. -pub const Permissions = struct { - /// You may use the `inner` field to use platform-specific functionality - inner: switch (builtin.os.tag) { - .windows => PermissionsWindows, - else => PermissionsUnix, - }, - - const Self = @This(); - - /// Returns `true` if permissions represent an unwritable file. - /// On Unix, `true` is returned only if no class has write permissions. - pub fn readOnly(self: Self) bool { - return self.inner.readOnly(); - } - - /// Sets whether write permissions are provided. - /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`. - /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` - pub fn setReadOnly(self: *Self, read_only: bool) void { - self.inner.setReadOnly(read_only); - } -}; - -pub const PermissionsWindows = struct { - attributes: windows.DWORD, - - const Self = @This(); - - /// Returns `true` if permissions represent an unwritable file. - pub fn readOnly(self: Self) bool { - return self.attributes & windows.FILE_ATTRIBUTE_READONLY != 0; - } - - /// Sets whether write permissions are provided. - /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` - pub fn setReadOnly(self: *Self, read_only: bool) void { - if (read_only) { - self.attributes |= windows.FILE_ATTRIBUTE_READONLY; - } else { - self.attributes &= ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY); - } - } -}; - -pub const PermissionsUnix = struct { - mode: Mode, - - const Self = @This(); - - /// Returns `true` if permissions represent an unwritable file. - /// `true` is returned only if no class has write permissions. - pub fn readOnly(self: Self) bool { - return self.mode & 0o222 == 0; - } - - /// Sets whether write permissions are provided. - /// This affects *all* classes. If this is undesired, use `unixSet`. - /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` - pub fn setReadOnly(self: *Self, read_only: bool) void { - if (read_only) { - self.mode &= ~@as(Mode, 0o222); - } else { - self.mode |= @as(Mode, 0o222); - } - } - - pub const Class = enum(u2) { - user = 2, - group = 1, - other = 0, - }; - - pub const Permission = enum(u3) { - read = 0o4, - write = 0o2, - execute = 0o1, - }; - - /// Returns `true` if the chosen class has the selected permission. - /// This method is only available on Unix platforms. - pub fn unixHas(self: Self, class: Class, permission: Permission) bool { - const mask = @as(Mode, @intFromEnum(permission)) << @as(u3, @intFromEnum(class)) * 3; - return self.mode & mask != 0; - } - - /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged. - /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` - pub fn unixSet(self: *Self, class: Class, permissions: struct { - read: ?bool = null, - write: ?bool = null, - execute: ?bool = null, - }) void { - const shift = @as(u3, @intFromEnum(class)) * 3; - if (permissions.read) |r| { - if (r) { - self.mode |= @as(Mode, 0o4) << shift; - } else { - self.mode &= ~(@as(Mode, 0o4) << shift); - } - } - if (permissions.write) |w| { - if (w) { - self.mode |= @as(Mode, 0o2) << shift; - } else { - self.mode &= ~(@as(Mode, 0o2) << shift); - } - } - if (permissions.execute) |x| { - if (x) { - self.mode |= @as(Mode, 0o1) << shift; - } else { - self.mode &= ~(@as(Mode, 0o1) << shift); - } - } - } - - /// Returns a `Permissions` struct representing the permissions from the passed mode. - pub fn unixNew(new_mode: Mode) Self { - return Self{ - .mode = new_mode, - }; - } -}; - -pub const SetPermissionsError = ChmodError; - -/// Sets permissions according to the provided `Permissions` struct. -/// This method is *NOT* available on WASI -pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void { - switch (builtin.os.tag) { - .windows => { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - var info = windows.FILE_BASIC_INFORMATION{ - .CreationTime = 0, - .LastAccessTime = 0, - .LastWriteTime = 0, - .ChangeTime = 0, - .FileAttributes = permissions.inner.attributes, - }; - const rc = windows.ntdll.NtSetInformationFile( - self.handle, - &io_status_block, - &info, - @sizeOf(windows.FILE_BASIC_INFORMATION), - .Basic, - ); - switch (rc) { - .SUCCESS => return, - .INVALID_HANDLE => unreachable, - .ACCESS_DENIED => return error.AccessDenied, - else => return windows.unexpectedStatus(rc), - } - }, - .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod - else => { - try self.chmod(permissions.inner.mode); - }, - } -} - -pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError; - -/// The underlying file system may have a different granularity than nanoseconds, -/// and therefore this function cannot guarantee any precision will be stored. -/// Further, the maximum value is limited by the system ABI. When a value is provided -/// that exceeds this range, the value is clamped to the maximum. -/// TODO: integrate with async I/O -pub fn updateTimes( - self: File, - /// access timestamp in nanoseconds - atime: Io.Timestamp, - /// last modification timestamp in nanoseconds - mtime: Io.Timestamp, -) UpdateTimesError!void { - if (builtin.os.tag == .windows) { - const atime_ft = windows.nanoSecondsToFileTime(atime); - const mtime_ft = windows.nanoSecondsToFileTime(mtime); - return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft); - } - const times = [2]posix.timespec{ - posix.timespec{ - .sec = math.cast(isize, @divFloor(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), - .nsec = math.cast(isize, @mod(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), - }, - posix.timespec{ - .sec = math.cast(isize, @divFloor(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), - .nsec = math.cast(isize, @mod(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), - }, - }; - try posix.futimens(self.handle, ×); -} - -pub const ReadError = posix.ReadError; -pub const PReadError = posix.PReadError; - -pub fn read(self: File, buffer: []u8) ReadError!usize { - if (is_windows) { - return windows.ReadFile(self.handle, buffer, null); - } - - return posix.read(self.handle, buffer); -} - -/// On Windows, this function currently does alter the file pointer. -/// https://github.com/ziglang/zig/issues/12783 -pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize { - if (is_windows) { - return windows.ReadFile(self.handle, buffer, offset); - } - - return posix.pread(self.handle, buffer, offset); -} - -/// Deprecated in favor of `Reader`. -pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize { - var index: usize = 0; - while (index != buffer.len) { - const amt = try self.pread(buffer[index..], offset + index); - if (amt == 0) break; - index += amt; - } - return index; -} - -/// See https://github.com/ziglang/zig/issues/7699 -pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize { - if (is_windows) { - if (iovecs.len == 0) return 0; - const first = iovecs[0]; - return windows.ReadFile(self.handle, first.base[0..first.len], null); - } - - return posix.readv(self.handle, iovecs); -} - -/// See https://github.com/ziglang/zig/issues/7699 -/// On Windows, this function currently does alter the file pointer. -/// https://github.com/ziglang/zig/issues/12783 -pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!usize { - if (is_windows) { - if (iovecs.len == 0) return 0; - const first = iovecs[0]; - return windows.ReadFile(self.handle, first.base[0..first.len], offset); - } - - return posix.preadv(self.handle, iovecs, offset); -} - -pub const WriteError = posix.WriteError; -pub const PWriteError = posix.PWriteError; - -pub fn write(self: File, bytes: []const u8) WriteError!usize { - if (is_windows) { - return windows.WriteFile(self.handle, bytes, null); - } - - return posix.write(self.handle, bytes); -} - -pub fn writeAll(self: File, bytes: []const u8) WriteError!void { - var index: usize = 0; - while (index < bytes.len) { - index += try self.write(bytes[index..]); - } -} - -/// Deprecated in favor of `Writer`. -pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void { - var index: usize = 0; - while (index < bytes.len) { - index += try self.pwrite(bytes[index..], offset + index); - } -} - -/// On Windows, this function currently does alter the file pointer. -/// https://github.com/ziglang/zig/issues/12783 -pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize { - if (is_windows) { - return windows.WriteFile(self.handle, bytes, offset); - } - - return posix.pwrite(self.handle, bytes, offset); -} - -/// See https://github.com/ziglang/zig/issues/7699 -pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize { - if (is_windows) { - // TODO improve this to use WriteFileScatter - if (iovecs.len == 0) return 0; - const first = iovecs[0]; - return windows.WriteFile(self.handle, first.base[0..first.len], null); - } - - return posix.writev(self.handle, iovecs); -} - -/// See https://github.com/ziglang/zig/issues/7699 -/// On Windows, this function currently does alter the file pointer. -/// https://github.com/ziglang/zig/issues/12783 -pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!usize { - if (is_windows) { - if (iovecs.len == 0) return 0; - const first = iovecs[0]; - return windows.WriteFile(self.handle, first.base[0..first.len], offset); - } - - return posix.pwritev(self.handle, iovecs, offset); -} - -/// Deprecated in favor of `Writer`. -pub const CopyRangeError = posix.CopyFileRangeError; - -/// Deprecated in favor of `Writer`. -pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { - const adjusted_len = math.cast(usize, len) orelse maxInt(usize); - const result = try posix.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0); - return result; -} - -/// Deprecated in favor of `Writer`. -pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { - var total_bytes_copied: u64 = 0; - var in_off = in_offset; - var out_off = out_offset; - while (total_bytes_copied < len) { - const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied); - if (amt_copied == 0) return total_bytes_copied; - total_bytes_copied += amt_copied; - in_off += amt_copied; - out_off += amt_copied; - } - return total_bytes_copied; -} - -/// Deprecated in favor of `Io.File.Reader`. -pub const Reader = Io.File.Reader; - -pub const Writer = struct { - file: File, - err: ?WriteError = null, - mode: Writer.Mode = .positional, - /// Tracks the true seek position in the file. To obtain the logical - /// position, add the buffer size to this value. - pos: u64 = 0, - sendfile_err: ?SendfileError = null, - copy_file_range_err: ?CopyFileRangeError = null, - fcopyfile_err: ?FcopyfileError = null, - seek_err: ?Writer.SeekError = null, - interface: Io.Writer, - - pub const Mode = Reader.Mode; - - pub const SendfileError = error{ - UnsupportedOperation, - SystemResources, - InputOutput, - BrokenPipe, - WouldBlock, - Unexpected, - }; - - pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError; - - pub const FcopyfileError = error{ - OperationNotSupported, - OutOfMemory, - Unexpected, - }; - - pub const SeekError = File.SeekError; - - /// Number of slices to store on the stack, when trying to send as many byte - /// vectors through the underlying write calls as possible. - const max_buffers_len = 16; - - pub fn init(file: File, buffer: []u8) Writer { - return .{ - .file = file, - .interface = initInterface(buffer), - .mode = .positional, - }; - } - - /// Positional is more threadsafe, since the global seek position is not - /// affected, but when such syscalls are not available, preemptively - /// initializing in streaming mode will skip a failed syscall. - pub fn initStreaming(file: File, buffer: []u8) Writer { - return .{ - .file = file, - .interface = initInterface(buffer), - .mode = .streaming, - }; - } - - pub fn initInterface(buffer: []u8) Io.Writer { - return .{ - .vtable = &.{ - .drain = drain, - .sendFile = sendFile, - }, - .buffer = buffer, - }; - } - - /// TODO when this logic moves from fs.File to Io.File the io parameter should be deleted - pub fn moveToReader(w: *Writer, io: Io) Reader { - defer w.* = undefined; - return .{ - .io = io, - .file = .{ .handle = w.file.handle }, - .mode = w.mode, - .pos = w.pos, - .interface = Reader.initInterface(w.interface.buffer), - .seek_err = w.seek_err, - }; - } - - pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const handle = w.file.handle; - const buffered = io_w.buffered(); - if (is_windows) switch (w.mode) { - .positional, .positional_reading => { - if (buffered.len != 0) { - const n = windows.WriteFile(handle, buffered, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - for (data[0 .. data.len - 1]) |buf| { - if (buf.len == 0) continue; - const n = windows.WriteFile(handle, buf, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - const pattern = data[data.len - 1]; - if (pattern.len == 0 or splat == 0) return 0; - const n = windows.WriteFile(handle, pattern, w.pos) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, - .streaming, .streaming_reading => { - if (buffered.len != 0) { - const n = windows.WriteFile(handle, buffered, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - for (data[0 .. data.len - 1]) |buf| { - if (buf.len == 0) continue; - const n = windows.WriteFile(handle, buf, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - } - const pattern = data[data.len - 1]; - if (pattern.len == 0 or splat == 0) return 0; - const n = windows.WriteFile(handle, pattern, null) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, - .failure => return error.WriteFailed, - }; - var iovecs: [max_buffers_len]std.posix.iovec_const = undefined; - var len: usize = 0; - if (buffered.len > 0) { - iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len }; - len += 1; - } - for (data[0 .. data.len - 1]) |d| { - if (d.len == 0) continue; - iovecs[len] = .{ .base = d.ptr, .len = d.len }; - len += 1; - if (iovecs.len - len == 0) break; - } - const pattern = data[data.len - 1]; - if (iovecs.len - len != 0) switch (splat) { - 0 => {}, - 1 => if (pattern.len != 0) { - iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; - len += 1; - }, - else => switch (pattern.len) { - 0 => {}, - 1 => { - const splat_buffer_candidate = io_w.buffer[io_w.end..]; - var backup_buffer: [64]u8 = undefined; - const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len) - splat_buffer_candidate - else - &backup_buffer; - const memset_len = @min(splat_buffer.len, splat); - const buf = splat_buffer[0..memset_len]; - @memset(buf, pattern[0]); - iovecs[len] = .{ .base = buf.ptr, .len = buf.len }; - len += 1; - var remaining_splat = splat - buf.len; - while (remaining_splat > splat_buffer.len and iovecs.len - len != 0) { - assert(buf.len == splat_buffer.len); - iovecs[len] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len }; - len += 1; - remaining_splat -= splat_buffer.len; - } - if (remaining_splat > 0 and iovecs.len - len != 0) { - iovecs[len] = .{ .base = splat_buffer.ptr, .len = remaining_splat }; - len += 1; - } - }, - else => for (0..splat) |_| { - iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len }; - len += 1; - if (iovecs.len - len == 0) break; - }, - }, - }; - if (len == 0) return 0; - switch (w.mode) { - .positional, .positional_reading => { - const n = std.posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) { - error.Unseekable => { - w.mode = w.mode.toStreaming(); - const pos = w.pos; - if (pos != 0) { - w.pos = 0; - w.seekTo(@intCast(pos)) catch { - w.mode = .failure; - return error.WriteFailed; - }; - } - return 0; - }, - else => |e| { - w.err = e; - return error.WriteFailed; - }, - }; - w.pos += n; - return io_w.consume(n); - }, - .streaming, .streaming_reading => { - const n = std.posix.writev(handle, iovecs[0..len]) catch |err| { - w.err = err; - return error.WriteFailed; - }; - w.pos += n; - return io_w.consume(n); - }, - .failure => return error.WriteFailed, - } - } - - pub fn sendFile( - io_w: *Io.Writer, - file_reader: *Io.File.Reader, - limit: Io.Limit, - ) Io.Writer.FileError!usize { - const reader_buffered = file_reader.interface.buffered(); - if (reader_buffered.len >= @intFromEnum(limit)) - return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered)); - const writer_buffered = io_w.buffered(); - const file_limit = @intFromEnum(limit) - reader_buffered.len; - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - const out_fd = w.file.handle; - const in_fd = file_reader.file.handle; - - if (file_reader.size) |size| { - if (size - file_reader.pos == 0) { - if (reader_buffered.len != 0) { - return sendFileBuffered(io_w, file_reader, reader_buffered); - } else { - return error.EndOfStream; - } - } - } - - if (native_os == .freebsd and w.mode == .streaming) sf: { - // Try using sendfile on FreeBSD. - if (w.sendfile_err != null) break :sf; - const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; - var hdtr_data: std.c.sf_hdtr = undefined; - var headers: [2]posix.iovec_const = undefined; - var headers_i: u8 = 0; - if (writer_buffered.len != 0) { - headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; - headers_i += 1; - } - if (reader_buffered.len != 0) { - headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; - headers_i += 1; - } - const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { - hdtr_data = .{ - .headers = &headers, - .hdr_cnt = headers_i, - .trailers = null, - .trl_cnt = 0, - }; - break :b &hdtr_data; - }; - var sbytes: std.c.off_t = undefined; - const nbytes: usize = @min(file_limit, maxInt(usize)); - const flags = 0; - switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { - .SUCCESS, .INTR => {}, - .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, - .BADF => if (builtin.mode == .Debug) @panic("race condition") else { - w.sendfile_err = error.Unexpected; - }, - .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { - w.sendfile_err = error.Unexpected; - }, - .NOTCONN => w.sendfile_err = error.BrokenPipe, - .AGAIN, .BUSY => if (sbytes == 0) { - w.sendfile_err = error.WouldBlock; - }, - .IO => w.sendfile_err = error.InputOutput, - .PIPE => w.sendfile_err = error.BrokenPipe, - .NOBUFS => w.sendfile_err = error.SystemResources, - else => |err| w.sendfile_err = posix.unexpectedErrno(err), - } - if (w.sendfile_err != null) { - // Give calling code chance to observe the error before trying - // something else. - return 0; - } - if (sbytes == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - const consumed = io_w.consume(@intCast(sbytes)); - file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; - return consumed; - } - - if (native_os.isDarwin() and w.mode == .streaming) sf: { - // Try using sendfile on macOS. - if (w.sendfile_err != null) break :sf; - const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; - var hdtr_data: std.c.sf_hdtr = undefined; - var headers: [2]posix.iovec_const = undefined; - var headers_i: u8 = 0; - if (writer_buffered.len != 0) { - headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len }; - headers_i += 1; - } - if (reader_buffered.len != 0) { - headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; - headers_i += 1; - } - const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { - hdtr_data = .{ - .headers = &headers, - .hdr_cnt = headers_i, - .trailers = null, - .trl_cnt = 0, - }; - break :b &hdtr_data; - }; - const max_count = maxInt(i32); // Avoid EINVAL. - var len: std.c.off_t = @min(file_limit, max_count); - const flags = 0; - switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { - .SUCCESS, .INTR => {}, - .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation, - .BADF => if (builtin.mode == .Debug) @panic("race condition") else { - w.sendfile_err = error.Unexpected; - }, - .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else { - w.sendfile_err = error.Unexpected; - }, - .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { - w.sendfile_err = error.Unexpected; - }, - .NOTCONN => w.sendfile_err = error.BrokenPipe, - .AGAIN => if (len == 0) { - w.sendfile_err = error.WouldBlock; - }, - .IO => w.sendfile_err = error.InputOutput, - .PIPE => w.sendfile_err = error.BrokenPipe, - else => |err| w.sendfile_err = posix.unexpectedErrno(err), - } - if (w.sendfile_err != null) { - // Give calling code chance to observe the error before trying - // something else. - return 0; - } - if (len == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - const consumed = io_w.consume(@bitCast(len)); - file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; - return consumed; - } - - if (native_os == .linux and w.mode == .streaming) sf: { - // Try using sendfile on Linux. - if (w.sendfile_err != null) break :sf; - // Linux sendfile does not support headers. - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - const max_count = 0x7ffff000; // Avoid EINVAL. - var off: std.os.linux.off_t = undefined; - const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { - .positional => o: { - const size = file_reader.getSize() catch return 0; - off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; - break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; - }, - .streaming => .{ null, limit.minInt(max_count) }, - .streaming_reading, .positional_reading => break :sf, - .failure => return error.ReadFailed, - }; - const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) { - error.Unseekable => { - file_reader.mode = file_reader.mode.toStreaming(); - const pos = file_reader.pos; - if (pos != 0) { - file_reader.pos = 0; - file_reader.seekBy(@intCast(pos)) catch { - file_reader.mode = .failure; - return error.ReadFailed; - }; - } - return 0; - }, - else => |e| { - w.sendfile_err = e; - return 0; - }, - }; - if (n == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - file_reader.pos += n; - w.pos += n; - return n; - } - - const copy_file_range = switch (native_os) { - .freebsd => std.os.freebsd.copy_file_range, - .linux => std.os.linux.wrapped.copy_file_range, - else => {}, - }; - if (@TypeOf(copy_file_range) != void) cfr: { - if (w.copy_file_range_err != null) break :cfr; - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - var off_in: i64 = undefined; - var off_out: i64 = undefined; - const off_in_ptr: ?*i64 = switch (file_reader.mode) { - .positional_reading, .streaming_reading => return error.Unimplemented, - .positional => p: { - off_in = @intCast(file_reader.pos); - break :p &off_in; - }, - .streaming => null, - .failure => return error.WriteFailed, - }; - const off_out_ptr: ?*i64 = switch (w.mode) { - .positional_reading, .streaming_reading => return error.Unimplemented, - .positional => p: { - off_out = @intCast(w.pos); - break :p &off_out; - }, - .streaming => null, - .failure => return error.WriteFailed, - }; - const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| { - w.copy_file_range_err = err; - return 0; - }; - if (n == 0) { - file_reader.size = file_reader.pos; - return error.EndOfStream; - } - file_reader.pos += n; - w.pos += n; - return n; - } - - if (builtin.os.tag.isDarwin()) fcf: { - if (w.fcopyfile_err != null) break :fcf; - if (file_reader.pos != 0) break :fcf; - if (w.pos != 0) break :fcf; - if (limit != .unlimited) break :fcf; - const size = file_reader.getSize() catch break :fcf; - if (writer_buffered.len != 0 or reader_buffered.len != 0) - return sendFileBuffered(io_w, file_reader, reader_buffered); - const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); - switch (posix.errno(rc)) { - .SUCCESS => {}, - .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { - w.fcopyfile_err = error.Unexpected; - return 0; - }, - .NOMEM => { - w.fcopyfile_err = error.OutOfMemory; - return 0; - }, - .OPNOTSUPP => { - w.fcopyfile_err = error.OperationNotSupported; - return 0; - }, - else => |err| { - w.fcopyfile_err = posix.unexpectedErrno(err); - return 0; - }, - } - file_reader.pos = size; - w.pos = size; - return size; - } - - return error.Unimplemented; - } - - fn sendFileBuffered( - io_w: *Io.Writer, - file_reader: *Io.File.Reader, - reader_buffered: []const u8, - ) Io.Writer.FileError!usize { - const n = try drain(io_w, &.{reader_buffered}, 1); - file_reader.seekBy(@intCast(n)) catch return error.ReadFailed; - return n; - } - - pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void { - try w.interface.flush(); - try seekToUnbuffered(w, offset); - } - - /// Asserts that no data is currently buffered. - pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void { - assert(w.interface.buffered().len == 0); - switch (w.mode) { - .positional, .positional_reading => { - w.pos = offset; - }, - .streaming, .streaming_reading => { - if (w.seek_err) |err| return err; - posix.lseek_SET(w.file.handle, offset) catch |err| { - w.seek_err = err; - return err; - }; - w.pos = offset; - }, - .failure => return w.seek_err.?, - } - } - - pub const EndError = SetEndPosError || Io.Writer.Error; - - /// Flushes any buffered data and sets the end position of the file. - /// - /// If not overwriting existing contents, then calling `interface.flush` - /// directly is sufficient. - /// - /// Flush failure is handled by setting `err` so that it can be handled - /// along with other write failures. - pub fn end(w: *Writer) EndError!void { - try w.interface.flush(); - switch (w.mode) { - .positional, - .positional_reading, - => w.file.setEndPos(w.pos) catch |err| switch (err) { - error.NonResizable => return, - else => |e| return e, - }, - - .streaming, - .streaming_reading, - .failure, - => {}, - } - } -}; - -/// Defaults to positional reading; falls back to streaming. -/// -/// Positional is more threadsafe, since the global seek position is not -/// affected. -pub fn reader(file: File, io: Io, buffer: []u8) Reader { - return .init(.{ .handle = file.handle }, io, buffer); -} - -/// Positional is more threadsafe, since the global seek position is not -/// affected, but when such syscalls are not available, preemptively -/// initializing in streaming mode skips a failed syscall. -pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader { - return .initStreaming(.{ .handle = file.handle }, io, buffer); -} - -/// Defaults to positional reading; falls back to streaming. -/// -/// Positional is more threadsafe, since the global seek position is not -/// affected. -pub fn writer(file: File, buffer: []u8) Writer { - return .init(file, buffer); -} - -/// Positional is more threadsafe, since the global seek position is not -/// affected, but when such syscalls are not available, preemptively -/// initializing in streaming mode will skip a failed syscall. -pub fn writerStreaming(file: File, buffer: []u8) Writer { - return .initStreaming(file, buffer); -} - -const range_off: windows.LARGE_INTEGER = 0; -const range_len: windows.LARGE_INTEGER = 1; - -pub const LockError = error{ - SystemResources, - FileLocksNotSupported, -} || posix.UnexpectedError; - -/// Blocks when an incompatible lock is held by another process. -/// A process may hold only one type of lock (shared or exclusive) on -/// a file. When a process terminates in any way, the lock is released. -/// -/// Assumes the file is unlocked. -/// -/// TODO: integrate with async I/O -pub fn lock(file: File, l: Lock) LockError!void { - if (is_windows) { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const exclusive = switch (l) { - .none => return, - .shared => false, - .exclusive => true, - }; - return windows.LockFile( - file.handle, - null, - null, - null, - &io_status_block, - &range_off, - &range_len, - null, - windows.FALSE, // non-blocking=false - @intFromBool(exclusive), - ) catch |err| switch (err) { - error.WouldBlock => unreachable, // non-blocking=false - else => |e| return e, - }; - } else { - return posix.flock(file.handle, switch (l) { - .none => posix.LOCK.UN, - .shared => posix.LOCK.SH, - .exclusive => posix.LOCK.EX, - }) catch |err| switch (err) { - error.WouldBlock => unreachable, // non-blocking=false - else => |e| return e, - }; - } -} - -/// Assumes the file is locked. -pub fn unlock(file: File) void { - if (is_windows) { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - return windows.UnlockFile( - file.handle, - &io_status_block, - &range_off, - &range_len, - 0, - ) catch |err| switch (err) { - error.RangeNotLocked => unreachable, // Function assumes unlocked. - error.Unexpected => unreachable, // Resource deallocation must succeed. - }; - } else { - return posix.flock(file.handle, posix.LOCK.UN) catch |err| switch (err) { - error.WouldBlock => unreachable, // unlocking can't block - error.SystemResources => unreachable, // We are deallocating resources. - error.FileLocksNotSupported => unreachable, // We already got the lock. - error.Unexpected => unreachable, // Resource deallocation must succeed. - }; - } -} - -/// Attempts to obtain a lock, returning `true` if the lock is -/// obtained, and `false` if there was an existing incompatible lock held. -/// A process may hold only one type of lock (shared or exclusive) on -/// a file. When a process terminates in any way, the lock is released. -/// -/// Assumes the file is unlocked. -/// -/// TODO: integrate with async I/O -pub fn tryLock(file: File, l: Lock) LockError!bool { - if (is_windows) { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const exclusive = switch (l) { - .none => return, - .shared => false, - .exclusive => true, - }; - windows.LockFile( - file.handle, - null, - null, - null, - &io_status_block, - &range_off, - &range_len, - null, - windows.TRUE, // non-blocking=true - @intFromBool(exclusive), - ) catch |err| switch (err) { - error.WouldBlock => return false, - else => |e| return e, - }; - } else { - posix.flock(file.handle, switch (l) { - .none => posix.LOCK.UN, - .shared => posix.LOCK.SH | posix.LOCK.NB, - .exclusive => posix.LOCK.EX | posix.LOCK.NB, - }) catch |err| switch (err) { - error.WouldBlock => return false, - else => |e| return e, - }; - } - return true; -} - -/// Assumes the file is already locked in exclusive mode. -/// Atomically modifies the lock to be in shared mode, without releasing it. -/// -/// TODO: integrate with async I/O -pub fn downgradeLock(file: File) LockError!void { - if (is_windows) { - // On Windows it works like a semaphore + exclusivity flag. To implement this - // function, we first obtain another lock in shared mode. This changes the - // exclusivity flag, but increments the semaphore to 2. So we follow up with - // an NtUnlockFile which decrements the semaphore but does not modify the - // exclusivity flag. - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - windows.LockFile( - file.handle, - null, - null, - null, - &io_status_block, - &range_off, - &range_len, - null, - windows.TRUE, // non-blocking=true - windows.FALSE, // exclusive=false - ) catch |err| switch (err) { - error.WouldBlock => unreachable, // File was not locked in exclusive mode. - else => |e| return e, - }; - return windows.UnlockFile( - file.handle, - &io_status_block, - &range_off, - &range_len, - 0, - ) catch |err| switch (err) { - error.RangeNotLocked => unreachable, // File was not locked. - error.Unexpected => unreachable, // Resource deallocation must succeed. - }; - } else { - return posix.flock(file.handle, posix.LOCK.SH | posix.LOCK.NB) catch |err| switch (err) { - error.WouldBlock => unreachable, // File was not locked in exclusive mode. - else => |e| return e, - }; - } -} - -pub fn adaptToNewApi(file: File) Io.File { - return .{ .handle = file.handle }; -} - -pub fn adaptFromNewApi(file: Io.File) File { - return .{ .handle = file.handle }; -} |
