authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-03-13 21:06:07-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-03-13 21:22:08-04:00
log66e76a0209586000a78fe896071e73202a80b81f
tree15b80516b0aaddd2e114205e41ee0cda49c32030
parenteb4d313dbc406b37f6bfdd98988c88c3b8ed542e
signature Commit is signed but in an unrecognized format.

zig build system: correctly handle multiple output artifacts

Previously the zig build system incorrectly assumed that the only build artifact was a binary. Now, when you enable the cache, only the output dir is printed to stdout, and the zig build system iterates over the files in that directory, copying them to the output directory. To support this change: * Add `std.os.renameat`, `std.os.renameatZ`, and `std.os.renameatW`. * Fix `std.os.linux.renameat` not compiling due to typos. * Deprecate `std.fs.updateFile` and `std.fs.updateFileMode`. * Add `std.fs.Dir.updateFile`, which supports using open directory handles for both the source and destination paths, as well as an options parameter which allows overriding the mode. * Update `std.fs.AtomicFile` to support operating based on an open directory handle. Instead of `std.fs.AtomicFile.init`, use `std.fs.Dir.atomicFile`. * `std.fs.AtomicFile` deinit() better handles the situation when the rename fails but the temporary file still exists, by still attempting to remove the temporary file. * `std.fs.Dir.openFileWindows` is moved to `std.os.windows.OpenFileW`. * `std.os.RenameError` gains the error codes `NoDevice`, `SharingViolation`, and `PipeBusy` which have been observed from Windows. Closes #4733

8 files changed, 333 insertions(+), 158 deletions(-)

lib/std/build.zig+13-8
......@@ -2144,17 +2144,22 @@ pub const LibExeObjStep = struct {
21442144 try zig_args.append("--cache");
21452145 try zig_args.append("on");
21462146
2147 const output_path_nl = try builder.execFromStep(zig_args.toSliceConst(), &self.step);
2148 const output_path = mem.trimRight(u8, output_path_nl, "\r\n");
2147 const output_dir_nl = try builder.execFromStep(zig_args.toSliceConst(), &self.step);
2148 const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n");
21492149
21502150 if (self.output_dir) |output_dir| {
2151 const full_dest = try fs.path.join(builder.allocator, &[_][]const u8{
2152 output_dir,
2153 fs.path.basename(output_path),
2154 });
2155 try builder.updateFile(output_path, full_dest);
2151 var src_dir = try std.fs.cwd().openDirTraverse(build_output_dir);
2152 defer src_dir.close();
2153
2154 var dest_dir = try std.fs.cwd().openDirList(output_dir);
2155 defer dest_dir.close();
2156
2157 var it = src_dir.iterate();
2158 while (try it.next()) |entry| {
2159 _ = try src_dir.updateFile(entry.name, dest_dir, entry.name, .{});
2160 }
21562161 } else {
2157 self.output_dir = fs.path.dirname(output_path).?;
2162 self.output_dir = build_output_dir;
21582163 }
21592164 }
21602165
lib/std/c.zig+1
......@@ -106,6 +106,7 @@ pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int;
106106pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int;
107107pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int;
108108pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int;
109pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, new: [*:0]const u8) c_int;
109110pub extern "c" fn chdir(path: [*:0]const u8) c_int;
110111pub extern "c" fn fchdir(fd: fd_t) c_int;
111112pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;
lib/std/fs.zig+119-142
......@@ -81,60 +81,21 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
8181 }
8282}
8383
84// TODO fix enum literal not casting to error union
85const PrevStatus = enum {
84pub const PrevStatus = enum {
8685 stale,
8786 fresh,
8887};
8988
89/// Deprecated; use `Dir.updateFile`.
9090pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus {
91 return updateFileMode(source_path, dest_path, null);
91 const my_cwd = cwd();
92 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, .{});
9293}
9394
94/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
95/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
96/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
97/// Returns the previous status of the file before updating.
98/// If any of the directories do not exist for dest_path, they are created.
99/// TODO rework this to integrate with Dir
100pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !PrevStatus {
95/// Deprecated; use `Dir.updateFile`.
96pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !Dir.PrevStatus {
10197 const my_cwd = cwd();
102
103 var src_file = try my_cwd.openFile(source_path, .{});
104 defer src_file.close();
105
106 const src_stat = try src_file.stat();
107 check_dest_stat: {
108 const dest_stat = blk: {
109 var dest_file = my_cwd.openFile(dest_path, .{}) catch |err| switch (err) {
110 error.FileNotFound => break :check_dest_stat,
111 else => |e| return e,
112 };
113 defer dest_file.close();
114
115 break :blk try dest_file.stat();
116 };
117
118 if (src_stat.size == dest_stat.size and
119 src_stat.mtime == dest_stat.mtime and
120 src_stat.mode == dest_stat.mode)
121 {
122 return PrevStatus.fresh;
123 }
124 }
125 const actual_mode = mode orelse src_stat.mode;
126
127 if (path.dirname(dest_path)) |dirname| {
128 try cwd().makePath(dirname);
129 }
130
131 var atomic_file = try AtomicFile.init(dest_path, actual_mode);
132 defer atomic_file.deinit();
133
134 try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
135 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
136 try atomic_file.finish();
137 return PrevStatus.stale;
98 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, .{ .override_mode = mode });
13899}
139100
140101/// Guaranteed to be atomic.
......@@ -172,43 +133,40 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M
172133 return atomic_file.finish();
173134}
174135
175/// TODO update this API to avoid a getrandom syscall for every operation. It
176/// should accept a random interface.
177/// TODO rework this to integrate with Dir
136/// TODO update this API to avoid a getrandom syscall for every operation.
178137pub const AtomicFile = struct {
179138 file: File,
180 tmp_path_buf: [MAX_PATH_BYTES]u8,
139 tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8,
181140 dest_path: []const u8,
182 finished: bool,
141 file_open: bool,
142 file_exists: bool,
143 dir: Dir,
183144
184145 const InitError = File.OpenError;
185146
186 /// dest_path must remain valid for the lifetime of AtomicFile
187 /// call finish to atomically replace dest_path with contents
188 pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
147 /// TODO rename this. Callers should go through Dir API
148 pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir) InitError!AtomicFile {
189149 const dirname = path.dirname(dest_path);
190150 var rand_buf: [12]u8 = undefined;
191151 const dirname_component_len = if (dirname) |d| d.len + 1 else 0;
192152 const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);
193153 const tmp_path_len = dirname_component_len + encoded_rand_len;
194 var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined;
195 if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong;
154 var tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8 = undefined;
155 if (tmp_path_len > tmp_path_buf.len) return error.NameTooLong;
196156
197 if (dirname) |dir| {
198 mem.copy(u8, tmp_path_buf[0..], dir);
199 tmp_path_buf[dir.len] = path.sep;
157 if (dirname) |dn| {
158 mem.copy(u8, tmp_path_buf[0..], dn);
159 tmp_path_buf[dn.len] = path.sep;
200160 }
201161
202162 tmp_path_buf[tmp_path_len] = 0;
203163 const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0];
204164
205 const my_cwd = cwd();
206
207165 while (true) {
208166 try crypto.randomBytes(rand_buf[0..]);
209167 base64_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf);
210168
211 const file = my_cwd.createFileC(
169 const file = dir.createFileC(
212170 tmp_path_slice,
213171 .{ .mode = mode, .exclusive = true },
214172 ) catch |err| switch (err) {
......@@ -220,33 +178,46 @@ pub const AtomicFile = struct {
220178 .file = file,
221179 .tmp_path_buf = tmp_path_buf,
222180 .dest_path = dest_path,
223 .finished = false,
181 .file_open = true,
182 .file_exists = true,
183 .dir = dir,
224184 };
225185 }
226186 }
227187
188 /// Deprecated. Use `Dir.atomicFile`.
189 pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
190 return init2(dest_path, mode, cwd());
191 }
192
228193 /// always call deinit, even after successful finish()
229194 pub fn deinit(self: *AtomicFile) void {
230 if (!self.finished) {
195 if (self.file_open) {
231196 self.file.close();
232 cwd().deleteFileC(@ptrCast([*:0]u8, &self.tmp_path_buf)) catch {};
233 self.finished = true;
197 self.file_open = false;
198 }
199 if (self.file_exists) {
200 self.dir.deleteFileC(&self.tmp_path_buf) catch {};
201 self.file_exists = false;
234202 }
203 self.* = undefined;
235204 }
236205
237206 pub fn finish(self: *AtomicFile) !void {
238 assert(!self.finished);
207 assert(self.file_exists);
208 if (self.file_open) {
209 self.file.close();
210 self.file_open = false;
211 }
239212 if (std.Target.current.os.tag == .windows) {
240213 const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);
241 const tmp_path_w = try os.windows.cStrToPrefixedFileW(@ptrCast([*:0]u8, &self.tmp_path_buf));
242 self.file.close();
243 self.finished = true;
244 return os.renameW(&tmp_path_w, &dest_path_w);
214 const tmp_path_w = try os.windows.cStrToPrefixedFileW(&self.tmp_path_buf);
215 try os.renameatW(self.dir.fd, &tmp_path_w, self.dir.fd, &dest_path_w, os.windows.TRUE);
216 self.file_exists = false;
245217 } else {
246218 const dest_path_c = try os.toPosixPath(self.dest_path);
247 self.file.close();
248 self.finished = true;
249 return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c);
219 try os.renameatZ(self.dir.fd, &self.tmp_path_buf, self.dir.fd, &dest_path_c);
220 self.file_exists = false;
250221 }
251222 }
252223};
......@@ -694,7 +665,10 @@ pub const Dir = struct {
694665 const access_mask = w.SYNCHRONIZE |
695666 (if (flags.read) @as(u32, w.GENERIC_READ) else 0) |
696667 (if (flags.write) @as(u32, w.GENERIC_WRITE) else 0);
697 return self.openFileWindows(sub_path_w, access_mask, w.FILE_OPEN);
668 return @as(File, .{
669 .handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, w.FILE_OPEN),
670 .io_mode = .blocking,
671 });
698672 }
699673
700674 /// Creates, opens, or overwrites a file with write access.
......@@ -739,7 +713,10 @@ pub const Dir = struct {
739713 @as(u32, w.FILE_OVERWRITE_IF)
740714 else
741715 @as(u32, w.FILE_OPEN_IF);
742 return self.openFileWindows(sub_path_w, access_mask, creation);
716 return @as(File, .{
717 .handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, creation),
718 .io_mode = .blocking,
719 });
743720 }
744721
745722 /// Deprecated; call `openFile` directly.
......@@ -757,72 +734,6 @@ pub const Dir = struct {
757734 return self.openFileW(sub_path, .{});
758735 }
759736
760 pub fn openFileWindows(
761 self: Dir,
762 sub_path_w: [*:0]const u16,
763 access_mask: os.windows.ACCESS_MASK,
764 creation: os.windows.ULONG,
765 ) File.OpenError!File {
766 const w = os.windows;
767
768 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
769 return error.IsDir;
770 }
771 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
772 return error.IsDir;
773 }
774
775 var result = File{
776 .handle = undefined,
777 .io_mode = .blocking,
778 };
779
780 const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
781 error.Overflow => return error.NameTooLong,
782 };
783 var nt_name = w.UNICODE_STRING{
784 .Length = path_len_bytes,
785 .MaximumLength = path_len_bytes,
786 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
787 };
788 var attr = w.OBJECT_ATTRIBUTES{
789 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
790 .RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
791 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
792 .ObjectName = &nt_name,
793 .SecurityDescriptor = null,
794 .SecurityQualityOfService = null,
795 };
796 var io: w.IO_STATUS_BLOCK = undefined;
797 const rc = w.ntdll.NtCreateFile(
798 &result.handle,
799 access_mask,
800 &attr,
801 &io,
802 null,
803 w.FILE_ATTRIBUTE_NORMAL,
804 w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
805 creation,
806 w.FILE_NON_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT,
807 null,
808 0,
809 );
810 switch (rc) {
811 .SUCCESS => return result,
812 .OBJECT_NAME_INVALID => unreachable,
813 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
814 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
815 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
816 .INVALID_PARAMETER => unreachable,
817 .SHARING_VIOLATION => return error.SharingViolation,
818 .ACCESS_DENIED => return error.AccessDenied,
819 .PIPE_BUSY => return error.PipeBusy,
820 .OBJECT_PATH_SYNTAX_BAD => unreachable,
821 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
822 else => return w.unexpectedStatus(rc),
823 }
824 }
825
826737 pub fn makeDir(self: Dir, sub_path: []const u8) !void {
827738 try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
828739 }
......@@ -898,6 +809,7 @@ pub const Dir = struct {
898809 /// Call `close` on the result when done.
899810 ///
900811 /// Asserts that the path parameter has no null bytes.
812 /// TODO collapse this and `openDirList` into one function with an options parameter
901813 pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir {
902814 if (builtin.os.tag == .windows) {
903815 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
......@@ -915,6 +827,7 @@ pub const Dir = struct {
915827 /// Call `close` on the result when done.
916828 ///
917829 /// Asserts that the path parameter has no null bytes.
830 /// TODO collapse this and `openDirTraverse` into one function with an options parameter
918831 pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir {
919832 if (builtin.os.tag == .windows) {
920833 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
......@@ -1370,6 +1283,70 @@ pub const Dir = struct {
13701283 pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
13711284 return os.faccessatW(self.fd, sub_path_w, 0, 0);
13721285 }
1286
1287 pub const UpdateFileOptions = struct {
1288 override_mode: ?File.Mode = null,
1289 };
1290
1291 /// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
1292 /// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
1293 /// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
1294 /// Returns the previous status of the file before updating.
1295 /// If any of the directories do not exist for dest_path, they are created.
1296 /// If `override_mode` is provided, then that value is used rather than the source path's mode.
1297 pub fn updateFile(
1298 source_dir: Dir,
1299 source_path: []const u8,
1300 dest_dir: Dir,
1301 dest_path: []const u8,
1302 options: UpdateFileOptions,
1303 ) !PrevStatus {
1304 var src_file = try source_dir.openFile(source_path, .{});
1305 defer src_file.close();
1306
1307 const src_stat = try src_file.stat();
1308 const actual_mode = options.override_mode orelse src_stat.mode;
1309 check_dest_stat: {
1310 const dest_stat = blk: {
1311 var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
1312 error.FileNotFound => break :check_dest_stat,
1313 else => |e| return e,
1314 };
1315 defer dest_file.close();
1316
1317 break :blk try dest_file.stat();
1318 };
1319
1320 if (src_stat.size == dest_stat.size and
1321 src_stat.mtime == dest_stat.mtime and
1322 actual_mode == dest_stat.mode)
1323 {
1324 return PrevStatus.fresh;
1325 }
1326 }
1327
1328 if (path.dirname(dest_path)) |dirname| {
1329 try dest_dir.makePath(dirname);
1330 }
1331
1332 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = actual_mode });
1333 defer atomic_file.deinit();
1334
1335 try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
1336 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
1337 try atomic_file.finish();
1338 return PrevStatus.stale;
1339 }
1340
1341 pub const AtomicFileOptions = struct {
1342 mode: File.Mode = File.default_mode,
1343 };
1344
1345 /// `dest_path` must remain valid for the lifetime of `AtomicFile`.
1346 /// Call `AtomicFile.finish` to atomically replace `dest_path` with contents.
1347 pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
1348 return AtomicFile.init2(dest_path, options.mode, self);
1349 }
13731350};
13741351
13751352/// Returns an handle to the current working directory that is open for traversal.
lib/std/os.zig+111-3
......@@ -461,13 +461,11 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
461461 );
462462
463463 switch (rc) {
464 .SUCCESS => {},
464 .SUCCESS => return,
465465 .INVALID_HANDLE => unreachable, // Handle not open for writing
466466 .ACCESS_DENIED => return error.CannotTruncate,
467467 else => return windows.unexpectedStatus(rc),
468468 }
469
470 return;
471469 }
472470
473471 while (true) {
......@@ -852,6 +850,7 @@ pub const OpenError = error{
852850
853851/// Open and possibly create a file. Keeps trying if it gets interrupted.
854852/// See also `openC`.
853/// TODO support windows
855854pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
856855 const file_path_c = try toPosixPath(file_path);
857856 return openC(&file_path_c, flags, perm);
......@@ -859,6 +858,7 @@ pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
859858
860859/// Open and possibly create a file. Keeps trying if it gets interrupted.
861860/// See also `open`.
861/// TODO support windows
862862pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
863863 while (true) {
864864 const rc = system.open(file_path, flags, perm);
......@@ -892,6 +892,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
892892/// Open and possibly create a file. Keeps trying if it gets interrupted.
893893/// `file_path` is relative to the open directory handle `dir_fd`.
894894/// See also `openatC`.
895/// TODO support windows
895896pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
896897 const file_path_c = try toPosixPath(file_path);
897898 return openatC(dir_fd, &file_path_c, flags, mode);
......@@ -900,6 +901,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope
900901/// Open and possibly create a file. Keeps trying if it gets interrupted.
901902/// `file_path` is relative to the open directory handle `dir_fd`.
902903/// See also `openat`.
904/// TODO support windows
903905pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
904906 while (true) {
905907 const rc = system.openat(dir_fd, file_path, flags, mode);
......@@ -1527,6 +1529,9 @@ const RenameError = error{
15271529 RenameAcrossMountPoints,
15281530 InvalidUtf8,
15291531 BadPathName,
1532 NoDevice,
1533 SharingViolation,
1534 PipeBusy,
15301535} || UnexpectedError;
15311536
15321537/// Change the name or location of a file.
......@@ -1580,6 +1585,108 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v
15801585 return windows.MoveFileExW(old_path, new_path, flags);
15811586}
15821587
1588/// Change the name or location of a file based on an open directory handle.
1589pub fn renameat(
1590 old_dir_fd: fd_t,
1591 old_path: []const u8,
1592 new_dir_fd: fd_t,
1593 new_path: []const u8,
1594) RenameError!void {
1595 if (builtin.os.tag == .windows) {
1596 const old_path_w = try windows.sliceToPrefixedFileW(old_path);
1597 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
1598 return renameatW(old_dir_fd, &old_path_w, new_dir_fd, &new_path_w, windows.TRUE);
1599 } else {
1600 const old_path_c = try toPosixPath(old_path);
1601 const new_path_c = try toPosixPath(new_path);
1602 return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c);
1603 }
1604}
1605
1606/// Same as `renameat` except the parameters are null-terminated byte arrays.
1607pub fn renameatZ(
1608 old_dir_fd: fd_t,
1609 old_path: [*:0]const u8,
1610 new_dir_fd: fd_t,
1611 new_path: [*:0]const u8,
1612) RenameError!void {
1613 if (builtin.os.tag == .windows) {
1614 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
1615 const new_path_w = try windows.cStrToPrefixedFileW(new_path);
1616 return renameatW(old_dir_fd, &old_path_w, new_dir_fd, &new_path_w, windows.TRUE);
1617 }
1618
1619 switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
1620 0 => return,
1621 EACCES => return error.AccessDenied,
1622 EPERM => return error.AccessDenied,
1623 EBUSY => return error.FileBusy,
1624 EDQUOT => return error.DiskQuota,
1625 EFAULT => unreachable,
1626 EINVAL => unreachable,
1627 EISDIR => return error.IsDir,
1628 ELOOP => return error.SymLinkLoop,
1629 EMLINK => return error.LinkQuotaExceeded,
1630 ENAMETOOLONG => return error.NameTooLong,
1631 ENOENT => return error.FileNotFound,
1632 ENOTDIR => return error.NotDir,
1633 ENOMEM => return error.SystemResources,
1634 ENOSPC => return error.NoSpaceLeft,
1635 EEXIST => return error.PathAlreadyExists,
1636 ENOTEMPTY => return error.PathAlreadyExists,
1637 EROFS => return error.ReadOnlyFileSystem,
1638 EXDEV => return error.RenameAcrossMountPoints,
1639 else => |err| return unexpectedErrno(err),
1640 }
1641}
1642
1643/// Same as `renameat` except the parameters are null-terminated UTF16LE encoded byte arrays.
1644/// Assumes target is Windows.
1645/// TODO these args can actually be slices when using ntdll. audit the rest of the W functions too.
1646pub fn renameatW(
1647 old_dir_fd: fd_t,
1648 old_path: [*:0]const u16,
1649 new_dir_fd: fd_t,
1650 new_path_w: [*:0]const u16,
1651 ReplaceIfExists: windows.BOOLEAN,
1652) RenameError!void {
1653 const access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE;
1654 const src_fd = try windows.OpenFileW(old_dir_fd, old_path, null, access_mask, windows.FILE_OPEN);
1655 defer windows.CloseHandle(src_fd);
1656
1657 const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (MAX_PATH_BYTES - 1);
1658 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined;
1659 const new_path = mem.span(new_path_w);
1660 const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path.len * 2;
1661 if (struct_len > struct_buf_len) return error.NameTooLong;
1662
1663 const rename_info = @ptrCast(*windows.FILE_RENAME_INFORMATION, &rename_info_buf);
1664
1665 rename_info.* = .{
1666 .ReplaceIfExists = ReplaceIfExists,
1667 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(new_path_w)) null else new_dir_fd,
1668 .FileNameLength = @intCast(u32, new_path.len * 2), // already checked error.NameTooLong
1669 .FileName = undefined,
1670 };
1671 std.mem.copy(u16, @as([*]u16, &rename_info.FileName)[0..new_path.len], new_path);
1672
1673 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1674
1675 const rc = windows.ntdll.NtSetInformationFile(
1676 src_fd,
1677 &io_status_block,
1678 rename_info,
1679 @intCast(u32, struct_len), // already checked for error.NameTooLong
1680 .FileRenameInformation,
1681 );
1682
1683 switch (rc) {
1684 .SUCCESS => return,
1685 .INVALID_HANDLE => unreachable,
1686 else => return windows.unexpectedStatus(rc),
1687 }
1688}
1689
15831690pub const MakeDirError = error{
15841691 AccessDenied,
15851692 DiskQuota,
......@@ -3125,6 +3232,7 @@ pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
31253232}
31263233
31273234/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
3235/// TODO use ntdll for better semantics
31283236pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
31293237 const h_file = try windows.CreateFileW(
31303238 pathname,
lib/std/os/linux.zig+4-4
......@@ -465,17 +465,17 @@ pub fn renameat(oldfd: i32, oldpath: [*]const u8, newfd: i32, newpath: [*]const
465465 return syscall4(
466466 SYS_renameat,
467467 @bitCast(usize, @as(isize, oldfd)),
468 @ptrToInt(old),
468 @ptrToInt(oldpath),
469469 @bitCast(usize, @as(isize, newfd)),
470 @ptrToInt(new),
470 @ptrToInt(newpath),
471471 );
472472 } else {
473473 return syscall5(
474474 SYS_renameat2,
475475 @bitCast(usize, @as(isize, oldfd)),
476 @ptrToInt(old),
476 @ptrToInt(oldpath),
477477 @bitCast(usize, @as(isize, newfd)),
478 @ptrToInt(new),
478 @ptrToInt(newpath),
479479 0,
480480 );
481481 }
lib/std/os/windows.zig+76
......@@ -88,6 +88,82 @@ pub fn CreateFileW(
8888 return result;
8989}
9090
91pub const OpenError = error{
92 IsDir,
93 FileNotFound,
94 NoDevice,
95 SharingViolation,
96 AccessDenied,
97 PipeBusy,
98 PathAlreadyExists,
99 Unexpected,
100 NameTooLong,
101};
102
103/// TODO rename to CreateFileW
104/// TODO actually we don't need the path parameter to be null terminated
105pub fn OpenFileW(
106 dir: ?HANDLE,
107 sub_path_w: [*:0]const u16,
108 sa: ?*SECURITY_ATTRIBUTES,
109 access_mask: ACCESS_MASK,
110 creation: ULONG,
111) OpenError!HANDLE {
112 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
113 return error.IsDir;
114 }
115 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
116 return error.IsDir;
117 }
118
119 var result: HANDLE = undefined;
120
121 const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
122 error.Overflow => return error.NameTooLong,
123 };
124 var nt_name = UNICODE_STRING{
125 .Length = path_len_bytes,
126 .MaximumLength = path_len_bytes,
127 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
128 };
129 var attr = OBJECT_ATTRIBUTES{
130 .Length = @sizeOf(OBJECT_ATTRIBUTES),
131 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
132 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
133 .ObjectName = &nt_name,
134 .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null,
135 .SecurityQualityOfService = null,
136 };
137 var io: IO_STATUS_BLOCK = undefined;
138 const rc = ntdll.NtCreateFile(
139 &result,
140 access_mask,
141 &attr,
142 &io,
143 null,
144 FILE_ATTRIBUTE_NORMAL,
145 FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
146 creation,
147 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
148 null,
149 0,
150 );
151 switch (rc) {
152 .SUCCESS => return result,
153 .OBJECT_NAME_INVALID => unreachable,
154 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
155 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
156 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
157 .INVALID_PARAMETER => unreachable,
158 .SHARING_VIOLATION => return error.SharingViolation,
159 .ACCESS_DENIED => return error.AccessDenied,
160 .PIPE_BUSY => return error.PipeBusy,
161 .OBJECT_PATH_SYNTAX_BAD => unreachable,
162 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
163 else => return unexpectedStatus(rc),
164 }
165}
166
91167pub const CreatePipeError = error{Unexpected};
92168
93169pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
lib/std/os/windows/bits.zig+7
......@@ -242,6 +242,13 @@ pub const FILE_NAME_INFORMATION = extern struct {
242242 FileName: [1]WCHAR,
243243};
244244
245pub const FILE_RENAME_INFORMATION = extern struct {
246 ReplaceIfExists: BOOLEAN,
247 RootDirectory: ?HANDLE,
248 FileNameLength: ULONG,
249 FileName: [1]WCHAR,
250};
251
245252pub const IO_STATUS_BLOCK = extern struct {
246253 // "DUMMYUNIONNAME" expands to "u"
247254 u: extern union {
src/main.cpp+2-1
......@@ -1290,6 +1290,7 @@ static int main0(int argc, char **argv) {
12901290 if (g->enable_cache) {
12911291#if defined(ZIG_OS_WINDOWS)
12921292 buf_replace(&g->bin_file_output_path, '/', '\\');
1293 buf_replace(g->output_dir, '/', '\\');
12931294#endif
12941295 if (final_output_dir_step != nullptr) {
12951296 Buf *dest_basename = buf_alloc();
......@@ -1303,7 +1304,7 @@ static int main0(int argc, char **argv) {
13031304 return main_exit(root_progress_node, EXIT_FAILURE);
13041305 }
13051306 } else {
1306 if (g->emit_bin && printf("%s\n", buf_ptr(&g->bin_file_output_path)) < 0)
1307 if (printf("%s\n", buf_ptr(g->output_dir)) < 0)
13071308 return main_exit(root_progress_node, EXIT_FAILURE);
13081309 }
13091310 }