master
  1const Dir = @This();
  2
  3const builtin = @import("builtin");
  4const native_os = builtin.os.tag;
  5
  6const std = @import("../std.zig");
  7const Io = std.Io;
  8const File = Io.File;
  9
 10handle: Handle,
 11
 12pub const Mode = Io.File.Mode;
 13pub const default_mode: Mode = 0o755;
 14
 15/// Returns a handle to the current working directory.
 16///
 17/// It is not opened with iteration capability. Iterating over the result is
 18/// illegal behavior.
 19///
 20/// Closing the returned `Dir` is checked illegal behavior.
 21///
 22/// On POSIX targets, this function is comptime-callable.
 23pub fn cwd() Dir {
 24    return switch (native_os) {
 25        .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },
 26        .wasi => .{ .handle = std.options.wasiCwd() },
 27        else => .{ .handle = std.posix.AT.FDCWD },
 28    };
 29}
 30
 31pub const Handle = std.posix.fd_t;
 32
 33pub const PathNameError = error{
 34    NameTooLong,
 35    /// File system cannot encode the requested file name bytes.
 36    /// Could be due to invalid WTF-8 on Windows, invalid UTF-8 on WASI,
 37    /// invalid characters on Windows, etc. Filesystem and operating specific.
 38    BadPathName,
 39};
 40
 41pub const AccessError = error{
 42    AccessDenied,
 43    PermissionDenied,
 44    FileNotFound,
 45    InputOutput,
 46    SystemResources,
 47    FileBusy,
 48    SymLinkLoop,
 49    ReadOnlyFileSystem,
 50} || PathNameError || Io.Cancelable || Io.UnexpectedError;
 51
 52pub const AccessOptions = packed struct {
 53    follow_symlinks: bool = true,
 54    read: bool = false,
 55    write: bool = false,
 56    execute: bool = false,
 57};
 58
 59/// Test accessing `sub_path`.
 60///
 61/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
 62/// On WASI, `sub_path` should be encoded as valid UTF-8.
 63/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 64///
 65/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this
 66/// function. For example, instead of testing if a file exists and then opening
 67/// it, just open it and handle the error for file not found.
 68pub fn access(dir: Dir, io: Io, sub_path: []const u8, options: AccessOptions) AccessError!void {
 69    return io.vtable.dirAccess(io.userdata, dir, sub_path, options);
 70}
 71
 72pub const OpenError = error{
 73    FileNotFound,
 74    NotDir,
 75    AccessDenied,
 76    PermissionDenied,
 77    SymLinkLoop,
 78    ProcessFdQuotaExceeded,
 79    SystemFdQuotaExceeded,
 80    NoDevice,
 81    SystemResources,
 82    DeviceBusy,
 83    /// On Windows, `\\server` or `\\server\share` was not found.
 84    NetworkNotFound,
 85} || PathNameError || Io.Cancelable || Io.UnexpectedError;
 86
 87pub const OpenOptions = struct {
 88    /// `true` means the opened directory can be used as the `Dir` parameter
 89    /// for functions which operate based on an open directory handle. When `false`,
 90    /// such operations are Illegal Behavior.
 91    access_sub_paths: bool = true,
 92    /// `true` means the opened directory can be scanned for the files and sub-directories
 93    /// of the result. It means the `iterate` function can be called.
 94    iterate: bool = false,
 95    /// `false` means it won't dereference the symlinks.
 96    follow_symlinks: bool = true,
 97};
 98
 99/// Opens a directory at the given path. The directory is a system resource that remains
100/// open until `close` is called on the result.
101///
102/// The directory cannot be iterated unless the `iterate` option is set to `true`.
103///
104/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
105/// On WASI, `sub_path` should be encoded as valid UTF-8.
106/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
107pub fn openDir(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) OpenError!Dir {
108    return io.vtable.dirOpenDir(io.userdata, dir, sub_path, options);
109}
110
111pub fn close(dir: Dir, io: Io) void {
112    return io.vtable.dirClose(io.userdata, dir);
113}
114
115/// Opens a file for reading or writing, without attempting to create a new file.
116///
117/// To create a new file, see `createFile`.
118///
119/// Allocates a resource to be released with `File.close`.
120///
121/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
122/// On WASI, `sub_path` should be encoded as valid UTF-8.
123/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
124pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
125    return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);
126}
127
128/// Creates, opens, or overwrites a file with write access.
129///
130/// Allocates a resource to be dellocated with `File.close`.
131///
132/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
133/// On WASI, `sub_path` should be encoded as valid UTF-8.
134/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
135pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
136    return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags);
137}
138
139pub const WriteFileOptions = struct {
140    /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
141    /// On WASI, `sub_path` should be encoded as valid UTF-8.
142    /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
143    sub_path: []const u8,
144    data: []const u8,
145    flags: File.CreateFlags = .{},
146};
147
148pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable;
149
150/// Writes content to the file system, using the file creation flags provided.
151pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
152    var file = try dir.createFile(io, options.sub_path, options.flags);
153    defer file.close(io);
154    try file.writeAll(io, options.data);
155}
156
157pub const PrevStatus = enum {
158    stale,
159    fresh,
160};
161
162pub const UpdateFileError = File.OpenError;
163
164/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If
165/// they are equal, does nothing. Otherwise, atomically copies `source_path` to
166/// `dest_path`, creating the parent directory hierarchy as needed. The
167/// destination file gains the mtime, atime, and mode of the source file so
168/// that the next call to `updateFile` will not need a copy.
169///
170/// Returns the previous status of the file before updating.
171///
172/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
173/// * On WASI, both paths should be encoded as valid UTF-8.
174/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
175pub fn updateFile(
176    source_dir: Dir,
177    io: Io,
178    source_path: []const u8,
179    dest_dir: Dir,
180    /// If directories in this path do not exist, they are created.
181    dest_path: []const u8,
182    options: std.fs.Dir.CopyFileOptions,
183) !PrevStatus {
184    var src_file = try source_dir.openFile(io, source_path, .{});
185    defer src_file.close(io);
186
187    const src_stat = try src_file.stat(io);
188    const actual_mode = options.override_mode orelse src_stat.mode;
189    check_dest_stat: {
190        const dest_stat = blk: {
191            var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {
192                error.FileNotFound => break :check_dest_stat,
193                else => |e| return e,
194            };
195            defer dest_file.close(io);
196
197            break :blk try dest_file.stat(io);
198        };
199
200        if (src_stat.size == dest_stat.size and
201            src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and
202            actual_mode == dest_stat.mode)
203        {
204            return .fresh;
205        }
206    }
207
208    if (std.fs.path.dirname(dest_path)) |dirname| {
209        try dest_dir.makePath(io, dirname);
210    }
211
212    var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
213    var atomic_file = try std.fs.Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{
214        .mode = actual_mode,
215        .write_buffer = &buffer,
216    });
217    defer atomic_file.deinit();
218
219    var src_reader: File.Reader = .initSize(src_file, io, &.{}, src_stat.size);
220    const dest_writer = &atomic_file.file_writer.interface;
221
222    _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
223        error.ReadFailed => return src_reader.err.?,
224        error.WriteFailed => return atomic_file.file_writer.err.?,
225    };
226    try atomic_file.flush();
227    try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime);
228    try atomic_file.renameIntoPlace();
229    return .stale;
230}
231
232pub const ReadFileError = File.OpenError || File.Reader.Error;
233
234/// Read all of file contents using a preallocated buffer.
235///
236/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
237/// the situation is ambiguous. It could either mean that the entire file was read, and
238/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
239/// entire file.
240///
241/// * On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
242/// * On WASI, `file_path` should be encoded as valid UTF-8.
243/// * On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
244pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileError![]u8 {
245    var file = try dir.openFile(io, file_path, .{});
246    defer file.close(io);
247
248    var reader = file.reader(io, &.{});
249    const n = reader.interface.readSliceShort(buffer) catch |err| switch (err) {
250        error.ReadFailed => return reader.err.?,
251    };
252
253    return buffer[0..n];
254}
255
256pub const MakeError = error{
257    /// In WASI, this error may occur when the file descriptor does
258    /// not hold the required rights to create a new directory relative to it.
259    AccessDenied,
260    PermissionDenied,
261    DiskQuota,
262    PathAlreadyExists,
263    SymLinkLoop,
264    LinkQuotaExceeded,
265    FileNotFound,
266    SystemResources,
267    NoSpaceLeft,
268    NotDir,
269    ReadOnlyFileSystem,
270    NoDevice,
271    /// On Windows, `\\server` or `\\server\share` was not found.
272    NetworkNotFound,
273} || PathNameError || Io.Cancelable || Io.UnexpectedError;
274
275/// Creates a single directory with a relative or absolute path.
276///
277/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
278/// * On WASI, `sub_path` should be encoded as valid UTF-8.
279/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
280///
281/// Related:
282/// * `makePath`
283/// * `makeDirAbsolute`
284pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void {
285    return io.vtable.dirMake(io.userdata, dir, sub_path, default_mode);
286}
287
288pub const MakePathError = MakeError || StatPathError;
289
290/// Calls makeDir iteratively to make an entire path, creating any parent
291/// directories that do not exist.
292///
293/// Returns success if the path already exists and is a directory.
294///
295/// This function is not atomic, and if it returns an error, the file system
296/// may have been modified regardless.
297///
298/// Fails on an empty path with `error.BadPathName` as that is not a path that
299/// can be created.
300///
301/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
302/// On WASI, `sub_path` should be encoded as valid UTF-8.
303/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
304///
305/// Paths containing `..` components are handled differently depending on the platform:
306/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
307///   a `sub_path` like "first/../second" will resolve to "second" and only a
308///   `./second` directory will be created.
309/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
310///   meaning a `sub_path` like "first/../second" will create both a `./first`
311///   and a `./second` directory.
312pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void {
313    _ = try makePathStatus(dir, io, sub_path);
314}
315
316pub const MakePathStatus = enum { existed, created };
317
318/// Same as `makePath` except returns whether the path already existed or was
319/// successfully created.
320pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus {
321    var it = std.fs.path.componentIterator(sub_path);
322    var status: MakePathStatus = .existed;
323    var component = it.last() orelse return error.BadPathName;
324    while (true) {
325        if (makeDir(dir, io, component.path)) |_| {
326            status = .created;
327        } else |err| switch (err) {
328            error.PathAlreadyExists => {
329                // stat the file and return an error if it's not a directory
330                // this is important because otherwise a dangling symlink
331                // could cause an infinite loop
332                check_dir: {
333                    // workaround for windows, see https://github.com/ziglang/zig/issues/16738
334                    const fstat = statPath(dir, io, component.path, .{}) catch |stat_err| switch (stat_err) {
335                        error.IsDir => break :check_dir,
336                        else => |e| return e,
337                    };
338                    if (fstat.kind != .directory) return error.NotDir;
339                }
340            },
341            error.FileNotFound => |e| {
342                component = it.previous() orelse return e;
343                continue;
344            },
345            else => |e| return e,
346        }
347        component = it.next() orelse return status;
348    }
349}
350
351pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
352
353/// Performs the equivalent of `makePath` followed by `openDir`, atomically if possible.
354///
355/// When this operation is canceled, it may leave the file system in a
356/// partially modified state.
357///
358/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
359/// On WASI, `sub_path` should be encoded as valid UTF-8.
360/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
361pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) MakeOpenPathError!Dir {
362    return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options);
363}
364
365pub const Stat = File.Stat;
366pub const StatError = File.StatError;
367
368pub fn stat(dir: Dir, io: Io) StatError!Stat {
369    return io.vtable.dirStat(io.userdata, dir);
370}
371
372pub const StatPathError = File.OpenError || File.StatError;
373
374pub const StatPathOptions = struct {
375    follow_symlinks: bool = true,
376};
377
378/// Returns metadata for a file inside the directory.
379///
380/// On Windows, this requires three syscalls. On other operating systems, it
381/// only takes one.
382///
383/// Symlinks are followed.
384///
385/// `sub_path` may be absolute, in which case `self` is ignored.
386///
387/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
388/// * On WASI, `sub_path` should be encoded as valid UTF-8.
389/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
390pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat {
391    return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);
392}