structstd.fs.File[src]

Types

TypeReader[src]

Source Code

Source code
pub const Reader = io.Reader(File, ReadError, read)

TypeWriter[src]

Source Code

Source code
pub const Writer = io.Writer(File, WriteError, write)

TypeSeekableStream[src]

Source Code

Source code
pub const SeekableStream = io.SeekableStream(
    File,
    SeekError,
    GetSeekPosError,
    seekTo,
    seekBy,
    getPos,
    getEndPos,
)

Fields

handle: Handle

The OS-specific file descriptor or file handle.

Values

Constantfd_t[src]

Source Code

Source code
pub const fd_t = system.fd_t

Constantmode_t[src]

Source Code

Source code
pub const mode_t = system.mode_t

Constantino_t[src]

Source Code

Source code
pub const ino_t = system.ino_t

Constantuid_t[src]

Source Code

Source code
pub const uid_t = system.uid_t

Constantgid_t[src]

Source Code

Source code
pub const gid_t = system.gid_t

Constantdefault_mode[src]

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.

Source Code

Source code
pub const default_mode = switch (builtin.os.tag) {
    .windows => 0,
    .wasi => 0,
    else => 0o666,
}

Error Sets

Error SetOpenError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied OpenError

In WASI, this error may occur when the file descriptor does not hold the required rights to open a new resource relative to it.

AntivirusInterference

On Windows, antivirus software is enabled by default. It can be disabled, but Windows Update sometimes ignores the user's preference and re-enables it. When enabled, antivirus software on Windows intercepts file system operations and makes them significantly slower in addition to possibly failing with this error code.

BadPathName

On Windows, file paths cannot contain these characters: '/', '*', '?', '"', '<', '>', '|'

DeviceBusy OpenError
FileBusy OpenError

One of these three things:

  • pathname refers to an executable image which is currently being executed and write access was requested.
  • pathname refers to a file that is currently in use as a swap file, and the O_TRUNC flag was specified.
  • pathname refers to a file that is currently being read by the kernel (e.g., for module/firmware loading), and write access was requested.
FileLocksNotSupported OpenError

The underlying filesystem does not support file locks

FileNotFound OpenError

Either:

  • One of the path components does not exist.
  • Cwd was used, but cwd has been deleted.
  • The path associated with the open directory handle has been deleted.
  • On macOS, multiple processes or threads raced to create the same file with O.EXCL set to false.
FileTooBig OpenError

The file is too large to be opened. This error is unreachable for 64-bit targets, as well as when opening directories.

InvalidUtf8

WASI-only; file paths must be valid UTF-8.

InvalidWtf8

Windows-only; file paths provided by the user must be valid WTF-8. https://simonsapin.github.io/wtf-8/

IsDir OpenError

The path refers to directory but the DIRECTORY flag was not provided.

NameTooLong OpenError

The path exceeded max_path_bytes bytes.

NetworkNotFound

On Windows, \\server or \\server\share was not found.

NoDevice
NoSpaceLeft OpenError

A new path cannot be created because the device has no room for the new file. This error is only reachable when the CREAT flag is provided.

NotDir OpenError

A component used as a directory in the path was not, in fact, a directory, or DIRECTORY was specified and the path was not a directory.

PathAlreadyExists OpenError

The path already exists and the CREAT and EXCL flags were provided.

PipeBusy
ProcessFdQuotaExceeded OpenError
SharingViolation
SymLinkLoop OpenError
SystemFdQuotaExceeded OpenError
SystemResources OpenError

Insufficient kernel memory was available, or the named file is a FIFO and per-user hard limit on memory allocation for pipes has been reached.

Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

WouldBlock OpenError

Source Code

Source code
pub const OpenError = error{
    SharingViolation,
    PathAlreadyExists,
    FileNotFound,
    AccessDenied,
    PipeBusy,
    NoDevice,
    NameTooLong,
    /// WASI-only; file paths must be valid UTF-8.
    InvalidUtf8,
    /// Windows-only; file paths provided by the user must be valid WTF-8.
    /// https://simonsapin.github.io/wtf-8/
    InvalidWtf8,
    /// On Windows, file paths cannot contain these characters:
    /// '/', '*', '?', '"', '<', '>', '|'
    BadPathName,
    Unexpected,
    /// On Windows, `\\server` or `\\server\share` was not found.
    NetworkNotFound,
    /// On Windows, antivirus software is enabled by default. It can be
    /// disabled, but Windows Update sometimes ignores the user's preference
    /// and re-enables it. When enabled, antivirus software on Windows
    /// intercepts file system operations and makes them significantly slower
    /// in addition to possibly failing with this error code.
    AntivirusInterference,
} || posix.OpenError || posix.FlockError

Error SetSyncError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied
DiskQuota
InputOutput
NoSpaceLeft
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const SyncError = error{
    InputOutput,
    NoSpaceLeft,
    DiskQuota,
    AccessDenied,
} || UnexpectedError

Error SetTruncateError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied

In WASI, this error occurs when the file descriptor does not hold the required rights to call ftruncate on it.

FileBusy
FileTooBig
InputOutput
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const TruncateError = error{
    FileTooBig,
    InputOutput,
    FileBusy,

    /// In WASI, this error occurs when the file descriptor does
    /// not hold the required rights to call `ftruncate` on it.
    AccessDenied,
} || UnexpectedError

Error SetSeekError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied

In WASI, this error may occur when the file descriptor does not hold the required rights to seek on it.

Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Unseekable

Source Code

Source code
pub const SeekError = error{
    Unseekable,

    /// In WASI, this error may occur when the file descriptor does
    /// not hold the required rights to seek on it.
    AccessDenied,
} || UnexpectedError

Error SetGetSeekPosError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied SeekError

In WASI, this error may occur when the file descriptor does not hold the required rights to seek on it.

SystemResources FStatError
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Unseekable SeekError

Source Code

Source code
pub const GetSeekPosError = posix.SeekError || StatError

Error SetFStatError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied

In WASI, this error may occur when the file descriptor does not hold the required rights to get its filestat information.

SystemResources
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const FStatError = error{
    SystemResources,

    /// In WASI, this error may occur when the file descriptor does
    /// not hold the required rights to get its filestat information.
    AccessDenied,
} || UnexpectedError

Error SetFStatError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied

In WASI, this error may occur when the file descriptor does not hold the required rights to get its filestat information.

SystemResources
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const FStatError = error{
    SystemResources,

    /// In WASI, this error may occur when the file descriptor does
    /// not hold the required rights to get its filestat information.
    AccessDenied,
} || UnexpectedError

Error SetFChmodError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied
FileNotFound
InputOutput
ReadOnlyFileSystem
SymLinkLoop
SystemResources
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const FChmodError = error{
    AccessDenied,
    InputOutput,
    SymLinkLoop,
    FileNotFound,
    SystemResources,
    ReadOnlyFileSystem,
} || UnexpectedError

Error SetFChownError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied
FileNotFound
InputOutput
ReadOnlyFileSystem
SymLinkLoop
SystemResources
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const FChownError = error{
    AccessDenied,
    InputOutput,
    SymLinkLoop,
    FileNotFound,
    SystemResources,
    ReadOnlyFileSystem,
} || UnexpectedError

Error SetFChmodError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied
FileNotFound
InputOutput
ReadOnlyFileSystem
SymLinkLoop
SystemResources
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const FChmodError = error{
    AccessDenied,
    InputOutput,
    SymLinkLoop,
    FileNotFound,
    SystemResources,
    ReadOnlyFileSystem,
} || UnexpectedError

Error SetFStatError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied

In WASI, this error may occur when the file descriptor does not hold the required rights to get its filestat information.

SystemResources
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const FStatError = error{
    SystemResources,

    /// In WASI, this error may occur when the file descriptor does
    /// not hold the required rights to get its filestat information.
    AccessDenied,
} || UnexpectedError

Error SetUpdateTimesError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied FutimensError

times is NULL, or both nsec values are UTIME_NOW, and either:

  • the effective user ID of the caller does not match the owner of the file, the caller does not have write access to the file, and the caller is not privileged (Linux: does not have either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability); or,
  • the file is marked immutable (see chattr(1)).
PermissionDenied FutimensError

The caller attempted to change one or both timestamps to a value other than the current time, or to change one of the timestamps to the current time while leaving the other timestamp unchanged, (i.e., times is not NULL, neither nsec field is UTIME_NOW, and neither nsec field is UTIME_OMIT) and either:

  • the caller's effective user ID does not match the owner of file, and the caller is not privileged (Linux: does not have the CAP_FOWNER capability); or,
  • the file is marked append-only or immutable (see chattr(1)).
ReadOnlyFileSystem FutimensError
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError

Error SetReadError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied

In WASI, this error occurs when the file descriptor does not hold the required rights to read from it.

BrokenPipe
Canceled

reading a timerfd with CANCEL_ON_SET will lead to this error when the clock goes through a discontinuous change

ConnectionResetByPeer
ConnectionTimedOut
InputOutput
IsDir
LockViolation

Unable to read file due to lock.

NotOpenForReading
OperationAborted
ProcessNotFound

This error occurs in Linux if the process to be read from no longer exists.

SocketNotConnected
SystemResources
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

WouldBlock

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const ReadError = error{
    InputOutput,
    SystemResources,
    IsDir,
    OperationAborted,
    BrokenPipe,
    ConnectionResetByPeer,
    ConnectionTimedOut,
    NotOpenForReading,
    SocketNotConnected,

    /// This error occurs when no global event loop is configured,
    /// and reading from the file descriptor would block.
    WouldBlock,

    /// reading a timerfd with CANCEL_ON_SET will lead to this error
    /// when the clock goes through a discontinuous change
    Canceled,

    /// In WASI, this error occurs when the file descriptor does
    /// not hold the required rights to read from it.
    AccessDenied,

    /// This error occurs in Linux if the process to be read from
    /// no longer exists.
    ProcessNotFound,

    /// Unable to read file due to lock.
    LockViolation,
} || UnexpectedError

Error SetPReadError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied ReadError

In WASI, this error occurs when the file descriptor does not hold the required rights to read from it.

BrokenPipe ReadError
Canceled ReadError

reading a timerfd with CANCEL_ON_SET will lead to this error when the clock goes through a discontinuous change

ConnectionResetByPeer ReadError
ConnectionTimedOut ReadError
InputOutput ReadError
IsDir ReadError
LockViolation ReadError

Unable to read file due to lock.

NotOpenForReading ReadError
OperationAborted ReadError
ProcessNotFound ReadError

This error occurs in Linux if the process to be read from no longer exists.

SocketNotConnected ReadError
SystemResources ReadError
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Unseekable
WouldBlock ReadError

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const PReadError = ReadError || error{Unseekable}

Error SetWriteError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied

File descriptor does not hold the required rights to write to it.

BrokenPipe
ConnectionResetByPeer

Connection reset by peer.

DeviceBusy
DiskQuota
FileTooBig
InputOutput
InvalidArgument
LockViolation

The process cannot access the file because another process has locked a portion of the file. Windows-only.

NoDevice

This error occurs when a device gets disconnected before or mid-flush while it's being written to - errno(6): No such device or address.

NoSpaceLeft
NotOpenForWriting
OperationAborted
ProcessNotFound

This error occurs in Linux if the process being written to no longer exists.

SystemResources
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

WouldBlock

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const WriteError = error{
    DiskQuota,
    FileTooBig,
    InputOutput,
    NoSpaceLeft,
    DeviceBusy,
    InvalidArgument,

    /// File descriptor does not hold the required rights to write to it.
    AccessDenied,
    BrokenPipe,
    SystemResources,
    OperationAborted,
    NotOpenForWriting,

    /// The process cannot access the file because another process has locked
    /// a portion of the file. Windows-only.
    LockViolation,

    /// This error occurs when no global event loop is configured,
    /// and reading from the file descriptor would block.
    WouldBlock,

    /// Connection reset by peer.
    ConnectionResetByPeer,

    /// This error occurs in Linux if the process being written to
    /// no longer exists.
    ProcessNotFound,
    /// This error occurs when a device gets disconnected before or mid-flush
    /// while it's being written to - errno(6): No such device or address.
    NoDevice,
} || UnexpectedError

Error SetPWriteError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied WriteError

File descriptor does not hold the required rights to write to it.

BrokenPipe WriteError
ConnectionResetByPeer WriteError

Connection reset by peer.

DeviceBusy WriteError
DiskQuota WriteError
FileTooBig WriteError
InputOutput WriteError
InvalidArgument WriteError
LockViolation WriteError

The process cannot access the file because another process has locked a portion of the file. Windows-only.

NoDevice WriteError

This error occurs when a device gets disconnected before or mid-flush while it's being written to - errno(6): No such device or address.

NoSpaceLeft WriteError
NotOpenForWriting WriteError
OperationAborted WriteError
ProcessNotFound WriteError

This error occurs in Linux if the process being written to no longer exists.

SystemResources WriteError
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Unseekable
WouldBlock WriteError

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const PWriteError = WriteError || error{Unseekable}

Error SetCopyFileRangeError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied ReadError

In WASI, this error occurs when the file descriptor does not hold the required rights to read from it.

BrokenPipe ReadError
Canceled ReadError

reading a timerfd with CANCEL_ON_SET will lead to this error when the clock goes through a discontinuous change

ConnectionResetByPeer WriteError

Connection reset by peer.

ConnectionTimedOut ReadError
CorruptedData
DeviceBusy WriteError
DiskQuota WriteError
FileTooBig
FilesOpenedWithWrongFlags

fd_in is not open for reading; or fd_out is not open for writing; or the APPEND flag is set for fd_out.

InputOutput
InvalidArgument WriteError
IsDir
LockViolation ReadError

Unable to read file due to lock.

NoDevice WriteError

This error occurs when a device gets disconnected before or mid-flush while it's being written to - errno(6): No such device or address.

NoSpaceLeft
NotOpenForReading ReadError
NotOpenForWriting WriteError
OperationAborted ReadError
OutOfMemory
PermissionDenied
ProcessNotFound ReadError

This error occurs in Linux if the process to be read from no longer exists.

SocketNotConnected ReadError
SwapFile
SystemResources ReadError
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Unseekable
WouldBlock ReadError

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const CopyFileRangeError = error{
    FileTooBig,
    InputOutput,
    /// `fd_in` is not open for reading; or `fd_out` is not open  for  writing;
    /// or the  `APPEND`  flag  is  set  for `fd_out`.
    FilesOpenedWithWrongFlags,
    IsDir,
    OutOfMemory,
    NoSpaceLeft,
    Unseekable,
    PermissionDenied,
    SwapFile,
    CorruptedData,
} || PReadError || PWriteError || UnexpectedError

Error SetWriteFileError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied ReadError

In WASI, this error occurs when the file descriptor does not hold the required rights to read from it.

BrokenPipe ReadError
Canceled ReadError

reading a timerfd with CANCEL_ON_SET will lead to this error when the clock goes through a discontinuous change

ConnectionResetByPeer WriteError

Connection reset by peer.

ConnectionTimedOut ReadError
DeviceBusy WriteError
DiskQuota WriteError
EndOfStream
FileTooBig WriteError
InputOutput ReadError
InvalidArgument WriteError
IsDir ReadError
LockViolation ReadError

Unable to read file due to lock.

NoDevice WriteError

This error occurs when a device gets disconnected before or mid-flush while it's being written to - errno(6): No such device or address.

NoSpaceLeft WriteError
NotOpenForReading ReadError
NotOpenForWriting WriteError
OperationAborted ReadError
ProcessNotFound ReadError

This error occurs in Linux if the process to be read from no longer exists.

SocketNotConnected ReadError
SystemResources ReadError
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

WouldBlock ReadError

This error occurs when no global event loop is configured, and reading from the file descriptor would block.

Source Code

Source code
pub const WriteFileError = ReadError || error{EndOfStream} || WriteError

Error SetLockError[src]

Errors

anyerror means the error set is known only at runtime.

FileLocksNotSupported
SystemResources
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const LockError = error{
    SystemResources,
    FileLocksNotSupported,
} || posix.UnexpectedError

Functions

Functionclose[src]

pub fn close(self: File) void

Upon success, the stream is in an uninitialized state. To continue using it, you must use the open() function.

Parameters

self: File

Source Code

Source code
pub fn close(self: File) void {
    if (is_windows) {
        windows.CloseHandle(self.handle);
    } else {
        posix.close(self.handle);
    }
}

Functionsync[src]

pub fn sync(self: File) SyncError!void

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.

Parameters

self: File

Source Code

Source code
pub fn sync(self: File) SyncError!void {
    return posix.fsync(self.handle);
}

FunctionisTty[src]

pub fn isTty(self: File) bool

Test whether the file refers to a terminal. See also getOrEnableAnsiEscapeSupport and supportsAnsiEscapeCodes.

Parameters

self: File

Source Code

Source code
pub fn isTty(self: File) bool {
    return posix.isatty(self.handle);
}

FunctionisCygwinPty[src]

pub fn isCygwinPty(file: File) bool

Parameters

file: File

Source Code

Source code
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), .FileFsDeviceInformation);
        switch (rc) {
            .SUCCESS => {},
            else => return false,
        }
        if (device_info.DeviceType != windows.FILE_DEVICE_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), .FileNameInformation);
    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.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null;
}

FunctiongetOrEnableAnsiEscapeSupport[src]

pub fn getOrEnableAnsiEscapeSupport(self: File) bool

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.

Parameters

self: File

Source Code

Source code
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();
}

FunctionsupportsAnsiEscapeCodes[src]

pub fn supportsAnsiEscapeCodes(self: File) bool

Test whether ANSI escape codes will be treated as such without attempting to enable support for ANSI escape codes.

See also getOrEnableAnsiEscapeSupport.

Parameters

self: File

Source Code

Source code
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;
}

FunctionsetEndPos[src]

pub fn setEndPos(self: File, length: u64) SetEndPosError!void

Shrinks or expands the file. The file offset after this call is left unchanged.

Parameters

self: File
length: u64

Source Code

Source code
pub fn setEndPos(self: File, length: u64) SetEndPosError!void {
    try posix.ftruncate(self.handle, length);
}

FunctionseekBy[src]

pub fn seekBy(self: File, offset: i64) SeekError!void

Repositions read/write file offset relative to the current offset. TODO: integrate with async I/O

Parameters

self: File
offset: i64

Source Code

Source code
pub fn seekBy(self: File, offset: i64) SeekError!void {
    return posix.lseek_CUR(self.handle, offset);
}

FunctionseekFromEnd[src]

pub fn seekFromEnd(self: File, offset: i64) SeekError!void

Repositions read/write file offset relative to the end. TODO: integrate with async I/O

Parameters

self: File
offset: i64

Source Code

Source code
pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
    return posix.lseek_END(self.handle, offset);
}

FunctionseekTo[src]

pub fn seekTo(self: File, offset: u64) SeekError!void

Repositions read/write file offset relative to the beginning. TODO: integrate with async I/O

Parameters

self: File
offset: u64

Source Code

Source code
pub fn seekTo(self: File, offset: u64) SeekError!void {
    return posix.lseek_SET(self.handle, offset);
}

FunctiongetPos[src]

pub fn getPos(self: File) GetSeekPosError!u64

TODO: integrate with async I/O

Parameters

self: File

Source Code

Source code
pub fn getPos(self: File) GetSeekPosError!u64 {
    return posix.lseek_CUR_get(self.handle);
}

FunctiongetEndPos[src]

pub fn getEndPos(self: File) GetSeekPosError!u64

TODO: integrate with async I/O

Parameters

self: File

Source Code

Source code
pub fn getEndPos(self: File) GetSeekPosError!u64 {
    if (builtin.os.tag == .windows) {
        return windows.GetFileSizeEx(self.handle);
    }
    return (try self.stat()).size;
}

Functionmode[src]

pub fn mode(self: File) ModeError!Mode

TODO: integrate with async I/O

Parameters

self: File

Source Code

Source code
pub fn mode(self: File) ModeError!Mode {
    if (builtin.os.tag == .windows) {
        return 0;
    }
    return (try self.stat()).mode;
}

Functionstat[src]

pub fn stat(self: File) StatError!Stat

Returns Stat containing basic information about the File. Use metadata to retrieve more detailed information (e.g. creation time, permissions). TODO: integrate with async I/O

Parameters

self: File

Source Code

Source code
pub fn stat(self: File) StatError!Stat {
    if (builtin.os.tag == .windows) {
        var io_status_block: windows.IO_STATUS_BLOCK = undefined;
        var info: windows.FILE_ALL_INFORMATION = undefined;
        const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
        switch (rc) {
            .SUCCESS => {},
            // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
            // size provided. This is treated as success because the type of variable-length information that this would be relevant for
            // (name, volume name, etc) we don't care about.
            .BUFFER_OVERFLOW => {},
            .INVALID_PARAMETER => unreachable,
            .ACCESS_DENIED => return error.AccessDenied,
            else => return windows.unexpectedStatus(rc),
        }
        return .{
            .inode = info.InternalInformation.IndexNumber,
            .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
            .mode = 0,
            .kind = if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) reparse_point: {
                var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined;
                const tag_rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation);
                switch (tag_rc) {
                    .SUCCESS => {},
                    // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
                    // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
                    .INFO_LENGTH_MISMATCH => unreachable,
                    .ACCESS_DENIED => return error.AccessDenied,
                    else => return windows.unexpectedStatus(rc),
                }
                if (tag_info.ReparseTag & windows.reparse_tag_name_surrogate_bit != 0) {
                    break :reparse_point .sym_link;
                }
                // Unknown reparse point
                break :reparse_point .unknown;
            } else if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0)
                .directory
            else
                .file,
            .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
            .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
            .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
        };
    }

    if (builtin.os.tag == .wasi and !builtin.link_libc) {
        const st = try std.os.fstat_wasi(self.handle);
        return Stat.fromWasi(st);
    }

    if (builtin.os.tag == .linux) {
        var stx = std.mem.zeroes(linux.Statx);

        const rc = linux.statx(
            self.handle,
            "",
            linux.AT.EMPTY_PATH,
            linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
            &stx,
        );

        return switch (linux.E.init(rc)) {
            .SUCCESS => Stat.fromLinux(stx),
            .ACCES => unreachable,
            .BADF => unreachable,
            .FAULT => unreachable,
            .INVAL => unreachable,
            .LOOP => unreachable,
            .NAMETOOLONG => unreachable,
            .NOENT => unreachable,
            .NOMEM => error.SystemResources,
            .NOTDIR => unreachable,
            else => |err| posix.unexpectedErrno(err),
        };
    }

    const st = try posix.fstat(self.handle);
    return Stat.fromPosix(st);
}

Functionchmod[src]

pub fn chmod(self: File, new_mode: Mode) ChmodError!void

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.

Parameters

self: File
new_mode: Mode

Source Code

Source code
pub fn chmod(self: File, new_mode: Mode) ChmodError!void {
    try posix.fchmod(self.handle, new_mode);
}

Functionchown[src]

pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void

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.

Parameters

self: File
owner: ?Uid
group: ?Gid

Source Code

Source code
pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void {
    try posix.fchown(self.handle, owner, group);
}

FunctionsetPermissions[src]

pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void

Sets permissions according to the provided Permissions struct. This method is NOT available on WASI

Parameters

self: File
permissions: Permissions

Source Code

Source code
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),
                .FileBasicInformation,
            );
            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);
        },
    }
}

Functionmetadata[src]

pub fn metadata(self: File) MetadataError!Metadata

Parameters

self: File

Source Code

Source code
pub fn metadata(self: File) MetadataError!Metadata {
    return .{
        .inner = switch (builtin.os.tag) {
            .windows => blk: {
                var io_status_block: windows.IO_STATUS_BLOCK = undefined;
                var info: windows.FILE_ALL_INFORMATION = undefined;

                const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
                switch (rc) {
                    .SUCCESS => {},
                    // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
                    // size provided. This is treated as success because the type of variable-length information that this would be relevant for
                    // (name, volume name, etc) we don't care about.
                    .BUFFER_OVERFLOW => {},
                    .INVALID_PARAMETER => unreachable,
                    .ACCESS_DENIED => return error.AccessDenied,
                    else => return windows.unexpectedStatus(rc),
                }

                const reparse_tag: windows.DWORD = reparse_blk: {
                    if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
                        var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined;
                        const tag_rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation);
                        switch (tag_rc) {
                            .SUCCESS => {},
                            // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
                            // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
                            .INFO_LENGTH_MISMATCH => unreachable,
                            .ACCESS_DENIED => return error.AccessDenied,
                            else => return windows.unexpectedStatus(rc),
                        }
                        break :reparse_blk tag_info.ReparseTag;
                    }
                    break :reparse_blk 0;
                };

                break :blk .{
                    .attributes = info.BasicInformation.FileAttributes,
                    .reparse_tag = reparse_tag,
                    ._size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
                    .access_time = windows.fromSysTime(info.BasicInformation.LastAccessTime),
                    .modified_time = windows.fromSysTime(info.BasicInformation.LastWriteTime),
                    .creation_time = windows.fromSysTime(info.BasicInformation.CreationTime),
                };
            },
            .linux => blk: {
                var stx = std.mem.zeroes(linux.Statx);

                // We are gathering information for Metadata, which is meant to contain all the
                // native OS information about the file, so use all known flags.
                const rc = linux.statx(
                    self.handle,
                    "",
                    linux.AT.EMPTY_PATH,
                    linux.STATX_BASIC_STATS | linux.STATX_BTIME,
                    &stx,
                );

                switch (linux.E.init(rc)) {
                    .SUCCESS => {},
                    .ACCES => unreachable,
                    .BADF => unreachable,
                    .FAULT => unreachable,
                    .INVAL => unreachable,
                    .LOOP => unreachable,
                    .NAMETOOLONG => unreachable,
                    .NOENT => unreachable,
                    .NOMEM => return error.SystemResources,
                    .NOTDIR => unreachable,
                    else => |err| return posix.unexpectedErrno(err),
                }

                break :blk .{
                    .statx = stx,
                };
            },
            .wasi => .{ .stat = try std.os.fstat_wasi(self.handle) },
            else => .{ .stat = try posix.fstat(self.handle) },
        },
    };
}

FunctionupdateTimes[src]

pub fn updateTimes( self: File, atime: i128, mtime: i128, ) UpdateTimesError!void

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

Parameters

self: File
atime: i128

access timestamp in nanoseconds

mtime: i128

last modification timestamp in nanoseconds

Source Code

Source code
pub fn updateTimes(
    self: File,
    /// access timestamp in nanoseconds
    atime: i128,
    /// last modification timestamp in nanoseconds
    mtime: i128,
) 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, std.time.ns_per_s)) orelse maxInt(isize),
            .nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize),
        },
        posix.timespec{
            .sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize),
            .nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize),
        },
    };
    try posix.futimens(self.handle, &times);
}

FunctionreadToEndAlloc[src]

pub fn readToEndAlloc(self: File, allocator: Allocator, max_bytes: usize) ![]u8

Reads all the bytes from the current position to the end of the file. On success, caller owns returned buffer. If the file is larger than max_bytes, returns error.FileTooBig.

Parameters

self: File
allocator: Allocator
max_bytes: usize

Source Code

Source code
pub fn readToEndAlloc(self: File, allocator: Allocator, max_bytes: usize) ![]u8 {
    return self.readToEndAllocOptions(allocator, max_bytes, null, @alignOf(u8), null);
}

FunctionreadToEndAllocOptions[src]

pub fn readToEndAllocOptions( self: File, allocator: Allocator, max_bytes: usize, size_hint: ?usize, comptime alignment: u29, comptime optional_sentinel: ?u8, ) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8)

Reads all the bytes from the current position to the end of the file. On success, caller owns returned buffer. If the file is larger than max_bytes, returns error.FileTooBig. If size_hint is specified the initial buffer size is calculated using that value, otherwise an arbitrary value is used instead. Allows specifying alignment and a sentinel value.

Parameters

self: File
allocator: Allocator
max_bytes: usize
size_hint: ?usize
alignment: u29
optional_sentinel: ?u8

Source Code

Source code
pub fn readToEndAllocOptions(
    self: File,
    allocator: Allocator,
    max_bytes: usize,
    size_hint: ?usize,
    comptime alignment: u29,
    comptime optional_sentinel: ?u8,
) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
    // If no size hint is provided fall back to the size=0 code path
    const size = size_hint orelse 0;

    // The file size returned by stat is used as hint to set the buffer
    // size. If the reported size is zero, as it happens on Linux for files
    // in /proc, a small buffer is allocated instead.
    const initial_cap = @min((if (size > 0) size else 1024), max_bytes) + @intFromBool(optional_sentinel != null);
    var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap);
    defer array_list.deinit();

    self.reader().readAllArrayListAligned(alignment, &array_list, max_bytes) catch |err| switch (err) {
        error.StreamTooLong => return error.FileTooBig,
        else => |e| return e,
    };

    if (optional_sentinel) |sentinel| {
        return try array_list.toOwnedSliceSentinel(sentinel);
    } else {
        return try array_list.toOwnedSlice();
    }
}

Functionread[src]

pub fn read(self: File, buffer: []u8) ReadError!usize

Parameters

self: File
buffer: []u8

Source Code

Source code
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);
}

FunctionreadAll[src]

pub fn readAll(self: File, buffer: []u8) ReadError!usize

Returns the number of bytes read. If the number read is smaller than buffer.len, it means the file reached the end. Reaching the end of a file is not an error condition.

Parameters

self: File
buffer: []u8

Source Code

Source code
pub fn readAll(self: File, buffer: []u8) ReadError!usize {
    var index: usize = 0;
    while (index != buffer.len) {
        const amt = try self.read(buffer[index..]);
        if (amt == 0) break;
        index += amt;
    }
    return index;
}

Functionpread[src]

pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize

On Windows, this function currently does alter the file pointer. https://github.com/ziglang/zig/issues/12783

Parameters

self: File
buffer: []u8
offset: u64

Source Code

Source code
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);
}

FunctionpreadAll[src]

pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize

Returns the number of bytes read. If the number read is smaller than buffer.len, it means the file reached the end. Reaching the end of a file is not an error condition. On Windows, this function currently does alter the file pointer. https://github.com/ziglang/zig/issues/12783

Parameters

self: File
buffer: []u8
offset: u64

Source Code

Source code
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;
}

Functionreadv[src]

pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize

Parameters

self: File
iovecs: []const posix.iovec

Source Code

Source code
pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize {
    if (is_windows) {
        // TODO improve this to use ReadFileScatter
        if (iovecs.len == 0) return @as(usize, 0);
        const first = iovecs[0];
        return windows.ReadFile(self.handle, first.base[0..first.len], null);
    }

    return posix.readv(self.handle, iovecs);
}

FunctionreadvAll[src]

pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize

Returns the number of bytes read. If the number read is smaller than the total bytes from all the buffers, it means the file reached the end. Reaching the end of a file is not an error condition.

The iovecs parameter is mutable because:

  • This function needs to mutate the fields in order to handle partial reads from the underlying OS layer.
  • The OS layer expects pointer addresses to be inside the application's address space even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer addresses when the length is zero. So this function modifies the base fields when the length is zero.

Related open issue: https://github.com/ziglang/zig/issues/7699

Parameters

self: File
iovecs: []posix.iovec

Source Code

Source code
pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize {
    if (iovecs.len == 0) return 0;

    // We use the address of this local variable for all zero-length
    // vectors so that the OS does not complain that we are giving it
    // addresses outside the application's address space.
    var garbage: [1]u8 = undefined;
    for (iovecs) |*v| {
        if (v.len == 0) v.base = &garbage;
    }

    var i: usize = 0;
    var off: usize = 0;
    while (true) {
        var amt = try self.readv(iovecs[i..]);
        var eof = amt == 0;
        off += amt;
        while (amt >= iovecs[i].len) {
            amt -= iovecs[i].len;
            i += 1;
            if (i >= iovecs.len) return off;
            eof = false;
        }
        if (eof) return off;
        iovecs[i].base += amt;
        iovecs[i].len -= amt;
    }
}

Functionpreadv[src]

pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!usize

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

Parameters

self: File
iovecs: []const posix.iovec
offset: u64

Source Code

Source code
pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!usize {
    if (is_windows) {
        // TODO improve this to use ReadFileScatter
        if (iovecs.len == 0) return @as(usize, 0);
        const first = iovecs[0];
        return windows.ReadFile(self.handle, first.base[0..first.len], offset);
    }

    return posix.preadv(self.handle, iovecs, offset);
}

FunctionpreadvAll[src]

pub fn preadvAll(self: File, iovecs: []posix.iovec, offset: u64) PReadError!usize

Returns the number of bytes read. If the number read is smaller than the total bytes from all the buffers, it means the file reached the end. Reaching the end of a file is not an error condition. The iovecs parameter is mutable because this function needs to mutate the fields in order to handle partial reads from the underlying OS layer. 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

Parameters

self: File
iovecs: []posix.iovec
offset: u64

Source Code

Source code
pub fn preadvAll(self: File, iovecs: []posix.iovec, offset: u64) PReadError!usize {
    if (iovecs.len == 0) return 0;

    var i: usize = 0;
    var off: usize = 0;
    while (true) {
        var amt = try self.preadv(iovecs[i..], offset + off);
        var eof = amt == 0;
        off += amt;
        while (amt >= iovecs[i].len) {
            amt -= iovecs[i].len;
            i += 1;
            if (i >= iovecs.len) return off;
            eof = false;
        }
        if (eof) return off;
        iovecs[i].base += amt;
        iovecs[i].len -= amt;
    }
}

Functionwrite[src]

pub fn write(self: File, bytes: []const u8) WriteError!usize

Parameters

self: File
bytes: []const u8

Source Code

Source code
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);
}

FunctionwriteAll[src]

pub fn writeAll(self: File, bytes: []const u8) WriteError!void

Parameters

self: File
bytes: []const u8

Source Code

Source code
pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
    var index: usize = 0;
    while (index < bytes.len) {
        index += try self.write(bytes[index..]);
    }
}

Functionpwrite[src]

pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize

On Windows, this function currently does alter the file pointer. https://github.com/ziglang/zig/issues/12783

Parameters

self: File
bytes: []const u8
offset: u64

Source Code

Source code
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);
}

FunctionpwriteAll[src]

pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void

On Windows, this function currently does alter the file pointer. https://github.com/ziglang/zig/issues/12783

Parameters

self: File
bytes: []const u8
offset: u64

Source Code

Source code
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);
    }
}

Functionwritev[src]

pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize

Parameters

self: File
iovecs: []const posix.iovec_const

Source Code

Source code
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 @as(usize, 0);
        const first = iovecs[0];
        return windows.WriteFile(self.handle, first.base[0..first.len], null);
    }

    return posix.writev(self.handle, iovecs);
}

FunctionwritevAll[src]

pub fn writevAll(self: File, iovecs: []posix.iovec_const) WriteError!void

The iovecs parameter is mutable because:

  • This function needs to mutate the fields in order to handle partial writes from the underlying OS layer.
  • The OS layer expects pointer addresses to be inside the application's address space even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer addresses when the length is zero. So this function modifies the base fields when the length is zero. See https://github.com/ziglang/zig/issues/7699 See equivalent function: std.net.Stream.writevAll.

Parameters

self: File
iovecs: []posix.iovec_const

Source Code

Source code
pub fn writevAll(self: File, iovecs: []posix.iovec_const) WriteError!void {
    if (iovecs.len == 0) return;

    // We use the address of this local variable for all zero-length
    // vectors so that the OS does not complain that we are giving it
    // addresses outside the application's address space.
    var garbage: [1]u8 = undefined;
    for (iovecs) |*v| {
        if (v.len == 0) v.base = &garbage;
    }

    var i: usize = 0;
    while (true) {
        var amt = try self.writev(iovecs[i..]);
        while (amt >= iovecs[i].len) {
            amt -= iovecs[i].len;
            i += 1;
            if (i >= iovecs.len) return;
        }
        iovecs[i].base += amt;
        iovecs[i].len -= amt;
    }
}

Functionpwritev[src]

pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!usize

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

Parameters

self: File
iovecs: []posix.iovec_const
offset: u64

Source Code

Source code
pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!usize {
    if (is_windows) {
        // TODO improve this to use WriteFileScatter
        if (iovecs.len == 0) return @as(usize, 0);
        const first = iovecs[0];
        return windows.WriteFile(self.handle, first.base[0..first.len], offset);
    }

    return posix.pwritev(self.handle, iovecs, offset);
}

FunctionpwritevAll[src]

pub fn pwritevAll(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!void

The iovecs parameter is mutable because this function needs to mutate the fields in order to handle partial writes from the underlying OS layer. 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

Parameters

self: File
iovecs: []posix.iovec_const
offset: u64

Source Code

Source code
pub fn pwritevAll(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!void {
    if (iovecs.len == 0) return;

    var i: usize = 0;
    var off: u64 = 0;
    while (true) {
        var amt = try self.pwritev(iovecs[i..], offset + off);
        off += amt;
        while (amt >= iovecs[i].len) {
            amt -= iovecs[i].len;
            i += 1;
            if (i >= iovecs.len) return;
        }
        iovecs[i].base += amt;
        iovecs[i].len -= amt;
    }
}

FunctioncopyRange[src]

pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64

Parameters

in: File
in_offset: u64
out: File
out_offset: u64
len: u64

Source Code

Source code
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;
}

FunctioncopyRangeAll[src]

pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64

Returns the number of bytes copied. If the number read is smaller than buffer.len, it means the in file reached the end. Reaching the end of a file is not an error condition.

Parameters

in: File
in_offset: u64
out: File
out_offset: u64
len: u64

Source Code

Source code
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;
}

FunctionwriteFileAll[src]

pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void

Parameters

self: File
in_file: File

Source Code

Source code
pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
    return self.writeFileAllSendfile(in_file, args) catch |err| switch (err) {
        error.Unseekable,
        error.FastOpenAlreadyInProgress,
        error.MessageTooBig,
        error.FileDescriptorNotASocket,
        error.NetworkUnreachable,
        error.NetworkSubsystemFailed,
        => return self.writeFileAllUnseekable(in_file, args),

        else => |e| return e,
    };
}

FunctionwriteFileAllUnseekable[src]

pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void

Does not try seeking in either of the File parameters. See writeFileAll as an alternative to calling this.

Parameters

self: File
in_file: File

Source Code

Source code
pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
    const headers = args.headers_and_trailers[0..args.header_count];
    const trailers = args.headers_and_trailers[args.header_count..];

    try self.writevAll(headers);

    try in_file.reader().skipBytes(args.in_offset, .{ .buf_size = 4096 });

    var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
    if (args.in_len) |len| {
        var stream = std.io.limitedReader(in_file.reader(), len);
        try fifo.pump(stream.reader(), self.writer());
    } else {
        try fifo.pump(in_file.reader(), self.writer());
    }

    try self.writevAll(trailers);
}

Functionreader[src]

pub fn reader(file: File) Reader

Parameters

file: File

Source Code

Source code
pub fn reader(file: File) Reader {
    return .{ .context = file };
}

Functionwriter[src]

pub fn writer(file: File) Writer

Parameters

file: File

Source Code

Source code
pub fn writer(file: File) Writer {
    return .{ .context = file };
}

FunctionseekableStream[src]

pub fn seekableStream(file: File) SeekableStream

Parameters

file: File

Source Code

Source code
pub fn seekableStream(file: File) SeekableStream {
    return .{ .context = file };
}

Functionlock[src]

pub fn lock(file: File, l: Lock) LockError!void

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

Parameters

file: File
l: Lock

Source Code

Source code
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,
        };
    }
}

Functionunlock[src]

pub fn unlock(file: File) void

Assumes the file is locked.

Parameters

file: File

Source Code

Source code
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,
            null,
        ) 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.
        };
    }
}

FunctiontryLock[src]

pub fn tryLock(file: File, l: Lock) LockError!bool

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

Parameters

file: File
l: Lock

Source Code

Source code
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;
}

FunctiondowngradeLock[src]

pub fn downgradeLock(file: File) LockError!void

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

Parameters

file: File

Source Code

Source code
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,
            null,
        ) 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,
        };
    }
}

Source Code

Source code
/// The OS-specific file descriptor or file handle.
handle: Handle,

pub const Handle = posix.fd_t;
pub const Mode = posix.mode_t;
pub const INode = posix.ino_t;
pub const Uid = posix.uid_t;
pub const Gid = posix.gid_t;

pub const Kind = enum {
    block_device,
    character_device,
    directory,
    named_pipe,
    sym_link,
    file,
    unix_domain_socket,
    whiteout,
    door,
    event_port,
    unknown,
};

/// 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 = switch (builtin.os.tag) {
    .windows => 0,
    .wasi => 0,
    else => 0o666,
};

pub const OpenError = error{
    SharingViolation,
    PathAlreadyExists,
    FileNotFound,
    AccessDenied,
    PipeBusy,
    NoDevice,
    NameTooLong,
    /// WASI-only; file paths must be valid UTF-8.
    InvalidUtf8,
    /// Windows-only; file paths provided by the user must be valid WTF-8.
    /// https://simonsapin.github.io/wtf-8/
    InvalidWtf8,
    /// On Windows, file paths cannot contain these characters:
    /// '/', '*', '?', '"', '<', '>', '|'
    BadPathName,
    Unexpected,
    /// On Windows, `\\server` or `\\server\share` was not found.
    NetworkNotFound,
    /// On Windows, antivirus software is enabled by default. It can be
    /// disabled, but Windows Update sometimes ignores the user's preference
    /// and re-enables it. When enabled, antivirus software on Windows
    /// intercepts file system operations and makes them significantly slower
    /// in addition to possibly failing with this error code.
    AntivirusInterference,
} || posix.OpenError || posix.FlockError;

pub const OpenMode = enum {
    read_only,
    write_only,
    read_write,
};

pub const Lock = enum {
    none,
    shared,
    exclusive,
};

pub const OpenFlags = struct {
    mode: OpenMode = .read_only,

    /// 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,

    /// Set this to allow the opened file to automatically become the
    /// controlling TTY for the current process.
    allow_ctty: bool = false,

    pub fn isRead(self: OpenFlags) bool {
        return self.mode != .write_only;
    }

    pub fn isWrite(self: OpenFlags) bool {
        return self.mode != .read_only;
    }
};

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,
};

/// 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), .FileFsDeviceInformation);
        switch (rc) {
            .SUCCESS => {},
            else => return false,
        }
        if (device_info.DeviceType != windows.FILE_DEVICE_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), .FileNameInformation);
    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.indexOf(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);
}

/// TODO: integrate with async I/O
pub fn getEndPos(self: File) GetSeekPosError!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 = struct {
    /// A number that the system uses to point to the file metadata. This
    /// number is not guaranteed to be unique across time, as some file
    /// systems may reuse an inode after its file has been deleted. Some
    /// systems may change the inode of a file over time.
    ///
    /// On Linux, the inode is a structure that stores the metadata, and
    /// the inode _number_ is what you see here: the index number of the
    /// inode.
    ///
    /// The FileIndex on Windows is similar. It is a number for a file that
    /// is unique to each filesystem.
    inode: INode,
    size: u64,
    /// This is available on POSIX systems and is always 0 otherwise.
    mode: Mode,
    kind: Kind,

    /// Last access time in nanoseconds, relative to UTC 1970-01-01.
    atime: i128,
    /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
    mtime: i128,
    /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
    ctime: i128,

    pub fn fromPosix(st: posix.Stat) Stat {
        const atime = st.atime();
        const mtime = st.mtime();
        const ctime = st.ctime();
        return .{
            .inode = st.ino,
            .size = @bitCast(st.size),
            .mode = st.mode,
            .kind = k: {
                const m = st.mode & posix.S.IFMT;
                switch (m) {
                    posix.S.IFBLK => break :k .block_device,
                    posix.S.IFCHR => break :k .character_device,
                    posix.S.IFDIR => break :k .directory,
                    posix.S.IFIFO => break :k .named_pipe,
                    posix.S.IFLNK => break :k .sym_link,
                    posix.S.IFREG => break :k .file,
                    posix.S.IFSOCK => break :k .unix_domain_socket,
                    else => {},
                }
                if (builtin.os.tag.isSolarish()) switch (m) {
                    posix.S.IFDOOR => break :k .door,
                    posix.S.IFPORT => break :k .event_port,
                    else => {},
                };

                break :k .unknown;
            },
            .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
            .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
            .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
        };
    }

    pub fn fromLinux(stx: linux.Statx) Stat {
        const atime = stx.atime;
        const mtime = stx.mtime;
        const ctime = stx.ctime;

        return .{
            .inode = stx.ino,
            .size = stx.size,
            .mode = stx.mode,
            .kind = switch (stx.mode & linux.S.IFMT) {
                linux.S.IFDIR => .directory,
                linux.S.IFCHR => .character_device,
                linux.S.IFBLK => .block_device,
                linux.S.IFREG => .file,
                linux.S.IFIFO => .named_pipe,
                linux.S.IFLNK => .sym_link,
                linux.S.IFSOCK => .unix_domain_socket,
                else => .unknown,
            },
            .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
            .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
            .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
        };
    }

    pub fn fromWasi(st: std.os.wasi.filestat_t) Stat {
        return .{
            .inode = st.ino,
            .size = @bitCast(st.size),
            .mode = 0,
            .kind = switch (st.filetype) {
                .BLOCK_DEVICE => .block_device,
                .CHARACTER_DEVICE => .character_device,
                .DIRECTORY => .directory,
                .SYMBOLIC_LINK => .sym_link,
                .REGULAR_FILE => .file,
                .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
                else => .unknown,
            },
            .atime = st.atim,
            .mtime = st.mtim,
            .ctime = st.ctim,
        };
    }
};

pub const StatError = posix.FStatError;

/// Returns `Stat` containing basic information about the `File`.
/// Use `metadata` to retrieve more detailed information (e.g. creation time, permissions).
/// TODO: integrate with async I/O
pub fn stat(self: File) StatError!Stat {
    if (builtin.os.tag == .windows) {
        var io_status_block: windows.IO_STATUS_BLOCK = undefined;
        var info: windows.FILE_ALL_INFORMATION = undefined;
        const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
        switch (rc) {
            .SUCCESS => {},
            // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
            // size provided. This is treated as success because the type of variable-length information that this would be relevant for
            // (name, volume name, etc) we don't care about.
            .BUFFER_OVERFLOW => {},
            .INVALID_PARAMETER => unreachable,
            .ACCESS_DENIED => return error.AccessDenied,
            else => return windows.unexpectedStatus(rc),
        }
        return .{
            .inode = info.InternalInformation.IndexNumber,
            .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
            .mode = 0,
            .kind = if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) reparse_point: {
                var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined;
                const tag_rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation);
                switch (tag_rc) {
                    .SUCCESS => {},
                    // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
                    // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
                    .INFO_LENGTH_MISMATCH => unreachable,
                    .ACCESS_DENIED => return error.AccessDenied,
                    else => return windows.unexpectedStatus(rc),
                }
                if (tag_info.ReparseTag & windows.reparse_tag_name_surrogate_bit != 0) {
                    break :reparse_point .sym_link;
                }
                // Unknown reparse point
                break :reparse_point .unknown;
            } else if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0)
                .directory
            else
                .file,
            .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
            .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
            .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
        };
    }

    if (builtin.os.tag == .wasi and !builtin.link_libc) {
        const st = try std.os.fstat_wasi(self.handle);
        return Stat.fromWasi(st);
    }

    if (builtin.os.tag == .linux) {
        var stx = std.mem.zeroes(linux.Statx);

        const rc = linux.statx(
            self.handle,
            "",
            linux.AT.EMPTY_PATH,
            linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
            &stx,
        );

        return switch (linux.E.init(rc)) {
            .SUCCESS => Stat.fromLinux(stx),
            .ACCES => unreachable,
            .BADF => unreachable,
            .FAULT => unreachable,
            .INVAL => unreachable,
            .LOOP => unreachable,
            .NAMETOOLONG => unreachable,
            .NOENT => unreachable,
            .NOMEM => error.SystemResources,
            .NOTDIR => unreachable,
            else => |err| posix.unexpectedErrno(err),
        };
    }

    const st = try posix.fstat(self.handle);
    return Stat.fromPosix(st);
}

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),
                .FileBasicInformation,
            );
            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);
        },
    }
}

/// Cross-platform representation of file metadata.
/// Platform-specific functionality is available through the `inner` field.
pub const Metadata = struct {
    /// Exposes platform-specific functionality.
    inner: switch (builtin.os.tag) {
        .windows => MetadataWindows,
        .linux => MetadataLinux,
        .wasi => MetadataWasi,
        else => MetadataUnix,
    },

    const Self = @This();

    /// Returns the size of the file
    pub fn size(self: Self) u64 {
        return self.inner.size();
    }

    /// Returns a `Permissions` struct, representing the permissions on the file
    pub fn permissions(self: Self) Permissions {
        return self.inner.permissions();
    }

    /// Returns the `Kind` of file.
    /// On Windows, can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
    pub fn kind(self: Self) Kind {
        return self.inner.kind();
    }

    /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
    pub fn accessed(self: Self) i128 {
        return self.inner.accessed();
    }

    /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
    pub fn modified(self: Self) i128 {
        return self.inner.modified();
    }

    /// Returns the time the file was created in nanoseconds since UTC 1970-01-01
    /// On Windows, this cannot return null
    /// On Linux, this returns null if the filesystem does not support creation times
    /// On Unices, this returns null if the filesystem or OS does not support creation times
    /// On MacOS, this returns the ctime if the filesystem does not support creation times; this is insanity, and yet another reason to hate on Apple
    pub fn created(self: Self) ?i128 {
        return self.inner.created();
    }
};

pub const MetadataUnix = struct {
    stat: posix.Stat,

    const Self = @This();

    /// Returns the size of the file
    pub fn size(self: Self) u64 {
        return @intCast(self.stat.size);
    }

    /// Returns a `Permissions` struct, representing the permissions on the file
    pub fn permissions(self: Self) Permissions {
        return .{ .inner = .{ .mode = self.stat.mode } };
    }

    /// Returns the `Kind` of the file
    pub fn kind(self: Self) Kind {
        if (builtin.os.tag == .wasi and !builtin.link_libc) return switch (self.stat.filetype) {
            .BLOCK_DEVICE => .block_device,
            .CHARACTER_DEVICE => .character_device,
            .DIRECTORY => .directory,
            .SYMBOLIC_LINK => .sym_link,
            .REGULAR_FILE => .file,
            .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
            else => .unknown,
        };

        const m = self.stat.mode & posix.S.IFMT;

        switch (m) {
            posix.S.IFBLK => return .block_device,
            posix.S.IFCHR => return .character_device,
            posix.S.IFDIR => return .directory,
            posix.S.IFIFO => return .named_pipe,
            posix.S.IFLNK => return .sym_link,
            posix.S.IFREG => return .file,
            posix.S.IFSOCK => return .unix_domain_socket,
            else => {},
        }

        if (builtin.os.tag.isSolarish()) switch (m) {
            posix.S.IFDOOR => return .door,
            posix.S.IFPORT => return .event_port,
            else => {},
        };

        return .unknown;
    }

    /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
    pub fn accessed(self: Self) i128 {
        const atime = self.stat.atime();
        return @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec;
    }

    /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
    pub fn modified(self: Self) i128 {
        const mtime = self.stat.mtime();
        return @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec;
    }

    /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
    /// Returns null if this is not supported by the OS or filesystem
    pub fn created(self: Self) ?i128 {
        if (!@hasDecl(@TypeOf(self.stat), "birthtime")) return null;
        const birthtime = self.stat.birthtime();

        // If the filesystem doesn't support this the value *should* be:
        // On FreeBSD: nsec = 0, sec = -1
        // On NetBSD and OpenBSD: nsec = 0, sec = 0
        // On MacOS, it is set to ctime -- we cannot detect this!!
        switch (builtin.os.tag) {
            .freebsd => if (birthtime.sec == -1 and birthtime.nsec == 0) return null,
            .netbsd, .openbsd => if (birthtime.sec == 0 and birthtime.nsec == 0) return null,
            .macos => {},
            else => @compileError("Creation time detection not implemented for OS"),
        }

        return @as(i128, birthtime.sec) * std.time.ns_per_s + birthtime.nsec;
    }
};

/// `MetadataUnix`, but using Linux's `statx` syscall.
pub const MetadataLinux = struct {
    statx: std.os.linux.Statx,

    const Self = @This();

    /// Returns the size of the file
    pub fn size(self: Self) u64 {
        return self.statx.size;
    }

    /// Returns a `Permissions` struct, representing the permissions on the file
    pub fn permissions(self: Self) Permissions {
        return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } };
    }

    /// Returns the `Kind` of the file
    pub fn kind(self: Self) Kind {
        const m = self.statx.mode & posix.S.IFMT;

        switch (m) {
            posix.S.IFBLK => return .block_device,
            posix.S.IFCHR => return .character_device,
            posix.S.IFDIR => return .directory,
            posix.S.IFIFO => return .named_pipe,
            posix.S.IFLNK => return .sym_link,
            posix.S.IFREG => return .file,
            posix.S.IFSOCK => return .unix_domain_socket,
            else => {},
        }

        return .unknown;
    }

    /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
    pub fn accessed(self: Self) i128 {
        return @as(i128, self.statx.atime.sec) * std.time.ns_per_s + self.statx.atime.nsec;
    }

    /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
    pub fn modified(self: Self) i128 {
        return @as(i128, self.statx.mtime.sec) * std.time.ns_per_s + self.statx.mtime.nsec;
    }

    /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
    /// Returns null if this is not supported by the filesystem, or on kernels before than version 4.11
    pub fn created(self: Self) ?i128 {
        if (self.statx.mask & std.os.linux.STATX_BTIME == 0) return null;
        return @as(i128, self.statx.btime.sec) * std.time.ns_per_s + self.statx.btime.nsec;
    }
};

pub const MetadataWasi = struct {
    stat: std.os.wasi.filestat_t,

    pub fn size(self: @This()) u64 {
        return self.stat.size;
    }

    pub fn permissions(self: @This()) Permissions {
        return .{ .inner = .{ .mode = self.stat.mode } };
    }

    pub fn kind(self: @This()) Kind {
        return switch (self.stat.filetype) {
            .BLOCK_DEVICE => .block_device,
            .CHARACTER_DEVICE => .character_device,
            .DIRECTORY => .directory,
            .SYMBOLIC_LINK => .sym_link,
            .REGULAR_FILE => .file,
            .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
            else => .unknown,
        };
    }

    pub fn accessed(self: @This()) i128 {
        return self.stat.atim;
    }

    pub fn modified(self: @This()) i128 {
        return self.stat.mtim;
    }

    pub fn created(self: @This()) ?i128 {
        return self.stat.ctim;
    }
};

pub const MetadataWindows = struct {
    attributes: windows.DWORD,
    reparse_tag: windows.DWORD,
    _size: u64,
    access_time: i128,
    modified_time: i128,
    creation_time: i128,

    const Self = @This();

    /// Returns the size of the file
    pub fn size(self: Self) u64 {
        return self._size;
    }

    /// Returns a `Permissions` struct, representing the permissions on the file
    pub fn permissions(self: Self) Permissions {
        return .{ .inner = .{ .attributes = self.attributes } };
    }

    /// Returns the `Kind` of the file.
    /// Can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
    pub fn kind(self: Self) Kind {
        if (self.attributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
            if (self.reparse_tag & windows.reparse_tag_name_surrogate_bit != 0) {
                return .sym_link;
            }
        } else if (self.attributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) {
            return .directory;
        } else {
            return .file;
        }
        return .unknown;
    }

    /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
    pub fn accessed(self: Self) i128 {
        return self.access_time;
    }

    /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
    pub fn modified(self: Self) i128 {
        return self.modified_time;
    }

    /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
    /// This never returns null, only returning an optional for compatibility with other OSes
    pub fn created(self: Self) ?i128 {
        return self.creation_time;
    }
};

pub const MetadataError = posix.FStatError;

pub fn metadata(self: File) MetadataError!Metadata {
    return .{
        .inner = switch (builtin.os.tag) {
            .windows => blk: {
                var io_status_block: windows.IO_STATUS_BLOCK = undefined;
                var info: windows.FILE_ALL_INFORMATION = undefined;

                const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
                switch (rc) {
                    .SUCCESS => {},
                    // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
                    // size provided. This is treated as success because the type of variable-length information that this would be relevant for
                    // (name, volume name, etc) we don't care about.
                    .BUFFER_OVERFLOW => {},
                    .INVALID_PARAMETER => unreachable,
                    .ACCESS_DENIED => return error.AccessDenied,
                    else => return windows.unexpectedStatus(rc),
                }

                const reparse_tag: windows.DWORD = reparse_blk: {
                    if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
                        var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined;
                        const tag_rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation);
                        switch (tag_rc) {
                            .SUCCESS => {},
                            // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
                            // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
                            .INFO_LENGTH_MISMATCH => unreachable,
                            .ACCESS_DENIED => return error.AccessDenied,
                            else => return windows.unexpectedStatus(rc),
                        }
                        break :reparse_blk tag_info.ReparseTag;
                    }
                    break :reparse_blk 0;
                };

                break :blk .{
                    .attributes = info.BasicInformation.FileAttributes,
                    .reparse_tag = reparse_tag,
                    ._size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
                    .access_time = windows.fromSysTime(info.BasicInformation.LastAccessTime),
                    .modified_time = windows.fromSysTime(info.BasicInformation.LastWriteTime),
                    .creation_time = windows.fromSysTime(info.BasicInformation.CreationTime),
                };
            },
            .linux => blk: {
                var stx = std.mem.zeroes(linux.Statx);

                // We are gathering information for Metadata, which is meant to contain all the
                // native OS information about the file, so use all known flags.
                const rc = linux.statx(
                    self.handle,
                    "",
                    linux.AT.EMPTY_PATH,
                    linux.STATX_BASIC_STATS | linux.STATX_BTIME,
                    &stx,
                );

                switch (linux.E.init(rc)) {
                    .SUCCESS => {},
                    .ACCES => unreachable,
                    .BADF => unreachable,
                    .FAULT => unreachable,
                    .INVAL => unreachable,
                    .LOOP => unreachable,
                    .NAMETOOLONG => unreachable,
                    .NOENT => unreachable,
                    .NOMEM => return error.SystemResources,
                    .NOTDIR => unreachable,
                    else => |err| return posix.unexpectedErrno(err),
                }

                break :blk .{
                    .statx = stx,
                };
            },
            .wasi => .{ .stat = try std.os.fstat_wasi(self.handle) },
            else => .{ .stat = try posix.fstat(self.handle) },
        },
    };
}

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: i128,
    /// last modification timestamp in nanoseconds
    mtime: i128,
) 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, std.time.ns_per_s)) orelse maxInt(isize),
            .nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize),
        },
        posix.timespec{
            .sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize),
            .nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize),
        },
    };
    try posix.futimens(self.handle, &times);
}

/// Reads all the bytes from the current position to the end of the file.
/// On success, caller owns returned buffer.
/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
pub fn readToEndAlloc(self: File, allocator: Allocator, max_bytes: usize) ![]u8 {
    return self.readToEndAllocOptions(allocator, max_bytes, null, @alignOf(u8), null);
}

/// Reads all the bytes from the current position to the end of the file.
/// On success, caller owns returned buffer.
/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
/// If `size_hint` is specified the initial buffer size is calculated using
/// that value, otherwise an arbitrary value is used instead.
/// Allows specifying alignment and a sentinel value.
pub fn readToEndAllocOptions(
    self: File,
    allocator: Allocator,
    max_bytes: usize,
    size_hint: ?usize,
    comptime alignment: u29,
    comptime optional_sentinel: ?u8,
) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
    // If no size hint is provided fall back to the size=0 code path
    const size = size_hint orelse 0;

    // The file size returned by stat is used as hint to set the buffer
    // size. If the reported size is zero, as it happens on Linux for files
    // in /proc, a small buffer is allocated instead.
    const initial_cap = @min((if (size > 0) size else 1024), max_bytes) + @intFromBool(optional_sentinel != null);
    var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap);
    defer array_list.deinit();

    self.reader().readAllArrayListAligned(alignment, &array_list, max_bytes) catch |err| switch (err) {
        error.StreamTooLong => return error.FileTooBig,
        else => |e| return e,
    };

    if (optional_sentinel) |sentinel| {
        return try array_list.toOwnedSliceSentinel(sentinel);
    } else {
        return try array_list.toOwnedSlice();
    }
}

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);
}

/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
/// means the file reached the end. Reaching the end of a file is not an error condition.
pub fn readAll(self: File, buffer: []u8) ReadError!usize {
    var index: usize = 0;
    while (index != buffer.len) {
        const amt = try self.read(buffer[index..]);
        if (amt == 0) break;
        index += amt;
    }
    return index;
}

/// 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);
}

/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
/// means the file reached the end. Reaching the end of a file is not an error condition.
/// On Windows, this function currently does alter the file pointer.
/// https://github.com/ziglang/zig/issues/12783
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) {
        // TODO improve this to use ReadFileScatter
        if (iovecs.len == 0) return @as(usize, 0);
        const first = iovecs[0];
        return windows.ReadFile(self.handle, first.base[0..first.len], null);
    }

    return posix.readv(self.handle, iovecs);
}

/// Returns the number of bytes read. If the number read is smaller than the total bytes
/// from all the buffers, it means the file reached the end. Reaching the end of a file
/// is not an error condition.
///
/// The `iovecs` parameter is mutable because:
/// * This function needs to mutate the fields in order to handle partial
///   reads from the underlying OS layer.
/// * The OS layer expects pointer addresses to be inside the application's address space
///   even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
///   addresses when the length is zero. So this function modifies the base fields
///   when the length is zero.
///
/// Related open issue: https://github.com/ziglang/zig/issues/7699
pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize {
    if (iovecs.len == 0) return 0;

    // We use the address of this local variable for all zero-length
    // vectors so that the OS does not complain that we are giving it
    // addresses outside the application's address space.
    var garbage: [1]u8 = undefined;
    for (iovecs) |*v| {
        if (v.len == 0) v.base = &garbage;
    }

    var i: usize = 0;
    var off: usize = 0;
    while (true) {
        var amt = try self.readv(iovecs[i..]);
        var eof = amt == 0;
        off += amt;
        while (amt >= iovecs[i].len) {
            amt -= iovecs[i].len;
            i += 1;
            if (i >= iovecs.len) return off;
            eof = false;
        }
        if (eof) return off;
        iovecs[i].base += amt;
        iovecs[i].len -= amt;
    }
}

/// 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) {
        // TODO improve this to use ReadFileScatter
        if (iovecs.len == 0) return @as(usize, 0);
        const first = iovecs[0];
        return windows.ReadFile(self.handle, first.base[0..first.len], offset);
    }

    return posix.preadv(self.handle, iovecs, offset);
}

/// Returns the number of bytes read. If the number read is smaller than the total bytes
/// from all the buffers, it means the file reached the end. Reaching the end of a file
/// is not an error condition.
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial reads from the underlying OS layer.
/// 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 preadvAll(self: File, iovecs: []posix.iovec, offset: u64) PReadError!usize {
    if (iovecs.len == 0) return 0;

    var i: usize = 0;
    var off: usize = 0;
    while (true) {
        var amt = try self.preadv(iovecs[i..], offset + off);
        var eof = amt == 0;
        off += amt;
        while (amt >= iovecs[i].len) {
            amt -= iovecs[i].len;
            i += 1;
            if (i >= iovecs.len) return off;
            eof = false;
        }
        if (eof) return off;
        iovecs[i].base += amt;
        iovecs[i].len -= amt;
    }
}

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..]);
    }
}

/// 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);
}

/// On Windows, this function currently does alter the file pointer.
/// https://github.com/ziglang/zig/issues/12783
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);
    }
}

/// See https://github.com/ziglang/zig/issues/7699
/// See equivalent function: `std.net.Stream.writev`.
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 @as(usize, 0);
        const first = iovecs[0];
        return windows.WriteFile(self.handle, first.base[0..first.len], null);
    }

    return posix.writev(self.handle, iovecs);
}

/// The `iovecs` parameter is mutable because:
/// * This function needs to mutate the fields in order to handle partial
///   writes from the underlying OS layer.
/// * The OS layer expects pointer addresses to be inside the application's address space
///   even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
///   addresses when the length is zero. So this function modifies the base fields
///   when the length is zero.
/// See https://github.com/ziglang/zig/issues/7699
/// See equivalent function: `std.net.Stream.writevAll`.
pub fn writevAll(self: File, iovecs: []posix.iovec_const) WriteError!void {
    if (iovecs.len == 0) return;

    // We use the address of this local variable for all zero-length
    // vectors so that the OS does not complain that we are giving it
    // addresses outside the application's address space.
    var garbage: [1]u8 = undefined;
    for (iovecs) |*v| {
        if (v.len == 0) v.base = &garbage;
    }

    var i: usize = 0;
    while (true) {
        var amt = try self.writev(iovecs[i..]);
        while (amt >= iovecs[i].len) {
            amt -= iovecs[i].len;
            i += 1;
            if (i >= iovecs.len) return;
        }
        iovecs[i].base += amt;
        iovecs[i].len -= amt;
    }
}

/// 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) {
        // TODO improve this to use WriteFileScatter
        if (iovecs.len == 0) return @as(usize, 0);
        const first = iovecs[0];
        return windows.WriteFile(self.handle, first.base[0..first.len], offset);
    }

    return posix.pwritev(self.handle, iovecs, offset);
}

/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial writes from the underlying OS layer.
/// 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 pwritevAll(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!void {
    if (iovecs.len == 0) return;

    var i: usize = 0;
    var off: u64 = 0;
    while (true) {
        var amt = try self.pwritev(iovecs[i..], offset + off);
        off += amt;
        while (amt >= iovecs[i].len) {
            amt -= iovecs[i].len;
            i += 1;
            if (i >= iovecs.len) return;
        }
        iovecs[i].base += amt;
        iovecs[i].len -= amt;
    }
}

pub const CopyRangeError = posix.CopyFileRangeError;

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;
}

/// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it
/// means the in file reached the end. Reaching the end of a file is not an error condition.
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;
}

pub const WriteFileOptions = struct {
    in_offset: u64 = 0,

    /// `null` means the entire file. `0` means no bytes from the file.
    /// When this is `null`, trailers must be sent in a separate writev() call
    /// due to a flaw in the BSD sendfile API. Other operating systems, such as
    /// Linux, already do this anyway due to API limitations.
    /// If the size of the source file is known, passing the size here will save one syscall.
    in_len: ?u64 = null,

    headers_and_trailers: []posix.iovec_const = &[0]posix.iovec_const{},

    /// The trailer count is inferred from `headers_and_trailers.len - header_count`
    header_count: usize = 0,
};

pub const WriteFileError = ReadError || error{EndOfStream} || WriteError;

pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
    return self.writeFileAllSendfile(in_file, args) catch |err| switch (err) {
        error.Unseekable,
        error.FastOpenAlreadyInProgress,
        error.MessageTooBig,
        error.FileDescriptorNotASocket,
        error.NetworkUnreachable,
        error.NetworkSubsystemFailed,
        => return self.writeFileAllUnseekable(in_file, args),

        else => |e| return e,
    };
}

/// Does not try seeking in either of the File parameters.
/// See `writeFileAll` as an alternative to calling this.
pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
    const headers = args.headers_and_trailers[0..args.header_count];
    const trailers = args.headers_and_trailers[args.header_count..];

    try self.writevAll(headers);

    try in_file.reader().skipBytes(args.in_offset, .{ .buf_size = 4096 });

    var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
    if (args.in_len) |len| {
        var stream = std.io.limitedReader(in_file.reader(), len);
        try fifo.pump(stream.reader(), self.writer());
    } else {
        try fifo.pump(in_file.reader(), self.writer());
    }

    try self.writevAll(trailers);
}

/// Low level function which can fail for OS-specific reasons.
/// See `writeFileAll` as an alternative to calling this.
/// TODO integrate with async I/O
fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix.SendFileError!void {
    const count = blk: {
        if (args.in_len) |l| {
            if (l == 0) {
                return self.writevAll(args.headers_and_trailers);
            } else {
                break :blk l;
            }
        } else {
            break :blk 0;
        }
    };
    const headers = args.headers_and_trailers[0..args.header_count];
    const trailers = args.headers_and_trailers[args.header_count..];
    const zero_iovec = &[0]posix.iovec_const{};
    // When reading the whole file, we cannot put the trailers in the sendfile() syscall,
    // because we have no way to determine whether a partial write is past the end of the file or not.
    const trls = if (count == 0) zero_iovec else trailers;
    const offset = args.in_offset;
    const out_fd = self.handle;
    const in_fd = in_file.handle;
    const flags = 0;
    var amt: usize = 0;
    hdrs: {
        var i: usize = 0;
        while (i < headers.len) {
            amt = try posix.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags);
            while (amt >= headers[i].len) {
                amt -= headers[i].len;
                i += 1;
                if (i >= headers.len) break :hdrs;
            }
            headers[i].base += amt;
            headers[i].len -= amt;
        }
    }
    if (count == 0) {
        var off: u64 = amt;
        while (true) {
            amt = try posix.sendfile(out_fd, in_fd, offset + off, 0, zero_iovec, zero_iovec, flags);
            if (amt == 0) break;
            off += amt;
        }
    } else {
        var off: u64 = amt;
        while (off < count) {
            amt = try posix.sendfile(out_fd, in_fd, offset + off, count - off, zero_iovec, trailers, flags);
            off += amt;
        }
        amt = @as(usize, @intCast(off - count));
    }
    var i: usize = 0;
    while (i < trailers.len) {
        while (amt >= trailers[i].len) {
            amt -= trailers[i].len;
            i += 1;
            if (i >= trailers.len) return;
        }
        trailers[i].base += amt;
        trailers[i].len -= amt;
        amt = try posix.writev(self.handle, trailers[i..]);
    }
}

pub const Reader = io.Reader(File, ReadError, read);

pub fn reader(file: File) Reader {
    return .{ .context = file };
}

pub const Writer = io.Writer(File, WriteError, write);

pub fn writer(file: File) Writer {
    return .{ .context = file };
}

pub const SeekableStream = io.SeekableStream(
    File,
    SeekError,
    GetSeekPosError,
    seekTo,
    seekBy,
    getPos,
    getEndPos,
);

pub fn seekableStream(file: File) SeekableStream {
    return .{ .context = file };
}

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,
            null,
        ) 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,
            null,
        ) 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,
        };
    }
}

const File = @This();
const std = @import("../std.zig");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const posix = std.posix;
const io = std.io;
const math = std.math;
const assert = std.debug.assert;
const linux = std.os.linux;
const windows = std.os.windows;
const Os = std.builtin.Os;
const maxInt = std.math.maxInt;
const is_windows = builtin.os.tag == .windows;