mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
In the future, it might be nice to introduce a type for file system path names. This would be a way to avoid having InvalidFileName in the error set, since construction of such type could validate it above the interface.
269 lines
10 KiB
Zig
269 lines
10 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 fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
|
|
return io.vtable.fileOpen(io.userdata, dir, sub_path, flags);
|
|
}
|
|
|
|
pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
|
|
return io.vtable.createFile(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,
|
|
NameTooLong,
|
|
FileNotFound,
|
|
SystemResources,
|
|
NoSpaceLeft,
|
|
NotDir,
|
|
ReadOnlyFileSystem,
|
|
/// Windows-only; file paths provided by the user must be valid WTF-8.
|
|
/// https://simonsapin.github.io/wtf-8/
|
|
InvalidWtf8,
|
|
BadPathName,
|
|
NoDevice,
|
|
/// On Windows, `\\server` or `\\server\share` was not found.
|
|
NetworkNotFound,
|
|
/// File system cannot encode the requested file name bytes.
|
|
InvalidFileName,
|
|
} || 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;
|
|
|
|
/// 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) StatPathError!File.Stat {
|
|
return io.vtable.dirStatPath(io.userdata, dir, sub_path);
|
|
}
|