authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-03-03 15:01:08-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-03-03 15:23:27-05:00
log4a67dd04c99954af2fd8e38b99704a1faea16267
treec9d66453e4e5bb0a9814db1f2ab5502bc3628207
parent1ca5f06762401d2e90c8119acb4837571696dd5e
signature Commit is signed but in an unrecognized format.

breaking changes to std.fs, std.os

* improve `std.fs.AtomicFile` to use sendfile() - also fix AtomicFile cleanup not destroying tmp files under some error conditions * improve `std.fs.updateFile` to take advantage of the new `makePath` which no longer needs an Allocator. * rename std.fs.makeDir to std.fs.makeDirAbsolute * rename std.fs.Dir.makeDirC to std.fs.Dir.makeDirZ * add std.fs.Dir.makeDirW and provide Windows implementation of std.os.mkdirat. std.os.windows.CreateDirectory is now implemented by calling ntdll, supports an optional root directory handle, and returns an open directory handle. Its error set has a few more errors in it. * rename std.fs.Dir.changeTo to std.fs.Dir.setAsCwd * fix std.fs.File.writevAll and related functions when len 0 iovecs supplied. * introduce `std.fs.File.writeFileAll`, exposing a convenient cross-platform API on top of sendfile(). * `NoDevice` added to std.os.MakeDirError error set. * std.os.fchdir gets a smaller error set. * std.os.windows.CloseHandle is implemented with ntdll call rather than kernel32.

5 files changed, 259 insertions(+), 143 deletions(-)

lib/std/fs.zig+51-53
...@@ -123,47 +123,21 @@ pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?Fil...@@ -123,47 +123,21 @@ pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?Fil
123 }123 }
124 const actual_mode = mode orelse src_stat.mode;124 const actual_mode = mode orelse src_stat.mode;
125125
126 // TODO this logic could be made more efficient by calling makePath, once126 if (path.dirname(dest_path)) |dirname| {
127 // that API does not require an allocator127 try cwd().makePath(dirname);
128 var atomic_file = make_atomic_file: while (true) {128 }
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();
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;133 try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
154 while (true) {134 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
155 const amt = try in_stream.readFull(buf[0..]);135 try atomic_file.finish();
156 try atomic_file.file.writeAll(buf[0..amt]);136 return PrevStatus.stale;
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 }
163}137}
164138
165/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is139/// Guaranteed to be atomic.
166/// merged and readily available,140/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
167/// there is a possibility of power loss or application termination leaving temporary files present141/// there is a possibility of power loss or application termination leaving temporary files present
168/// in the same directory as dest_path.142/// in the same directory as dest_path.
169/// Destination file will have the same mode as the source file.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,6 +181,9 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M
207 }181 }
208}182}
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
210pub const AtomicFile = struct {187pub const AtomicFile = struct {
211 file: File,188 file: File,
212 tmp_path_buf: [MAX_PATH_BYTES]u8,189 tmp_path_buf: [MAX_PATH_BYTES]u8,
...@@ -268,33 +245,42 @@ pub const AtomicFile = struct {...@@ -268,33 +245,42 @@ pub const AtomicFile = struct {
268245
269 pub fn finish(self: *AtomicFile) !void {246 pub fn finish(self: *AtomicFile) !void {
270 assert(!self.finished);247 assert(!self.finished);
271 self.file.close();248 if (std.Target.current.os.tag == .windows) {
272 self.finished = true;
273 if (builtin.os.tag == .windows) {
274 const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);249 const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);
275 const tmp_path_w = try os.windows.cStrToPrefixedFileW(@ptrCast([*:0]u8, &self.tmp_path_buf));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 return os.renameW(&tmp_path_w, &dest_path_w);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};
282262
283const default_new_dir_mode = 0o755;263const default_new_dir_mode = 0o755;
284264
285/// Create a new directory.265/// Create a new directory, based on an absolute path.
286pub fn makeDir(dir_path: []const u8) !void {266/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
287 return os.mkdir(dir_path, default_new_dir_mode);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);
288}271}
289272
290/// Same as `makeDir` except the parameter is a null-terminated UTF8-encoded string.273/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF8-encoded string.
291pub fn makeDirC(dir_path: [*:0]const u8) !void {274pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
292 return os.mkdirC(dir_path, default_new_dir_mode);275 assert(path.isAbsoluteC(absolute_path_z));
276 return os.mkdirZ(absolute_path_z, default_new_dir_mode);
293}277}
294278
295/// Same as `makeDir` except the parameter is a null-terminated UTF16LE-encoded string.279/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string.
296pub fn makeDirW(dir_path: [*:0]const u16) !void {280pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
297 return os.mkdirW(dir_path, default_new_dir_mode);281 assert(path.isAbsoluteWindowsW(absolute_path_w));
282 const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null);
283 os.windows.CloseHandle(handle);
298}284}
299285
300/// Returns `error.DirNotEmpty` if the directory is not empty.286/// Returns `error.DirNotEmpty` if the directory is not empty.
...@@ -847,10 +833,15 @@ pub const Dir = struct {...@@ -847,10 +833,15 @@ pub const Dir = struct {
847 try os.mkdirat(self.fd, sub_path, default_new_dir_mode);833 try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
848 }834 }
849835
850 pub fn makeDirC(self: Dir, sub_path: [*:0]const u8) !void {836 pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
851 try os.mkdiratC(self.fd, sub_path, default_new_dir_mode);837 try os.mkdiratC(self.fd, sub_path, default_new_dir_mode);
852 }838 }
853839
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
854 /// Calls makeDir recursively to make an entire path. Returns success if the path845 /// Calls makeDir recursively to make an entire path. Returns success if the path
855 /// already exists and is a directory.846 /// already exists and is a directory.
856 /// This function is not atomic, and if it returns an error, the file system may847 /// This function is not atomic, and if it returns an error, the file system may
...@@ -885,7 +876,14 @@ pub const Dir = struct {...@@ -885,7 +876,14 @@ pub const Dir = struct {
885 }876 }
886 }877 }
887878
888 pub fn changeTo(self: Dir) !void {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 {
889 try os.fchdir(self.fd);887 try os.fchdir(self.fd);
890 }888 }
891889
lib/std/fs/file.zig+91
...@@ -271,6 +271,8 @@ pub const File = struct {...@@ -271,6 +271,8 @@ pub const File = struct {
271 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in271 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
272 /// order to handle partial reads from the underlying OS layer.272 /// order to handle partial reads from the underlying OS layer.
273 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!void {273 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!void {
274 if (iovecs.len == 0) return;
275
274 var i: usize = 0;276 var i: usize = 0;
275 while (true) {277 while (true) {
276 var amt = try self.readv(iovecs[i..]);278 var amt = try self.readv(iovecs[i..]);
...@@ -295,6 +297,8 @@ pub const File = struct {...@@ -295,6 +297,8 @@ pub const File = struct {
295 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in297 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
296 /// order to handle partial reads from the underlying OS layer.298 /// order to handle partial reads from the underlying OS layer.
297 pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void {299 pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void {
300 if (iovecs.len == 0) return;
301
298 var i: usize = 0;302 var i: usize = 0;
299 var off: usize = 0;303 var off: usize = 0;
300 while (true) {304 while (true) {
...@@ -354,6 +358,8 @@ pub const File = struct {...@@ -354,6 +358,8 @@ pub const File = struct {
354 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in358 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
355 /// order to handle partial writes from the underlying OS layer.359 /// order to handle partial writes from the underlying OS layer.
356 pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {360 pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
361 if (iovecs.len == 0) return;
362
357 var i: usize = 0;363 var i: usize = 0;
358 while (true) {364 while (true) {
359 var amt = try self.writev(iovecs[i..]);365 var amt = try self.writev(iovecs[i..]);
...@@ -378,6 +384,8 @@ pub const File = struct {...@@ -378,6 +384,8 @@ pub const File = struct {
378 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in384 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
379 /// order to handle partial writes from the underlying OS layer.385 /// order to handle partial writes from the underlying OS layer.
380 pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!void {386 pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!void {
387 if (iovecs.len == 0) return;
388
381 var i: usize = 0;389 var i: usize = 0;
382 var off: usize = 0;390 var off: usize = 0;
383 while (true) {391 while (true) {
...@@ -393,6 +401,89 @@ pub const File = struct {...@@ -393,6 +401,89 @@ pub const File = struct {
393 }401 }
394 }402 }
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
396 pub fn inStream(file: File) InStream {487 pub fn inStream(file: File) InStream {
397 return InStream{488 return InStream{
398 .file = file,489 .file = file,
lib/std/os.zig+46-24
...@@ -1539,12 +1539,13 @@ pub const MakeDirError = error{...@@ -1539,12 +1539,13 @@ pub const MakeDirError = error{
1539 ReadOnlyFileSystem,1539 ReadOnlyFileSystem,
1540 InvalidUtf8,1540 InvalidUtf8,
1541 BadPathName,1541 BadPathName,
1542 NoDevice,
1542} || UnexpectedError;1543} || UnexpectedError;
15431544
1544pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {1545pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
1545 if (builtin.os == .windows) {1546 if (builtin.os.tag == .windows) {
1546 const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);1547 const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
1547 @compileError("TODO implement mkdirat for Windows");1548 return mkdiratW(dir_fd, &sub_dir_path_w, mode);
1548 } else {1549 } else {
1549 const sub_dir_path_c = try toPosixPath(sub_dir_path);1550 const sub_dir_path_c = try toPosixPath(sub_dir_path);
1550 return mkdiratC(dir_fd, &sub_dir_path_c, mode);1551 return mkdiratC(dir_fd, &sub_dir_path_c, mode);
...@@ -1552,9 +1553,9 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v...@@ -1552,9 +1553,9 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v
1552}1553}
15531554
1554pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {1555pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
1555 if (builtin.os == .windows) {1556 if (builtin.os.tag == .windows) {
1556 const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);1557 const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);
1557 @compileError("TODO implement mkdiratC for Windows");1558 return mkdiratW(dir_fd, &sub_dir_path_w, mode);
1558 }1559 }
1559 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {1560 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
1560 0 => return,1561 0 => return,
...@@ -1576,23 +1577,31 @@ pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr...@@ -1576,23 +1577,31 @@ pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
1576 }1577 }
1577}1578}
15781579
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
1579/// Create a directory.1585/// Create a directory.
1580/// `mode` is ignored on Windows.1586/// `mode` is ignored on Windows.
1581pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {1587pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
1582 if (builtin.os.tag == .windows) {1588 if (builtin.os.tag == .windows) {
1583 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);1589 const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
1584 return windows.CreateDirectoryW(&dir_path_w, null);1590 windows.CloseHandle(sub_dir_handle);
1591 return;
1585 } else {1592 } else {
1586 const dir_path_c = try toPosixPath(dir_path);1593 const dir_path_c = try toPosixPath(dir_path);
1587 return mkdirC(&dir_path_c, mode);1594 return mkdirZ(&dir_path_c, mode);
1588 }1595 }
1589}1596}
15901597
1591/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.1598/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
1592pub fn mkdirC(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {1599pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
1593 if (builtin.os.tag == .windows) {1600 if (builtin.os.tag == .windows) {
1594 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);1601 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
1595 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;
1596 }1605 }
1597 switch (errno(system.mkdir(dir_path, mode))) {1606 switch (errno(system.mkdir(dir_path, mode))) {
1598 0 => return,1607 0 => return,
...@@ -1705,7 +1714,13 @@ pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void {...@@ -1705,7 +1714,13 @@ pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void {
1705 }1714 }
1706}1715}
17071716
1708pub fn fchdir(dirfd: fd_t) ChangeCurDirError!void {1717pub const FchdirError = error{
1718 AccessDenied,
1719 NotDir,
1720 FileSystem,
1721} || UnexpectedError;
1722
1723pub fn fchdir(dirfd: fd_t) FchdirError!void {
1709 while (true) {1724 while (true) {
1710 switch (errno(system.fchdir(dirfd))) {1725 switch (errno(system.fchdir(dirfd))) {
1711 0 => return,1726 0 => return,
...@@ -3564,12 +3579,12 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize {...@@ -3564,12 +3579,12 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize {
3564}3579}
35653580
3566/// Transfer data between file descriptors, with optional headers and trailers.3581/// Transfer data between file descriptors, with optional headers and trailers.
3567/// 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.
3568///3583///
3569/// 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,
3570/// this is done within the operating system kernel, which can provide better performance3585/// this is done within the operating system kernel, which can provide better performance
3571/// characteristics than transferring data from kernel to user space and back, such as with3586/// characteristics than transferring data from kernel to user space and back, such as with
3572/// `read` and `write` calls. When `count` is `0`, it means to copy until the end of the input file has been3587/// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been
3573/// reached. Note, however, that partial writes are still possible in this case.3588/// reached. Note, however, that partial writes are still possible in this case.
3574///3589///
3575/// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor3590/// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor
...@@ -3578,7 +3593,8 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize {...@@ -3578,7 +3593,8 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize {
3578/// atomicity guarantees no longer apply.3593/// atomicity guarantees no longer apply.
3579///3594///
3580/// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated.3595/// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated.
3581/// 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.
3582///3598///
3583/// `flags` has different meanings per operating system; refer to the respective man pages.3599/// `flags` has different meanings per operating system; refer to the respective man pages.
3584///3600///
...@@ -3599,7 +3615,7 @@ pub fn sendfile(...@@ -3599,7 +3615,7 @@ pub fn sendfile(
3599 out_fd: fd_t,3615 out_fd: fd_t,
3600 in_fd: fd_t,3616 in_fd: fd_t,
3601 in_offset: u64,3617 in_offset: u64,
3602 count: usize,3618 in_len: u64,
3603 headers: []const iovec_const,3619 headers: []const iovec_const,
3604 trailers: []const iovec_const,3620 trailers: []const iovec_const,
3605 flags: u32,3621 flags: u32,
...@@ -3608,9 +3624,15 @@ pub fn sendfile(...@@ -3608,9 +3624,15 @@ pub fn sendfile(
3608 var total_written: usize = 0;3624 var total_written: usize = 0;
36093625
3610 // Prevents EOVERFLOW.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 });
3611 const max_count = switch (std.Target.current.os.tag) {3633 const max_count = switch (std.Target.current.os.tag) {
3612 .linux => 0x7ffff000,3634 .linux => 0x7ffff000,
3613 else => math.maxInt(isize),3635 else => math.maxInt(size_t),
3614 };3636 };
36153637
3616 switch (std.Target.current.os.tag) {3638 switch (std.Target.current.os.tag) {
...@@ -3630,7 +3652,7 @@ pub fn sendfile(...@@ -3630,7 +3652,7 @@ pub fn sendfile(
3630 }3652 }
36313653
3632 // Here we match BSD behavior, making a zero count value send as many bytes as possible.3654 // Here we match BSD behavior, making a zero count value send as many bytes as possible.
3633 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));
36343656
3635 while (true) {3657 while (true) {
3636 var offset: off_t = @bitCast(off_t, in_offset);3658 var offset: off_t = @bitCast(off_t, in_offset);
...@@ -3639,10 +3661,10 @@ pub fn sendfile(...@@ -3639,10 +3661,10 @@ pub fn sendfile(
3639 0 => {3661 0 => {
3640 const amt = @bitCast(usize, rc);3662 const amt = @bitCast(usize, rc);
3641 total_written += amt;3663 total_written += amt;
3642 if (count == 0 and amt == 0) {3664 if (in_len == 0 and amt == 0) {
3643 // We have detected EOF from `in_fd`.3665 // We have detected EOF from `in_fd`.
3644 break;3666 break;
3645 } else if (amt < count) {3667 } else if (amt < in_len) {
3646 return total_written;3668 return total_written;
3647 } else {3669 } else {
3648 break;3670 break;
...@@ -3708,7 +3730,7 @@ pub fn sendfile(...@@ -3708,7 +3730,7 @@ pub fn sendfile(
3708 hdtr = &hdtr_data;3730 hdtr = &hdtr_data;
3709 }3731 }
37103732
3711 const adjusted_count = math.min(count, max_count);3733 const adjusted_count = math.min(in_len, max_count);
37123734
3713 while (true) {3735 while (true) {
3714 var sbytes: off_t = undefined;3736 var sbytes: off_t = undefined;
...@@ -3786,7 +3808,7 @@ pub fn sendfile(...@@ -3786,7 +3808,7 @@ pub fn sendfile(
3786 hdtr = &hdtr_data;3808 hdtr = &hdtr_data;
3787 }3809 }
37883810
3789 const adjusted_count = math.min(count, @as(u63, max_count));3811 const adjusted_count = math.min(in_len, @as(u63, max_count));
37903812
3791 while (true) {3813 while (true) {
3792 var sbytes: off_t = adjusted_count;3814 var sbytes: off_t = adjusted_count;
...@@ -3840,10 +3862,10 @@ pub fn sendfile(...@@ -3840,10 +3862,10 @@ pub fn sendfile(
3840 rw: {3862 rw: {
3841 var buf: [8 * 4096]u8 = undefined;3863 var buf: [8 * 4096]u8 = undefined;
3842 // Here we match BSD behavior, making a zero count value send as many bytes as possible.3864 // Here we match BSD behavior, making a zero count value send as many bytes as possible.
3843 const adjusted_count = if (count == 0) buf.len else math.min(buf.len, count);3865 const adjusted_count = if (in_len == 0) buf.len else math.min(buf.len, in_len);
3844 const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);3866 const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);
3845 if (amt_read == 0) {3867 if (amt_read == 0) {
3846 if (count == 0) {3868 if (in_len == 0) {
3847 // We have detected EOF from `in_fd`.3869 // We have detected EOF from `in_fd`.
3848 break :rw;3870 break :rw;
3849 } else {3871 } else {
...@@ -3852,7 +3874,7 @@ pub fn sendfile(...@@ -3852,7 +3874,7 @@ pub fn sendfile(
3852 }3874 }
3853 const amt_written = try write(out_fd, buf[0..amt_read]);3875 const amt_written = try write(out_fd, buf[0..amt_read]);
3854 total_written += amt_written;3876 total_written += amt_written;
3855 if (amt_written < count or count == 0) return total_written;3877 if (amt_written < in_len or in_len == 0) return total_written;
3856 }3878 }
38573879
3858 if (trailers.len != 0) {3880 if (trailers.len != 0) {
lib/std/os/test.zig+10-56
...@@ -45,7 +45,7 @@ fn testThreadIdFn(thread_id: *Thread.Id) void {...@@ -45,7 +45,7 @@ fn testThreadIdFn(thread_id: *Thread.Id) void {
45}45}
4646
47test "sendfile" {47test "sendfile" {
48 try fs.makePath(a, "os_test_tmp");48 try fs.cwd().makePath("os_test_tmp");
49 defer fs.deleteTree("os_test_tmp") catch {};49 defer fs.deleteTree("os_test_tmp") catch {};
5050
51 var dir = try fs.cwd().openDirList("os_test_tmp");51 var dir = try fs.cwd().openDirList("os_test_tmp");
...@@ -74,7 +74,9 @@ test "sendfile" {...@@ -74,7 +74,9 @@ test "sendfile" {
7474
75 const header1 = "header1\n";75 const header1 = "header1\n";
76 const header2 = "second header\n";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 .iov_base = header1,81 .iov_base = header1,
80 .iov_len = header1.len,82 .iov_len = header1.len,
...@@ -83,11 +85,6 @@ test "sendfile" {...@@ -83,11 +85,6 @@ test "sendfile" {
83 .iov_base = header2,85 .iov_base = header2,
84 .iov_len = header2.len,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 .iov_base = trailer1,89 .iov_base = trailer1,
93 .iov_len = trailer1.len,90 .iov_len = trailer1.len,
...@@ -99,59 +96,16 @@ test "sendfile" {...@@ -99,59 +96,16 @@ test "sendfile" {
99 };96 };
10097
101 var written_buf: [header1.len + header2.len + 10 + trailer1.len + trailer2.len]u8 = undefined;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);99 try dest_file.writeFileAll(src_file, .{
103100 .in_offset = 1,
101 .in_len = 10,
102 .headers_and_trailers = &hdtr,
103 .header_count = 2,
104 });
104 try dest_file.preadAll(&written_buf, 0);105 try dest_file.preadAll(&written_buf, 0);
105 expect(mem.eql(u8, &written_buf, "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n"));106 expect(mem.eql(u8, &written_buf, "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n"));
106}107}
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
155test "std.Thread.getCurrentId" {109test "std.Thread.getCurrentId" {
156 if (builtin.single_threaded) return error.SkipZigTest;110 if (builtin.single_threaded) return error.SkipZigTest;
157111
lib/std/os/windows.zig+61-10
...@@ -337,7 +337,7 @@ pub fn GetQueuedCompletionStatus(...@@ -337,7 +337,7 @@ pub fn GetQueuedCompletionStatus(
337}337}
338338
339pub fn CloseHandle(hObject: HANDLE) void {339pub fn CloseHandle(hObject: HANDLE) void {
340 assert(kernel32.CloseHandle(hObject) != 0);340 assert(ntdll.NtClose(hObject) == .SUCCESS);
341}341}
342342
343pub fn FindClose(hFindFile: HANDLE) void {343pub 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,23 +586,74 @@ pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DW
586}586}
587587
588pub const CreateDirectoryError = error{588pub const CreateDirectoryError = error{
589 NameTooLong,
589 PathAlreadyExists,590 PathAlreadyExists,
590 FileNotFound,591 FileNotFound,
592 NoDevice,
593 AccessDenied,
591 Unexpected,594 Unexpected,
592};595};
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 {
595 const pathname_w = try sliceToPrefixedFileW(pathname);599 const pathname_w = try sliceToPrefixedFileW(pathname);
596 return CreateDirectoryW(&pathname_w, attrs);600 return CreateDirectoryW(dir, &pathname_w, sa);
597}601}
598602
599pub fn CreateDirectoryW(pathname: [*:0]const u16, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void {603/// Same as `CreateDirectory` except takes a WTF-16 encoded path.
600 if (kernel32.CreateDirectoryW(pathname, attrs) == 0) {604pub fn CreateDirectoryW(
601 switch (kernel32.GetLastError()) {605 dir: ?HANDLE,
602 .ALREADY_EXISTS => return error.PathAlreadyExists,606 sub_path_w: [*:0]const u16,
603 .PATH_NOT_FOUND => return error.FileNotFound,607 sa: ?*SECURITY_ATTRIBUTES,
604 else => |err| return unexpectedError(err),608) CreateDirectoryError!HANDLE {
605 }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}
608659