aboutsummaryrefslogtreecommitdiff
path: root/lib/std/fs/File.zig
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/fs/File.zig')
-rw-r--r--lib/std/fs/File.zig1437
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, &times);
-}
-
-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 };
-}