| author | |
| committer | |
| log | f6f0b019bee7910702e113be6d33865c592afa2a |
| tree | 65cd5209cf69c03445f85bb244d15ca00bb51a32 |
| parent | 582db68a157520a0cf7777807f466d1f6a2e31e7 |
| parent | 1141bfb21b82f8d3fc353e968a591f2ad9aaa571 |
| signature |
improvements to std.fs, std.os14 files changed, 395 insertions(+), 196 deletions(-)
doc/docgen.zig+1-1| ... | ... | @@ -50,7 +50,7 @@ pub fn main() !void { |
| 50 | 50 | var tokenizer = Tokenizer.init(in_file_name, input_file_bytes); |
| 51 | 51 | var toc = try genToc(allocator, &tokenizer); |
| 52 | 52 | |
| 53 | try fs.makePath(allocator, tmp_dir_name); | |
| 53 | try fs.cwd().makePath(tmp_dir_name); | |
| 54 | 54 | defer fs.deleteTree(tmp_dir_name) catch {}; |
| 55 | 55 | |
| 56 | 56 | try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream, zig_exe); |
lib/std/build.zig+2-2| ... | ... | @@ -763,7 +763,7 @@ pub const Builder = struct { |
| 763 | 763 | } |
| 764 | 764 | |
| 765 | 765 | pub fn makePath(self: *Builder, path: []const u8) !void { |
| 766 | fs.makePath(self.allocator, self.pathFromRoot(path)) catch |err| { | |
| 766 | fs.cwd().makePath(self.pathFromRoot(path)) catch |err| { | |
| 767 | 767 | warn("Unable to create path {}: {}\n", .{ path, @errorName(err) }); |
| 768 | 768 | return err; |
| 769 | 769 | }; |
| ... | ... | @@ -2311,7 +2311,7 @@ pub const InstallDirStep = struct { |
| 2311 | 2311 | const rel_path = entry.path[full_src_dir.len + 1 ..]; |
| 2312 | 2312 | const dest_path = try fs.path.join(self.builder.allocator, &[_][]const u8{ dest_prefix, rel_path }); |
| 2313 | 2313 | switch (entry.kind) { |
| 2314 | .Directory => try fs.makePath(self.builder.allocator, dest_path), | |
| 2314 | .Directory => try fs.cwd().makePath(dest_path), | |
| 2315 | 2315 | .File => try self.builder.updateFile(entry.path, dest_path), |
| 2316 | 2316 | else => continue, |
| 2317 | 2317 | } |
lib/std/build/write_file.zig+1-1| ... | ... | @@ -74,7 +74,7 @@ pub const WriteFileStep = struct { |
| 74 | 74 | &hash_basename, |
| 75 | 75 | }); |
| 76 | 76 | // TODO replace with something like fs.makePathAndOpenDir |
| 77 | fs.makePath(self.builder.allocator, self.output_dir) catch |err| { | |
| 77 | fs.cwd().makePath(self.output_dir) catch |err| { | |
| 78 | 78 | warn("unable to make path {}: {}\n", .{ self.output_dir, @errorName(err) }); |
| 79 | 79 | return err; |
| 80 | 80 | }; |
lib/std/c.zig+3| ... | ... | @@ -75,6 +75,7 @@ pub extern "c" fn isatty(fd: fd_t) c_int; |
| 75 | 75 | pub extern "c" fn close(fd: fd_t) c_int; |
| 76 | 76 | pub extern "c" fn fstat(fd: fd_t, buf: *Stat) c_int; |
| 77 | 77 | pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int; |
| 78 | pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, flags: u32) c_int; | |
| 78 | 79 | pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: c_int) off_t; |
| 79 | 80 | pub extern "c" fn open(path: [*:0]const u8, oflag: c_uint, ...) c_int; |
| 80 | 81 | pub extern "c" fn openat(fd: c_int, path: [*:0]const u8, oflag: c_uint, ...) c_int; |
| ... | ... | @@ -101,9 +102,11 @@ pub extern "c" fn faccessat(dirfd: fd_t, path: [*:0]const u8, mode: c_uint, flag |
| 101 | 102 | pub extern "c" fn pipe(fds: *[2]fd_t) c_int; |
| 102 | 103 | pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; |
| 103 | 104 | pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int; |
| 105 | pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int; | |
| 104 | 106 | pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int; |
| 105 | 107 | pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int; |
| 106 | 108 | pub extern "c" fn chdir(path: [*:0]const u8) c_int; |
| 109 | pub extern "c" fn fchdir(fd: fd_t) c_int; | |
| 107 | 110 | pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int; |
| 108 | 111 | pub extern "c" fn dup(fd: fd_t) c_int; |
| 109 | 112 | pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int; |
lib/std/fs.zig+95-94| ... | ... | @@ -123,47 +123,21 @@ pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?Fil |
| 123 | 123 | } |
| 124 | 124 | const actual_mode = mode orelse src_stat.mode; |
| 125 | 125 | |
| 126 | // TODO this logic could be made more efficient by calling makePath, once | |
| 127 | // that API does not require an allocator | |
| 128 | var atomic_file = make_atomic_file: while (true) { | |
| 129 | const af = AtomicFile.init(dest_path, actual_mode) catch |err| switch (err) { | |
| 130 | error.FileNotFound => { | |
| 131 | var p = dest_path; | |
| 132 | while (path.dirname(p)) |dirname| { | |
| 133 | makeDir(dirname) catch |e| switch (e) { | |
| 134 | error.FileNotFound => { | |
| 135 | p = dirname; | |
| 136 | continue; | |
| 137 | }, | |
| 138 | else => return e, | |
| 139 | }; | |
| 140 | continue :make_atomic_file; | |
| 141 | } else { | |
| 142 | return err; | |
| 143 | } | |
| 144 | }, | |
| 145 | else => |e| return e, | |
| 146 | }; | |
| 147 | break af; | |
| 148 | } else unreachable; | |
| 149 | defer atomic_file.deinit(); | |
| 126 | if (path.dirname(dest_path)) |dirname| { | |
| 127 | try cwd().makePath(dirname); | |
| 128 | } | |
| 150 | 129 | |
| 151 | const in_stream = &src_file.inStream().stream; | |
| 130 | var atomic_file = try AtomicFile.init(dest_path, actual_mode); | |
| 131 | defer atomic_file.deinit(); | |
| 152 | 132 | |
| 153 | var buf: [mem.page_size * 6]u8 = undefined; | |
| 154 | while (true) { | |
| 155 | const amt = try in_stream.readFull(buf[0..]); | |
| 156 | try atomic_file.file.writeAll(buf[0..amt]); | |
| 157 | if (amt != buf.len) { | |
| 158 | try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime); | |
| 159 | try atomic_file.finish(); | |
| 160 | return PrevStatus.stale; | |
| 161 | } | |
| 162 | } | |
| 133 | try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size }); | |
| 134 | try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime); | |
| 135 | try atomic_file.finish(); | |
| 136 | return PrevStatus.stale; | |
| 163 | 137 | } |
| 164 | 138 | |
| 165 | /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is | |
| 166 | /// merged and readily available, | |
| 139 | /// Guaranteed to be atomic. | |
| 140 | /// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available, | |
| 167 | 141 | /// there is a possibility of power loss or application termination leaving temporary files present |
| 168 | 142 | /// in the same directory as dest_path. |
| 169 | 143 | /// 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 |
| 207 | 181 | } |
| 208 | 182 | } |
| 209 | 183 | |
| 184 | /// TODO update this API to avoid a getrandom syscall for every operation. It | |
| 185 | /// should accept a random interface. | |
| 186 | /// TODO rework this to integrate with Dir | |
| 210 | 187 | pub const AtomicFile = struct { |
| 211 | 188 | file: File, |
| 212 | 189 | tmp_path_buf: [MAX_PATH_BYTES]u8, |
| ... | ... | @@ -268,70 +245,42 @@ pub const AtomicFile = struct { |
| 268 | 245 | |
| 269 | 246 | pub fn finish(self: *AtomicFile) !void { |
| 270 | 247 | assert(!self.finished); |
| 271 | self.file.close(); | |
| 272 | self.finished = true; | |
| 273 | if (builtin.os.tag == .windows) { | |
| 248 | if (std.Target.current.os.tag == .windows) { | |
| 274 | 249 | const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path); |
| 275 | 250 | const tmp_path_w = try os.windows.cStrToPrefixedFileW(@ptrCast([*:0]u8, &self.tmp_path_buf)); |
| 251 | self.file.close(); | |
| 252 | self.finished = true; | |
| 276 | 253 | return os.renameW(&tmp_path_w, &dest_path_w); |
| 254 | } else { | |
| 255 | const dest_path_c = try os.toPosixPath(self.dest_path); | |
| 256 | self.file.close(); | |
| 257 | self.finished = true; | |
| 258 | return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c); | |
| 277 | 259 | } |
| 278 | const dest_path_c = try os.toPosixPath(self.dest_path); | |
| 279 | return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c); | |
| 280 | 260 | } |
| 281 | 261 | }; |
| 282 | 262 | |
| 283 | 263 | const default_new_dir_mode = 0o755; |
| 284 | 264 | |
| 285 | /// Create a new directory. | |
| 286 | pub fn makeDir(dir_path: []const u8) !void { | |
| 287 | return os.mkdir(dir_path, default_new_dir_mode); | |
| 265 | /// Create a new directory, based on an absolute path. | |
| 266 | /// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates | |
| 267 | /// on both absolute and relative paths. | |
| 268 | pub fn makeDirAbsolute(absolute_path: []const u8) !void { | |
| 269 | assert(path.isAbsoluteC(absolute_path)); | |
| 270 | return os.mkdir(absolute_path, default_new_dir_mode); | |
| 288 | 271 | } |
| 289 | 272 | |
| 290 | /// Same as `makeDir` except the parameter is a null-terminated UTF8-encoded string. | |
| 291 | pub fn makeDirC(dir_path: [*:0]const u8) !void { | |
| 292 | return os.mkdirC(dir_path, default_new_dir_mode); | |
| 273 | /// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF8-encoded string. | |
| 274 | pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { | |
| 275 | assert(path.isAbsoluteC(absolute_path_z)); | |
| 276 | return os.mkdirZ(absolute_path_z, default_new_dir_mode); | |
| 293 | 277 | } |
| 294 | 278 | |
| 295 | /// Same as `makeDir` except the parameter is a null-terminated UTF16LE-encoded string. | |
| 296 | pub fn makeDirW(dir_path: [*:0]const u16) !void { | |
| 297 | return os.mkdirW(dir_path, default_new_dir_mode); | |
| 298 | } | |
| 299 | ||
| 300 | /// Calls makeDir recursively to make an entire path. Returns success if the path | |
| 301 | /// already exists and is a directory. | |
| 302 | /// This function is not atomic, and if it returns an error, the file system may | |
| 303 | /// have been modified regardless. | |
| 304 | /// TODO determine if we can remove the allocator requirement from this function | |
| 305 | pub fn makePath(allocator: *Allocator, full_path: []const u8) !void { | |
| 306 | const resolved_path = try path.resolve(allocator, &[_][]const u8{full_path}); | |
| 307 | defer allocator.free(resolved_path); | |
| 308 | ||
| 309 | var end_index: usize = resolved_path.len; | |
| 310 | while (true) { | |
| 311 | makeDir(resolved_path[0..end_index]) catch |err| switch (err) { | |
| 312 | error.PathAlreadyExists => { | |
| 313 | // TODO stat the file and return an error if it's not a directory | |
| 314 | // this is important because otherwise a dangling symlink | |
| 315 | // could cause an infinite loop | |
| 316 | if (end_index == resolved_path.len) return; | |
| 317 | }, | |
| 318 | error.FileNotFound => { | |
| 319 | // march end_index backward until next path component | |
| 320 | while (true) { | |
| 321 | end_index -= 1; | |
| 322 | if (path.isSep(resolved_path[end_index])) break; | |
| 323 | } | |
| 324 | continue; | |
| 325 | }, | |
| 326 | else => return err, | |
| 327 | }; | |
| 328 | if (end_index == resolved_path.len) return; | |
| 329 | // march end_index forward until next path component | |
| 330 | while (true) { | |
| 331 | end_index += 1; | |
| 332 | if (end_index == resolved_path.len or path.isSep(resolved_path[end_index])) break; | |
| 333 | } | |
| 334 | } | |
| 279 | /// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string. | |
| 280 | pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { | |
| 281 | assert(path.isAbsoluteWindowsW(absolute_path_w)); | |
| 282 | const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null); | |
| 283 | os.windows.CloseHandle(handle); | |
| 335 | 284 | } |
| 336 | 285 | |
| 337 | 286 | /// Returns `error.DirNotEmpty` if the directory is not empty. |
| ... | ... | @@ -709,7 +658,6 @@ pub const Dir = struct { |
| 709 | 658 | /// Call `File.close` to release the resource. |
| 710 | 659 | /// Asserts that the path parameter has no null bytes. |
| 711 | 660 | pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { |
| 712 | if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); | |
| 713 | 661 | if (builtin.os.tag == .windows) { |
| 714 | 662 | const path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 715 | 663 | return self.openFileW(&path_w, flags); |
| ... | ... | @@ -759,7 +707,6 @@ pub const Dir = struct { |
| 759 | 707 | /// Call `File.close` on the result when done. |
| 760 | 708 | /// Asserts that the path parameter has no null bytes. |
| 761 | 709 | pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { |
| 762 | if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); | |
| 763 | 710 | if (builtin.os.tag == .windows) { |
| 764 | 711 | const path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 765 | 712 | return self.createFileW(&path_w, flags); |
| ... | ... | @@ -882,6 +829,64 @@ pub const Dir = struct { |
| 882 | 829 | } |
| 883 | 830 | } |
| 884 | 831 | |
| 832 | pub fn makeDir(self: Dir, sub_path: []const u8) !void { | |
| 833 | try os.mkdirat(self.fd, sub_path, default_new_dir_mode); | |
| 834 | } | |
| 835 | ||
| 836 | pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void { | |
| 837 | try os.mkdiratC(self.fd, sub_path, default_new_dir_mode); | |
| 838 | } | |
| 839 | ||
| 840 | pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void { | |
| 841 | const handle = try os.windows.CreateDirectoryW(self.fd, sub_path, null); | |
| 842 | os.windows.CloseHandle(handle); | |
| 843 | } | |
| 844 | ||
| 845 | /// Calls makeDir recursively to make an entire path. Returns success if the path | |
| 846 | /// already exists and is a directory. | |
| 847 | /// This function is not atomic, and if it returns an error, the file system may | |
| 848 | /// have been modified regardless. | |
| 849 | pub fn makePath(self: Dir, sub_path: []const u8) !void { | |
| 850 | var end_index: usize = sub_path.len; | |
| 851 | while (true) { | |
| 852 | self.makeDir(sub_path[0..end_index]) catch |err| switch (err) { | |
| 853 | error.PathAlreadyExists => { | |
| 854 | // TODO stat the file and return an error if it's not a directory | |
| 855 | // this is important because otherwise a dangling symlink | |
| 856 | // could cause an infinite loop | |
| 857 | if (end_index == sub_path.len) return; | |
| 858 | }, | |
| 859 | error.FileNotFound => { | |
| 860 | if (end_index == 0) return err; | |
| 861 | // march end_index backward until next path component | |
| 862 | while (true) { | |
| 863 | end_index -= 1; | |
| 864 | if (path.isSep(sub_path[end_index])) break; | |
| 865 | } | |
| 866 | continue; | |
| 867 | }, | |
| 868 | else => return err, | |
| 869 | }; | |
| 870 | if (end_index == sub_path.len) return; | |
| 871 | // march end_index forward until next path component | |
| 872 | while (true) { | |
| 873 | end_index += 1; | |
| 874 | if (end_index == sub_path.len or path.isSep(sub_path[end_index])) break; | |
| 875 | } | |
| 876 | } | |
| 877 | } | |
| 878 | ||
| 879 | /// Changes the current working directory to the open directory handle. | |
| 880 | /// This modifies global state and can have surprising effects in multi- | |
| 881 | /// threaded applications. Most applications and especially libraries should | |
| 882 | /// not call this function as a general rule, however it can have use cases | |
| 883 | /// in, for example, implementing a shell, or child process execution. | |
| 884 | /// Not all targets support this. For example, WASI does not have the concept | |
| 885 | /// of a current working directory. | |
| 886 | pub fn setAsCwd(self: Dir) !void { | |
| 887 | try os.fchdir(self.fd); | |
| 888 | } | |
| 889 | ||
| 885 | 890 | /// Deprecated; call `openDirList` directly. |
| 886 | 891 | pub fn openDir(self: Dir, sub_path: []const u8) OpenError!Dir { |
| 887 | 892 | return self.openDirList(sub_path); |
| ... | ... | @@ -900,7 +905,6 @@ pub const Dir = struct { |
| 900 | 905 | /// |
| 901 | 906 | /// Asserts that the path parameter has no null bytes. |
| 902 | 907 | pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir { |
| 903 | if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); | |
| 904 | 908 | if (builtin.os.tag == .windows) { |
| 905 | 909 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 906 | 910 | return self.openDirTraverseW(&sub_path_w); |
| ... | ... | @@ -918,7 +922,6 @@ pub const Dir = struct { |
| 918 | 922 | /// |
| 919 | 923 | /// Asserts that the path parameter has no null bytes. |
| 920 | 924 | pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir { |
| 921 | if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); | |
| 922 | 925 | if (builtin.os.tag == .windows) { |
| 923 | 926 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 924 | 927 | return self.openDirListW(&sub_path_w); |
| ... | ... | @@ -1082,7 +1085,6 @@ pub const Dir = struct { |
| 1082 | 1085 | /// To delete a directory recursively, see `deleteTree`. |
| 1083 | 1086 | /// Asserts that the path parameter has no null bytes. |
| 1084 | 1087 | pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { |
| 1085 | if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); | |
| 1086 | 1088 | if (builtin.os.tag == .windows) { |
| 1087 | 1089 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 1088 | 1090 | return self.deleteDirW(&sub_path_w); |
| ... | ... | @@ -1112,7 +1114,6 @@ pub const Dir = struct { |
| 1112 | 1114 | /// The return value is a slice of `buffer`, from index `0`. |
| 1113 | 1115 | /// Asserts that the path parameter has no null bytes. |
| 1114 | 1116 | pub fn readLink(self: Dir, sub_path: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { |
| 1115 | if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); | |
| 1116 | 1117 | const sub_path_c = try os.toPosixPath(sub_path); |
| 1117 | 1118 | return self.readLinkC(&sub_path_c, buffer); |
| 1118 | 1119 | } |
lib/std/fs/file.zig+91| ... | ... | @@ -271,6 +271,8 @@ pub const File = struct { |
| 271 | 271 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 272 | 272 | /// order to handle partial reads from the underlying OS layer. |
| 273 | 273 | pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!void { |
| 274 | if (iovecs.len == 0) return; | |
| 275 | ||
| 274 | 276 | var i: usize = 0; |
| 275 | 277 | while (true) { |
| 276 | 278 | var amt = try self.readv(iovecs[i..]); |
| ... | ... | @@ -295,6 +297,8 @@ pub const File = struct { |
| 295 | 297 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 296 | 298 | /// order to handle partial reads from the underlying OS layer. |
| 297 | 299 | pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void { |
| 300 | if (iovecs.len == 0) return; | |
| 301 | ||
| 298 | 302 | var i: usize = 0; |
| 299 | 303 | var off: usize = 0; |
| 300 | 304 | while (true) { |
| ... | ... | @@ -354,6 +358,8 @@ pub const File = struct { |
| 354 | 358 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 355 | 359 | /// order to handle partial writes from the underlying OS layer. |
| 356 | 360 | pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void { |
| 361 | if (iovecs.len == 0) return; | |
| 362 | ||
| 357 | 363 | var i: usize = 0; |
| 358 | 364 | while (true) { |
| 359 | 365 | var amt = try self.writev(iovecs[i..]); |
| ... | ... | @@ -378,6 +384,8 @@ pub const File = struct { |
| 378 | 384 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 379 | 385 | /// order to handle partial writes from the underlying OS layer. |
| 380 | 386 | pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!void { |
| 387 | if (iovecs.len == 0) return; | |
| 388 | ||
| 381 | 389 | var i: usize = 0; |
| 382 | 390 | var off: usize = 0; |
| 383 | 391 | while (true) { |
| ... | ... | @@ -393,6 +401,89 @@ pub const File = struct { |
| 393 | 401 | } |
| 394 | 402 | } |
| 395 | 403 | |
| 404 | pub const WriteFileOptions = struct { | |
| 405 | in_offset: u64 = 0, | |
| 406 | ||
| 407 | /// `null` means the entire file. `0` means no bytes from the file. | |
| 408 | /// When this is `null`, trailers must be sent in a separate writev() call | |
| 409 | /// due to a flaw in the BSD sendfile API. Other operating systems, such as | |
| 410 | /// Linux, already do this anyway due to API limitations. | |
| 411 | /// If the size of the source file is known, passing the size here will save one syscall. | |
| 412 | in_len: ?u64 = null, | |
| 413 | ||
| 414 | headers_and_trailers: []os.iovec_const = &[0]os.iovec_const{}, | |
| 415 | ||
| 416 | /// The trailer count is inferred from `headers_and_trailers.len - header_count` | |
| 417 | header_count: usize = 0, | |
| 418 | }; | |
| 419 | ||
| 420 | pub const WriteFileError = os.SendFileError; | |
| 421 | ||
| 422 | /// TODO integrate with async I/O | |
| 423 | pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void { | |
| 424 | const count = blk: { | |
| 425 | if (args.in_len) |l| { | |
| 426 | if (l == 0) { | |
| 427 | return self.writevAll(args.headers_and_trailers); | |
| 428 | } else { | |
| 429 | break :blk l; | |
| 430 | } | |
| 431 | } else { | |
| 432 | break :blk 0; | |
| 433 | } | |
| 434 | }; | |
| 435 | const headers = args.headers_and_trailers[0..args.header_count]; | |
| 436 | const trailers = args.headers_and_trailers[args.header_count..]; | |
| 437 | const zero_iovec = &[0]os.iovec_const{}; | |
| 438 | // When reading the whole file, we cannot put the trailers in the sendfile() syscall, | |
| 439 | // because we have no way to determine whether a partial write is past the end of the file or not. | |
| 440 | const trls = if (count == 0) zero_iovec else trailers; | |
| 441 | const offset = args.in_offset; | |
| 442 | const out_fd = self.handle; | |
| 443 | const in_fd = in_file.handle; | |
| 444 | const flags = 0; | |
| 445 | var amt: usize = 0; | |
| 446 | hdrs: { | |
| 447 | var i: usize = 0; | |
| 448 | while (i < headers.len) { | |
| 449 | amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags); | |
| 450 | while (amt >= headers[i].iov_len) { | |
| 451 | amt -= headers[i].iov_len; | |
| 452 | i += 1; | |
| 453 | if (i >= headers.len) break :hdrs; | |
| 454 | } | |
| 455 | headers[i].iov_base += amt; | |
| 456 | headers[i].iov_len -= amt; | |
| 457 | } | |
| 458 | } | |
| 459 | if (count == 0) { | |
| 460 | var off: u64 = amt; | |
| 461 | while (true) { | |
| 462 | amt = try os.sendfile(out_fd, in_fd, offset + off, 0, zero_iovec, zero_iovec, flags); | |
| 463 | if (amt == 0) break; | |
| 464 | off += amt; | |
| 465 | } | |
| 466 | } else { | |
| 467 | var off: u64 = amt; | |
| 468 | while (off < count) { | |
| 469 | amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, zero_iovec, trailers, flags); | |
| 470 | off += amt; | |
| 471 | } | |
| 472 | amt = @intCast(usize, off - count); | |
| 473 | } | |
| 474 | var i: usize = 0; | |
| 475 | while (i < trailers.len) { | |
| 476 | while (amt >= headers[i].iov_len) { | |
| 477 | amt -= trailers[i].iov_len; | |
| 478 | i += 1; | |
| 479 | if (i >= trailers.len) return; | |
| 480 | } | |
| 481 | trailers[i].iov_base += amt; | |
| 482 | trailers[i].iov_len -= amt; | |
| 483 | amt = try os.writev(self.handle, trailers[i..]); | |
| 484 | } | |
| 485 | } | |
| 486 | ||
| 396 | 487 | pub fn inStream(file: File) InStream { |
| 397 | 488 | return InStream{ |
| 398 | 489 | .file = file, |
lib/std/fs/watch.zig+2-3| ... | ... | @@ -618,11 +618,10 @@ test "write a file, watch it, write it again" { |
| 618 | 618 | // TODO re-enable this test |
| 619 | 619 | if (true) return error.SkipZigTest; |
| 620 | 620 | |
| 621 | const allocator = std.heap.page_allocator; | |
| 622 | ||
| 623 | try os.makePath(allocator, test_tmp_dir); | |
| 621 | try fs.cwd().makePath(test_tmp_dir); | |
| 624 | 622 | defer os.deleteTree(test_tmp_dir) catch {}; |
| 625 | 623 | |
| 624 | const allocator = std.heap.page_allocator; | |
| 626 | 625 | return testFsWatch(&allocator); |
| 627 | 626 | } |
| 628 | 627 |
lib/std/os.zig+118-22| ... | ... | @@ -1355,7 +1355,6 @@ pub const UnlinkatError = UnlinkError || error{ |
| 1355 | 1355 | /// Delete a file name and possibly the file it refers to, based on an open directory handle. |
| 1356 | 1356 | /// Asserts that the path parameter has no null bytes. |
| 1357 | 1357 | pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { |
| 1358 | if (std.debug.runtime_safety) for (file_path) |byte| assert(byte != 0); | |
| 1359 | 1358 | if (builtin.os.tag == .windows) { |
| 1360 | 1359 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 1361 | 1360 | return unlinkatW(dirfd, &file_path_w, flags); |
| ... | ... | @@ -1540,25 +1539,69 @@ pub const MakeDirError = error{ |
| 1540 | 1539 | ReadOnlyFileSystem, |
| 1541 | 1540 | InvalidUtf8, |
| 1542 | 1541 | BadPathName, |
| 1542 | NoDevice, | |
| 1543 | 1543 | } || UnexpectedError; |
| 1544 | 1544 | |
| 1545 | pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { | |
| 1546 | if (builtin.os.tag == .windows) { | |
| 1547 | const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); | |
| 1548 | return mkdiratW(dir_fd, &sub_dir_path_w, mode); | |
| 1549 | } else { | |
| 1550 | const sub_dir_path_c = try toPosixPath(sub_dir_path); | |
| 1551 | return mkdiratC(dir_fd, &sub_dir_path_c, mode); | |
| 1552 | } | |
| 1553 | } | |
| 1554 | ||
| 1555 | pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { | |
| 1556 | if (builtin.os.tag == .windows) { | |
| 1557 | const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path); | |
| 1558 | return mkdiratW(dir_fd, &sub_dir_path_w, mode); | |
| 1559 | } | |
| 1560 | switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { | |
| 1561 | 0 => return, | |
| 1562 | EACCES => return error.AccessDenied, | |
| 1563 | EBADF => unreachable, | |
| 1564 | EPERM => return error.AccessDenied, | |
| 1565 | EDQUOT => return error.DiskQuota, | |
| 1566 | EEXIST => return error.PathAlreadyExists, | |
| 1567 | EFAULT => unreachable, | |
| 1568 | ELOOP => return error.SymLinkLoop, | |
| 1569 | EMLINK => return error.LinkQuotaExceeded, | |
| 1570 | ENAMETOOLONG => return error.NameTooLong, | |
| 1571 | ENOENT => return error.FileNotFound, | |
| 1572 | ENOMEM => return error.SystemResources, | |
| 1573 | ENOSPC => return error.NoSpaceLeft, | |
| 1574 | ENOTDIR => return error.NotDir, | |
| 1575 | EROFS => return error.ReadOnlyFileSystem, | |
| 1576 | else => |err| return unexpectedErrno(err), | |
| 1577 | } | |
| 1578 | } | |
| 1579 | ||
| 1580 | pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void { | |
| 1581 | const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null); | |
| 1582 | windows.CloseHandle(sub_dir_handle); | |
| 1583 | } | |
| 1584 | ||
| 1545 | 1585 | /// Create a directory. |
| 1546 | 1586 | /// `mode` is ignored on Windows. |
| 1547 | 1587 | pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 1548 | 1588 | if (builtin.os.tag == .windows) { |
| 1549 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); | |
| 1550 | return windows.CreateDirectoryW(&dir_path_w, null); | |
| 1589 | const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null); | |
| 1590 | windows.CloseHandle(sub_dir_handle); | |
| 1591 | return; | |
| 1551 | 1592 | } else { |
| 1552 | 1593 | const dir_path_c = try toPosixPath(dir_path); |
| 1553 | return mkdirC(&dir_path_c, mode); | |
| 1594 | return mkdirZ(&dir_path_c, mode); | |
| 1554 | 1595 | } |
| 1555 | 1596 | } |
| 1556 | 1597 | |
| 1557 | 1598 | /// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string. |
| 1558 | pub fn mkdirC(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { | |
| 1599 | pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { | |
| 1559 | 1600 | if (builtin.os.tag == .windows) { |
| 1560 | 1601 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); |
| 1561 | return windows.CreateDirectoryW(&dir_path_w, null); | |
| 1602 | const sub_dir_handle = try windows.CreateDirectoryW(null, &dir_path_w, null); | |
| 1603 | windows.CloseHandle(sub_dir_handle); | |
| 1604 | return; | |
| 1562 | 1605 | } |
| 1563 | 1606 | switch (errno(system.mkdir(dir_path, mode))) { |
| 1564 | 1607 | 0 => return, |
| ... | ... | @@ -1671,6 +1714,26 @@ pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void { |
| 1671 | 1714 | } |
| 1672 | 1715 | } |
| 1673 | 1716 | |
| 1717 | pub const FchdirError = error{ | |
| 1718 | AccessDenied, | |
| 1719 | NotDir, | |
| 1720 | FileSystem, | |
| 1721 | } || UnexpectedError; | |
| 1722 | ||
| 1723 | pub fn fchdir(dirfd: fd_t) FchdirError!void { | |
| 1724 | while (true) { | |
| 1725 | switch (errno(system.fchdir(dirfd))) { | |
| 1726 | 0 => return, | |
| 1727 | EACCES => return error.AccessDenied, | |
| 1728 | EBADF => unreachable, | |
| 1729 | ENOTDIR => return error.NotDir, | |
| 1730 | EINTR => continue, | |
| 1731 | EIO => return error.FileSystem, | |
| 1732 | else => |err| return unexpectedErrno(err), | |
| 1733 | } | |
| 1734 | } | |
| 1735 | } | |
| 1736 | ||
| 1674 | 1737 | pub const ReadLinkError = error{ |
| 1675 | 1738 | AccessDenied, |
| 1676 | 1739 | FileSystem, |
| ... | ... | @@ -2322,6 +2385,29 @@ pub fn fstat(fd: fd_t) FStatError!Stat { |
| 2322 | 2385 | } |
| 2323 | 2386 | } |
| 2324 | 2387 | |
| 2388 | const FStatAtError = FStatError || error{NameTooLong}; | |
| 2389 | ||
| 2390 | pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError![]Stat { | |
| 2391 | const pathname_c = try toPosixPath(pathname); | |
| 2392 | return fstatatC(dirfd, &pathname_c, flags); | |
| 2393 | } | |
| 2394 | ||
| 2395 | pub fn fstatatC(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { | |
| 2396 | var stat: Stat = undefined; | |
| 2397 | switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) { | |
| 2398 | 0 => return stat, | |
| 2399 | EINVAL => unreachable, | |
| 2400 | EBADF => unreachable, // Always a race condition. | |
| 2401 | ENOMEM => return error.SystemResources, | |
| 2402 | EACCES => return error.AccessDenied, | |
| 2403 | EFAULT => unreachable, | |
| 2404 | ENAMETOOLONG => return error.NameTooLong, | |
| 2405 | ENOENT => return error.FileNotFound, | |
| 2406 | ENOTDIR => return error.FileNotFound, | |
| 2407 | else => |err| return unexpectedErrno(err), | |
| 2408 | } | |
| 2409 | } | |
| 2410 | ||
| 2325 | 2411 | pub const KQueueError = error{ |
| 2326 | 2412 | /// The per-process limit on the number of open file descriptors has been reached. |
| 2327 | 2413 | ProcessFdQuotaExceeded, |
| ... | ... | @@ -3169,6 +3255,7 @@ pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t { |
| 3169 | 3255 | /// Used to convert a slice to a null terminated slice on the stack. |
| 3170 | 3256 | /// TODO https://github.com/ziglang/zig/issues/287 |
| 3171 | 3257 | pub fn toPosixPath(file_path: []const u8) ![PATH_MAX - 1:0]u8 { |
| 3258 | if (std.debug.runtime_safety) assert(std.mem.indexOfScalar(u8, file_path, 0) == null); | |
| 3172 | 3259 | var path_with_null: [PATH_MAX - 1:0]u8 = undefined; |
| 3173 | 3260 | // >= rather than > to make room for the null byte |
| 3174 | 3261 | if (file_path.len >= PATH_MAX) return error.NameTooLong; |
| ... | ... | @@ -3492,12 +3579,12 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize { |
| 3492 | 3579 | } |
| 3493 | 3580 | |
| 3494 | 3581 | /// Transfer data between file descriptors, with optional headers and trailers. |
| 3495 | /// Returns the number of bytes written. This will be zero if `in_offset` falls beyond the end of the file. | |
| 3582 | /// Returns the number of bytes written, which can be zero. | |
| 3496 | 3583 | /// |
| 3497 | /// The `sendfile` call copies `count` bytes from one file descriptor to another. When possible, | |
| 3584 | /// The `sendfile` call copies `in_len` bytes from one file descriptor to another. When possible, | |
| 3498 | 3585 | /// this is done within the operating system kernel, which can provide better performance |
| 3499 | 3586 | /// characteristics than transferring data from kernel to user space and back, such as with |
| 3500 | /// `read` and `write` calls. When `count` is `0`, it means to copy until the end of the input file has been | |
| 3587 | /// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been | |
| 3501 | 3588 | /// reached. Note, however, that partial writes are still possible in this case. |
| 3502 | 3589 | /// |
| 3503 | 3590 | /// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor |
| ... | ... | @@ -3506,7 +3593,8 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize { |
| 3506 | 3593 | /// atomicity guarantees no longer apply. |
| 3507 | 3594 | /// |
| 3508 | 3595 | /// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated. |
| 3509 | /// If the output file descriptor has a seek position, it is updated as bytes are written. | |
| 3596 | /// If the output file descriptor has a seek position, it is updated as bytes are written. When | |
| 3597 | /// `in_offset` is past the end of the input file, it successfully reads 0 bytes. | |
| 3510 | 3598 | /// |
| 3511 | 3599 | /// `flags` has different meanings per operating system; refer to the respective man pages. |
| 3512 | 3600 | /// |
| ... | ... | @@ -3527,7 +3615,7 @@ pub fn sendfile( |
| 3527 | 3615 | out_fd: fd_t, |
| 3528 | 3616 | in_fd: fd_t, |
| 3529 | 3617 | in_offset: u64, |
| 3530 | count: usize, | |
| 3618 | in_len: u64, | |
| 3531 | 3619 | headers: []const iovec_const, |
| 3532 | 3620 | trailers: []const iovec_const, |
| 3533 | 3621 | flags: u32, |
| ... | ... | @@ -3536,9 +3624,15 @@ pub fn sendfile( |
| 3536 | 3624 | var total_written: usize = 0; |
| 3537 | 3625 | |
| 3538 | 3626 | // Prevents EOVERFLOW. |
| 3627 | const size_t = @Type(std.builtin.TypeInfo{ | |
| 3628 | .Int = .{ | |
| 3629 | .is_signed = false, | |
| 3630 | .bits = @typeInfo(usize).Int.bits - 1, | |
| 3631 | }, | |
| 3632 | }); | |
| 3539 | 3633 | const max_count = switch (std.Target.current.os.tag) { |
| 3540 | 3634 | .linux => 0x7ffff000, |
| 3541 | else => math.maxInt(isize), | |
| 3635 | else => math.maxInt(size_t), | |
| 3542 | 3636 | }; |
| 3543 | 3637 | |
| 3544 | 3638 | switch (std.Target.current.os.tag) { |
| ... | ... | @@ -3558,7 +3652,7 @@ pub fn sendfile( |
| 3558 | 3652 | } |
| 3559 | 3653 | |
| 3560 | 3654 | // Here we match BSD behavior, making a zero count value send as many bytes as possible. |
| 3561 | const adjusted_count = if (count == 0) max_count else math.min(count, max_count); | |
| 3655 | const adjusted_count = if (in_len == 0) max_count else math.min(in_len, @as(size_t, max_count)); | |
| 3562 | 3656 | |
| 3563 | 3657 | while (true) { |
| 3564 | 3658 | var offset: off_t = @bitCast(off_t, in_offset); |
| ... | ... | @@ -3567,10 +3661,10 @@ pub fn sendfile( |
| 3567 | 3661 | 0 => { |
| 3568 | 3662 | const amt = @bitCast(usize, rc); |
| 3569 | 3663 | total_written += amt; |
| 3570 | if (count == 0 and amt == 0) { | |
| 3664 | if (in_len == 0 and amt == 0) { | |
| 3571 | 3665 | // We have detected EOF from `in_fd`. |
| 3572 | 3666 | break; |
| 3573 | } else if (amt < count) { | |
| 3667 | } else if (amt < in_len) { | |
| 3574 | 3668 | return total_written; |
| 3575 | 3669 | } else { |
| 3576 | 3670 | break; |
| ... | ... | @@ -3636,7 +3730,7 @@ pub fn sendfile( |
| 3636 | 3730 | hdtr = &hdtr_data; |
| 3637 | 3731 | } |
| 3638 | 3732 | |
| 3639 | const adjusted_count = math.min(count, max_count); | |
| 3733 | const adjusted_count = math.min(in_len, max_count); | |
| 3640 | 3734 | |
| 3641 | 3735 | while (true) { |
| 3642 | 3736 | var sbytes: off_t = undefined; |
| ... | ... | @@ -3714,7 +3808,7 @@ pub fn sendfile( |
| 3714 | 3808 | hdtr = &hdtr_data; |
| 3715 | 3809 | } |
| 3716 | 3810 | |
| 3717 | const adjusted_count = math.min(count, @as(u63, max_count)); | |
| 3811 | const adjusted_count = math.min(in_len, @as(u63, max_count)); | |
| 3718 | 3812 | |
| 3719 | 3813 | while (true) { |
| 3720 | 3814 | var sbytes: off_t = adjusted_count; |
| ... | ... | @@ -3724,12 +3818,14 @@ pub fn sendfile( |
| 3724 | 3818 | switch (err) { |
| 3725 | 3819 | 0 => return amt, |
| 3726 | 3820 | |
| 3727 | EBADF => unreachable, // Always a race condition. | |
| 3728 | 3821 | EFAULT => unreachable, // Segmentation fault. |
| 3729 | 3822 | EINVAL => unreachable, |
| 3730 | 3823 | ENOTCONN => unreachable, // `out_fd` is an unconnected socket. |
| 3731 | 3824 | |
| 3732 | ENOTSUP, ENOTSOCK, ENOSYS => break :sf, | |
| 3825 | // On macOS version 10.14.6, I observed Darwin return EBADF when | |
| 3826 | // using sendfile on a valid open file descriptor of a file | |
| 3827 | // system file. | |
| 3828 | ENOTSUP, ENOTSOCK, ENOSYS, EBADF => break :sf, | |
| 3733 | 3829 | |
| 3734 | 3830 | EINTR => if (amt != 0) return amt else continue, |
| 3735 | 3831 | |
| ... | ... | @@ -3768,10 +3864,10 @@ pub fn sendfile( |
| 3768 | 3864 | rw: { |
| 3769 | 3865 | var buf: [8 * 4096]u8 = undefined; |
| 3770 | 3866 | // Here we match BSD behavior, making a zero count value send as many bytes as possible. |
| 3771 | const adjusted_count = if (count == 0) buf.len else math.min(buf.len, count); | |
| 3867 | const adjusted_count = if (in_len == 0) buf.len else math.min(buf.len, in_len); | |
| 3772 | 3868 | const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset); |
| 3773 | 3869 | if (amt_read == 0) { |
| 3774 | if (count == 0) { | |
| 3870 | if (in_len == 0) { | |
| 3775 | 3871 | // We have detected EOF from `in_fd`. |
| 3776 | 3872 | break :rw; |
| 3777 | 3873 | } else { |
| ... | ... | @@ -3780,7 +3876,7 @@ pub fn sendfile( |
| 3780 | 3876 | } |
| 3781 | 3877 | const amt_written = try write(out_fd, buf[0..amt_read]); |
| 3782 | 3878 | total_written += amt_written; |
| 3783 | if (amt_written < count or count == 0) return total_written; | |
| 3879 | if (amt_written < in_len or in_len == 0) return total_written; | |
| 3784 | 3880 | } |
| 3785 | 3881 | |
| 3786 | 3882 | if (trailers.len != 0) { |
lib/std/os/linux.zig+4| ... | ... | @@ -76,6 +76,10 @@ pub fn chdir(path: [*:0]const u8) usize { |
| 76 | 76 | return syscall1(SYS_chdir, @ptrToInt(path)); |
| 77 | 77 | } |
| 78 | 78 | |
| 79 | pub fn fchdir(fd: fd_t) usize { | |
| 80 | return syscall1(SYS_fchdir, @bitCast(usize, @as(isize, fd))); | |
| 81 | } | |
| 82 | ||
| 79 | 83 | pub fn chroot(path: [*:0]const u8) usize { |
| 80 | 84 | return syscall1(SYS_chroot, @ptrToInt(path)); |
| 81 | 85 | } |
lib/std/os/test.zig+12-58| ... | ... | @@ -16,7 +16,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp; |
| 16 | 16 | const AtomicOrder = builtin.AtomicOrder; |
| 17 | 17 | |
| 18 | 18 | test "makePath, put some files in it, deleteTree" { |
| 19 | try fs.makePath(a, "os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); | |
| 19 | try fs.cwd().makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); | |
| 20 | 20 | try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); |
| 21 | 21 | try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); |
| 22 | 22 | try fs.deleteTree("os_test_tmp"); |
| ... | ... | @@ -28,7 +28,7 @@ test "makePath, put some files in it, deleteTree" { |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | 30 | test "access file" { |
| 31 | try fs.makePath(a, "os_test_tmp"); | |
| 31 | try fs.cwd().makePath("os_test_tmp"); | |
| 32 | 32 | if (fs.cwd().access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| { |
| 33 | 33 | @panic("expected error"); |
| 34 | 34 | } else |err| { |
| ... | ... | @@ -45,7 +45,7 @@ fn testThreadIdFn(thread_id: *Thread.Id) void { |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | 47 | test "sendfile" { |
| 48 | try fs.makePath(a, "os_test_tmp"); | |
| 48 | try fs.cwd().makePath("os_test_tmp"); | |
| 49 | 49 | defer fs.deleteTree("os_test_tmp") catch {}; |
| 50 | 50 | |
| 51 | 51 | var dir = try fs.cwd().openDirList("os_test_tmp"); |
| ... | ... | @@ -74,7 +74,9 @@ test "sendfile" { |
| 74 | 74 | |
| 75 | 75 | const header1 = "header1\n"; |
| 76 | 76 | const header2 = "second header\n"; |
| 77 | var headers = [_]os.iovec_const{ | |
| 77 | const trailer1 = "trailer1\n"; | |
| 78 | const trailer2 = "second trailer\n"; | |
| 79 | var hdtr = [_]os.iovec_const{ | |
| 78 | 80 | .{ |
| 79 | 81 | .iov_base = header1, |
| 80 | 82 | .iov_len = header1.len, |
| ... | ... | @@ -83,11 +85,6 @@ test "sendfile" { |
| 83 | 85 | .iov_base = header2, |
| 84 | 86 | .iov_len = header2.len, |
| 85 | 87 | }, |
| 86 | }; | |
| 87 | ||
| 88 | const trailer1 = "trailer1\n"; | |
| 89 | const trailer2 = "second trailer\n"; | |
| 90 | var trailers = [_]os.iovec_const{ | |
| 91 | 88 | .{ |
| 92 | 89 | .iov_base = trailer1, |
| 93 | 90 | .iov_len = trailer1.len, |
| ... | ... | @@ -99,59 +96,16 @@ test "sendfile" { |
| 99 | 96 | }; |
| 100 | 97 | |
| 101 | 98 | var written_buf: [header1.len + header2.len + 10 + trailer1.len + trailer2.len]u8 = undefined; |
| 102 | try sendfileAll(dest_file.handle, src_file.handle, 1, 10, &headers, &trailers, 0); | |
| 103 | ||
| 99 | try dest_file.writeFileAll(src_file, .{ | |
| 100 | .in_offset = 1, | |
| 101 | .in_len = 10, | |
| 102 | .headers_and_trailers = &hdtr, | |
| 103 | .header_count = 2, | |
| 104 | }); | |
| 104 | 105 | try dest_file.preadAll(&written_buf, 0); |
| 105 | 106 | expect(mem.eql(u8, &written_buf, "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n")); |
| 106 | 107 | } |
| 107 | 108 | |
| 108 | fn sendfileAll( | |
| 109 | out_fd: os.fd_t, | |
| 110 | in_fd: os.fd_t, | |
| 111 | offset: u64, | |
| 112 | count: usize, | |
| 113 | headers: []os.iovec_const, | |
| 114 | trailers: []os.iovec_const, | |
| 115 | flags: u32, | |
| 116 | ) os.SendFileError!void { | |
| 117 | var amt: usize = undefined; | |
| 118 | hdrs: { | |
| 119 | var i: usize = 0; | |
| 120 | while (i < headers.len) { | |
| 121 | amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trailers, flags); | |
| 122 | while (amt >= headers[i].iov_len) { | |
| 123 | amt -= headers[i].iov_len; | |
| 124 | i += 1; | |
| 125 | if (i >= headers.len) break :hdrs; | |
| 126 | } | |
| 127 | headers[i].iov_base += amt; | |
| 128 | headers[i].iov_len -= amt; | |
| 129 | } | |
| 130 | } | |
| 131 | var off = amt; | |
| 132 | while (off < count) { | |
| 133 | amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, &[0]os.iovec_const{}, trailers, flags); | |
| 134 | off += amt; | |
| 135 | } | |
| 136 | amt = off - count; | |
| 137 | var i: usize = 0; | |
| 138 | while (i < trailers.len) { | |
| 139 | while (amt >= headers[i].iov_len) { | |
| 140 | amt -= trailers[i].iov_len; | |
| 141 | i += 1; | |
| 142 | if (i >= trailers.len) return; | |
| 143 | } | |
| 144 | trailers[i].iov_base += amt; | |
| 145 | trailers[i].iov_len -= amt; | |
| 146 | if (std.Target.current.os.tag == .windows) { | |
| 147 | amt = try os.writev(out_fd, trailers[i..]); | |
| 148 | } else { | |
| 149 | // Here we must use send because it's the only way to give the flags. | |
| 150 | amt = try os.send(out_fd, trailers[i].iov_base[0..trailers[i].iov_len], flags); | |
| 151 | } | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | 109 | test "std.Thread.getCurrentId" { |
| 156 | 110 | if (builtin.single_threaded) return error.SkipZigTest; |
| 157 | 111 |
lib/std/os/windows.zig+61-10| ... | ... | @@ -337,7 +337,7 @@ pub fn GetQueuedCompletionStatus( |
| 337 | 337 | } |
| 338 | 338 | |
| 339 | 339 | pub fn CloseHandle(hObject: HANDLE) void { |
| 340 | assert(kernel32.CloseHandle(hObject) != 0); | |
| 340 | assert(ntdll.NtClose(hObject) == .SUCCESS); | |
| 341 | 341 | } |
| 342 | 342 | |
| 343 | 343 | 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 |
| 586 | 586 | } |
| 587 | 587 | |
| 588 | 588 | pub const CreateDirectoryError = error{ |
| 589 | NameTooLong, | |
| 589 | 590 | PathAlreadyExists, |
| 590 | 591 | FileNotFound, |
| 592 | NoDevice, | |
| 593 | AccessDenied, | |
| 591 | 594 | Unexpected, |
| 592 | 595 | }; |
| 593 | 596 | |
| 594 | pub fn CreateDirectory(pathname: []const u8, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void { | |
| 597 | /// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`. | |
| 598 | pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE { | |
| 595 | 599 | const pathname_w = try sliceToPrefixedFileW(pathname); |
| 596 | return CreateDirectoryW(&pathname_w, attrs); | |
| 600 | return CreateDirectoryW(dir, &pathname_w, sa); | |
| 597 | 601 | } |
| 598 | 602 | |
| 599 | pub fn CreateDirectoryW(pathname: [*:0]const u16, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void { | |
| 600 | if (kernel32.CreateDirectoryW(pathname, attrs) == 0) { | |
| 601 | switch (kernel32.GetLastError()) { | |
| 602 | .ALREADY_EXISTS => return error.PathAlreadyExists, | |
| 603 | .PATH_NOT_FOUND => return error.FileNotFound, | |
| 604 | else => |err| return unexpectedError(err), | |
| 605 | } | |
| 603 | /// Same as `CreateDirectory` except takes a WTF-16 encoded path. | |
| 604 | pub fn CreateDirectoryW( | |
| 605 | dir: ?HANDLE, | |
| 606 | sub_path_w: [*:0]const u16, | |
| 607 | sa: ?*SECURITY_ATTRIBUTES, | |
| 608 | ) CreateDirectoryError!HANDLE { | |
| 609 | const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) { | |
| 610 | error.Overflow => return error.NameTooLong, | |
| 611 | }; | |
| 612 | var nt_name = UNICODE_STRING{ | |
| 613 | .Length = path_len_bytes, | |
| 614 | .MaximumLength = path_len_bytes, | |
| 615 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), | |
| 616 | }; | |
| 617 | ||
| 618 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { | |
| 619 | // Windows does not recognize this, but it does work with empty string. | |
| 620 | nt_name.Length = 0; | |
| 621 | } | |
| 622 | ||
| 623 | var attr = OBJECT_ATTRIBUTES{ | |
| 624 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | |
| 625 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, | |
| 626 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | |
| 627 | .ObjectName = &nt_name, | |
| 628 | .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null, | |
| 629 | .SecurityQualityOfService = null, | |
| 630 | }; | |
| 631 | var io: IO_STATUS_BLOCK = undefined; | |
| 632 | var result_handle: HANDLE = undefined; | |
| 633 | const rc = ntdll.NtCreateFile( | |
| 634 | &result_handle, | |
| 635 | GENERIC_READ | SYNCHRONIZE, | |
| 636 | &attr, | |
| 637 | &io, | |
| 638 | null, | |
| 639 | FILE_ATTRIBUTE_NORMAL, | |
| 640 | FILE_SHARE_READ, | |
| 641 | FILE_CREATE, | |
| 642 | FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, | |
| 643 | null, | |
| 644 | 0, | |
| 645 | ); | |
| 646 | switch (rc) { | |
| 647 | .SUCCESS => return result_handle, | |
| 648 | .OBJECT_NAME_INVALID => unreachable, | |
| 649 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | |
| 650 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | |
| 651 | .NO_MEDIA_IN_DEVICE => return error.NoDevice, | |
| 652 | .INVALID_PARAMETER => unreachable, | |
| 653 | .ACCESS_DENIED => return error.AccessDenied, | |
| 654 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | |
| 655 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | |
| 656 | else => return unexpectedStatus(rc), | |
| 606 | 657 | } |
| 607 | 658 | } |
| 608 | 659 |
src-self-hosted/compilation.zig+1-1| ... | ... | @@ -1179,7 +1179,7 @@ pub const Compilation = struct { |
| 1179 | 1179 | defer self.gpa().free(zig_dir_path); |
| 1180 | 1180 | |
| 1181 | 1181 | const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] }); |
| 1182 | try fs.makePath(self.gpa(), tmp_dir); | |
| 1182 | try fs.cwd().makePath(tmp_dir); | |
| 1183 | 1183 | return tmp_dir; |
| 1184 | 1184 | } |
| 1185 | 1185 |
src-self-hosted/test.zig+3-3| ... | ... | @@ -56,7 +56,7 @@ pub const TestContext = struct { |
| 56 | 56 | self.zig_lib_dir = try introspect.resolveZigLibDir(allocator); |
| 57 | 57 | errdefer allocator.free(self.zig_lib_dir); |
| 58 | 58 | |
| 59 | try std.fs.makePath(allocator, tmp_dir_name); | |
| 59 | try std.fs.cwd().makePath(tmp_dir_name); | |
| 60 | 60 | errdefer std.fs.deleteTree(tmp_dir_name) catch {}; |
| 61 | 61 | } |
| 62 | 62 | |
| ... | ... | @@ -85,7 +85,7 @@ pub const TestContext = struct { |
| 85 | 85 | const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 }); |
| 86 | 86 | |
| 87 | 87 | if (std.fs.path.dirname(file1_path)) |dirname| { |
| 88 | try std.fs.makePath(allocator, dirname); | |
| 88 | try std.fs.cwd().makePath(dirname); | |
| 89 | 89 | } |
| 90 | 90 | |
| 91 | 91 | // TODO async I/O |
| ... | ... | @@ -119,7 +119,7 @@ pub const TestContext = struct { |
| 119 | 119 | |
| 120 | 120 | const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", .{ file1_path, (Target{ .Native = {} }).exeFileExt() }); |
| 121 | 121 | if (std.fs.path.dirname(file1_path)) |dirname| { |
| 122 | try std.fs.makePath(allocator, dirname); | |
| 122 | try std.fs.cwd().makePath(dirname); | |
| 123 | 123 | } |
| 124 | 124 | |
| 125 | 125 | // TODO async I/O |
test/cli.zig+1-1| ... | ... | @@ -37,7 +37,7 @@ pub fn main() !void { |
| 37 | 37 | }; |
| 38 | 38 | for (test_fns) |testFn| { |
| 39 | 39 | try fs.deleteTree(dir_path); |
| 40 | try fs.makeDir(dir_path); | |
| 40 | try fs.cwd().makeDir(dir_path); | |
| 41 | 41 | try testFn(zig_exe, dir_path); |
| 42 | 42 | } |
| 43 | 43 | } |