mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
355 lines
13 KiB
Zig
355 lines
13 KiB
Zig
const Dir = @This();
|
|
|
|
const std = @import("../std.zig");
|
|
const Io = std.Io;
|
|
const File = Io.File;
|
|
|
|
handle: Handle,
|
|
|
|
pub const Mode = Io.File.Mode;
|
|
pub const default_mode: Mode = 0o755;
|
|
|
|
pub fn cwd() Dir {
|
|
return .{ .handle = std.fs.cwd().fd };
|
|
}
|
|
|
|
pub const Handle = std.posix.fd_t;
|
|
|
|
pub const PathNameError = error{
|
|
NameTooLong,
|
|
/// File system cannot encode the requested file name bytes.
|
|
/// Could be due to invalid WTF-8 on Windows, invalid UTF-8 on WASI,
|
|
/// invalid characters on Windows, etc. Filesystem and operating specific.
|
|
BadPathName,
|
|
};
|
|
|
|
pub const AccessError = error{
|
|
AccessDenied,
|
|
PermissionDenied,
|
|
FileNotFound,
|
|
InputOutput,
|
|
SystemResources,
|
|
FileBusy,
|
|
SymLinkLoop,
|
|
ReadOnlyFileSystem,
|
|
} || PathNameError || Io.Cancelable || Io.UnexpectedError;
|
|
|
|
pub const AccessOptions = packed struct {
|
|
follow_symlinks: bool = true,
|
|
read: bool = false,
|
|
write: bool = false,
|
|
execute: bool = false,
|
|
};
|
|
|
|
/// Test accessing `sub_path`.
|
|
///
|
|
/// 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.
|
|
///
|
|
/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this
|
|
/// function. For example, instead of testing if a file exists and then opening
|
|
/// it, just open it and handle the error for file not found.
|
|
pub fn access(dir: Dir, io: Io, sub_path: []const u8, options: AccessOptions) AccessError!void {
|
|
return io.vtable.dirAccess(io.userdata, dir, sub_path, options);
|
|
}
|
|
|
|
pub const OpenError = error{
|
|
FileNotFound,
|
|
NotDir,
|
|
AccessDenied,
|
|
PermissionDenied,
|
|
SymLinkLoop,
|
|
ProcessFdQuotaExceeded,
|
|
SystemFdQuotaExceeded,
|
|
NoDevice,
|
|
SystemResources,
|
|
DeviceBusy,
|
|
/// On Windows, `\\server` or `\\server\share` was not found.
|
|
NetworkNotFound,
|
|
} || PathNameError || Io.Cancelable || Io.UnexpectedError;
|
|
|
|
pub const OpenOptions = struct {
|
|
/// `true` means the opened directory can be used as the `Dir` parameter
|
|
/// for functions which operate based on an open directory handle. When `false`,
|
|
/// such operations are Illegal Behavior.
|
|
access_sub_paths: bool = true,
|
|
/// `true` means the opened directory can be scanned for the files and sub-directories
|
|
/// of the result. It means the `iterate` function can be called.
|
|
iterate: bool = false,
|
|
/// `false` means it won't dereference the symlinks.
|
|
follow_symlinks: bool = true,
|
|
};
|
|
|
|
/// Opens a directory at the given path. The directory is a system resource that remains
|
|
/// open until `close` is called on the result.
|
|
///
|
|
/// The directory cannot be iterated unless the `iterate` option is set to `true`.
|
|
///
|
|
/// 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 openDir(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) OpenError!Dir {
|
|
return io.vtable.dirOpenDir(io.userdata, dir, sub_path, options);
|
|
}
|
|
|
|
pub fn close(dir: Dir, io: Io) void {
|
|
return io.vtable.dirClose(io.userdata, dir);
|
|
}
|
|
|
|
pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
|
|
return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);
|
|
}
|
|
|
|
/// Creates, opens, or overwrites a file with write access.
|
|
///
|
|
/// Allocates a resource to be dellocated with `File.close`.
|
|
///
|
|
/// 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 createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
|
|
return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags);
|
|
}
|
|
|
|
pub const WriteFileOptions = struct {
|
|
/// 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.
|
|
sub_path: []const u8,
|
|
data: []const u8,
|
|
flags: File.CreateFlags = .{},
|
|
};
|
|
|
|
pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable;
|
|
|
|
/// Writes content to the file system, using the file creation flags provided.
|
|
pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
|
|
var file = try dir.createFile(io, options.sub_path, options.flags);
|
|
defer file.close(io);
|
|
try file.writeAll(io, options.data);
|
|
}
|
|
|
|
pub const PrevStatus = enum {
|
|
stale,
|
|
fresh,
|
|
};
|
|
|
|
pub const UpdateFileError = File.OpenError;
|
|
|
|
/// Check the file size, mtime, and mode 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
|
|
/// that the next call to `updateFile` will not need a copy.
|
|
///
|
|
/// Returns the previous status of the file before updating.
|
|
///
|
|
/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// * On WASI, both paths should be encoded as valid UTF-8.
|
|
/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
|
|
pub fn updateFile(
|
|
source_dir: Dir,
|
|
io: Io,
|
|
source_path: []const u8,
|
|
dest_dir: Dir,
|
|
/// If directories in this path do not exist, they are created.
|
|
dest_path: []const u8,
|
|
options: std.fs.Dir.CopyFileOptions,
|
|
) !PrevStatus {
|
|
var src_file = try source_dir.openFile(io, source_path, .{});
|
|
defer src_file.close(io);
|
|
|
|
const src_stat = try src_file.stat(io);
|
|
const actual_mode = options.override_mode orelse src_stat.mode;
|
|
check_dest_stat: {
|
|
const dest_stat = blk: {
|
|
var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {
|
|
error.FileNotFound => break :check_dest_stat,
|
|
else => |e| return e,
|
|
};
|
|
defer dest_file.close(io);
|
|
|
|
break :blk try dest_file.stat(io);
|
|
};
|
|
|
|
if (src_stat.size == dest_stat.size and
|
|
src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and
|
|
actual_mode == dest_stat.mode)
|
|
{
|
|
return .fresh;
|
|
}
|
|
}
|
|
|
|
if (std.fs.path.dirname(dest_path)) |dirname| {
|
|
try dest_dir.makePath(io, dirname);
|
|
}
|
|
|
|
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,
|
|
.write_buffer = &buffer,
|
|
});
|
|
defer atomic_file.deinit();
|
|
|
|
var src_reader: File.Reader = .initSize(src_file, io, &.{}, src_stat.size);
|
|
const dest_writer = &atomic_file.file_writer.interface;
|
|
|
|
_ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
|
|
error.ReadFailed => return src_reader.err.?,
|
|
error.WriteFailed => return atomic_file.file_writer.err.?,
|
|
};
|
|
try atomic_file.flush();
|
|
try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime);
|
|
try atomic_file.renameIntoPlace();
|
|
return .stale;
|
|
}
|
|
|
|
pub const ReadFileError = File.OpenError || File.Reader.Error;
|
|
|
|
/// Read all of file contents using a preallocated buffer.
|
|
///
|
|
/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
|
|
/// the situation is ambiguous. It could either mean that the entire file was read, and
|
|
/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
|
|
/// entire file.
|
|
///
|
|
/// * On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
|
|
/// * On WASI, `file_path` should be encoded as valid UTF-8.
|
|
/// * On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
|
|
pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileError![]u8 {
|
|
var file = try dir.openFile(io, file_path, .{});
|
|
defer file.close(io);
|
|
|
|
var reader = file.reader(io, &.{});
|
|
const n = reader.interface.readSliceShort(buffer) catch |err| switch (err) {
|
|
error.ReadFailed => return reader.err.?,
|
|
};
|
|
|
|
return buffer[0..n];
|
|
}
|
|
|
|
pub const MakeError = error{
|
|
/// In WASI, this error may occur when the file descriptor does
|
|
/// not hold the required rights to create a new directory relative to it.
|
|
AccessDenied,
|
|
PermissionDenied,
|
|
DiskQuota,
|
|
PathAlreadyExists,
|
|
SymLinkLoop,
|
|
LinkQuotaExceeded,
|
|
FileNotFound,
|
|
SystemResources,
|
|
NoSpaceLeft,
|
|
NotDir,
|
|
ReadOnlyFileSystem,
|
|
NoDevice,
|
|
/// On Windows, `\\server` or `\\server\share` was not found.
|
|
NetworkNotFound,
|
|
} || PathNameError || Io.Cancelable || Io.UnexpectedError;
|
|
|
|
/// Creates a single directory with a relative or absolute path.
|
|
///
|
|
/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
|
|
/// * 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.
|
|
///
|
|
/// 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 const MakePathError = MakeError || StatPathError;
|
|
|
|
/// Calls makeDir iteratively to make an entire path, creating any parent
|
|
/// directories that do not exist.
|
|
///
|
|
/// Returns success if the path already exists and is a directory.
|
|
///
|
|
/// This function is not atomic, and if it returns an error, the file system
|
|
/// may have been modified regardless.
|
|
///
|
|
/// Fails on an empty path with `error.BadPathName` as that is not a path that
|
|
/// can be created.
|
|
///
|
|
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
|
|
/// 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.
|
|
///
|
|
/// Paths containing `..` components are handled differently depending on the platform:
|
|
/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
|
|
/// a `sub_path` like "first/../second" will resolve to "second" and only a
|
|
/// `./second` directory will be created.
|
|
/// - 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 makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void {
|
|
_ = try makePathStatus(dir, io, sub_path);
|
|
}
|
|
|
|
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 {
|
|
var it = try std.fs.path.componentIterator(sub_path);
|
|
var status: MakePathStatus = .existed;
|
|
var component = it.last() orelse return error.BadPathName;
|
|
while (true) {
|
|
if (makeDir(dir, io, component.path)) |_| {
|
|
status = .created;
|
|
} else |err| switch (err) {
|
|
error.PathAlreadyExists => {
|
|
// stat the file and return an error if it's not a directory
|
|
// this is important because otherwise a dangling symlink
|
|
// could cause an infinite loop
|
|
check_dir: {
|
|
// workaround for windows, see https://github.com/ziglang/zig/issues/16738
|
|
const fstat = statPath(dir, io, component.path, .{}) catch |stat_err| switch (stat_err) {
|
|
error.IsDir => break :check_dir,
|
|
else => |e| return e,
|
|
};
|
|
if (fstat.kind != .directory) return error.NotDir;
|
|
}
|
|
},
|
|
error.FileNotFound => |e| {
|
|
component = it.previous() orelse return e;
|
|
continue;
|
|
},
|
|
else => |e| return e,
|
|
}
|
|
component = it.next() orelse return status;
|
|
}
|
|
}
|
|
|
|
pub const Stat = File.Stat;
|
|
pub const StatError = File.StatError;
|
|
|
|
pub fn stat(dir: Dir, io: Io) StatError!Stat {
|
|
return io.vtable.dirStat(io.userdata, dir);
|
|
}
|
|
|
|
pub const StatPathError = File.OpenError || File.StatError;
|
|
|
|
pub const StatPathOptions = struct {
|
|
follow_symlinks: bool = true,
|
|
};
|
|
|
|
/// Returns metadata for a file inside the directory.
|
|
///
|
|
/// On Windows, this requires three syscalls. On other operating systems, it
|
|
/// only takes one.
|
|
///
|
|
/// Symlinks are followed.
|
|
///
|
|
/// `sub_path` may be absolute, in which case `self` is ignored.
|
|
///
|
|
/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
|
|
/// * 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 statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat {
|
|
return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);
|
|
}
|