aboutsummaryrefslogtreecommitdiff
path: root/lib/std/Io/File.zig
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/Io/File.zig')
-rw-r--r--lib/std/Io/File.zig853
1 files changed, 446 insertions, 407 deletions
diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig
index 7c90176519..ee30103af0 100644
--- a/lib/std/Io/File.zig
+++ b/lib/std/Io/File.zig
@@ -7,12 +7,19 @@ const is_windows = native_os == .windows;
const std = @import("../std.zig");
const Io = std.Io;
const assert = std.debug.assert;
+const Dir = std.Io.Dir;
handle: Handle,
+pub const Reader = @import("File/Reader.zig");
+pub const Writer = @import("File/Writer.zig");
+pub const Atomic = @import("File/Atomic.zig");
+
pub const Handle = std.posix.fd_t;
-pub const Mode = std.posix.mode_t;
pub const INode = std.posix.ino_t;
+pub const NLink = std.posix.nlink_t;
+pub const Uid = std.posix.uid_t;
+pub const Gid = std.posix.gid_t;
pub const Kind = enum {
block_device,
@@ -41,9 +48,9 @@ pub const Stat = struct {
/// The FileIndex on Windows is similar. It is a number for a file that
/// is unique to each filesystem.
inode: INode,
+ nlink: NLink,
size: u64,
- /// This is available on POSIX systems and is always 0 otherwise.
- mode: Mode,
+ permissions: Permissions,
kind: Kind,
/// Last access time in nanoseconds, relative to UTC 1970-01-01.
atime: Io.Timestamp,
@@ -95,6 +102,26 @@ pub const Lock = enum {
pub const OpenFlags = struct {
mode: OpenMode = .read_only,
+ /// Determines the behavior when opening a path that refers to a directory.
+ ///
+ /// If set to true, directories may be opened, but `error.IsDir` is still
+ /// possible in certain scenarios, e.g. attempting to open a directory with
+ /// write permissions.
+ ///
+ /// If set to false, `error.IsDir` will always be returned when opening a directory.
+ ///
+ /// When set to false:
+ /// * On Windows, the behavior is implemented without any extra syscalls.
+ /// * On other operating systems, the behavior is implemented with an additional
+ /// `fstat` syscall.
+ allow_directory: bool = true,
+ /// Indicates intent for only some operations to be performed on this
+ /// opened file:
+ /// * `close`
+ /// * `stat`
+ /// On Linux and FreeBSD, this corresponds to `std.posix.O.PATH`.
+ path_only: 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
@@ -141,7 +168,51 @@ pub const OpenFlags = struct {
}
};
-pub const CreateFlags = std.fs.File.CreateFlags;
+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,
+
+ permissions: Permissions = .default_file,
+};
pub const OpenError = error{
SharingViolation,
@@ -149,7 +220,6 @@ pub const OpenError = error{
NoDevice,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
- ProcessNotFound,
/// 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
@@ -178,7 +248,9 @@ pub const OpenError = error{
/// The file is too large to be opened. This error is unreachable
/// for 64-bit targets, as well as when opening directories.
FileTooBig,
- /// The path refers to directory but the `DIRECTORY` flag was not provided.
+ /// Either:
+ /// * The path refers to a directory and write permissions were requested.
+ /// * The path refers to a directory and `allow_directory` was set to false.
IsDir,
/// 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.
@@ -189,7 +261,7 @@ pub const OpenError = error{
/// The path already exists and the `CREAT` and `EXCL` flags were provided.
PathAlreadyExists,
DeviceBusy,
- FileLocksNotSupported,
+ FileLocksUnsupported,
/// One of these three things:
/// * pathname refers to an executable image which is currently being
/// executed and write access was requested.
@@ -204,451 +276,418 @@ pub const OpenError = error{
} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
pub fn close(file: File, io: Io) void {
- return io.vtable.fileClose(io.userdata, file);
+ return io.vtable.fileClose(io.userdata, (&file)[0..1]);
}
-pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || std.posix.FlockError;
-
-pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File {
- return io.vtable.openSelfExe(io.userdata, flags);
+pub fn closeMany(io: Io, files: []const File) void {
+ return io.vtable.fileClose(io.userdata, files);
}
-pub const ReadPositionalError = Reader.Error || error{Unseekable};
+pub const SyncError = error{
+ InputOutput,
+ NoSpaceLeft,
+ DiskQuota,
+ AccessDenied,
+} || Io.Cancelable || Io.UnexpectedError;
-pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPositionalError!usize {
- return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
+/// Blocks until all pending file contents and metadata modifications for the
+/// file have been synchronized with the underlying filesystem.
+///
+/// This does not ensure that metadata for the directory containing the file
+/// has also reached disk.
+pub fn sync(file: File, io: Io) SyncError!void {
+ return io.vtable.fileSync(io.userdata, file);
}
-pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable;
-
-pub fn writeStreaming(file: File, io: Io, buffer: [][]const u8) WriteStreamingError!usize {
- return file.fileWriteStreaming(io, buffer);
+/// Test whether the file refers to a terminal (similar to libc "isatty").
+///
+/// See also:
+/// * `enableAnsiEscapeCodes`
+/// * `supportsAnsiEscapeCodes`.
+pub fn isTty(file: File, io: Io) Io.Cancelable!bool {
+ return io.vtable.fileIsTty(io.userdata, file);
}
-pub const WritePositionalError = WriteStreamingError || error{Unseekable};
+pub const EnableAnsiEscapeCodesError = error{
+ NotTerminalDevice,
+} || Io.Cancelable || Io.UnexpectedError;
-pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize {
- return io.vtable.fileWritePositional(io.userdata, file, buffer, offset);
+pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError!void {
+ return io.vtable.fileEnableAnsiEscapeCodes(io.userdata, file);
}
-pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File {
- assert(std.fs.path.isAbsolute(absolute_path));
- return Io.Dir.cwd().openFile(io, absolute_path, flags);
+/// Test whether ANSI escape codes will be treated as such without
+/// attempting to enable support for ANSI escape codes.
+pub fn supportsAnsiEscapeCodes(file: File, io: Io) Io.Cancelable!bool {
+ return io.vtable.fileSupportsAnsiEscapeCodes(io.userdata, file);
}
-/// Defaults to positional reading; falls back to streaming.
+pub const SetLengthError = error{
+ FileTooBig,
+ InputOutput,
+ FileBusy,
+ AccessDenied,
+ PermissionDenied,
+ NonResizable,
+} || Io.Cancelable || Io.UnexpectedError;
+
+/// Truncates or expands the file, populating any new data with zeroes.
///
-/// Positional is more threadsafe, since the global seek position is not
-/// affected.
-pub fn reader(file: File, io: Io, buffer: []u8) Reader {
- return .init(file, io, buffer);
+/// The file offset after this call is left unchanged.
+pub fn setLength(file: File, io: Io, new_length: u64) SetLengthError!void {
+ return io.vtable.fileSetLength(io.userdata, file, new_length);
}
-/// Positional is more threadsafe, since the global seek position is not
-/// affected, but when such syscalls are not available, preemptively
-/// initializing in streaming mode skips a failed syscall.
-pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
- return .initStreaming(file, io, buffer);
+pub const LengthError = StatError;
+
+/// Retrieve the ending byte index of the file.
+///
+/// Sometimes cheaper than `stat` if only the length is needed.
+pub fn length(file: File, io: Io) LengthError!u64 {
+ return io.vtable.fileLength(io.userdata, file);
}
-pub const SeekError = error{
- Unseekable,
- /// The file descriptor does not hold the required rights to seek on it.
+pub const SetPermissionsError = error{
AccessDenied,
+ PermissionDenied,
+ InputOutput,
+ SymLinkLoop,
+ FileNotFound,
+ SystemResources,
+ ReadOnlyFileSystem,
} || Io.Cancelable || Io.UnexpectedError;
-/// Memoizes key information about a file handle such as:
-/// * The size from calling stat, or the error that occurred therein.
-/// * The current seek position.
-/// * The error that occurred when trying to seek.
-/// * Whether reading should be done positionally or streaming.
-/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`)
-/// versus plain variants (e.g. `read`).
+/// Also known as "chmod".
///
-/// Fulfills the `Io.Reader` interface.
-pub const Reader = struct {
- io: Io,
- file: File,
- err: ?Error = null,
- mode: Reader.Mode = .positional,
- /// Tracks the true seek position in the file. To obtain the logical
- /// position, use `logicalPos`.
- pos: u64 = 0,
- size: ?u64 = null,
- size_err: ?SizeError = null,
- seek_err: ?Reader.SeekError = null,
- interface: Io.Reader,
-
- pub const Error = error{
- InputOutput,
- SystemResources,
- IsDir,
- BrokenPipe,
- ConnectionResetByPeer,
- Timeout,
- /// In WASI, EBADF is mapped to this error because it is returned when
- /// trying to read a directory file descriptor as if it were a file.
- NotOpenForReading,
- SocketUnconnected,
- /// This error occurs when no global event loop is configured,
- /// and reading from the file descriptor would block.
- WouldBlock,
- /// 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,
- } || Io.Cancelable || Io.UnexpectedError;
-
- pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{
- /// Occurs if, for example, the file handle is a network socket and therefore does not have a size.
- Streaming,
- };
-
- pub const SeekError = File.SeekError || error{
- /// Seeking fell back to reading, and reached the end before the requested seek position.
- /// `pos` remains at the end of the file.
- EndOfStream,
- /// Seeking fell back to reading, which failed.
- ReadFailed,
- };
-
- pub const Mode = enum {
- streaming,
- positional,
- /// Avoid syscalls other than `read` and `readv`.
- streaming_reading,
- /// Avoid syscalls other than `pread` and `preadv`.
- positional_reading,
- /// Indicates reading cannot continue because of a seek failure.
- failure,
-
- pub fn toStreaming(m: @This()) @This() {
- return switch (m) {
- .positional, .streaming => .streaming,
- .positional_reading, .streaming_reading => .streaming_reading,
- .failure => .failure,
- };
- }
-
- pub fn toReading(m: @This()) @This() {
- return switch (m) {
- .positional, .positional_reading => .positional_reading,
- .streaming, .streaming_reading => .streaming_reading,
- .failure => .failure,
- };
- }
- };
-
- pub fn initInterface(buffer: []u8) Io.Reader {
- return .{
- .vtable = &.{
- .stream = Reader.stream,
- .discard = Reader.discard,
- .readVec = Reader.readVec,
- },
- .buffer = buffer,
- .seek = 0,
- .end = 0,
- };
- }
+/// 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 setPermissions(file: File, io: Io, new_permissions: Permissions) SetPermissionsError!void {
+ return io.vtable.fileSetPermissions(io.userdata, file, new_permissions);
+}
- pub fn init(file: File, io: Io, buffer: []u8) Reader {
- return .{
- .io = io,
- .file = file,
- .interface = initInterface(buffer),
- };
- }
+pub const SetOwnerError = error{
+ AccessDenied,
+ PermissionDenied,
+ InputOutput,
+ SymLinkLoop,
+ FileNotFound,
+ SystemResources,
+ ReadOnlyFileSystem,
+} || Io.Cancelable || Io.UnexpectedError;
- /// Takes a legacy `std.fs.File` to help with upgrading.
- pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader {
- return .init(.{ .handle = file.handle }, io, buffer);
- }
+/// Also known as "chown".
+///
+/// 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 setOwner(file: File, io: Io, owner: ?Uid, group: ?Gid) SetOwnerError!void {
+ return io.vtable.fileSetOwner(io.userdata, file, owner, group);
+}
- pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
- return .{
- .io = io,
- .file = file,
- .interface = initInterface(buffer),
- .size = size,
- };
- }
+/// Cross-platform representation of permissions on a file.
+///
+/// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes".
+pub const Permissions = std.Options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) {
+ default_file = 0,
+ _,
+
+ pub const default_dir: @This() = .default_file;
+ pub const executable_file: @This() = .default_file;
+ pub const has_executable_bit = false;
+
+ const windows = std.os.windows;
- /// Positional is more threadsafe, since the global seek position is not
- /// affected, but when such syscalls are not available, preemptively
- /// initializing in streaming mode skips a failed syscall.
- pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader {
- return .{
- .io = io,
- .file = file,
- .interface = Reader.initInterface(buffer),
- .mode = .streaming,
- .seek_err = error.Unseekable,
- .size_err = error.Streaming,
- };
+ pub fn toAttributes(self: @This()) windows.FILE.ATTRIBUTE {
+ return @bitCast(@intFromEnum(self));
}
- pub fn getSize(r: *Reader) SizeError!u64 {
- return r.size orelse {
- if (r.size_err) |err| return err;
- if (stat(r.file, r.io)) |st| {
- if (st.kind == .file) {
- r.size = st.size;
- return st.size;
- } else {
- r.mode = r.mode.toStreaming();
- r.size_err = error.Streaming;
- return error.Streaming;
- }
- } else |err| {
- r.size_err = err;
- return err;
- }
- };
+ pub fn readOnly(self: @This()) bool {
+ const attributes = toAttributes(self);
+ return attributes & windows.FILE_ATTRIBUTE_READONLY != 0;
}
- pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
- const io = r.io;
- switch (r.mode) {
- .positional, .positional_reading => {
- setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
- },
- .streaming, .streaming_reading => {
- const seek_err = r.seek_err orelse e: {
- if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) {
- setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
- return;
- } else |err| {
- r.seek_err = err;
- break :e err;
- }
- };
- var remaining = std.math.cast(u64, offset) orelse return seek_err;
- while (remaining > 0) {
- remaining -= discard(&r.interface, .limited64(remaining)) catch |err| {
- r.seek_err = err;
- return err;
- };
- }
- r.interface.tossBuffered();
- },
- .failure => return r.seek_err.?,
- }
+ pub fn setReadOnly(self: @This(), read_only: bool) @This() {
+ const attributes = toAttributes(self);
+ return @enumFromInt(if (read_only)
+ attributes | windows.FILE_ATTRIBUTE_READONLY
+ else
+ attributes & ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY));
+ }
+} else if (std.posix.mode_t != u0) enum(std.posix.mode_t) {
+ /// 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.
+ default_file = 0o666,
+ default_dir = 0o755,
+ executable_file = 0o777,
+ _,
+
+ pub const has_executable_bit = native_os != .wasi;
+
+ pub fn toMode(self: @This()) std.posix.mode_t {
+ return @intFromEnum(self);
}
- /// Repositions logical read offset relative to the beginning of the file.
- pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
- const io = r.io;
- switch (r.mode) {
- .positional, .positional_reading => {
- setLogicalPos(r, offset);
- },
- .streaming, .streaming_reading => {
- const logical_pos = logicalPos(r);
- if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos));
- if (r.seek_err) |err| return err;
- io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
- r.seek_err = err;
- return err;
- };
- setLogicalPos(r, offset);
- },
- .failure => return r.seek_err.?,
- }
+ pub fn fromMode(mode: std.posix.mode_t) @This() {
+ return @enumFromInt(mode);
}
- pub fn logicalPos(r: *const Reader) u64 {
- return r.pos - r.interface.bufferedLen();
+ /// Returns `true` if and only if no class has write permissions.
+ pub fn readOnly(self: @This()) bool {
+ const mode = toMode(self);
+ return mode & 0o222 == 0;
}
- fn setLogicalPos(r: *Reader, offset: u64) void {
- const logical_pos = r.logicalPos();
- if (offset < logical_pos or offset >= r.pos) {
- r.interface.tossBuffered();
- r.pos = offset;
- } else r.interface.toss(@intCast(offset - logical_pos));
+ /// Enables write permission for all classes.
+ pub fn setReadOnly(self: @This(), read_only: bool) @This() {
+ const mode = toMode(self);
+ const o222 = @as(std.posix.mode_t, 0o222);
+ return @enumFromInt(if (read_only) mode & ~o222 else mode | o222);
}
+} else enum(u0) {
+ default_file = 0,
+ pub const default_dir: @This() = .default_file;
+ pub const executable_file: @This() = .default_file;
+ pub const has_executable_bit = false;
+};
- /// Number of slices to store on the stack, when trying to send as many byte
- /// vectors through the underlying read calls as possible.
- const max_buffers_len = 16;
+pub const SetTimestampsError = error{
+ /// 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)).
+ AccessDenied,
+ /// 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)).
+ PermissionDenied,
+ ReadOnlyFileSystem,
+} || Io.Cancelable || Io.UnexpectedError;
- fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
- const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
- return streamMode(r, w, limit, r.mode);
- }
+/// The granularity that ultimately is stored depends on the combination of
+/// operating system and file system. When a value as provided that exceeds
+/// this range, the value is clamped to the maximum.
+pub fn setTimestamps(
+ file: File,
+ io: Io,
+ last_accessed: Io.Timestamp,
+ last_modified: Io.Timestamp,
+) SetTimestampsError!void {
+ return io.vtable.fileSetTimestamps(io.userdata, file, last_accessed, last_modified);
+}
- pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize {
- switch (mode) {
- .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
- error.Unimplemented => {
- r.mode = r.mode.toReading();
- return 0;
- },
- else => |e| return e,
- },
- .positional_reading => {
- const dest = limit.slice(try w.writableSliceGreedy(1));
- var data: [1][]u8 = .{dest};
- const n = try readVecPositional(r, &data);
- w.advance(n);
- return n;
- },
- .streaming_reading => {
- const dest = limit.slice(try w.writableSliceGreedy(1));
- var data: [1][]u8 = .{dest};
- const n = try readVecStreaming(r, &data);
- w.advance(n);
- return n;
- },
- .failure => return error.ReadFailed,
- }
- }
+/// Sets the accessed and modification timestamps of `file` to the current wall
+/// clock time.
+///
+/// The granularity that ultimately is stored depends on the combination of
+/// operating system and file system.
+pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {
+ return io.vtable.fileSetTimestampsNow(io.userdata, file);
+}
- fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
- const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
- switch (r.mode) {
- .positional, .positional_reading => return readVecPositional(r, data),
- .streaming, .streaming_reading => return readVecStreaming(r, data),
- .failure => return error.ReadFailed,
- }
- }
+/// Returns 0 on stream end or if `buffer` has no space available for data.
+///
+/// See also:
+/// * `reader`
+pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize {
+ return io.vtable.fileReadStreaming(io.userdata, file, buffer);
+}
- fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
- const io = r.io;
- var iovecs_buffer: [max_buffers_len][]u8 = undefined;
- const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
- const dest = iovecs_buffer[0..dest_n];
- assert(dest[0].len > 0);
- const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) {
- error.Unseekable => {
- r.mode = r.mode.toStreaming();
- const pos = r.pos;
- if (pos != 0) {
- r.pos = 0;
- r.seekBy(@intCast(pos)) catch {
- r.mode = .failure;
- return error.ReadFailed;
- };
- }
- return 0;
- },
- else => |e| {
- r.err = e;
- return error.ReadFailed;
- },
- };
- if (n == 0) {
- r.size = r.pos;
- return error.EndOfStream;
- }
- r.pos += n;
- if (n > data_size) {
- r.interface.end += n - data_size;
- return data_size;
- }
- return n;
- }
+pub const ReadPositionalError = Reader.Error || error{Unseekable};
- fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
- const io = r.io;
- var iovecs_buffer: [max_buffers_len][]u8 = undefined;
- const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
- const dest = iovecs_buffer[0..dest_n];
- assert(dest[0].len > 0);
- const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| {
- r.err = err;
- return error.ReadFailed;
- };
- if (n == 0) {
- r.size = r.pos;
- return error.EndOfStream;
- }
- r.pos += n;
- if (n > data_size) {
- r.interface.end += n - data_size;
- return data_size;
- }
- return n;
- }
+/// Returns 0 on stream end or if `buffer` has no space available for data.
+///
+/// See also:
+/// * `reader`
+pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) ReadPositionalError!usize {
+ return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
+}
+
+pub const WritePositionalError = Writer.Error || error{Unseekable};
+
+/// See also:
+/// * `writer`
+pub fn writePositional(file: File, io: Io, buffer: []const []const u8, offset: u64) WritePositionalError!usize {
+ return io.vtable.fileWritePositional(io.userdata, file, &.{}, buffer, 1, offset);
+}
+
+/// Equivalent to creating a positional writer, writing `bytes`, and then flushing.
+pub fn writePositionalAll(file: File, io: Io, bytes: []const u8, offset: u64) WritePositionalError!void {
+ var index: usize = 0;
+ while (index < bytes.len)
+ index += try io.vtable.fileWritePositional(io.userdata, file, &.{}, &.{bytes[index..]}, 1, offset + index);
+}
+
+pub const SeekError = error{
+ Unseekable,
+ /// The file descriptor does not hold the required rights to seek on it.
+ AccessDenied,
+} || Io.Cancelable || Io.UnexpectedError;
+
+pub const WriteFilePositionalError = Writer.WriteFileError || error{Unseekable};
+
+/// Defaults to positional reading; falls back to streaming.
+///
+/// Positional is more threadsafe, since the global seek position is not
+/// affected.
+///
+/// See also:
+/// * `readerStreaming`
+pub fn reader(file: File, io: Io, buffer: []u8) Reader {
+ return .init(file, io, buffer);
+}
- fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
- const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
- const io = r.io;
- const file = r.file;
- switch (r.mode) {
- .positional, .positional_reading => {
- const size = r.getSize() catch {
- r.mode = r.mode.toStreaming();
- return 0;
- };
- const logical_pos = logicalPos(r);
- const delta = @min(@intFromEnum(limit), size - logical_pos);
- setLogicalPos(r, logical_pos + delta);
- return delta;
- },
- .streaming, .streaming_reading => {
- // Unfortunately we can't seek forward without knowing the
- // size because the seek syscalls provided to us will not
- // return the true end position if a seek would exceed the
- // end.
- fallback: {
- if (r.size_err == null and r.seek_err == null) break :fallback;
-
- const buffered_len = r.interface.bufferedLen();
- var remaining = @intFromEnum(limit);
- if (remaining <= buffered_len) {
- r.interface.seek += remaining;
- return remaining;
- }
- remaining -= buffered_len;
- r.interface.seek = 0;
- r.interface.end = 0;
-
- var trash_buffer: [128]u8 = undefined;
- var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]};
- var iovecs_buffer: [max_buffers_len][]u8 = undefined;
- const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);
- const dest = iovecs_buffer[0..dest_n];
- assert(dest[0].len > 0);
- const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| {
- r.err = err;
- return error.ReadFailed;
- };
- if (n == 0) {
- r.size = r.pos;
- return error.EndOfStream;
- }
- r.pos += n;
- if (n > data_size) {
- r.interface.end += n - data_size;
- remaining -= data_size;
- } else {
- remaining -= n;
- }
- return @intFromEnum(limit) - remaining;
- }
- const size = r.getSize() catch return 0;
- const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit));
- io.vtable.fileSeekBy(io.userdata, file, n) catch |err| {
- r.seek_err = err;
- return 0;
- };
- r.pos += n;
- return n;
- },
- .failure => return error.ReadFailed,
- }
+/// Equivalent to creating a positional reader and reading multiple times to fill `buffer`.
+///
+/// Returns number of bytes read into `buffer`. If less than `buffer.len`, end of file occurred.
+///
+/// See also:
+/// * `reader`
+pub fn readPositionalAll(file: File, io: Io, buffer: []u8, offset: u64) ReadPositionalError!usize {
+ var index: usize = 0;
+ while (index != buffer.len) {
+ const amt = try file.readPositional(io, &.{buffer[index..]}, offset + index);
+ if (amt == 0) break;
+ index += amt;
}
+ return index;
+}
+
+/// Positional is more threadsafe, since the global seek position is not
+/// affected, but when such syscalls are not available, preemptively
+/// initializing in streaming mode skips a failed syscall.
+///
+/// See also:
+/// * `reader`
+pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
+ return .initStreaming(file, io, buffer);
+}
- /// Returns whether the stream is at the logical end.
- pub fn atEnd(r: *Reader) bool {
- // Even if stat fails, size is set when end is encountered.
- const size = r.size orelse return false;
- return size - logicalPos(r) == 0;
+/// Defaults to positional reading; falls back to streaming.
+///
+/// Positional is more threadsafe, since the global seek position is not
+/// affected.
+pub fn writer(file: File, io: Io, buffer: []u8) Writer {
+ return .init(file, io, buffer);
+}
+
+/// Positional is more threadsafe, since the global seek position is not
+/// affected, but when such syscalls are not available, preemptively
+/// initializing in streaming mode will skip a failed syscall.
+pub fn writerStreaming(file: File, io: Io, buffer: []u8) Writer {
+ return .initStreaming(file, io, buffer);
+}
+
+/// Equivalent to creating a streaming writer, writing `bytes`, and then flushing.
+pub fn writeStreamingAll(file: File, io: Io, bytes: []const u8) Writer.Error!void {
+ var index: usize = 0;
+ while (index < bytes.len) {
+ index += try io.vtable.fileWriteStreaming(io.userdata, file, &.{}, &.{bytes[index..]}, 1);
}
-};
+}
+
+pub const LockError = error{
+ SystemResources,
+ FileLocksUnsupported,
+} || Io.Cancelable || Io.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.
+pub fn lock(file: File, io: Io, l: Lock) LockError!void {
+ return io.vtable.fileLock(io.userdata, file, l);
+}
+
+/// Assumes the file is locked.
+pub fn unlock(file: File, io: Io) void {
+ return io.vtable.fileUnlock(io.userdata, file);
+}
+
+/// 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.
+pub fn tryLock(file: File, io: Io, l: Lock) LockError!bool {
+ return io.vtable.fileTryLock(io.userdata, file, l);
+}
+
+pub const DowngradeLockError = Io.Cancelable || Io.UnexpectedError;
+
+/// Assumes the file is already locked in exclusive mode.
+/// Atomically modifies the lock to be in shared mode, without releasing it.
+pub fn downgradeLock(file: File, io: Io) LockError!void {
+ return io.vtable.fileDowngradeLock(io.userdata, file);
+}
+
+pub const RealPathError = error{
+ /// This operating system, file system, or `Io` implementation does not
+ /// support realpath operations.
+ OperationUnsupported,
+ /// The full file system path could not fit into the provided buffer, or
+ /// due to its length could not be obtained via realpath functions no
+ /// matter the buffer size provided.
+ NameTooLong,
+ FileNotFound,
+ AccessDenied,
+ PermissionDenied,
+ NotDir,
+ SymLinkLoop,
+ InputOutput,
+ FileTooBig,
+ IsDir,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ SystemResources,
+ NoSpaceLeft,
+ FileSystem,
+ DeviceBusy,
+ SharingViolation,
+ PipeBusy,
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+ PathAlreadyExists,
+ /// 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,
+ /// On Windows, the volume does not contain a recognized file system. File
+ /// system drivers might not be loaded, or the volume may be corrupt.
+ UnrecognizedVolume,
+} || Io.Cancelable || Io.UnexpectedError;
+
+/// Obtains the canonicalized absolute path name corresponding to an open file
+/// handle.
+///
+/// This function has limited platform support, and using it can lead to
+/// unnecessary failures and race conditions. It is generally advisable to
+/// avoid this function entirely.
+pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize {
+ return io.vtable.fileRealPath(io.userdata, file, out_buffer);
+}
+
+test {
+ _ = Reader;
+ _ = Writer;
+ _ = Atomic;
+}