authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-03-03 17:05:14-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-03-03 17:05:14-05:00
logf6f0b019bee7910702e113be6d33865c592afa2a
tree65cd5209cf69c03445f85bb244d15ca00bb51a32
parent582db68a157520a0cf7777807f466d1f6a2e31e7
parent1141bfb21b82f8d3fc353e968a591f2ad9aaa571
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #4618 from ziglang/daurnimator-paths

improvements to std.fs, std.os

14 files changed, 395 insertions(+), 196 deletions(-)

doc/docgen.zig+1-1
......@@ -50,7 +50,7 @@ pub fn main() !void {
5050 var tokenizer = Tokenizer.init(in_file_name, input_file_bytes);
5151 var toc = try genToc(allocator, &tokenizer);
5252
53 try fs.makePath(allocator, tmp_dir_name);
53 try fs.cwd().makePath(tmp_dir_name);
5454 defer fs.deleteTree(tmp_dir_name) catch {};
5555
5656 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 {
763763 }
764764
765765 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| {
767767 warn("Unable to create path {}: {}\n", .{ path, @errorName(err) });
768768 return err;
769769 };
......@@ -2311,7 +2311,7 @@ pub const InstallDirStep = struct {
23112311 const rel_path = entry.path[full_src_dir.len + 1 ..];
23122312 const dest_path = try fs.path.join(self.builder.allocator, &[_][]const u8{ dest_prefix, rel_path });
23132313 switch (entry.kind) {
2314 .Directory => try fs.makePath(self.builder.allocator, dest_path),
2314 .Directory => try fs.cwd().makePath(dest_path),
23152315 .File => try self.builder.updateFile(entry.path, dest_path),
23162316 else => continue,
23172317 }
lib/std/build/write_file.zig+1-1
......@@ -74,7 +74,7 @@ pub const WriteFileStep = struct {
7474 &hash_basename,
7575 });
7676 // 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| {
7878 warn("unable to make path {}: {}\n", .{ self.output_dir, @errorName(err) });
7979 return err;
8080 };
lib/std/c.zig+3
......@@ -75,6 +75,7 @@ pub extern "c" fn isatty(fd: fd_t) c_int;
7575pub extern "c" fn close(fd: fd_t) c_int;
7676pub extern "c" fn fstat(fd: fd_t, buf: *Stat) c_int;
7777pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int;
78pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, flags: u32) c_int;
7879pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: c_int) off_t;
7980pub extern "c" fn open(path: [*:0]const u8, oflag: c_uint, ...) c_int;
8081pub 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
101102pub extern "c" fn pipe(fds: *[2]fd_t) c_int;
102103pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int;
103104pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int;
105pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int;
104106pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int;
105107pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int;
106108pub extern "c" fn chdir(path: [*:0]const u8) c_int;
109pub extern "c" fn fchdir(fd: fd_t) c_int;
107110pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;
108111pub extern "c" fn dup(fd: fd_t) c_int;
109112pub 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
123123 }
124124 const actual_mode = mode orelse src_stat.mode;
125125
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 }
150129
151 const in_stream = &src_file.inStream().stream;
130 var atomic_file = try AtomicFile.init(dest_path, actual_mode);
131 defer atomic_file.deinit();
152132
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;
163137}
164138
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,
167141/// there is a possibility of power loss or application termination leaving temporary files present
168142/// in the same directory as dest_path.
169143/// 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
207181 }
208182}
209183
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
210187pub const AtomicFile = struct {
211188 file: File,
212189 tmp_path_buf: [MAX_PATH_BYTES]u8,
......@@ -268,70 +245,42 @@ pub const AtomicFile = struct {
268245
269246 pub fn finish(self: *AtomicFile) !void {
270247 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) {
274249 const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);
275250 const tmp_path_w = try os.windows.cStrToPrefixedFileW(@ptrCast([*:0]u8, &self.tmp_path_buf));
251 self.file.close();
252 self.finished = true;
276253 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);
277259 }
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);
280260 }
281261};
282262
283263const default_new_dir_mode = 0o755;
284264
285/// Create a new directory.
286pub 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.
268pub fn makeDirAbsolute(absolute_path: []const u8) !void {
269 assert(path.isAbsoluteC(absolute_path));
270 return os.mkdir(absolute_path, default_new_dir_mode);
288271}
289272
290/// Same as `makeDir` except the parameter is a null-terminated UTF8-encoded string.
291pub 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.
274pub 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);
293277}
294278
295/// Same as `makeDir` except the parameter is a null-terminated UTF16LE-encoded string.
296pub 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
305pub 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.
280pub 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);
335284}
336285
337286/// Returns `error.DirNotEmpty` if the directory is not empty.
......@@ -709,7 +658,6 @@ pub const Dir = struct {
709658 /// Call `File.close` to release the resource.
710659 /// Asserts that the path parameter has no null bytes.
711660 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);
713661 if (builtin.os.tag == .windows) {
714662 const path_w = try os.windows.sliceToPrefixedFileW(sub_path);
715663 return self.openFileW(&path_w, flags);
......@@ -759,7 +707,6 @@ pub const Dir = struct {
759707 /// Call `File.close` on the result when done.
760708 /// Asserts that the path parameter has no null bytes.
761709 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);
763710 if (builtin.os.tag == .windows) {
764711 const path_w = try os.windows.sliceToPrefixedFileW(sub_path);
765712 return self.createFileW(&path_w, flags);
......@@ -882,6 +829,64 @@ pub const Dir = struct {
882829 }
883830 }
884831
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
885890 /// Deprecated; call `openDirList` directly.
886891 pub fn openDir(self: Dir, sub_path: []const u8) OpenError!Dir {
887892 return self.openDirList(sub_path);
......@@ -900,7 +905,6 @@ pub const Dir = struct {
900905 ///
901906 /// Asserts that the path parameter has no null bytes.
902907 pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir {
903 if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
904908 if (builtin.os.tag == .windows) {
905909 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
906910 return self.openDirTraverseW(&sub_path_w);
......@@ -918,7 +922,6 @@ pub const Dir = struct {
918922 ///
919923 /// Asserts that the path parameter has no null bytes.
920924 pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir {
921 if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
922925 if (builtin.os.tag == .windows) {
923926 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
924927 return self.openDirListW(&sub_path_w);
......@@ -1082,7 +1085,6 @@ pub const Dir = struct {
10821085 /// To delete a directory recursively, see `deleteTree`.
10831086 /// Asserts that the path parameter has no null bytes.
10841087 pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
1085 if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
10861088 if (builtin.os.tag == .windows) {
10871089 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
10881090 return self.deleteDirW(&sub_path_w);
......@@ -1112,7 +1114,6 @@ pub const Dir = struct {
11121114 /// The return value is a slice of `buffer`, from index `0`.
11131115 /// Asserts that the path parameter has no null bytes.
11141116 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);
11161117 const sub_path_c = try os.toPosixPath(sub_path);
11171118 return self.readLinkC(&sub_path_c, buffer);
11181119 }
lib/std/fs/file.zig+91
......@@ -271,6 +271,8 @@ pub const File = struct {
271271 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
272272 /// order to handle partial reads from the underlying OS layer.
273273 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!void {
274 if (iovecs.len == 0) return;
275
274276 var i: usize = 0;
275277 while (true) {
276278 var amt = try self.readv(iovecs[i..]);
......@@ -295,6 +297,8 @@ pub const File = struct {
295297 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
296298 /// order to handle partial reads from the underlying OS layer.
297299 pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void {
300 if (iovecs.len == 0) return;
301
298302 var i: usize = 0;
299303 var off: usize = 0;
300304 while (true) {
......@@ -354,6 +358,8 @@ pub const File = struct {
354358 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
355359 /// order to handle partial writes from the underlying OS layer.
356360 pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
361 if (iovecs.len == 0) return;
362
357363 var i: usize = 0;
358364 while (true) {
359365 var amt = try self.writev(iovecs[i..]);
......@@ -378,6 +384,8 @@ pub const File = struct {
378384 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
379385 /// order to handle partial writes from the underlying OS layer.
380386 pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!void {
387 if (iovecs.len == 0) return;
388
381389 var i: usize = 0;
382390 var off: usize = 0;
383391 while (true) {
......@@ -393,6 +401,89 @@ pub const File = struct {
393401 }
394402 }
395403
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
396487 pub fn inStream(file: File) InStream {
397488 return InStream{
398489 .file = file,
lib/std/fs/watch.zig+2-3
......@@ -618,11 +618,10 @@ test "write a file, watch it, write it again" {
618618 // TODO re-enable this test
619619 if (true) return error.SkipZigTest;
620620
621 const allocator = std.heap.page_allocator;
622
623 try os.makePath(allocator, test_tmp_dir);
621 try fs.cwd().makePath(test_tmp_dir);
624622 defer os.deleteTree(test_tmp_dir) catch {};
625623
624 const allocator = std.heap.page_allocator;
626625 return testFsWatch(&allocator);
627626}
628627
lib/std/os.zig+118-22
......@@ -1355,7 +1355,6 @@ pub const UnlinkatError = UnlinkError || error{
13551355/// Delete a file name and possibly the file it refers to, based on an open directory handle.
13561356/// Asserts that the path parameter has no null bytes.
13571357pub 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);
13591358 if (builtin.os.tag == .windows) {
13601359 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
13611360 return unlinkatW(dirfd, &file_path_w, flags);
......@@ -1540,25 +1539,69 @@ pub const MakeDirError = error{
15401539 ReadOnlyFileSystem,
15411540 InvalidUtf8,
15421541 BadPathName,
1542 NoDevice,
15431543} || UnexpectedError;
15441544
1545pub 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
1555pub 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
1580pub 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
15451585/// Create a directory.
15461586/// `mode` is ignored on Windows.
15471587pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
15481588 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;
15511592 } else {
15521593 const dir_path_c = try toPosixPath(dir_path);
1553 return mkdirC(&dir_path_c, mode);
1594 return mkdirZ(&dir_path_c, mode);
15541595 }
15551596}
15561597
15571598/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
1558pub fn mkdirC(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
1599pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
15591600 if (builtin.os.tag == .windows) {
15601601 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;
15621605 }
15631606 switch (errno(system.mkdir(dir_path, mode))) {
15641607 0 => return,
......@@ -1671,6 +1714,26 @@ pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void {
16711714 }
16721715}
16731716
1717pub const FchdirError = error{
1718 AccessDenied,
1719 NotDir,
1720 FileSystem,
1721} || UnexpectedError;
1722
1723pub 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
16741737pub const ReadLinkError = error{
16751738 AccessDenied,
16761739 FileSystem,
......@@ -2322,6 +2385,29 @@ pub fn fstat(fd: fd_t) FStatError!Stat {
23222385 }
23232386}
23242387
2388const FStatAtError = FStatError || error{NameTooLong};
2389
2390pub 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
2395pub 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
23252411pub const KQueueError = error{
23262412 /// The per-process limit on the number of open file descriptors has been reached.
23272413 ProcessFdQuotaExceeded,
......@@ -3169,6 +3255,7 @@ pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t {
31693255/// Used to convert a slice to a null terminated slice on the stack.
31703256/// TODO https://github.com/ziglang/zig/issues/287
31713257pub 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);
31723259 var path_with_null: [PATH_MAX - 1:0]u8 = undefined;
31733260 // >= rather than > to make room for the null byte
31743261 if (file_path.len >= PATH_MAX) return error.NameTooLong;
......@@ -3492,12 +3579,12 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize {
34923579}
34933580
34943581/// 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.
34963583///
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,
34983585/// this is done within the operating system kernel, which can provide better performance
34993586/// 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
35013588/// reached. Note, however, that partial writes are still possible in this case.
35023589///
35033590/// `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 {
35063593/// atomicity guarantees no longer apply.
35073594///
35083595/// 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.
35103598///
35113599/// `flags` has different meanings per operating system; refer to the respective man pages.
35123600///
......@@ -3527,7 +3615,7 @@ pub fn sendfile(
35273615 out_fd: fd_t,
35283616 in_fd: fd_t,
35293617 in_offset: u64,
3530 count: usize,
3618 in_len: u64,
35313619 headers: []const iovec_const,
35323620 trailers: []const iovec_const,
35333621 flags: u32,
......@@ -3536,9 +3624,15 @@ pub fn sendfile(
35363624 var total_written: usize = 0;
35373625
35383626 // 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 });
35393633 const max_count = switch (std.Target.current.os.tag) {
35403634 .linux => 0x7ffff000,
3541 else => math.maxInt(isize),
3635 else => math.maxInt(size_t),
35423636 };
35433637
35443638 switch (std.Target.current.os.tag) {
......@@ -3558,7 +3652,7 @@ pub fn sendfile(
35583652 }
35593653
35603654 // 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));
35623656
35633657 while (true) {
35643658 var offset: off_t = @bitCast(off_t, in_offset);
......@@ -3567,10 +3661,10 @@ pub fn sendfile(
35673661 0 => {
35683662 const amt = @bitCast(usize, rc);
35693663 total_written += amt;
3570 if (count == 0 and amt == 0) {
3664 if (in_len == 0 and amt == 0) {
35713665 // We have detected EOF from `in_fd`.
35723666 break;
3573 } else if (amt < count) {
3667 } else if (amt < in_len) {
35743668 return total_written;
35753669 } else {
35763670 break;
......@@ -3636,7 +3730,7 @@ pub fn sendfile(
36363730 hdtr = &hdtr_data;
36373731 }
36383732
3639 const adjusted_count = math.min(count, max_count);
3733 const adjusted_count = math.min(in_len, max_count);
36403734
36413735 while (true) {
36423736 var sbytes: off_t = undefined;
......@@ -3714,7 +3808,7 @@ pub fn sendfile(
37143808 hdtr = &hdtr_data;
37153809 }
37163810
3717 const adjusted_count = math.min(count, @as(u63, max_count));
3811 const adjusted_count = math.min(in_len, @as(u63, max_count));
37183812
37193813 while (true) {
37203814 var sbytes: off_t = adjusted_count;
......@@ -3724,12 +3818,14 @@ pub fn sendfile(
37243818 switch (err) {
37253819 0 => return amt,
37263820
3727 EBADF => unreachable, // Always a race condition.
37283821 EFAULT => unreachable, // Segmentation fault.
37293822 EINVAL => unreachable,
37303823 ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
37313824
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,
37333829
37343830 EINTR => if (amt != 0) return amt else continue,
37353831
......@@ -3768,10 +3864,10 @@ pub fn sendfile(
37683864 rw: {
37693865 var buf: [8 * 4096]u8 = undefined;
37703866 // 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);
37723868 const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);
37733869 if (amt_read == 0) {
3774 if (count == 0) {
3870 if (in_len == 0) {
37753871 // We have detected EOF from `in_fd`.
37763872 break :rw;
37773873 } else {
......@@ -3780,7 +3876,7 @@ pub fn sendfile(
37803876 }
37813877 const amt_written = try write(out_fd, buf[0..amt_read]);
37823878 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;
37843880 }
37853881
37863882 if (trailers.len != 0) {
lib/std/os/linux.zig+4
......@@ -76,6 +76,10 @@ pub fn chdir(path: [*:0]const u8) usize {
7676 return syscall1(SYS_chdir, @ptrToInt(path));
7777}
7878
79pub fn fchdir(fd: fd_t) usize {
80 return syscall1(SYS_fchdir, @bitCast(usize, @as(isize, fd)));
81}
82
7983pub fn chroot(path: [*:0]const u8) usize {
8084 return syscall1(SYS_chroot, @ptrToInt(path));
8185}
lib/std/os/test.zig+12-58
......@@ -16,7 +16,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
1616const AtomicOrder = builtin.AtomicOrder;
1717
1818test "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");
2020 try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
2121 try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
2222 try fs.deleteTree("os_test_tmp");
......@@ -28,7 +28,7 @@ test "makePath, put some files in it, deleteTree" {
2828}
2929
3030test "access file" {
31 try fs.makePath(a, "os_test_tmp");
31 try fs.cwd().makePath("os_test_tmp");
3232 if (fs.cwd().access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| {
3333 @panic("expected error");
3434 } else |err| {
......@@ -45,7 +45,7 @@ fn testThreadIdFn(thread_id: *Thread.Id) void {
4545}
4646
4747test "sendfile" {
48 try fs.makePath(a, "os_test_tmp");
48 try fs.cwd().makePath("os_test_tmp");
4949 defer fs.deleteTree("os_test_tmp") catch {};
5050
5151 var dir = try fs.cwd().openDirList("os_test_tmp");
......@@ -74,7 +74,9 @@ test "sendfile" {
7474
7575 const header1 = "header1\n";
7676 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{
7880 .{
7981 .iov_base = header1,
8082 .iov_len = header1.len,
......@@ -83,11 +85,6 @@ test "sendfile" {
8385 .iov_base = header2,
8486 .iov_len = header2.len,
8587 },
86 };
87
88 const trailer1 = "trailer1\n";
89 const trailer2 = "second trailer\n";
90 var trailers = [_]os.iovec_const{
9188 .{
9289 .iov_base = trailer1,
9390 .iov_len = trailer1.len,
......@@ -99,59 +96,16 @@ test "sendfile" {
9996 };
10097
10198 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 });
104105 try dest_file.preadAll(&written_buf, 0);
105106 expect(mem.eql(u8, &written_buf, "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n"));
106107}
107108
108fn 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
155109test "std.Thread.getCurrentId" {
156110 if (builtin.single_threaded) return error.SkipZigTest;
157111
lib/std/os/windows.zig+61-10
......@@ -337,7 +337,7 @@ pub fn GetQueuedCompletionStatus(
337337}
338338
339339pub fn CloseHandle(hObject: HANDLE) void {
340 assert(kernel32.CloseHandle(hObject) != 0);
340 assert(ntdll.NtClose(hObject) == .SUCCESS);
341341}
342342
343343pub fn FindClose(hFindFile: HANDLE) void {
......@@ -586,23 +586,74 @@ pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DW
586586}
587587
588588pub const CreateDirectoryError = error{
589 NameTooLong,
589590 PathAlreadyExists,
590591 FileNotFound,
592 NoDevice,
593 AccessDenied,
591594 Unexpected,
592595};
593596
594pub 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`.
598pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE {
595599 const pathname_w = try sliceToPrefixedFileW(pathname);
596 return CreateDirectoryW(&pathname_w, attrs);
600 return CreateDirectoryW(dir, &pathname_w, sa);
597601}
598602
599pub 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.
604pub 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),
606657 }
607658}
608659
src-self-hosted/compilation.zig+1-1
......@@ -1179,7 +1179,7 @@ pub const Compilation = struct {
11791179 defer self.gpa().free(zig_dir_path);
11801180
11811181 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);
11831183 return tmp_dir;
11841184 }
11851185
src-self-hosted/test.zig+3-3
......@@ -56,7 +56,7 @@ pub const TestContext = struct {
5656 self.zig_lib_dir = try introspect.resolveZigLibDir(allocator);
5757 errdefer allocator.free(self.zig_lib_dir);
5858
59 try std.fs.makePath(allocator, tmp_dir_name);
59 try std.fs.cwd().makePath(tmp_dir_name);
6060 errdefer std.fs.deleteTree(tmp_dir_name) catch {};
6161 }
6262
......@@ -85,7 +85,7 @@ pub const TestContext = struct {
8585 const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 });
8686
8787 if (std.fs.path.dirname(file1_path)) |dirname| {
88 try std.fs.makePath(allocator, dirname);
88 try std.fs.cwd().makePath(dirname);
8989 }
9090
9191 // TODO async I/O
......@@ -119,7 +119,7 @@ pub const TestContext = struct {
119119
120120 const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", .{ file1_path, (Target{ .Native = {} }).exeFileExt() });
121121 if (std.fs.path.dirname(file1_path)) |dirname| {
122 try std.fs.makePath(allocator, dirname);
122 try std.fs.cwd().makePath(dirname);
123123 }
124124
125125 // TODO async I/O
test/cli.zig+1-1
......@@ -37,7 +37,7 @@ pub fn main() !void {
3737 };
3838 for (test_fns) |testFn| {
3939 try fs.deleteTree(dir_path);
40 try fs.makeDir(dir_path);
40 try fs.cwd().makeDir(dir_path);
4141 try testFn(zig_exe, dir_path);
4242 }
4343}