From 88e27f01c8dbf4bda5726c93d12cc4a1d174989d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 15 Jan 2020 18:07:08 +1000 Subject: [PATCH 01/12] std: add mkdirat --- lib/std/c.zig | 1 + lib/std/os.zig | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/std/c.zig b/lib/std/c.zig index 4eb271c87c..79a6c07c02 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -101,6 +101,7 @@ pub extern "c" fn faccessat(dirfd: fd_t, path: [*:0]const u8, mode: c_uint, flag pub extern "c" fn pipe(fds: *[2]fd_t) c_int; pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int; +pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int; pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int; pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int; pub extern "c" fn chdir(path: [*:0]const u8) c_int; diff --git a/lib/std/os.zig b/lib/std/os.zig index 969e6407a6..f97676a821 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1542,6 +1542,41 @@ pub const MakeDirError = error{ BadPathName, } || UnexpectedError; +pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { + if (builtin.os == .windows) { + const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); + @compileError("TODO implement mkdirat for Windows"); + } else { + const sub_dir_path_c = try toPosixPath(sub_dir_path); + return mkdiratC(dir_fd, &sub_dir_path_c, mode); + } +} + +pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { + if (builtin.os == .windows) { + const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path); + @compileError("TODO implement mkdiratC for Windows"); + } + switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { + 0 => return, + EACCES => return error.AccessDenied, + EBADF => unreachable, + EPERM => return error.AccessDenied, + EDQUOT => return error.DiskQuota, + EEXIST => return error.PathAlreadyExists, + EFAULT => unreachable, + ELOOP => return error.SymLinkLoop, + EMLINK => return error.LinkQuotaExceeded, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + ENOTDIR => return error.NotDir, + EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } +} + /// Create a directory. /// `mode` is ignored on Windows. pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { From dfb420e6d779b9b6d60a277401aadba2800e3572 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 15 Jan 2020 18:09:10 +1000 Subject: [PATCH 02/12] std: add fs.Dir.makeDir --- lib/std/fs.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 769d4b395c..f245ab348d 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -882,6 +882,14 @@ 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 makeDirC(self: Dir, sub_path: [*:0]const u8) !void { + try os.mkdiratC(self.fd, sub_path, default_new_dir_mode); + } + /// Deprecated; call `openDirList` directly. pub fn openDir(self: Dir, sub_path: []const u8) OpenError!Dir { return self.openDirList(sub_path); From 627618a38d291d9cc76c8e30e33cad60dc26cf11 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 15 Jan 2020 18:11:54 +1000 Subject: [PATCH 03/12] std: add Dir.changeDir as wrapper around fchdir --- lib/std/c.zig | 1 + lib/std/fs.zig | 4 ++++ lib/std/os.zig | 14 ++++++++++++++ lib/std/os/linux.zig | 4 ++++ 4 files changed, 23 insertions(+) diff --git a/lib/std/c.zig b/lib/std/c.zig index 79a6c07c02..84a9e92dee 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -105,6 +105,7 @@ pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int; pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int; pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int; pub extern "c" fn chdir(path: [*:0]const u8) c_int; +pub extern "c" fn fchdir(fd: fd_t) c_int; pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int; pub extern "c" fn dup(fd: fd_t) c_int; pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index f245ab348d..f9c7071abf 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -890,6 +890,10 @@ pub const Dir = struct { try os.mkdiratC(self.fd, sub_path, default_new_dir_mode); } + pub fn changeTo(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); diff --git a/lib/std/os.zig b/lib/std/os.zig index f97676a821..7563a34f21 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1706,6 +1706,20 @@ pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void { } } +pub fn fchdir(dirfd: fd_t) ChangeCurDirError!void { + while (true) { + switch (errno(system.fchdir(dirfd))) { + 0 => return, + EACCES => return error.AccessDenied, + EBADF => unreachable, + ENOTDIR => return error.NotDir, + EINTR => continue, + EIO => return error.FileSystem, + else => |err| return unexpectedErrno(err), + } + } +} + pub const ReadLinkError = error{ AccessDenied, FileSystem, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index de2ff5871b..719e541846 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -76,6 +76,10 @@ pub fn chdir(path: [*:0]const u8) usize { return syscall1(SYS_chdir, @ptrToInt(path)); } +pub fn fchdir(fd: fd_t) usize { + return syscall1(SYS_fchdir, @bitCast(usize, @as(isize, fd))); +} + pub fn chroot(path: [*:0]const u8) usize { return syscall1(SYS_chroot, @ptrToInt(path)); } From bfc569bc9877a4f305080bc6cde0e42fed7433e0 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 15 Jan 2020 18:15:24 +1000 Subject: [PATCH 04/12] std: add os.fstatat --- lib/std/c.zig | 1 + lib/std/os.zig | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/std/c.zig b/lib/std/c.zig index 84a9e92dee..7c01908540 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -75,6 +75,7 @@ pub extern "c" fn isatty(fd: fd_t) c_int; pub extern "c" fn close(fd: fd_t) c_int; pub extern "c" fn fstat(fd: fd_t, buf: *Stat) c_int; pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int; +pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, flags: u32) c_int; pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: c_int) off_t; pub extern "c" fn open(path: [*:0]const u8, oflag: c_uint, ...) c_int; pub extern "c" fn openat(fd: c_int, path: [*:0]const u8, oflag: c_uint, ...) c_int; diff --git a/lib/std/os.zig b/lib/std/os.zig index 7563a34f21..a74a2343a6 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2371,6 +2371,29 @@ pub fn fstat(fd: fd_t) FStatError!Stat { } } +const FStatAtError = FStatError || error{NameTooLong}; + +pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError![]Stat { + const pathname_c = try toPosixPath(pathname); + return fstatatC(dirfd, &pathname_c, flags); +} + +pub fn fstatatC(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { + var stat: Stat = undefined; + switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) { + 0 => return stat, + EINVAL => unreachable, + EBADF => unreachable, // Always a race condition. + ENOMEM => return error.SystemResources, + EACCES => return error.AccessDenied, + EFAULT => unreachable, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOTDIR => return error.FileNotFound, + else => |err| return unexpectedErrno(err), + } +} + pub const KQueueError = error{ /// The per-process limit on the number of open file descriptors has been reached. ProcessFdQuotaExceeded, From d8f966a04b09ede06840b5fdd42b7d8e65b0cb25 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 15 Jan 2020 18:17:14 +1000 Subject: [PATCH 05/12] std: fix fs.makePath The previous behaviour of using path.resolve has unexpected behaviour around symlinks. This more simple implementation is more correct and doesn't require an allocator --- lib/std/fs.zig | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index f9c7071abf..11ff0b5022 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -301,35 +301,32 @@ pub fn makeDirW(dir_path: [*:0]const u16) !void { /// 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; +pub fn makePath(full_path: []const u8) !void { + var end_index: usize = full_path.len; while (true) { - makeDir(resolved_path[0..end_index]) catch |err| switch (err) { + cwd().makeDir(full_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; + if (end_index == full_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(resolved_path[end_index])) break; + if (path.isSep(full_path[end_index])) break; } continue; }, else => return err, }; - if (end_index == resolved_path.len) return; + if (end_index == full_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; + if (end_index == full_path.len or path.isSep(full_path[end_index])) break; } } } From a19a30bb1771f0fc935cd8c17070158d8c379346 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 15 Jan 2020 18:17:41 +1000 Subject: [PATCH 06/12] std: move null byte check into toPosixPath Note that windows NT paths *can* contain nulls --- lib/std/fs.zig | 6 ------ lib/std/os.zig | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 11ff0b5022..83d8f9c26a 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -706,7 +706,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); @@ -756,7 +755,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); @@ -909,7 +907,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); @@ -927,7 +924,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); @@ -1091,7 +1087,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); @@ -1121,7 +1116,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); } diff --git a/lib/std/os.zig b/lib/std/os.zig index a74a2343a6..3ba810445a 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1355,7 +1355,6 @@ pub const UnlinkatError = UnlinkError || error{ /// Delete a file name and possibly the file it refers to, based on an open directory handle. /// Asserts that the path parameter has no null bytes. pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { - if (std.debug.runtime_safety) for (file_path) |byte| assert(byte != 0); if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return unlinkatW(dirfd, &file_path_w, flags); @@ -3241,6 +3240,7 @@ pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t { /// Used to convert a slice to a null terminated slice on the stack. /// TODO https://github.com/ziglang/zig/issues/287 pub fn toPosixPath(file_path: []const u8) ![PATH_MAX - 1:0]u8 { + if (std.debug.runtime_safety) assert(std.mem.indexOfScalar(u8, file_path, 0) == null); var path_with_null: [PATH_MAX - 1:0]u8 = undefined; // >= rather than > to make room for the null byte if (file_path.len >= PATH_MAX) return error.NameTooLong; From 695b0976c3757325d4b2043151d267bcc7490f7e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 16 Jan 2020 10:07:32 +1000 Subject: [PATCH 07/12] std: move makePath to be a Dir method --- lib/std/fs.zig | 68 +++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 83d8f9c26a..db3affc490 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -297,40 +297,6 @@ 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. -pub fn makePath(full_path: []const u8) !void { - var end_index: usize = full_path.len; - while (true) { - cwd().makeDir(full_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 == full_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(full_path[end_index])) break; - } - continue; - }, - else => return err, - }; - if (end_index == full_path.len) return; - // march end_index forward until next path component - while (true) { - end_index += 1; - if (end_index == full_path.len or path.isSep(full_path[end_index])) break; - } - } -} - /// Returns `error.DirNotEmpty` if the directory is not empty. /// To delete a directory recursively, see `deleteTree`. pub fn deleteDir(dir_path: []const u8) !void { @@ -885,6 +851,40 @@ pub const Dir = struct { try os.mkdiratC(self.fd, sub_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. + 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; + } + } + } + pub fn changeTo(self: Dir) !void { try os.fchdir(self.fd); } From 1ca5f06762401d2e90c8119acb4837571696dd5e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 16 Jan 2020 12:21:00 +1000 Subject: [PATCH 08/12] Update callers of fs.makePath --- lib/std/build.zig | 4 ++-- lib/std/build/write_file.zig | 2 +- lib/std/fs/watch.zig | 5 ++--- lib/std/os/test.zig | 4 ++-- src-self-hosted/compilation.zig | 2 +- src-self-hosted/test.zig | 6 +++--- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/std/build.zig b/lib/std/build.zig index ecf3930551..59e6c4e87c 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -763,7 +763,7 @@ pub const Builder = struct { } pub fn makePath(self: *Builder, path: []const u8) !void { - fs.makePath(self.allocator, self.pathFromRoot(path)) catch |err| { + fs.cwd().makePath(self.pathFromRoot(path)) catch |err| { warn("Unable to create path {}: {}\n", .{ path, @errorName(err) }); return err; }; @@ -2311,7 +2311,7 @@ pub const InstallDirStep = struct { const rel_path = entry.path[full_src_dir.len + 1 ..]; const dest_path = try fs.path.join(self.builder.allocator, &[_][]const u8{ dest_prefix, rel_path }); switch (entry.kind) { - .Directory => try fs.makePath(self.builder.allocator, dest_path), + .Directory => try fs.cwd().makePath(dest_path), .File => try self.builder.updateFile(entry.path, dest_path), else => continue, } diff --git a/lib/std/build/write_file.zig b/lib/std/build/write_file.zig index 13d131ac61..60c54336e0 100644 --- a/lib/std/build/write_file.zig +++ b/lib/std/build/write_file.zig @@ -74,7 +74,7 @@ pub const WriteFileStep = struct { &hash_basename, }); // TODO replace with something like fs.makePathAndOpenDir - fs.makePath(self.builder.allocator, self.output_dir) catch |err| { + fs.cwd().makePath(self.output_dir) catch |err| { warn("unable to make path {}: {}\n", .{ self.output_dir, @errorName(err) }); return err; }; diff --git a/lib/std/fs/watch.zig b/lib/std/fs/watch.zig index 1eb5a97ff1..3180240a72 100644 --- a/lib/std/fs/watch.zig +++ b/lib/std/fs/watch.zig @@ -618,11 +618,10 @@ test "write a file, watch it, write it again" { // TODO re-enable this test if (true) return error.SkipZigTest; - const allocator = std.heap.page_allocator; - - try os.makePath(allocator, test_tmp_dir); + try fs.cwd().makePath(test_tmp_dir); defer os.deleteTree(test_tmp_dir) catch {}; + const allocator = std.heap.page_allocator; return testFsWatch(&allocator); } diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 717380ea30..a8544c5b83 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -16,7 +16,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp; const AtomicOrder = builtin.AtomicOrder; test "makePath, put some files in it, deleteTree" { - try fs.makePath(a, "os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); + try fs.cwd().makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); try fs.deleteTree("os_test_tmp"); @@ -28,7 +28,7 @@ test "makePath, put some files in it, deleteTree" { } test "access file" { - try fs.makePath(a, "os_test_tmp"); + try fs.cwd().makePath("os_test_tmp"); if (fs.cwd().access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| { @panic("expected error"); } else |err| { diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index 15453fabcc..7a45bb3c37 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -1179,7 +1179,7 @@ pub const Compilation = struct { defer self.gpa().free(zig_dir_path); const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] }); - try fs.makePath(self.gpa(), tmp_dir); + try fs.cwd().makePath(tmp_dir); return tmp_dir; } diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 8c322e5fb6..e87164c9fb 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -56,7 +56,7 @@ pub const TestContext = struct { self.zig_lib_dir = try introspect.resolveZigLibDir(allocator); errdefer allocator.free(self.zig_lib_dir); - try std.fs.makePath(allocator, tmp_dir_name); + try std.fs.cwd().makePath(tmp_dir_name); errdefer std.fs.deleteTree(tmp_dir_name) catch {}; } @@ -85,7 +85,7 @@ pub const TestContext = struct { const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 }); if (std.fs.path.dirname(file1_path)) |dirname| { - try std.fs.makePath(allocator, dirname); + try std.fs.cwd().makePath(dirname); } // TODO async I/O @@ -119,7 +119,7 @@ pub const TestContext = struct { const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", .{ file1_path, (Target{ .Native = {} }).exeFileExt() }); if (std.fs.path.dirname(file1_path)) |dirname| { - try std.fs.makePath(allocator, dirname); + try std.fs.cwd().makePath(dirname); } // TODO async I/O From 4a67dd04c99954af2fd8e38b99704a1faea16267 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Mar 2020 15:01:08 -0500 Subject: [PATCH 09/12] breaking changes to std.fs, std.os * improve `std.fs.AtomicFile` to use sendfile() - also fix AtomicFile cleanup not destroying tmp files under some error conditions * improve `std.fs.updateFile` to take advantage of the new `makePath` which no longer needs an Allocator. * rename std.fs.makeDir to std.fs.makeDirAbsolute * rename std.fs.Dir.makeDirC to std.fs.Dir.makeDirZ * add std.fs.Dir.makeDirW and provide Windows implementation of std.os.mkdirat. std.os.windows.CreateDirectory is now implemented by calling ntdll, supports an optional root directory handle, and returns an open directory handle. Its error set has a few more errors in it. * rename std.fs.Dir.changeTo to std.fs.Dir.setAsCwd * fix std.fs.File.writevAll and related functions when len 0 iovecs supplied. * introduce `std.fs.File.writeFileAll`, exposing a convenient cross-platform API on top of sendfile(). * `NoDevice` added to std.os.MakeDirError error set. * std.os.fchdir gets a smaller error set. * std.os.windows.CloseHandle is implemented with ntdll call rather than kernel32. --- lib/std/fs.zig | 104 ++++++++++++++++++++--------------------- lib/std/fs/file.zig | 91 ++++++++++++++++++++++++++++++++++++ lib/std/os.zig | 70 +++++++++++++++++---------- lib/std/os/test.zig | 66 ++++---------------------- lib/std/os/windows.zig | 71 ++++++++++++++++++++++++---- 5 files changed, 259 insertions(+), 143 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index db3affc490..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; + if (path.dirname(dest_path)) |dirname| { + try cwd().makePath(dirname); + } + + var atomic_file = try AtomicFile.init(dest_path, actual_mode); defer atomic_file.deinit(); - const in_stream = &src_file.inStream().stream; - - 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,33 +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); +/// 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. @@ -847,10 +833,15 @@ pub const Dir = struct { try os.mkdirat(self.fd, sub_path, default_new_dir_mode); } - pub fn makeDirC(self: Dir, sub_path: [*:0]const u8) !void { + 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 @@ -885,7 +876,14 @@ pub const Dir = struct { } } - pub fn changeTo(self: Dir) !void { + /// 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); } diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 2715129934..5b51f19f41 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -271,6 +271,8 @@ pub const File = struct { /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying OS layer. pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!void { + if (iovecs.len == 0) return; + var i: usize = 0; while (true) { var amt = try self.readv(iovecs[i..]); @@ -295,6 +297,8 @@ pub const File = struct { /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying OS layer. pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void { + if (iovecs.len == 0) return; + var i: usize = 0; var off: usize = 0; while (true) { @@ -354,6 +358,8 @@ pub const File = struct { /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial writes from the underlying OS layer. pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void { + if (iovecs.len == 0) return; + var i: usize = 0; while (true) { var amt = try self.writev(iovecs[i..]); @@ -378,6 +384,8 @@ pub const File = struct { /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial writes from the underlying OS layer. pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!void { + if (iovecs.len == 0) return; + var i: usize = 0; var off: usize = 0; while (true) { @@ -393,6 +401,89 @@ pub const File = struct { } } + pub const WriteFileOptions = struct { + in_offset: u64 = 0, + + /// `null` means the entire file. `0` means no bytes from the file. + /// When this is `null`, trailers must be sent in a separate writev() call + /// due to a flaw in the BSD sendfile API. Other operating systems, such as + /// Linux, already do this anyway due to API limitations. + /// If the size of the source file is known, passing the size here will save one syscall. + in_len: ?u64 = null, + + headers_and_trailers: []os.iovec_const = &[0]os.iovec_const{}, + + /// The trailer count is inferred from `headers_and_trailers.len - header_count` + header_count: usize = 0, + }; + + pub const WriteFileError = os.SendFileError; + + /// TODO integrate with async I/O + pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void { + const count = blk: { + if (args.in_len) |l| { + if (l == 0) { + return self.writevAll(args.headers_and_trailers); + } else { + break :blk l; + } + } else { + break :blk 0; + } + }; + const headers = args.headers_and_trailers[0..args.header_count]; + const trailers = args.headers_and_trailers[args.header_count..]; + const zero_iovec = &[0]os.iovec_const{}; + // When reading the whole file, we cannot put the trailers in the sendfile() syscall, + // because we have no way to determine whether a partial write is past the end of the file or not. + const trls = if (count == 0) zero_iovec else trailers; + const offset = args.in_offset; + const out_fd = self.handle; + const in_fd = in_file.handle; + const flags = 0; + var amt: usize = 0; + hdrs: { + var i: usize = 0; + while (i < headers.len) { + amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags); + while (amt >= headers[i].iov_len) { + amt -= headers[i].iov_len; + i += 1; + if (i >= headers.len) break :hdrs; + } + headers[i].iov_base += amt; + headers[i].iov_len -= amt; + } + } + if (count == 0) { + var off: u64 = amt; + while (true) { + amt = try os.sendfile(out_fd, in_fd, offset + off, 0, zero_iovec, zero_iovec, flags); + if (amt == 0) break; + off += amt; + } + } else { + var off: u64 = amt; + while (off < count) { + amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, zero_iovec, trailers, flags); + off += amt; + } + amt = @intCast(usize, off - count); + } + var i: usize = 0; + while (i < trailers.len) { + while (amt >= headers[i].iov_len) { + amt -= trailers[i].iov_len; + i += 1; + if (i >= trailers.len) return; + } + trailers[i].iov_base += amt; + trailers[i].iov_len -= amt; + amt = try os.writev(self.handle, trailers[i..]); + } + } + pub fn inStream(file: File) InStream { return InStream{ .file = file, diff --git a/lib/std/os.zig b/lib/std/os.zig index 3ba810445a..b530ac94ab 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1539,12 +1539,13 @@ pub const MakeDirError = error{ ReadOnlyFileSystem, InvalidUtf8, BadPathName, + NoDevice, } || UnexpectedError; pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); - @compileError("TODO implement mkdirat for Windows"); + return mkdiratW(dir_fd, &sub_dir_path_w, mode); } else { const sub_dir_path_c = try toPosixPath(sub_dir_path); return mkdiratC(dir_fd, &sub_dir_path_c, mode); @@ -1552,9 +1553,9 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v } pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path); - @compileError("TODO implement mkdiratC for Windows"); + return mkdiratW(dir_fd, &sub_dir_path_w, mode); } switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { 0 => return, @@ -1576,23 +1577,31 @@ pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr } } +pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void { + const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null); + windows.CloseHandle(sub_dir_handle); +} + /// Create a directory. /// `mode` is ignored on Windows. pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .windows) { - const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); - return windows.CreateDirectoryW(&dir_path_w, null); + const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null); + windows.CloseHandle(sub_dir_handle); + return; } else { const dir_path_c = try toPosixPath(dir_path); - return mkdirC(&dir_path_c, mode); + return mkdirZ(&dir_path_c, mode); } } /// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string. -pub fn mkdirC(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { +pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .windows) { const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); - return windows.CreateDirectoryW(&dir_path_w, null); + const sub_dir_handle = try windows.CreateDirectoryW(null, &dir_path_w, null); + windows.CloseHandle(sub_dir_handle); + return; } switch (errno(system.mkdir(dir_path, mode))) { 0 => return, @@ -1705,7 +1714,13 @@ pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void { } } -pub fn fchdir(dirfd: fd_t) ChangeCurDirError!void { +pub const FchdirError = error{ + AccessDenied, + NotDir, + FileSystem, +} || UnexpectedError; + +pub fn fchdir(dirfd: fd_t) FchdirError!void { while (true) { switch (errno(system.fchdir(dirfd))) { 0 => return, @@ -3564,12 +3579,12 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize { } /// Transfer data between file descriptors, with optional headers and trailers. -/// Returns the number of bytes written. This will be zero if `in_offset` falls beyond the end of the file. +/// Returns the number of bytes written, which can be zero. /// -/// The `sendfile` call copies `count` bytes from one file descriptor to another. When possible, +/// The `sendfile` call copies `in_len` bytes from one file descriptor to another. When possible, /// this is done within the operating system kernel, which can provide better performance /// characteristics than transferring data from kernel to user space and back, such as with -/// `read` and `write` calls. When `count` is `0`, it means to copy until the end of the input file has been +/// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been /// reached. Note, however, that partial writes are still possible in this case. /// /// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor @@ -3578,7 +3593,8 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize { /// atomicity guarantees no longer apply. /// /// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated. -/// If the output file descriptor has a seek position, it is updated as bytes are written. +/// If the output file descriptor has a seek position, it is updated as bytes are written. When +/// `in_offset` is past the end of the input file, it successfully reads 0 bytes. /// /// `flags` has different meanings per operating system; refer to the respective man pages. /// @@ -3599,7 +3615,7 @@ pub fn sendfile( out_fd: fd_t, in_fd: fd_t, in_offset: u64, - count: usize, + in_len: u64, headers: []const iovec_const, trailers: []const iovec_const, flags: u32, @@ -3608,9 +3624,15 @@ pub fn sendfile( var total_written: usize = 0; // Prevents EOVERFLOW. + const size_t = @Type(std.builtin.TypeInfo{ + .Int = .{ + .is_signed = false, + .bits = @typeInfo(usize).Int.bits - 1, + }, + }); const max_count = switch (std.Target.current.os.tag) { .linux => 0x7ffff000, - else => math.maxInt(isize), + else => math.maxInt(size_t), }; switch (std.Target.current.os.tag) { @@ -3630,7 +3652,7 @@ pub fn sendfile( } // Here we match BSD behavior, making a zero count value send as many bytes as possible. - const adjusted_count = if (count == 0) max_count else math.min(count, max_count); + const adjusted_count = if (in_len == 0) max_count else math.min(in_len, @as(size_t, max_count)); while (true) { var offset: off_t = @bitCast(off_t, in_offset); @@ -3639,10 +3661,10 @@ pub fn sendfile( 0 => { const amt = @bitCast(usize, rc); total_written += amt; - if (count == 0 and amt == 0) { + if (in_len == 0 and amt == 0) { // We have detected EOF from `in_fd`. break; - } else if (amt < count) { + } else if (amt < in_len) { return total_written; } else { break; @@ -3708,7 +3730,7 @@ pub fn sendfile( hdtr = &hdtr_data; } - const adjusted_count = math.min(count, max_count); + const adjusted_count = math.min(in_len, max_count); while (true) { var sbytes: off_t = undefined; @@ -3786,7 +3808,7 @@ pub fn sendfile( hdtr = &hdtr_data; } - const adjusted_count = math.min(count, @as(u63, max_count)); + const adjusted_count = math.min(in_len, @as(u63, max_count)); while (true) { var sbytes: off_t = adjusted_count; @@ -3840,10 +3862,10 @@ pub fn sendfile( rw: { var buf: [8 * 4096]u8 = undefined; // Here we match BSD behavior, making a zero count value send as many bytes as possible. - const adjusted_count = if (count == 0) buf.len else math.min(buf.len, count); + const adjusted_count = if (in_len == 0) buf.len else math.min(buf.len, in_len); const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset); if (amt_read == 0) { - if (count == 0) { + if (in_len == 0) { // We have detected EOF from `in_fd`. break :rw; } else { @@ -3852,7 +3874,7 @@ pub fn sendfile( } const amt_written = try write(out_fd, buf[0..amt_read]); total_written += amt_written; - if (amt_written < count or count == 0) return total_written; + if (amt_written < in_len or in_len == 0) return total_written; } if (trailers.len != 0) { diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index a8544c5b83..5f97597537 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -45,7 +45,7 @@ fn testThreadIdFn(thread_id: *Thread.Id) void { } test "sendfile" { - try fs.makePath(a, "os_test_tmp"); + try fs.cwd().makePath("os_test_tmp"); defer fs.deleteTree("os_test_tmp") catch {}; var dir = try fs.cwd().openDirList("os_test_tmp"); @@ -74,7 +74,9 @@ test "sendfile" { const header1 = "header1\n"; const header2 = "second header\n"; - var headers = [_]os.iovec_const{ + const trailer1 = "trailer1\n"; + const trailer2 = "second trailer\n"; + var hdtr = [_]os.iovec_const{ .{ .iov_base = header1, .iov_len = header1.len, @@ -83,11 +85,6 @@ test "sendfile" { .iov_base = header2, .iov_len = header2.len, }, - }; - - const trailer1 = "trailer1\n"; - const trailer2 = "second trailer\n"; - var trailers = [_]os.iovec_const{ .{ .iov_base = trailer1, .iov_len = trailer1.len, @@ -99,59 +96,16 @@ test "sendfile" { }; var written_buf: [header1.len + header2.len + 10 + trailer1.len + trailer2.len]u8 = undefined; - try sendfileAll(dest_file.handle, src_file.handle, 1, 10, &headers, &trailers, 0); - + try dest_file.writeFileAll(src_file, .{ + .in_offset = 1, + .in_len = 10, + .headers_and_trailers = &hdtr, + .header_count = 2, + }); try dest_file.preadAll(&written_buf, 0); expect(mem.eql(u8, &written_buf, "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n")); } -fn sendfileAll( - out_fd: os.fd_t, - in_fd: os.fd_t, - offset: u64, - count: usize, - headers: []os.iovec_const, - trailers: []os.iovec_const, - flags: u32, -) os.SendFileError!void { - var amt: usize = undefined; - hdrs: { - var i: usize = 0; - while (i < headers.len) { - amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trailers, flags); - while (amt >= headers[i].iov_len) { - amt -= headers[i].iov_len; - i += 1; - if (i >= headers.len) break :hdrs; - } - headers[i].iov_base += amt; - headers[i].iov_len -= amt; - } - } - var off = amt; - while (off < count) { - amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, &[0]os.iovec_const{}, trailers, flags); - off += amt; - } - amt = off - count; - var i: usize = 0; - while (i < trailers.len) { - while (amt >= headers[i].iov_len) { - amt -= trailers[i].iov_len; - i += 1; - if (i >= trailers.len) return; - } - trailers[i].iov_base += amt; - trailers[i].iov_len -= amt; - if (std.Target.current.os.tag == .windows) { - amt = try os.writev(out_fd, trailers[i..]); - } else { - // Here we must use send because it's the only way to give the flags. - amt = try os.send(out_fd, trailers[i].iov_base[0..trailers[i].iov_len], flags); - } - } -} - test "std.Thread.getCurrentId" { if (builtin.single_threaded) return error.SkipZigTest; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 92124511bd..b8e14a220d 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -337,7 +337,7 @@ pub fn GetQueuedCompletionStatus( } pub fn CloseHandle(hObject: HANDLE) void { - assert(kernel32.CloseHandle(hObject) != 0); + assert(ntdll.NtClose(hObject) == .SUCCESS); } pub fn FindClose(hFindFile: HANDLE) void { @@ -586,23 +586,74 @@ pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DW } pub const CreateDirectoryError = error{ + NameTooLong, PathAlreadyExists, FileNotFound, + NoDevice, + AccessDenied, Unexpected, }; -pub fn CreateDirectory(pathname: []const u8, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void { +/// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`. +pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE { const pathname_w = try sliceToPrefixedFileW(pathname); - return CreateDirectoryW(&pathname_w, attrs); + return CreateDirectoryW(dir, &pathname_w, sa); } -pub fn CreateDirectoryW(pathname: [*:0]const u16, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void { - if (kernel32.CreateDirectoryW(pathname, attrs) == 0) { - switch (kernel32.GetLastError()) { - .ALREADY_EXISTS => return error.PathAlreadyExists, - .PATH_NOT_FOUND => return error.FileNotFound, - else => |err| return unexpectedError(err), - } +/// Same as `CreateDirectory` except takes a WTF-16 encoded path. +pub fn CreateDirectoryW( + dir: ?HANDLE, + sub_path_w: [*:0]const u16, + sa: ?*SECURITY_ATTRIBUTES, +) CreateDirectoryError!HANDLE { + const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) { + error.Overflow => return error.NameTooLong, + }; + var nt_name = UNICODE_STRING{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), + }; + + if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { + // Windows does not recognize this, but it does work with empty string. + nt_name.Length = 0; + } + + var attr = OBJECT_ATTRIBUTES{ + .Length = @sizeOf(OBJECT_ATTRIBUTES), + .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, + .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. + .ObjectName = &nt_name, + .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null, + .SecurityQualityOfService = null, + }; + var io: IO_STATUS_BLOCK = undefined; + var result_handle: HANDLE = undefined; + const rc = ntdll.NtCreateFile( + &result_handle, + GENERIC_READ | SYNCHRONIZE, + &attr, + &io, + null, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_CREATE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + null, + 0, + ); + switch (rc) { + .SUCCESS => return result_handle, + .OBJECT_NAME_INVALID => unreachable, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NO_MEDIA_IN_DEVICE => return error.NoDevice, + .INVALID_PARAMETER => unreachable, + .ACCESS_DENIED => return error.AccessDenied, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, + else => return unexpectedStatus(rc), } } From 55dfedff42db3cedd12d5385bd4df4bc7676d3af Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Mar 2020 15:58:14 -0500 Subject: [PATCH 10/12] update cli test to new std.fs API --- test/cli.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli.zig b/test/cli.zig index 117c714a29..9bc4a21c90 100644 --- a/test/cli.zig +++ b/test/cli.zig @@ -37,7 +37,7 @@ pub fn main() !void { }; for (test_fns) |testFn| { try fs.deleteTree(dir_path); - try fs.makeDir(dir_path); + try fs.cwd().makeDir(dir_path); try testFn(zig_exe, dir_path); } } From c4f81586f1212dfae8d647ad390676b1fda7bf89 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Mar 2020 16:01:09 -0500 Subject: [PATCH 11/12] update docgen to new std.fs API --- doc/docgen.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index 5d7f2b7b38..319d9e0035 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -50,7 +50,7 @@ pub fn main() !void { var tokenizer = Tokenizer.init(in_file_name, input_file_bytes); var toc = try genToc(allocator, &tokenizer); - try fs.makePath(allocator, tmp_dir_name); + try fs.cwd().makePath(tmp_dir_name); defer fs.deleteTree(tmp_dir_name) catch {}; try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream, zig_exe); From 1141bfb21b82f8d3fc353e968a591f2ad9aaa571 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Mar 2020 16:52:32 -0500 Subject: [PATCH 12/12] Darwin can return EBADF for sendfile on non-files --- lib/std/os.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index b530ac94ab..90ad3e03a9 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -3818,12 +3818,14 @@ pub fn sendfile( switch (err) { 0 => return amt, - EBADF => unreachable, // Always a race condition. EFAULT => unreachable, // Segmentation fault. EINVAL => unreachable, ENOTCONN => unreachable, // `out_fd` is an unconnected socket. - ENOTSUP, ENOTSOCK, ENOSYS => break :sf, + // On macOS version 10.14.6, I observed Darwin return EBADF when + // using sendfile on a valid open file descriptor of a file + // system file. + ENOTSUP, ENOTSOCK, ENOSYS, EBADF => break :sf, EINTR => if (amt != 0) return amt else continue,