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
signaturelock-open 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
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,33 +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);
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);
298284}
299285
300286/// Returns `error.DirNotEmpty` if the directory is not empty.
......@@ -847,10 +833,15 @@ pub const Dir = struct {
847833 try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
848834 }
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 {
851837 try os.mkdiratC(self.fd, sub_path, default_new_dir_mode);
852838 }
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
854845 /// Calls makeDir recursively to make an entire path. Returns success if the path
855846 /// already exists and is a directory.
856847 /// This function is not atomic, and if it returns an error, the file system may
......@@ -885,7 +876,14 @@ pub const Dir = struct {
885876 }
886877 }
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 {
889887 try os.fchdir(self.fd);
890888 }
891889
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/os.zig+46-24
......@@ -1539,12 +1539,13 @@ pub const MakeDirError = error{
15391539 ReadOnlyFileSystem,
15401540 InvalidUtf8,
15411541 BadPathName,
1542 NoDevice,
15421543} || UnexpectedError;
15431544
15441545pub 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) {
15461547 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);
15481549 } else {
15491550 const sub_dir_path_c = try toPosixPath(sub_dir_path);
15501551 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
15521553}
15531554
15541555pub 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) {
15561557 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);
15581559 }
15591560 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
15601561 0 => return,
......@@ -1576,23 +1577,31 @@ pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
15761577 }
15771578}
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
15791585/// Create a directory.
15801586/// `mode` is ignored on Windows.
15811587pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
15821588 if (builtin.os.tag == .windows) {
1583 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
1584 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;
15851592 } else {
15861593 const dir_path_c = try toPosixPath(dir_path);
1587 return mkdirC(&dir_path_c, mode);
1594 return mkdirZ(&dir_path_c, mode);
15881595 }
15891596}
15901597
15911598/// 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 {
15931600 if (builtin.os.tag == .windows) {
15941601 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;
15961605 }
15971606 switch (errno(system.mkdir(dir_path, mode))) {
15981607 0 => return,
......@@ -1705,7 +1714,13 @@ pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void {
17051714 }
17061715}
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 {
17091724 while (true) {
17101725 switch (errno(system.fchdir(dirfd))) {
17111726 0 => return,
......@@ -3564,12 +3579,12 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize {
35643579}
35653580
35663581/// 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.
35683583///
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,
35703585/// this is done within the operating system kernel, which can provide better performance
35713586/// 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 been
3587/// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been
35733588/// reached. Note, however, that partial writes are still possible in this case.
35743589///
35753590/// `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 {
35783593/// atomicity guarantees no longer apply.
35793594///
35803595/// 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.
35823598///
35833599/// `flags` has different meanings per operating system; refer to the respective man pages.
35843600///
......@@ -3599,7 +3615,7 @@ pub fn sendfile(
35993615 out_fd: fd_t,
36003616 in_fd: fd_t,
36013617 in_offset: u64,
3602 count: usize,
3618 in_len: u64,
36033619 headers: []const iovec_const,
36043620 trailers: []const iovec_const,
36053621 flags: u32,
......@@ -3608,9 +3624,15 @@ pub fn sendfile(
36083624 var total_written: usize = 0;
36093625
36103626 // 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 });
36113633 const max_count = switch (std.Target.current.os.tag) {
36123634 .linux => 0x7ffff000,
3613 else => math.maxInt(isize),
3635 else => math.maxInt(size_t),
36143636 };
36153637
36163638 switch (std.Target.current.os.tag) {
......@@ -3630,7 +3652,7 @@ pub fn sendfile(
36303652 }
36313653
36323654 // 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
36353657 while (true) {
36363658 var offset: off_t = @bitCast(off_t, in_offset);
......@@ -3639,10 +3661,10 @@ pub fn sendfile(
36393661 0 => {
36403662 const amt = @bitCast(usize, rc);
36413663 total_written += amt;
3642 if (count == 0 and amt == 0) {
3664 if (in_len == 0 and amt == 0) {
36433665 // We have detected EOF from `in_fd`.
36443666 break;
3645 } else if (amt < count) {
3667 } else if (amt < in_len) {
36463668 return total_written;
36473669 } else {
36483670 break;
......@@ -3708,7 +3730,7 @@ pub fn sendfile(
37083730 hdtr = &hdtr_data;
37093731 }
37103732
3711 const adjusted_count = math.min(count, max_count);
3733 const adjusted_count = math.min(in_len, max_count);
37123734
37133735 while (true) {
37143736 var sbytes: off_t = undefined;
......@@ -3786,7 +3808,7 @@ pub fn sendfile(
37863808 hdtr = &hdtr_data;
37873809 }
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
37913813 while (true) {
37923814 var sbytes: off_t = adjusted_count;
......@@ -3840,10 +3862,10 @@ pub fn sendfile(
38403862 rw: {
38413863 var buf: [8 * 4096]u8 = undefined;
38423864 // 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);
38443866 const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);
38453867 if (amt_read == 0) {
3846 if (count == 0) {
3868 if (in_len == 0) {
38473869 // We have detected EOF from `in_fd`.
38483870 break :rw;
38493871 } else {
......@@ -3852,7 +3874,7 @@ pub fn sendfile(
38523874 }
38533875 const amt_written = try write(out_fd, buf[0..amt_read]);
38543876 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;
38563878 }
38573879
38583880 if (trailers.len != 0) {
lib/std/os/test.zig+10-56
......@@ -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