diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2020-03-03 17:05:14 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-03-03 17:05:14 -0500 |
| commit | f6f0b019bee7910702e113be6d33865c592afa2a (patch) | |
| tree | 65cd5209cf69c03445f85bb244d15ca00bb51a32 /lib/std/fs.zig | |
| parent | 582db68a157520a0cf7777807f466d1f6a2e31e7 (diff) | |
| parent | 1141bfb21b82f8d3fc353e968a591f2ad9aaa571 (diff) | |
| download | zig-f6f0b019bee7910702e113be6d33865c592afa2a.tar.gz zig-f6f0b019bee7910702e113be6d33865c592afa2a.zip | |
Merge pull request #4618 from ziglang/daurnimator-paths
improvements to std.fs, std.os
Diffstat (limited to 'lib/std/fs.zig')
| -rw-r--r-- | lib/std/fs.zig | 189 |
1 files changed, 95 insertions, 94 deletions
diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 769d4b395c..056a2f9def 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -123,47 +123,21 @@ pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?Fil } const actual_mode = mode orelse src_stat.mode; - // TODO this logic could be made more efficient by calling makePath, once - // that API does not require an allocator - var atomic_file = make_atomic_file: while (true) { - const af = AtomicFile.init(dest_path, actual_mode) catch |err| switch (err) { - error.FileNotFound => { - var p = dest_path; - while (path.dirname(p)) |dirname| { - makeDir(dirname) catch |e| switch (e) { - error.FileNotFound => { - p = dirname; - continue; - }, - else => return e, - }; - continue :make_atomic_file; - } else { - return err; - } - }, - else => |e| return e, - }; - break af; - } else unreachable; - defer atomic_file.deinit(); + if (path.dirname(dest_path)) |dirname| { + try cwd().makePath(dirname); + } - const in_stream = &src_file.inStream().stream; + var atomic_file = try AtomicFile.init(dest_path, actual_mode); + defer atomic_file.deinit(); - var buf: [mem.page_size * 6]u8 = undefined; - while (true) { - const amt = try in_stream.readFull(buf[0..]); - try atomic_file.file.writeAll(buf[0..amt]); - if (amt != buf.len) { - try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime); - try atomic_file.finish(); - return PrevStatus.stale; - } - } + try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size }); + try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime); + try atomic_file.finish(); + return PrevStatus.stale; } -/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is -/// merged and readily available, +/// Guaranteed to be atomic. +/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available, /// there is a possibility of power loss or application termination leaving temporary files present /// in the same directory as dest_path. /// Destination file will have the same mode as the source file. @@ -207,6 +181,9 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M } } +/// TODO update this API to avoid a getrandom syscall for every operation. It +/// should accept a random interface. +/// TODO rework this to integrate with Dir pub const AtomicFile = struct { file: File, tmp_path_buf: [MAX_PATH_BYTES]u8, @@ -268,70 +245,42 @@ pub const AtomicFile = struct { pub fn finish(self: *AtomicFile) !void { assert(!self.finished); - self.file.close(); - self.finished = true; - if (builtin.os.tag == .windows) { + if (std.Target.current.os.tag == .windows) { const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path); const tmp_path_w = try os.windows.cStrToPrefixedFileW(@ptrCast([*:0]u8, &self.tmp_path_buf)); + self.file.close(); + self.finished = true; return os.renameW(&tmp_path_w, &dest_path_w); + } else { + const dest_path_c = try os.toPosixPath(self.dest_path); + self.file.close(); + self.finished = true; + return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c); } - const dest_path_c = try os.toPosixPath(self.dest_path); - return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c); } }; const default_new_dir_mode = 0o755; -/// Create a new directory. -pub fn makeDir(dir_path: []const u8) !void { - return os.mkdir(dir_path, default_new_dir_mode); +/// Create a new directory, based on an absolute path. +/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates +/// on both absolute and relative paths. +pub fn makeDirAbsolute(absolute_path: []const u8) !void { + assert(path.isAbsoluteC(absolute_path)); + return os.mkdir(absolute_path, default_new_dir_mode); } -/// Same as `makeDir` except the parameter is a null-terminated UTF8-encoded string. -pub fn makeDirC(dir_path: [*:0]const u8) !void { - return os.mkdirC(dir_path, default_new_dir_mode); +/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF8-encoded string. +pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { + assert(path.isAbsoluteC(absolute_path_z)); + return os.mkdirZ(absolute_path_z, default_new_dir_mode); } -/// Same as `makeDir` except the parameter is a null-terminated UTF16LE-encoded string. -pub fn makeDirW(dir_path: [*:0]const u16) !void { - return os.mkdirW(dir_path, default_new_dir_mode); -} - -/// Calls makeDir recursively to make an entire path. 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. -/// TODO determine if we can remove the allocator requirement from this function -pub fn makePath(allocator: *Allocator, full_path: []const u8) !void { - const resolved_path = try path.resolve(allocator, &[_][]const u8{full_path}); - defer allocator.free(resolved_path); - - var end_index: usize = resolved_path.len; - while (true) { - makeDir(resolved_path[0..end_index]) catch |err| switch (err) { - error.PathAlreadyExists => { - // TODO 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 - if (end_index == resolved_path.len) return; - }, - error.FileNotFound => { - // march end_index backward until next path component - while (true) { - end_index -= 1; - if (path.isSep(resolved_path[end_index])) break; - } - continue; - }, - else => return err, - }; - if (end_index == resolved_path.len) return; - // march end_index forward until next path component - while (true) { - end_index += 1; - if (end_index == resolved_path.len or path.isSep(resolved_path[end_index])) break; - } - } +/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string. +pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { + assert(path.isAbsoluteWindowsW(absolute_path_w)); + const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null); + os.windows.CloseHandle(handle); } /// Returns `error.DirNotEmpty` if the directory is not empty. @@ -709,7 +658,6 @@ pub const Dir = struct { /// Call `File.close` to release the resource. /// Asserts that the path parameter has no null bytes. pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { - if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os.tag == .windows) { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openFileW(&path_w, flags); @@ -759,7 +707,6 @@ pub const Dir = struct { /// Call `File.close` on the result when done. /// Asserts that the path parameter has no null bytes. pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { - if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os.tag == .windows) { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.createFileW(&path_w, flags); @@ -882,6 +829,64 @@ pub const Dir = struct { } } + pub fn makeDir(self: Dir, sub_path: []const u8) !void { + try os.mkdirat(self.fd, sub_path, default_new_dir_mode); + } + + pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void { + try os.mkdiratC(self.fd, sub_path, default_new_dir_mode); + } + + pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void { + const handle = try os.windows.CreateDirectoryW(self.fd, sub_path, null); + os.windows.CloseHandle(handle); + } + + /// Calls makeDir recursively to make an entire path. 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. + pub fn makePath(self: Dir, sub_path: []const u8) !void { + var end_index: usize = sub_path.len; + while (true) { + self.makeDir(sub_path[0..end_index]) catch |err| switch (err) { + error.PathAlreadyExists => { + // TODO 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 + if (end_index == sub_path.len) return; + }, + error.FileNotFound => { + if (end_index == 0) return err; + // march end_index backward until next path component + while (true) { + end_index -= 1; + if (path.isSep(sub_path[end_index])) break; + } + continue; + }, + else => return err, + }; + if (end_index == sub_path.len) return; + // march end_index forward until next path component + while (true) { + end_index += 1; + if (end_index == sub_path.len or path.isSep(sub_path[end_index])) break; + } + } + } + + /// Changes the current working directory to the open directory handle. + /// This modifies global state and can have surprising effects in multi- + /// threaded applications. Most applications and especially libraries should + /// not call this function as a general rule, however it can have use cases + /// in, for example, implementing a shell, or child process execution. + /// Not all targets support this. For example, WASI does not have the concept + /// of a current working directory. + pub fn setAsCwd(self: Dir) !void { + try os.fchdir(self.fd); + } + /// Deprecated; call `openDirList` directly. pub fn openDir(self: Dir, sub_path: []const u8) OpenError!Dir { return self.openDirList(sub_path); @@ -900,7 +905,6 @@ pub const Dir = struct { /// /// Asserts that the path parameter has no null bytes. pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir { - if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openDirTraverseW(&sub_path_w); @@ -918,7 +922,6 @@ pub const Dir = struct { /// /// Asserts that the path parameter has no null bytes. pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir { - if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openDirListW(&sub_path_w); @@ -1082,7 +1085,6 @@ pub const Dir = struct { /// To delete a directory recursively, see `deleteTree`. /// Asserts that the path parameter has no null bytes. pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { - if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.deleteDirW(&sub_path_w); @@ -1112,7 +1114,6 @@ pub const Dir = struct { /// The return value is a slice of `buffer`, from index `0`. /// Asserts that the path parameter has no null bytes. pub fn readLink(self: Dir, sub_path: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { - if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); const sub_path_c = try os.toPosixPath(sub_path); return self.readLinkC(&sub_path_c, buffer); } |
