mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
std: all File functions moved to std.Io
This commit is contained in:
parent
48d70cfc38
commit
4f6658a67b
17 changed files with 2367 additions and 2325 deletions
|
|
@ -437,7 +437,6 @@ set(ZIG_STAGE2_SOURCES
|
|||
lib/std/fmt.zig
|
||||
lib/std/fmt/parse_float.zig
|
||||
lib/std/fs.zig
|
||||
lib/std/fs/File.zig
|
||||
lib/std/fs/get_app_data_dir.zig
|
||||
lib/std/fs/path.zig
|
||||
lib/std/hash.zig
|
||||
|
|
|
|||
|
|
@ -679,11 +679,13 @@ pub const VTable = struct {
|
|||
dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,
|
||||
dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.RenameError!void,
|
||||
dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,
|
||||
dirSetMode: *const fn (?*anyopaque, Dir, File.Mode) Dir.SetModeError!void,
|
||||
dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,
|
||||
dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void,
|
||||
dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
|
||||
dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
|
||||
|
||||
fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
|
||||
fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,
|
||||
fileClose: *const fn (?*anyopaque, File) void,
|
||||
fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize,
|
||||
fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize,
|
||||
|
|
@ -694,6 +696,19 @@ pub const VTable = struct {
|
|||
fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
|
||||
fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,
|
||||
openSelfExe: *const fn (?*anyopaque, File.OpenFlags) File.OpenSelfExeError!File,
|
||||
fileSync: *const fn (?*anyopaque, File) File.SyncError!void,
|
||||
fileIsTty: *const fn (?*anyopaque, File) Cancelable!bool,
|
||||
fileEnableAnsiEscapeCodes: *const fn (?*anyopaque, File) File.EnableAnsiEscapeCodesError!void,
|
||||
fileSupportsAnsiEscapeCodes: *const fn (?*anyopaque, File) Cancelable!bool,
|
||||
fileSetLength: *const fn (?*anyopaque, File, u64) File.SetLengthError!void,
|
||||
fileSetOwner: *const fn (?*anyopaque, File, ?File.Uid, ?File.Gid) File.SetOwnerError!void,
|
||||
fileSetPermissions: *const fn (?*anyopaque, File, File.Permissions) File.SetPermissionsError!void,
|
||||
fileSetTimestamps: *const fn (?*anyopaque, File, last_accessed: Timestamp, last_modified: Timestamp, File.SetTimestampsOptions) File.SetTimestampsError!void,
|
||||
fileSetTimestampsNow: *const fn (?*anyopaque, File, File.SetTimestampsOptions) File.SetTimestampsError!void,
|
||||
fileLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!void,
|
||||
fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool,
|
||||
fileUnlock: *const fn (?*anyopaque, File) void,
|
||||
fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void,
|
||||
|
||||
now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
|
||||
sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@ const Allocator = std.mem.Allocator;
|
|||
|
||||
handle: Handle,
|
||||
|
||||
pub const Mode = Io.File.Mode;
|
||||
pub const default_mode: Mode = 0o755;
|
||||
|
||||
pub const Entry = struct {
|
||||
name: []const u8,
|
||||
kind: File.Kind,
|
||||
|
|
@ -435,10 +432,10 @@ pub const PrevStatus = enum {
|
|||
|
||||
pub const UpdateFileError = File.OpenError;
|
||||
|
||||
/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If
|
||||
/// Check the file size, mtime, and permissions of `source_path` and `dest_path`. If
|
||||
/// they are equal, does nothing. Otherwise, atomically copies `source_path` to
|
||||
/// `dest_path`, creating the parent directory hierarchy as needed. The
|
||||
/// destination file gains the mtime, atime, and mode of the source file so
|
||||
/// destination file gains the mtime, atime, and permissions of the source file so
|
||||
/// that the next call to `updateFile` will not need a copy.
|
||||
///
|
||||
/// Returns the previous status of the file before updating.
|
||||
|
|
@ -459,7 +456,7 @@ pub fn updateFile(
|
|||
defer src_file.close(io);
|
||||
|
||||
const src_stat = try src_file.stat(io);
|
||||
const actual_mode = options.override_mode orelse src_stat.mode;
|
||||
const actual_permissions = options.override_permissions orelse src_stat.permissions;
|
||||
check_dest_stat: {
|
||||
const dest_stat = blk: {
|
||||
var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {
|
||||
|
|
@ -473,19 +470,19 @@ pub fn updateFile(
|
|||
|
||||
if (src_stat.size == dest_stat.size and
|
||||
src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and
|
||||
actual_mode == dest_stat.mode)
|
||||
actual_permissions == dest_stat.permissions)
|
||||
{
|
||||
return .fresh;
|
||||
}
|
||||
}
|
||||
|
||||
if (std.fs.path.dirname(dest_path)) |dirname| {
|
||||
try dest_dir.makePathMode(io, dirname, default_mode);
|
||||
try dest_dir.makePath(io, dirname, .default_dir);
|
||||
}
|
||||
|
||||
var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var atomic_file = try std.fs.Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{
|
||||
.mode = actual_mode,
|
||||
.permissions = actual_permissions,
|
||||
.write_buffer = &buffer,
|
||||
});
|
||||
defer atomic_file.deinit();
|
||||
|
|
@ -555,17 +552,12 @@ pub const MakeError = error{
|
|||
/// Related:
|
||||
/// * `makePath`
|
||||
/// * `makeDirAbsolute`
|
||||
pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void {
|
||||
return io.vtable.dirMake(io.userdata, dir, sub_path, default_mode);
|
||||
pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakeError!void {
|
||||
return io.vtable.dirMake(io.userdata, dir, sub_path, permissions);
|
||||
}
|
||||
|
||||
pub const MakePathError = MakeError || StatPathError;
|
||||
|
||||
/// Same as `makePathMode` but passes `default_mode`.
|
||||
pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void {
|
||||
_ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, default_mode);
|
||||
}
|
||||
|
||||
/// Creates parent directories as necessary to ensure `sub_path` exists as a directory.
|
||||
///
|
||||
/// Returns success if the path already exists and is a directory.
|
||||
|
|
@ -587,16 +579,16 @@ pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void {
|
|||
/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
|
||||
/// meaning a `sub_path` like "first/../second" will create both a `./first`
|
||||
/// and a `./second` directory.
|
||||
pub fn makePathMode(dir: Dir, io: Io, sub_path: []const u8, mode: Mode) MakePathError!void {
|
||||
_ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, mode);
|
||||
pub fn makePath(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakePathError!void {
|
||||
_ = try io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions);
|
||||
}
|
||||
|
||||
pub const MakePathStatus = enum { existed, created };
|
||||
|
||||
/// Same as `makePath` except returns whether the path already existed or was
|
||||
/// successfully created.
|
||||
pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus {
|
||||
return io.vtable.dirMakePath(io.userdata, dir, sub_path, default_mode);
|
||||
pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) MakePathError!MakePathStatus {
|
||||
return io.vtable.dirMakePath(io.userdata, dir, sub_path, permissions);
|
||||
}
|
||||
|
||||
pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
|
||||
|
|
@ -609,8 +601,8 @@ pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
|
|||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) MakeOpenPathError!Dir {
|
||||
return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options);
|
||||
pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions, options: OpenOptions) MakeOpenPathError!Dir {
|
||||
return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, permissions, options);
|
||||
}
|
||||
|
||||
pub const Stat = File.Stat;
|
||||
|
|
@ -1453,8 +1445,8 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, sub_path: []const u8, kind_hint: File.
|
|||
}
|
||||
|
||||
pub const CopyFileOptions = struct {
|
||||
/// When this is `null` the mode is copied from the source file.
|
||||
override_mode: ?File.Mode = null,
|
||||
/// When this is `null` the permissions are copied from the source file.
|
||||
override_permissions: ?File.Permissions = null,
|
||||
};
|
||||
|
||||
pub const CopyFileError = File.OpenError || File.StatError ||
|
||||
|
|
@ -1486,15 +1478,15 @@ pub fn copyFile(
|
|||
var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
|
||||
defer file_reader.file.close(io);
|
||||
|
||||
const mode = options.override_mode orelse blk: {
|
||||
const permissions = options.override_permissions orelse blk: {
|
||||
const st = try file_reader.file.stat(io);
|
||||
file_reader.size = st.size;
|
||||
break :blk st.mode;
|
||||
break :blk st.permissions;
|
||||
};
|
||||
|
||||
var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
|
||||
.mode = mode,
|
||||
.permissions = permissions,
|
||||
.write_buffer = &buffer,
|
||||
});
|
||||
defer atomic_file.deinit(io);
|
||||
|
|
@ -1508,7 +1500,7 @@ pub fn copyFile(
|
|||
}
|
||||
|
||||
pub const AtomicFileOptions = struct {
|
||||
mode: File.Mode = File.default_mode,
|
||||
permissions: File.Permissions = .default_file,
|
||||
make_path: bool = false,
|
||||
write_buffer: []u8,
|
||||
};
|
||||
|
|
@ -1530,13 +1522,14 @@ pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFil
|
|||
else
|
||||
try parent.openDir(io, dirname, .{});
|
||||
|
||||
return .init(std.fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer);
|
||||
return .init(std.fs.path.basename(dest_path), options.permissions, dir, true, options.write_buffer);
|
||||
} else {
|
||||
return .init(dest_path, options.mode, parent, false, options.write_buffer);
|
||||
return .init(dest_path, options.permissions, parent, false, options.write_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
pub const SetModeError = File.SetModeError;
|
||||
pub const SetPermissionsError = File.SetPermissionsError;
|
||||
pub const Permissions = File.Permissions;
|
||||
|
||||
/// Also known as "chmod".
|
||||
///
|
||||
|
|
@ -1544,8 +1537,8 @@ pub const SetModeError = File.SetModeError;
|
|||
/// successfully, or must have the effective user ID matching the owner
|
||||
/// of the directory. Additionally, the directory must have been opened
|
||||
/// with `OpenOptions.iterate` set to `true`.
|
||||
pub fn setMode(dir: Dir, io: Io, new_mode: File.Mode) SetModeError!void {
|
||||
return io.vtable.dirSetMode(io.userdata, dir, new_mode);
|
||||
pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPermissionsError!void {
|
||||
return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions);
|
||||
}
|
||||
|
||||
pub const SetOwnerError = File.SetOwnerError;
|
||||
|
|
@ -1561,9 +1554,31 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr
|
|||
return io.vtable.dirSetOwner(io.userdata, dir, owner, group);
|
||||
}
|
||||
|
||||
pub const SetPermissionsError = File.SetPermissionsError;
|
||||
pub const Permissions = File.Permissions;
|
||||
pub const SetTimestampsError = File.SetTimestampsError;
|
||||
|
||||
pub fn setPermissions(dir: Dir, io: Io, permissions: Permissions) SetPermissionsError!void {
|
||||
return io.vtable.dirSetPermissions(io.userdata, dir, permissions);
|
||||
pub const SetTimestampsOptions = struct {
|
||||
follow_symlinks: bool = true,
|
||||
};
|
||||
|
||||
/// 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(
|
||||
dir: Dir,
|
||||
io: Io,
|
||||
sub_path: []const u8,
|
||||
last_accessed: Io.Timestamp,
|
||||
last_modified: Io.Timestamp,
|
||||
options: SetTimestampsOptions,
|
||||
) SetTimestampsError!void {
|
||||
return io.vtable.dirSetTimestamps(io.userdata, dir, sub_path, last_accessed, last_modified, options);
|
||||
}
|
||||
|
||||
/// Sets the accessed and modification timestamps of the provided path 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(dir: Dir, io: Io, sub_path: []const u8, options: SetTimestampsOptions) SetTimestampsError!void {
|
||||
return io.vtable.fileSetTimestampsNow(io.userdata, dir, sub_path, options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,20 +11,15 @@ 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 Uid = std.posix.uid_t;
|
||||
pub const Gid = std.posix.gid_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.
|
||||
pub const default_mode: Mode = if (Mode == u0) 0 else 0o666;
|
||||
|
||||
pub const Kind = enum {
|
||||
block_device,
|
||||
character_device,
|
||||
|
|
@ -53,8 +48,7 @@ pub const Stat = struct {
|
|||
/// is unique to each filesystem.
|
||||
inode: INode,
|
||||
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,
|
||||
|
|
@ -103,11 +97,6 @@ pub const Lock = enum {
|
|||
exclusive,
|
||||
};
|
||||
|
||||
pub const LockError = error{
|
||||
SystemResources,
|
||||
FileLocksNotSupported,
|
||||
} || Io.UnexpectedError;
|
||||
|
||||
pub const OpenFlags = struct {
|
||||
mode: OpenMode = .read_only,
|
||||
|
||||
|
|
@ -200,9 +189,7 @@ pub const CreateFlags = struct {
|
|||
/// 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,
|
||||
permissions: Permissions = .default_file,
|
||||
};
|
||||
|
||||
pub const OpenError = error{
|
||||
|
|
@ -251,7 +238,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.
|
||||
|
|
@ -269,7 +256,216 @@ pub fn close(file: File, io: Io) void {
|
|||
return io.vtable.fileClose(io.userdata, file);
|
||||
}
|
||||
|
||||
pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || std.posix.FlockError;
|
||||
pub const SyncError = error{
|
||||
InputOutput,
|
||||
NoSpaceLeft,
|
||||
DiskQuota,
|
||||
AccessDenied,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Test whether the file refers to a terminal.
|
||||
///
|
||||
/// See also:
|
||||
/// * `getOrEnableAnsiEscapeSupport`
|
||||
/// * `supportsAnsiEscapeCodes`.
|
||||
pub fn isTty(file: File, io: Io) bool {
|
||||
return io.vtable.fileIsTty(io.userdata, file);
|
||||
}
|
||||
|
||||
pub const EnableAnsiEscapeCodesError = error{} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError {
|
||||
return io.vtable.fileEnableAnsiEscapeCodes(io.userdata, file);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// 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);
|
||||
}
|
||||
|
||||
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 SetPermissionsError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
InputOutput,
|
||||
SymLinkLoop,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// Also known as "chmod".
|
||||
///
|
||||
/// 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 const SetOwnerError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
InputOutput,
|
||||
SymLinkLoop,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Cross-platform representation of permissions on a file.
|
||||
///
|
||||
/// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes".
|
||||
///
|
||||
/// Overridable via `std.options`.
|
||||
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 has_executable_bit = false;
|
||||
|
||||
const windows = std.os.windows;
|
||||
|
||||
pub fn toAttributes(self: @This()) windows.DWORD {
|
||||
return @intFromEnum(self);
|
||||
}
|
||||
|
||||
pub fn readOnly(self: @This()) bool {
|
||||
const attributes = toAttributes(self);
|
||||
return attributes & windows.FILE_ATTRIBUTE_READONLY != 0;
|
||||
}
|
||||
|
||||
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,
|
||||
_,
|
||||
|
||||
pub const has_executable_bit = true;
|
||||
|
||||
pub fn toMode(self: @This()) std.posix.mode_t {
|
||||
return @intFromEnum(self);
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// 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 has_executable_bit = false;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
/// 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.fileUpdateTimes(io.userdata, file, last_accessed, last_modified);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || LockError;
|
||||
|
||||
pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File {
|
||||
return io.vtable.openSelfExe(io.userdata, flags);
|
||||
|
|
@ -309,6 +505,12 @@ pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenErr
|
|||
return Io.Dir.cwd().openFile(io, absolute_path, flags);
|
||||
}
|
||||
|
||||
pub const SeekError = error{
|
||||
Unseekable,
|
||||
/// The file descriptor does not hold the required rights to seek on it.
|
||||
AccessDenied,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// Defaults to positional reading; falls back to streaming.
|
||||
///
|
||||
/// Positional is more threadsafe, since the global seek position is not
|
||||
|
|
@ -324,509 +526,54 @@ pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
|
|||
return .initStreaming(file, io, buffer);
|
||||
}
|
||||
|
||||
pub const SeekError = error{
|
||||
Unseekable,
|
||||
/// The file descriptor does not hold the required rights to seek on it.
|
||||
AccessDenied,
|
||||
} || 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`).
|
||||
/// Defaults to positional reading; falls back to streaming.
|
||||
///
|
||||
/// 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,
|
||||
/// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
/// 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);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(file: File, io: Io, buffer: []u8) Reader {
|
||||
return .{
|
||||
.io = io,
|
||||
.file = file,
|
||||
.interface = initInterface(buffer),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
|
||||
return .{
|
||||
.io = io,
|
||||
.file = file,
|
||||
.interface = initInterface(buffer),
|
||||
.size = size,
|
||||
};
|
||||
}
|
||||
|
||||
/// 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 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 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.?,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 logicalPos(r: *const Reader) u64 {
|
||||
return r.pos - r.interface.bufferedLen();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Atomic = struct {
|
||||
file_writer: File.Writer,
|
||||
random_integer: u64,
|
||||
dest_basename: []const u8,
|
||||
file_open: bool,
|
||||
file_exists: bool,
|
||||
close_dir_on_deinit: bool,
|
||||
dir: Dir,
|
||||
|
||||
pub const InitError = File.OpenError;
|
||||
|
||||
/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
|
||||
pub fn init(
|
||||
dest_basename: []const u8,
|
||||
mode: File.Mode,
|
||||
dir: Dir,
|
||||
close_dir_on_deinit: bool,
|
||||
write_buffer: []u8,
|
||||
) InitError!Atomic {
|
||||
while (true) {
|
||||
const random_integer = std.crypto.random.int(u64);
|
||||
const tmp_sub_path = std.fmt.hex(random_integer);
|
||||
const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
else => |e| return e,
|
||||
};
|
||||
return .{
|
||||
.file_writer = file.writer(write_buffer),
|
||||
.random_integer = random_integer,
|
||||
.dest_basename = dest_basename,
|
||||
.file_open = true,
|
||||
.file_exists = true,
|
||||
.close_dir_on_deinit = close_dir_on_deinit,
|
||||
.dir = dir,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Always call deinit, even after a successful finish().
|
||||
pub fn deinit(af: *Atomic) void {
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close();
|
||||
af.file_open = false;
|
||||
}
|
||||
if (af.file_exists) {
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
af.dir.deleteFile(&tmp_sub_path) catch {};
|
||||
af.file_exists = false;
|
||||
}
|
||||
if (af.close_dir_on_deinit) {
|
||||
af.dir.close();
|
||||
}
|
||||
af.* = undefined;
|
||||
}
|
||||
|
||||
pub const FlushError = File.WriteError;
|
||||
|
||||
pub fn flush(af: *Atomic) FlushError!void {
|
||||
af.file_writer.interface.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => return af.file_writer.err.?,
|
||||
};
|
||||
}
|
||||
|
||||
pub const RenameIntoPlaceError = Dir.RenameError;
|
||||
|
||||
/// On Windows, this function introduces a period of time where some file
|
||||
/// system operations on the destination file will result in
|
||||
/// `error.AccessDenied`, including rename operations (such as the one used in
|
||||
/// this function).
|
||||
pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void {
|
||||
const io = af.file_writer.io;
|
||||
assert(af.file_exists);
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close();
|
||||
af.file_open = false;
|
||||
}
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io);
|
||||
af.file_exists = false;
|
||||
}
|
||||
|
||||
pub const FinishError = FlushError || RenameIntoPlaceError;
|
||||
|
||||
/// Combination of `flush` followed by `renameIntoPlace`.
|
||||
pub fn finish(af: *Atomic) FinishError!void {
|
||||
try af.flush();
|
||||
try af.renameIntoPlace();
|
||||
}
|
||||
};
|
||||
|
||||
pub const SetModeError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
InputOutput,
|
||||
SymLinkLoop,
|
||||
FileNotFound,
|
||||
pub const LockError = error{
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
FileLocksUnsupported,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
pub const SetOwnerError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
InputOutput,
|
||||
SymLinkLoop,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
} || 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);
|
||||
}
|
||||
|
|
|
|||
99
lib/std/Io/File/Atomic.zig
Normal file
99
lib/std/Io/File/Atomic.zig
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
const Atomic = @This();
|
||||
|
||||
const std = @import("../../std.zig");
|
||||
const Io = std.Io;
|
||||
const File = std.Io.File;
|
||||
const Dir = std.Io.Dir;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
file_writer: File.Writer,
|
||||
random_integer: u64,
|
||||
dest_basename: []const u8,
|
||||
file_open: bool,
|
||||
file_exists: bool,
|
||||
close_dir_on_deinit: bool,
|
||||
dir: Dir,
|
||||
|
||||
pub const InitError = File.OpenError;
|
||||
|
||||
/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
|
||||
pub fn init(
|
||||
io: Io,
|
||||
dest_basename: []const u8,
|
||||
mode: File.Mode,
|
||||
dir: Dir,
|
||||
close_dir_on_deinit: bool,
|
||||
write_buffer: []u8,
|
||||
) InitError!Atomic {
|
||||
while (true) {
|
||||
const random_integer = std.crypto.random.int(u64);
|
||||
const tmp_sub_path = std.fmt.hex(random_integer);
|
||||
const file = dir.createFile(io, &tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
else => |e| return e,
|
||||
};
|
||||
return .{
|
||||
.file_writer = file.writer(io, write_buffer),
|
||||
.random_integer = random_integer,
|
||||
.dest_basename = dest_basename,
|
||||
.file_open = true,
|
||||
.file_exists = true,
|
||||
.close_dir_on_deinit = close_dir_on_deinit,
|
||||
.dir = dir,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Always call deinit, even after a successful finish().
|
||||
pub fn deinit(af: *Atomic) void {
|
||||
const io = af.file_writer.io;
|
||||
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close(io);
|
||||
af.file_open = false;
|
||||
}
|
||||
if (af.file_exists) {
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
af.dir.deleteFile(io, &tmp_sub_path) catch {};
|
||||
af.file_exists = false;
|
||||
}
|
||||
if (af.close_dir_on_deinit) {
|
||||
af.dir.close(io);
|
||||
}
|
||||
af.* = undefined;
|
||||
}
|
||||
|
||||
pub const FlushError = File.WriteError;
|
||||
|
||||
pub fn flush(af: *Atomic) FlushError!void {
|
||||
af.file_writer.interface.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => return af.file_writer.err.?,
|
||||
};
|
||||
}
|
||||
|
||||
pub const RenameIntoPlaceError = Dir.RenameError;
|
||||
|
||||
/// On Windows, this function introduces a period of time where some file
|
||||
/// system operations on the destination file will result in
|
||||
/// `error.AccessDenied`, including rename operations (such as the one used in
|
||||
/// this function).
|
||||
pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void {
|
||||
const io = af.file_writer.io;
|
||||
|
||||
assert(af.file_exists);
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close(io);
|
||||
af.file_open = false;
|
||||
}
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io);
|
||||
af.file_exists = false;
|
||||
}
|
||||
|
||||
pub const FinishError = FlushError || RenameIntoPlaceError;
|
||||
|
||||
/// Combination of `flush` followed by `renameIntoPlace`.
|
||||
pub fn finish(af: *Atomic) FinishError!void {
|
||||
try af.flush();
|
||||
try af.renameIntoPlace();
|
||||
}
|
||||
395
lib/std/Io/File/Reader.zig
Normal file
395
lib/std/Io/File/Reader.zig
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
//! 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`).
|
||||
//!
|
||||
//! Fulfills the `Io.Reader` interface.
|
||||
const Reader = @This();
|
||||
|
||||
const std = @import("../../std.zig");
|
||||
const Io = std.Io;
|
||||
const File = std.Io.File;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
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 || File.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,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(file: File, io: Io, buffer: []u8) Reader {
|
||||
return .{
|
||||
.io = io,
|
||||
.file = file,
|
||||
.interface = initInterface(buffer),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
|
||||
return .{
|
||||
.io = io,
|
||||
.file = file,
|
||||
.interface = initInterface(buffer),
|
||||
.size = size,
|
||||
};
|
||||
}
|
||||
|
||||
/// 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 getSize(r: *Reader) SizeError!u64 {
|
||||
return r.size orelse {
|
||||
if (r.size_err) |err| return err;
|
||||
if (r.file.stat(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 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.?,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 logicalPos(r: *const Reader) u64 {
|
||||
return r.pos - r.interface.bufferedLen();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
566
lib/std/Io/File/Writer.zig
Normal file
566
lib/std/Io/File/Writer.zig
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
const Writer = @This();
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const native_os = builtin.os.tag;
|
||||
const is_windows = native_os == .windows;
|
||||
|
||||
const std = @import("../../std.zig");
|
||||
const Io = std.Io;
|
||||
const File = std.Io.File;
|
||||
const assert = std.debug.assert;
|
||||
const windows = std.os.windows;
|
||||
const posix = std.posix;
|
||||
|
||||
file: File,
|
||||
err: ?File.WriteError = null,
|
||||
mode: Writer.Mode = .positional,
|
||||
/// Tracks the true seek position in the file. To obtain the logical
|
||||
/// position, add the buffer size to this value.
|
||||
pos: u64 = 0,
|
||||
sendfile_err: ?SendfileError = null,
|
||||
copy_file_range_err: ?CopyFileRangeError = null,
|
||||
fcopyfile_err: ?FcopyfileError = null,
|
||||
seek_err: ?Writer.SeekError = null,
|
||||
interface: Io.Writer,
|
||||
|
||||
pub const Mode = File.Reader.Mode;
|
||||
|
||||
pub const SendfileError = error{
|
||||
UnsupportedOperation,
|
||||
SystemResources,
|
||||
InputOutput,
|
||||
BrokenPipe,
|
||||
WouldBlock,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError;
|
||||
|
||||
pub const FcopyfileError = error{
|
||||
OperationNotSupported,
|
||||
OutOfMemory,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub const SeekError = Io.File.SeekError;
|
||||
|
||||
/// Number of slices to store on the stack, when trying to send as many byte
|
||||
/// vectors through the underlying write calls as possible.
|
||||
const max_buffers_len = 16;
|
||||
|
||||
pub fn init(file: File, buffer: []u8) Writer {
|
||||
return .{
|
||||
.file = file,
|
||||
.interface = initInterface(buffer),
|
||||
.mode = .positional,
|
||||
};
|
||||
}
|
||||
|
||||
/// Positional is more threadsafe, since the global seek position is not
|
||||
/// affected, but when such syscalls are not available, preemptively
|
||||
/// initializing in streaming mode will skip a failed syscall.
|
||||
pub fn initStreaming(file: File, buffer: []u8) Writer {
|
||||
return .{
|
||||
.file = file,
|
||||
.interface = initInterface(buffer),
|
||||
.mode = .streaming,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initInterface(buffer: []u8) Io.Writer {
|
||||
return .{
|
||||
.vtable = &.{
|
||||
.drain = drain,
|
||||
.sendFile = sendFile,
|
||||
},
|
||||
.buffer = buffer,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn moveToReader(w: *Writer) File.Reader {
|
||||
defer w.* = undefined;
|
||||
return .{
|
||||
.io = w.io,
|
||||
.file = .{ .handle = w.file.handle },
|
||||
.mode = w.mode,
|
||||
.pos = w.pos,
|
||||
.interface = File.Reader.initInterface(w.interface.buffer),
|
||||
.seek_err = w.seek_err,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
|
||||
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
|
||||
const handle = w.file.handle;
|
||||
const buffered = io_w.buffered();
|
||||
if (is_windows) switch (w.mode) {
|
||||
.positional, .positional_reading => {
|
||||
if (buffered.len != 0) {
|
||||
const n = windows.WriteFile(handle, buffered, w.pos) catch |err| {
|
||||
w.err = err;
|
||||
return error.WriteFailed;
|
||||
};
|
||||
w.pos += n;
|
||||
return io_w.consume(n);
|
||||
}
|
||||
for (data[0 .. data.len - 1]) |buf| {
|
||||
if (buf.len == 0) continue;
|
||||
const n = windows.WriteFile(handle, buf, w.pos) catch |err| {
|
||||
w.err = err;
|
||||
return error.WriteFailed;
|
||||
};
|
||||
w.pos += n;
|
||||
return io_w.consume(n);
|
||||
}
|
||||
const pattern = data[data.len - 1];
|
||||
if (pattern.len == 0 or splat == 0) return 0;
|
||||
const n = windows.WriteFile(handle, pattern, w.pos) catch |err| {
|
||||
w.err = err;
|
||||
return error.WriteFailed;
|
||||
};
|
||||
w.pos += n;
|
||||
return io_w.consume(n);
|
||||
},
|
||||
.streaming, .streaming_reading => {
|
||||
if (buffered.len != 0) {
|
||||
const n = windows.WriteFile(handle, buffered, null) catch |err| {
|
||||
w.err = err;
|
||||
return error.WriteFailed;
|
||||
};
|
||||
w.pos += n;
|
||||
return io_w.consume(n);
|
||||
}
|
||||
for (data[0 .. data.len - 1]) |buf| {
|
||||
if (buf.len == 0) continue;
|
||||
const n = windows.WriteFile(handle, buf, null) catch |err| {
|
||||
w.err = err;
|
||||
return error.WriteFailed;
|
||||
};
|
||||
w.pos += n;
|
||||
return io_w.consume(n);
|
||||
}
|
||||
const pattern = data[data.len - 1];
|
||||
if (pattern.len == 0 or splat == 0) return 0;
|
||||
const n = windows.WriteFile(handle, pattern, null) catch |err| {
|
||||
w.err = err;
|
||||
return error.WriteFailed;
|
||||
};
|
||||
w.pos += n;
|
||||
return io_w.consume(n);
|
||||
},
|
||||
.failure => return error.WriteFailed,
|
||||
};
|
||||
var iovecs: [max_buffers_len]posix.iovec_const = undefined;
|
||||
var len: usize = 0;
|
||||
if (buffered.len > 0) {
|
||||
iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len };
|
||||
len += 1;
|
||||
}
|
||||
for (data[0 .. data.len - 1]) |d| {
|
||||
if (d.len == 0) continue;
|
||||
iovecs[len] = .{ .base = d.ptr, .len = d.len };
|
||||
len += 1;
|
||||
if (iovecs.len - len == 0) break;
|
||||
}
|
||||
const pattern = data[data.len - 1];
|
||||
if (iovecs.len - len != 0) switch (splat) {
|
||||
0 => {},
|
||||
1 => if (pattern.len != 0) {
|
||||
iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len };
|
||||
len += 1;
|
||||
},
|
||||
else => switch (pattern.len) {
|
||||
0 => {},
|
||||
1 => {
|
||||
const splat_buffer_candidate = io_w.buffer[io_w.end..];
|
||||
var backup_buffer: [64]u8 = undefined;
|
||||
const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
|
||||
splat_buffer_candidate
|
||||
else
|
||||
&backup_buffer;
|
||||
const memset_len = @min(splat_buffer.len, splat);
|
||||
const buf = splat_buffer[0..memset_len];
|
||||
@memset(buf, pattern[0]);
|
||||
iovecs[len] = .{ .base = buf.ptr, .len = buf.len };
|
||||
len += 1;
|
||||
var remaining_splat = splat - buf.len;
|
||||
while (remaining_splat > splat_buffer.len and iovecs.len - len != 0) {
|
||||
assert(buf.len == splat_buffer.len);
|
||||
iovecs[len] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len };
|
||||
len += 1;
|
||||
remaining_splat -= splat_buffer.len;
|
||||
}
|
||||
if (remaining_splat > 0 and iovecs.len - len != 0) {
|
||||
iovecs[len] = .{ .base = splat_buffer.ptr, .len = remaining_splat };
|
||||
len += 1;
|
||||
}
|
||||
},
|
||||
else => for (0..splat) |_| {
|
||||
iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len };
|
||||
len += 1;
|
||||
if (iovecs.len - len == 0) break;
|
||||
},
|
||||
},
|
||||
};
|
||||
if (len == 0) return 0;
|
||||
switch (w.mode) {
|
||||
.positional, .positional_reading => {
|
||||
const n = posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) {
|
||||
error.Unseekable => {
|
||||
w.mode = w.mode.toStreaming();
|
||||
const pos = w.pos;
|
||||
if (pos != 0) {
|
||||
w.pos = 0;
|
||||
w.seekTo(@intCast(pos)) catch {
|
||||
w.mode = .failure;
|
||||
return error.WriteFailed;
|
||||
};
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
else => |e| {
|
||||
w.err = e;
|
||||
return error.WriteFailed;
|
||||
},
|
||||
};
|
||||
w.pos += n;
|
||||
return io_w.consume(n);
|
||||
},
|
||||
.streaming, .streaming_reading => {
|
||||
const n = posix.writev(handle, iovecs[0..len]) catch |err| {
|
||||
w.err = err;
|
||||
return error.WriteFailed;
|
||||
};
|
||||
w.pos += n;
|
||||
return io_w.consume(n);
|
||||
},
|
||||
.failure => return error.WriteFailed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendFile(
|
||||
io_w: *Io.Writer,
|
||||
file_reader: *Io.File.Reader,
|
||||
limit: Io.Limit,
|
||||
) Io.Writer.FileError!usize {
|
||||
const reader_buffered = file_reader.interface.buffered();
|
||||
if (reader_buffered.len >= @intFromEnum(limit))
|
||||
return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered));
|
||||
const writer_buffered = io_w.buffered();
|
||||
const file_limit = @intFromEnum(limit) - reader_buffered.len;
|
||||
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
|
||||
const out_fd = w.file.handle;
|
||||
const in_fd = file_reader.file.handle;
|
||||
|
||||
if (file_reader.size) |size| {
|
||||
if (size - file_reader.pos == 0) {
|
||||
if (reader_buffered.len != 0) {
|
||||
return sendFileBuffered(io_w, file_reader, reader_buffered);
|
||||
} else {
|
||||
return error.EndOfStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (native_os == .freebsd and w.mode == .streaming) sf: {
|
||||
// Try using sendfile on FreeBSD.
|
||||
if (w.sendfile_err != null) break :sf;
|
||||
const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
|
||||
var hdtr_data: std.c.sf_hdtr = undefined;
|
||||
var headers: [2]posix.iovec_const = undefined;
|
||||
var headers_i: u8 = 0;
|
||||
if (writer_buffered.len != 0) {
|
||||
headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len };
|
||||
headers_i += 1;
|
||||
}
|
||||
if (reader_buffered.len != 0) {
|
||||
headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
|
||||
headers_i += 1;
|
||||
}
|
||||
const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
|
||||
hdtr_data = .{
|
||||
.headers = &headers,
|
||||
.hdr_cnt = headers_i,
|
||||
.trailers = null,
|
||||
.trl_cnt = 0,
|
||||
};
|
||||
break :b &hdtr_data;
|
||||
};
|
||||
var sbytes: std.c.off_t = undefined;
|
||||
const nbytes: usize = @min(file_limit, std.math.maxInt(usize));
|
||||
const flags = 0;
|
||||
switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) {
|
||||
.SUCCESS, .INTR => {},
|
||||
.INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation,
|
||||
.BADF => if (builtin.mode == .Debug) @panic("race condition") else {
|
||||
w.sendfile_err = error.Unexpected;
|
||||
},
|
||||
.FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else {
|
||||
w.sendfile_err = error.Unexpected;
|
||||
},
|
||||
.NOTCONN => w.sendfile_err = error.BrokenPipe,
|
||||
.AGAIN, .BUSY => if (sbytes == 0) {
|
||||
w.sendfile_err = error.WouldBlock;
|
||||
},
|
||||
.IO => w.sendfile_err = error.InputOutput,
|
||||
.PIPE => w.sendfile_err = error.BrokenPipe,
|
||||
.NOBUFS => w.sendfile_err = error.SystemResources,
|
||||
else => |err| w.sendfile_err = posix.unexpectedErrno(err),
|
||||
}
|
||||
if (w.sendfile_err != null) {
|
||||
// Give calling code chance to observe the error before trying
|
||||
// something else.
|
||||
return 0;
|
||||
}
|
||||
if (sbytes == 0) {
|
||||
file_reader.size = file_reader.pos;
|
||||
return error.EndOfStream;
|
||||
}
|
||||
const consumed = io_w.consume(@intCast(sbytes));
|
||||
file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
|
||||
return consumed;
|
||||
}
|
||||
|
||||
if (native_os.isDarwin() and w.mode == .streaming) sf: {
|
||||
// Try using sendfile on macOS.
|
||||
if (w.sendfile_err != null) break :sf;
|
||||
const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
|
||||
var hdtr_data: std.c.sf_hdtr = undefined;
|
||||
var headers: [2]posix.iovec_const = undefined;
|
||||
var headers_i: u8 = 0;
|
||||
if (writer_buffered.len != 0) {
|
||||
headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len };
|
||||
headers_i += 1;
|
||||
}
|
||||
if (reader_buffered.len != 0) {
|
||||
headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
|
||||
headers_i += 1;
|
||||
}
|
||||
const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
|
||||
hdtr_data = .{
|
||||
.headers = &headers,
|
||||
.hdr_cnt = headers_i,
|
||||
.trailers = null,
|
||||
.trl_cnt = 0,
|
||||
};
|
||||
break :b &hdtr_data;
|
||||
};
|
||||
const max_count = std.math.maxInt(i32); // Avoid EINVAL.
|
||||
var len: std.c.off_t = @min(file_limit, max_count);
|
||||
const flags = 0;
|
||||
switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) {
|
||||
.SUCCESS, .INTR => {},
|
||||
.OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation,
|
||||
.BADF => if (builtin.mode == .Debug) @panic("race condition") else {
|
||||
w.sendfile_err = error.Unexpected;
|
||||
},
|
||||
.FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else {
|
||||
w.sendfile_err = error.Unexpected;
|
||||
},
|
||||
.INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else {
|
||||
w.sendfile_err = error.Unexpected;
|
||||
},
|
||||
.NOTCONN => w.sendfile_err = error.BrokenPipe,
|
||||
.AGAIN => if (len == 0) {
|
||||
w.sendfile_err = error.WouldBlock;
|
||||
},
|
||||
.IO => w.sendfile_err = error.InputOutput,
|
||||
.PIPE => w.sendfile_err = error.BrokenPipe,
|
||||
else => |err| w.sendfile_err = posix.unexpectedErrno(err),
|
||||
}
|
||||
if (w.sendfile_err != null) {
|
||||
// Give calling code chance to observe the error before trying
|
||||
// something else.
|
||||
return 0;
|
||||
}
|
||||
if (len == 0) {
|
||||
file_reader.size = file_reader.pos;
|
||||
return error.EndOfStream;
|
||||
}
|
||||
const consumed = io_w.consume(@bitCast(len));
|
||||
file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
|
||||
return consumed;
|
||||
}
|
||||
|
||||
if (native_os == .linux and w.mode == .streaming) sf: {
|
||||
// Try using sendfile on Linux.
|
||||
if (w.sendfile_err != null) break :sf;
|
||||
// Linux sendfile does not support headers.
|
||||
if (writer_buffered.len != 0 or reader_buffered.len != 0)
|
||||
return sendFileBuffered(io_w, file_reader, reader_buffered);
|
||||
const max_count = 0x7ffff000; // Avoid EINVAL.
|
||||
var off: std.os.linux.off_t = undefined;
|
||||
const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) {
|
||||
.positional => o: {
|
||||
const size = file_reader.getSize() catch return 0;
|
||||
off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed;
|
||||
break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) };
|
||||
},
|
||||
.streaming => .{ null, limit.minInt(max_count) },
|
||||
.streaming_reading, .positional_reading => break :sf,
|
||||
.failure => return error.ReadFailed,
|
||||
};
|
||||
const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) {
|
||||
error.Unseekable => {
|
||||
file_reader.mode = file_reader.mode.toStreaming();
|
||||
const pos = file_reader.pos;
|
||||
if (pos != 0) {
|
||||
file_reader.pos = 0;
|
||||
file_reader.seekBy(@intCast(pos)) catch {
|
||||
file_reader.mode = .failure;
|
||||
return error.ReadFailed;
|
||||
};
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
else => |e| {
|
||||
w.sendfile_err = e;
|
||||
return 0;
|
||||
},
|
||||
};
|
||||
if (n == 0) {
|
||||
file_reader.size = file_reader.pos;
|
||||
return error.EndOfStream;
|
||||
}
|
||||
file_reader.pos += n;
|
||||
w.pos += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
const copy_file_range = switch (native_os) {
|
||||
.freebsd => std.os.freebsd.copy_file_range,
|
||||
.linux => std.os.linux.wrapped.copy_file_range,
|
||||
else => {},
|
||||
};
|
||||
if (@TypeOf(copy_file_range) != void) cfr: {
|
||||
if (w.copy_file_range_err != null) break :cfr;
|
||||
if (writer_buffered.len != 0 or reader_buffered.len != 0)
|
||||
return sendFileBuffered(io_w, file_reader, reader_buffered);
|
||||
var off_in: i64 = undefined;
|
||||
var off_out: i64 = undefined;
|
||||
const off_in_ptr: ?*i64 = switch (file_reader.mode) {
|
||||
.positional_reading, .streaming_reading => return error.Unimplemented,
|
||||
.positional => p: {
|
||||
off_in = @intCast(file_reader.pos);
|
||||
break :p &off_in;
|
||||
},
|
||||
.streaming => null,
|
||||
.failure => return error.WriteFailed,
|
||||
};
|
||||
const off_out_ptr: ?*i64 = switch (w.mode) {
|
||||
.positional_reading, .streaming_reading => return error.Unimplemented,
|
||||
.positional => p: {
|
||||
off_out = @intCast(w.pos);
|
||||
break :p &off_out;
|
||||
},
|
||||
.streaming => null,
|
||||
.failure => return error.WriteFailed,
|
||||
};
|
||||
const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| {
|
||||
w.copy_file_range_err = err;
|
||||
return 0;
|
||||
};
|
||||
if (n == 0) {
|
||||
file_reader.size = file_reader.pos;
|
||||
return error.EndOfStream;
|
||||
}
|
||||
file_reader.pos += n;
|
||||
w.pos += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
if (builtin.os.tag.isDarwin()) fcf: {
|
||||
if (w.fcopyfile_err != null) break :fcf;
|
||||
if (file_reader.pos != 0) break :fcf;
|
||||
if (w.pos != 0) break :fcf;
|
||||
if (limit != .unlimited) break :fcf;
|
||||
const size = file_reader.getSize() catch break :fcf;
|
||||
if (writer_buffered.len != 0 or reader_buffered.len != 0)
|
||||
return sendFileBuffered(io_w, file_reader, reader_buffered);
|
||||
const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true });
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
.INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else {
|
||||
w.fcopyfile_err = error.Unexpected;
|
||||
return 0;
|
||||
},
|
||||
.NOMEM => {
|
||||
w.fcopyfile_err = error.OutOfMemory;
|
||||
return 0;
|
||||
},
|
||||
.OPNOTSUPP => {
|
||||
w.fcopyfile_err = error.OperationNotSupported;
|
||||
return 0;
|
||||
},
|
||||
else => |err| {
|
||||
w.fcopyfile_err = posix.unexpectedErrno(err);
|
||||
return 0;
|
||||
},
|
||||
}
|
||||
file_reader.pos = size;
|
||||
w.pos = size;
|
||||
return size;
|
||||
}
|
||||
|
||||
return error.Unimplemented;
|
||||
}
|
||||
|
||||
fn sendFileBuffered(
|
||||
io_w: *Io.Writer,
|
||||
file_reader: *Io.File.Reader,
|
||||
reader_buffered: []const u8,
|
||||
) Io.Writer.FileError!usize {
|
||||
const n = try drain(io_w, &.{reader_buffered}, 1);
|
||||
file_reader.seekBy(@intCast(n)) catch return error.ReadFailed;
|
||||
return n;
|
||||
}
|
||||
|
||||
pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void {
|
||||
try w.interface.flush();
|
||||
try seekToUnbuffered(w, offset);
|
||||
}
|
||||
|
||||
/// Asserts that no data is currently buffered.
|
||||
pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void {
|
||||
assert(w.interface.buffered().len == 0);
|
||||
switch (w.mode) {
|
||||
.positional, .positional_reading => {
|
||||
w.pos = offset;
|
||||
},
|
||||
.streaming, .streaming_reading => {
|
||||
if (w.seek_err) |err| return err;
|
||||
posix.lseek_SET(w.file.handle, offset) catch |err| {
|
||||
w.seek_err = err;
|
||||
return err;
|
||||
};
|
||||
w.pos = offset;
|
||||
},
|
||||
.failure => return w.seek_err.?,
|
||||
}
|
||||
}
|
||||
|
||||
pub const EndError = File.SetEndPosError || Io.Writer.Error;
|
||||
|
||||
/// Flushes any buffered data and sets the end position of the file.
|
||||
///
|
||||
/// If not overwriting existing contents, then calling `interface.flush`
|
||||
/// directly is sufficient.
|
||||
///
|
||||
/// Flush failure is handled by setting `err` so that it can be handled
|
||||
/// along with other write failures.
|
||||
pub fn end(w: *Writer) EndError!void {
|
||||
try w.interface.flush();
|
||||
switch (w.mode) {
|
||||
.positional,
|
||||
.positional_reading,
|
||||
=> w.file.setEndPos(w.pos) catch |err| switch (err) {
|
||||
error.NonResizable => return,
|
||||
else => |e| return e,
|
||||
},
|
||||
|
||||
.streaming,
|
||||
.streaming_reading,
|
||||
.failure,
|
||||
=> {},
|
||||
}
|
||||
}
|
||||
|
|
@ -1093,7 +1093,7 @@ fn createFile(
|
|||
.PERM => return error.PermissionDenied,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.BUSY => return error.DeviceBusy,
|
||||
.OPNOTSUPP => return error.FileLocksNotSupported,
|
||||
.OPNOTSUPP => return error.FileLocksUnsupported,
|
||||
.AGAIN => return error.WouldBlock,
|
||||
.TXTBSY => return error.FileBusy,
|
||||
.NXIO => return error.NoDevice,
|
||||
|
|
@ -1201,7 +1201,7 @@ fn fileOpen(
|
|||
.PERM => return error.PermissionDenied,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.BUSY => return error.DeviceBusy,
|
||||
.OPNOTSUPP => return error.FileLocksNotSupported,
|
||||
.OPNOTSUPP => return error.FileLocksUnsupported,
|
||||
.AGAIN => return error.WouldBlock,
|
||||
.TXTBSY => return error.FileBusy,
|
||||
.NXIO => return error.NoDevice,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -343,7 +343,7 @@ const Module = struct {
|
|||
error.AntivirusInterference,
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.SystemFdQuotaExceeded,
|
||||
error.FileLocksNotSupported,
|
||||
error.FileLocksUnsupported,
|
||||
error.FileBusy,
|
||||
=> return error.ReadFailed,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,12 +23,6 @@ pub const Dir = std.Io.Dir;
|
|||
pub const File = std.Io.File;
|
||||
|
||||
pub const path = @import("fs/path.zig");
|
||||
|
||||
pub const has_executable_bit = switch (native_os) {
|
||||
.windows, .wasi => false,
|
||||
else => true,
|
||||
};
|
||||
|
||||
pub const wasi = @import("fs/wasi.zig");
|
||||
|
||||
// TODO audit these APIs with respect to Dir and absolute paths
|
||||
|
|
|
|||
1356
lib/std/fs/File.zig
1356
lib/std/fs/File.zig
File diff suppressed because it is too large
Load diff
|
|
@ -1994,80 +1994,6 @@ pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter
|
|||
assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0);
|
||||
}
|
||||
|
||||
pub const SetFileTimeError = error{Unexpected};
|
||||
|
||||
pub fn SetFileTime(
|
||||
hFile: HANDLE,
|
||||
lpCreationTime: ?*const FILETIME,
|
||||
lpLastAccessTime: ?*const FILETIME,
|
||||
lpLastWriteTime: ?*const FILETIME,
|
||||
) SetFileTimeError!void {
|
||||
const rc = kernel32.SetFileTime(hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime);
|
||||
if (rc == 0) {
|
||||
switch (GetLastError()) {
|
||||
else => |err| return unexpectedError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const LockFileError = error{
|
||||
SystemResources,
|
||||
WouldBlock,
|
||||
} || UnexpectedError;
|
||||
|
||||
pub fn LockFile(
|
||||
FileHandle: HANDLE,
|
||||
Event: ?HANDLE,
|
||||
ApcRoutine: ?*IO_APC_ROUTINE,
|
||||
ApcContext: ?*anyopaque,
|
||||
IoStatusBlock: *IO_STATUS_BLOCK,
|
||||
ByteOffset: *const LARGE_INTEGER,
|
||||
Length: *const LARGE_INTEGER,
|
||||
Key: ?*ULONG,
|
||||
FailImmediately: BOOLEAN,
|
||||
ExclusiveLock: BOOLEAN,
|
||||
) !void {
|
||||
const rc = ntdll.NtLockFile(
|
||||
FileHandle,
|
||||
Event,
|
||||
ApcRoutine,
|
||||
ApcContext,
|
||||
IoStatusBlock,
|
||||
ByteOffset,
|
||||
Length,
|
||||
Key,
|
||||
FailImmediately,
|
||||
ExclusiveLock,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => return,
|
||||
.INSUFFICIENT_RESOURCES => return error.SystemResources,
|
||||
.LOCK_NOT_GRANTED => return error.WouldBlock,
|
||||
.ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
|
||||
else => return unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
||||
pub const UnlockFileError = error{
|
||||
RangeNotLocked,
|
||||
} || UnexpectedError;
|
||||
|
||||
pub fn UnlockFile(
|
||||
FileHandle: HANDLE,
|
||||
IoStatusBlock: *IO_STATUS_BLOCK,
|
||||
ByteOffset: *const LARGE_INTEGER,
|
||||
Length: *const LARGE_INTEGER,
|
||||
Key: ?*ULONG,
|
||||
) !void {
|
||||
const rc = ntdll.NtUnlockFile(FileHandle, IoStatusBlock, ByteOffset, Length, Key);
|
||||
switch (rc) {
|
||||
.SUCCESS => return,
|
||||
.RANGE_NOT_LOCKED => return error.RangeNotLocked,
|
||||
.ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
|
||||
else => return unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a workaround for the C backend until zig has the ability to put
|
||||
/// C code in inline assembly.
|
||||
extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque;
|
||||
|
|
|
|||
|
|
@ -298,17 +298,7 @@ pub fn close(fd: fd_t) void {
|
|||
}
|
||||
}
|
||||
|
||||
pub const FChmodError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
InputOutput,
|
||||
SymLinkLoop,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
} || UnexpectedError;
|
||||
|
||||
pub const FChmodAtError = FChmodError || error{
|
||||
pub const FChmodAtError = std.Io.File.SetPermissionsError || error{
|
||||
/// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded
|
||||
/// `PATH_MAX`.
|
||||
NameTooLong,
|
||||
|
|
@ -324,7 +314,6 @@ pub const FChmodAtError = FChmodError || error{
|
|||
ProcessFdQuotaExceeded,
|
||||
/// The procfs fallback was used but the system exceeded it open file limit.
|
||||
SystemFdQuotaExceeded,
|
||||
Canceled,
|
||||
};
|
||||
|
||||
/// Changes the `mode` of `path` relative to the directory referred to by
|
||||
|
|
@ -952,74 +941,6 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
|
|||
}
|
||||
}
|
||||
|
||||
pub const TruncateError = error{
|
||||
FileTooBig,
|
||||
InputOutput,
|
||||
FileBusy,
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
NonResizable,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Length must be positive when treated as an i64.
|
||||
pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
|
||||
const signed_len: i64 = @bitCast(length);
|
||||
if (signed_len < 0) return error.FileTooBig; // avoid ambiguous EINVAL errors
|
||||
|
||||
if (native_os == .windows) {
|
||||
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
|
||||
var eof_info = windows.FILE_END_OF_FILE_INFORMATION{
|
||||
.EndOfFile = signed_len,
|
||||
};
|
||||
|
||||
const rc = windows.ntdll.NtSetInformationFile(
|
||||
fd,
|
||||
&io_status_block,
|
||||
&eof_info,
|
||||
@sizeOf(windows.FILE_END_OF_FILE_INFORMATION),
|
||||
.FileEndOfFileInformation,
|
||||
);
|
||||
|
||||
switch (rc) {
|
||||
.SUCCESS => return,
|
||||
.INVALID_HANDLE => unreachable, // Handle not open for writing
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.USER_MAPPED_FILE => return error.AccessDenied,
|
||||
.INVALID_PARAMETER => return error.FileTooBig,
|
||||
else => return windows.unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
switch (wasi.fd_filestat_set_size(fd, length)) {
|
||||
.SUCCESS => return,
|
||||
.INTR => unreachable,
|
||||
.FBIG => return error.FileTooBig,
|
||||
.IO => return error.InputOutput,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.TXTBSY => return error.FileBusy,
|
||||
.BADF => unreachable, // Handle not open for writing
|
||||
.INVAL => return error.NonResizable,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate;
|
||||
while (true) {
|
||||
switch (errno(ftruncate_sym(fd, signed_len))) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.FBIG => return error.FileTooBig,
|
||||
.IO => return error.InputOutput,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.TXTBSY => return error.FileBusy,
|
||||
.BADF => unreachable, // Handle not open for writing
|
||||
.INVAL => return error.NonResizable, // This is returned for /dev/null for example.
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
|
||||
///
|
||||
/// Retries when interrupted by a signal.
|
||||
|
|
@ -1587,7 +1508,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O
|
|||
.PERM => return error.PermissionDenied,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.BUSY => return error.DeviceBusy,
|
||||
.OPNOTSUPP => return error.FileLocksNotSupported,
|
||||
.OPNOTSUPP => return error.FileLocksUnsupported,
|
||||
.AGAIN => return error.WouldBlock,
|
||||
.TXTBSY => return error.FileBusy,
|
||||
.NXIO => return error.NoDevice,
|
||||
|
|
@ -2295,47 +2216,6 @@ pub fn getegid() gid_t {
|
|||
return system.getegid();
|
||||
}
|
||||
|
||||
/// Test whether a file descriptor refers to a terminal.
|
||||
pub fn isatty(handle: fd_t) bool {
|
||||
if (native_os == .windows) {
|
||||
if (fs.File.isCygwinPty(.{ .handle = handle }))
|
||||
return true;
|
||||
|
||||
var out: windows.DWORD = undefined;
|
||||
return windows.kernel32.GetConsoleMode(handle, &out) != 0;
|
||||
}
|
||||
if (builtin.link_libc) {
|
||||
return system.isatty(handle) != 0;
|
||||
}
|
||||
if (native_os == .wasi) {
|
||||
var statbuf: wasi.fdstat_t = undefined;
|
||||
const err = wasi.fd_fdstat_get(handle, &statbuf);
|
||||
if (err != .SUCCESS)
|
||||
return false;
|
||||
|
||||
// A tty is a character device that we can't seek or tell on.
|
||||
if (statbuf.fs_filetype != .CHARACTER_DEVICE)
|
||||
return false;
|
||||
if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
if (native_os == .linux) {
|
||||
while (true) {
|
||||
var wsz: winsize = undefined;
|
||||
const fd: usize = @bitCast(@as(isize, handle));
|
||||
const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
|
||||
switch (linux.errno(rc)) {
|
||||
.SUCCESS => return true,
|
||||
.INTR => continue,
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
return system.isatty(handle) != 0;
|
||||
}
|
||||
|
||||
pub const SocketError = error{
|
||||
/// Permission to create a socket of the specified type and/or
|
||||
/// pro‐tocol is denied.
|
||||
|
|
@ -3910,34 +3790,6 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {
|
|||
}
|
||||
}
|
||||
|
||||
pub const FlockError = error{
|
||||
WouldBlock,
|
||||
|
||||
/// The kernel ran out of memory for allocating file locks
|
||||
SystemResources,
|
||||
|
||||
/// The underlying filesystem does not support file locks
|
||||
FileLocksNotSupported,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Depending on the operating system `flock` may or may not interact with
|
||||
/// `fcntl` locks made by other processes.
|
||||
pub fn flock(fd: fd_t, operation: i32) FlockError!void {
|
||||
while (true) {
|
||||
const rc = system.flock(fd, operation);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return,
|
||||
.BADF => unreachable,
|
||||
.INTR => continue,
|
||||
.INVAL => unreachable, // invalid parameters
|
||||
.NOLCK => return error.SystemResources,
|
||||
.AGAIN => return error.WouldBlock, // TODO: integrate with async instead of just returning an error
|
||||
.OPNOTSUPP => return error.FileLocksNotSupported,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spurious wakeups are possible and no precision of timing is guaranteed.
|
||||
pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
|
||||
var req = timespec{
|
||||
|
|
@ -4202,74 +4054,6 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*
|
|||
}
|
||||
}
|
||||
|
||||
pub const FutimensError = 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,
|
||||
} || UnexpectedError;
|
||||
|
||||
pub fn futimens(fd: fd_t, times: ?*const [2]timespec) FutimensError!void {
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
// TODO WASI encodes `wasi.fstflags` to signify magic values
|
||||
// similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore
|
||||
// this here, but we should really handle it somehow.
|
||||
const error_code = blk: {
|
||||
if (times) |times_arr| {
|
||||
const atim = times_arr[0].toTimestamp();
|
||||
const mtim = times_arr[1].toTimestamp();
|
||||
break :blk wasi.fd_filestat_set_times(fd, atim, mtim, .{
|
||||
.ATIM = true,
|
||||
.MTIM = true,
|
||||
});
|
||||
}
|
||||
|
||||
break :blk wasi.fd_filestat_set_times(fd, 0, 0, .{
|
||||
.ATIM_NOW = true,
|
||||
.MTIM_NOW = true,
|
||||
});
|
||||
};
|
||||
switch (error_code) {
|
||||
.SUCCESS => return,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BADF => unreachable, // always a race condition
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
switch (errno(system.futimens(fd, times))) {
|
||||
.SUCCESS => return,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BADF => unreachable, // always a race condition
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub const GetHostNameError = error{PermissionDenied} || UnexpectedError;
|
||||
|
||||
pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {
|
||||
|
|
@ -5091,12 +4875,7 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t {
|
|||
}
|
||||
}
|
||||
|
||||
pub const SyncError = error{
|
||||
InputOutput,
|
||||
NoSpaceLeft,
|
||||
DiskQuota,
|
||||
AccessDenied,
|
||||
} || UnexpectedError;
|
||||
pub const SyncError = std.Io.File.SyncError;
|
||||
|
||||
/// Write all pending file contents and metadata modifications to all filesystems.
|
||||
pub fn sync() void {
|
||||
|
|
@ -5116,38 +4895,8 @@ pub fn syncfs(fd: fd_t) SyncError!void {
|
|||
}
|
||||
}
|
||||
|
||||
/// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem.
|
||||
pub fn fsync(fd: fd_t) SyncError!void {
|
||||
if (native_os == .windows) {
|
||||
if (windows.kernel32.FlushFileBuffers(fd) != 0)
|
||||
return;
|
||||
switch (windows.GetLastError()) {
|
||||
.SUCCESS => return,
|
||||
.INVALID_HANDLE => unreachable,
|
||||
.ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time
|
||||
.UNEXP_NET_ERR => return error.InputOutput,
|
||||
else => return error.InputOutput,
|
||||
}
|
||||
}
|
||||
const rc = system.fsync(fd);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return,
|
||||
.BADF, .INVAL, .ROFS => unreachable,
|
||||
.IO => return error.InputOutput,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata.
|
||||
pub fn fdatasync(fd: fd_t) SyncError!void {
|
||||
if (native_os == .windows) {
|
||||
return fsync(fd) catch |err| switch (err) {
|
||||
SyncError.AccessDenied => return, // fdatasync doesn't promise that the access time was synced
|
||||
else => return err,
|
||||
};
|
||||
}
|
||||
const rc = system.fdatasync(fd);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return,
|
||||
|
|
|
|||
|
|
@ -596,7 +596,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
|
|||
error.NoSpaceLeft => unreachable,
|
||||
error.FileTooBig => unreachable,
|
||||
error.DeviceBusy => unreachable,
|
||||
error.FileLocksNotSupported => unreachable,
|
||||
error.FileLocksUnsupported => unreachable,
|
||||
error.BadPathName => unreachable, // Windows-only
|
||||
error.WouldBlock => unreachable,
|
||||
error.NetworkNotFound => unreachable, // Windows-only
|
||||
|
|
|
|||
|
|
@ -173,6 +173,9 @@ pub const Options = struct {
|
|||
/// If this is `false`, then captured stack traces will always be empty, and attempts to write
|
||||
/// stack traces will just print an error to the relevant `Io.Writer` and return.
|
||||
allow_stack_tracing: bool = !@import("builtin").strip_debug_info,
|
||||
|
||||
/// Overrides `std.Io.File.Permissions`.
|
||||
FilePermissions: ?type = null,
|
||||
};
|
||||
|
||||
// This forces the start.zig file to be imported, and the comptime logic inside that
|
||||
|
|
|
|||
|
|
@ -824,7 +824,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
|
|||
error.SharingViolation => return error.Unexpected, // Windows-only
|
||||
error.NetworkNotFound => return error.Unexpected, // Windows-only
|
||||
error.AntivirusInterference => return error.Unexpected, // Windows-only
|
||||
error.FileLocksNotSupported => return error.Unexpected, // No lock requested.
|
||||
error.FileLocksUnsupported => return error.Unexpected, // No lock requested.
|
||||
error.NoSpaceLeft => return error.Unexpected, // read-only
|
||||
error.PathAlreadyExists => return error.Unexpected, // read-only
|
||||
error.DeviceBusy => return error.Unexpected, // read-only
|
||||
|
|
@ -1033,7 +1033,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
|
|||
error.SharingViolation => return error.Unexpected,
|
||||
error.BadPathName => return error.Unexpected,
|
||||
error.PipeBusy => return error.Unexpected,
|
||||
error.FileLocksNotSupported => return error.Unexpected,
|
||||
error.FileLocksUnsupported => return error.Unexpected,
|
||||
error.FileBusy => return error.Unexpected, // opened without write permissions
|
||||
error.AntivirusInterference => return error.Unexpected, // Windows-only error
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue