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
signaturelock-open 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 {...@@ -2144,17 +2144,22 @@ pub const LibExeObjStep = struct {
2144 try zig_args.append("--cache");2144 try zig_args.append("--cache");
2145 try zig_args.append("on");2145 try zig_args.append("on");
21462146
2147 const output_path_nl = try builder.execFromStep(zig_args.toSliceConst(), &self.step);2147 const output_dir_nl = try builder.execFromStep(zig_args.toSliceConst(), &self.step);
2148 const output_path = mem.trimRight(u8, output_path_nl, "\r\n");2148 const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n");
21492149
2150 if (self.output_dir) |output_dir| {2150 if (self.output_dir) |output_dir| {
2151 const full_dest = try fs.path.join(builder.allocator, &[_][]const u8{2151 var src_dir = try std.fs.cwd().openDirTraverse(build_output_dir);
2152 output_dir,2152 defer src_dir.close();
2153 fs.path.basename(output_path),2153
2154 });2154 var dest_dir = try std.fs.cwd().openDirList(output_dir);
2155 try builder.updateFile(output_path, full_dest);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 }
2156 } else {2161 } else {
2157 self.output_dir = fs.path.dirname(output_path).?;2162 self.output_dir = build_output_dir;
2158 }2163 }
2159 }2164 }
21602165
lib/std/c.zig+1
...@@ -106,6 +106,7 @@ pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int;...@@ -106,6 +106,7 @@ pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int;
106pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int;106pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int;
107pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int;107pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int;
108pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int;108pub 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;
109pub extern "c" fn chdir(path: [*:0]const u8) c_int;110pub extern "c" fn chdir(path: [*:0]const u8) c_int;
110pub extern "c" fn fchdir(fd: fd_t) c_int;111pub extern "c" fn fchdir(fd: fd_t) c_int;
111pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;112pub 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:...@@ -81,60 +81,21 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
81 }81 }
82}82}
8383
84// TODO fix enum literal not casting to error union84pub const PrevStatus = enum {
85const PrevStatus = enum {
86 stale,85 stale,
87 fresh,86 fresh,
88};87};
8988
89/// Deprecated; use `Dir.updateFile`.
90pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus {90pub 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, .{});
92}93}
9394
94/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.95/// Deprecated; use `Dir.updateFile`.
95/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,96pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !Dir.PrevStatus {
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 {
101 const my_cwd = cwd();97 const my_cwd = cwd();
10298 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, .{ .override_mode = mode });
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;
138}99}
139100
140/// Guaranteed to be atomic.101/// Guaranteed to be atomic.
...@@ -172,43 +133,40 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M...@@ -172,43 +133,40 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M
172 return atomic_file.finish();133 return atomic_file.finish();
173}134}
174135
175/// TODO update this API to avoid a getrandom syscall for every operation. It136/// TODO update this API to avoid a getrandom syscall for every operation.
176/// should accept a random interface.
177/// TODO rework this to integrate with Dir
178pub const AtomicFile = struct {137pub const AtomicFile = struct {
179 file: File,138 file: File,
180 tmp_path_buf: [MAX_PATH_BYTES]u8,139 tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8,
181 dest_path: []const u8,140 dest_path: []const u8,
182 finished: bool,141 file_open: bool,
142 file_exists: bool,
143 dir: Dir,
183144
184 const InitError = File.OpenError;145 const InitError = File.OpenError;
185146
186 /// dest_path must remain valid for the lifetime of AtomicFile147 /// TODO rename this. Callers should go through Dir API
187 /// call finish to atomically replace dest_path with contents148 pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir) InitError!AtomicFile {
188 pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
189 const dirname = path.dirname(dest_path);149 const dirname = path.dirname(dest_path);
190 var rand_buf: [12]u8 = undefined;150 var rand_buf: [12]u8 = undefined;
191 const dirname_component_len = if (dirname) |d| d.len + 1 else 0;151 const dirname_component_len = if (dirname) |d| d.len + 1 else 0;
192 const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);152 const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);
193 const tmp_path_len = dirname_component_len + encoded_rand_len;153 const tmp_path_len = dirname_component_len + encoded_rand_len;
194 var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined;154 var tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8 = undefined;
195 if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong;155 if (tmp_path_len > tmp_path_buf.len) return error.NameTooLong;
196156
197 if (dirname) |dir| {157 if (dirname) |dn| {
198 mem.copy(u8, tmp_path_buf[0..], dir);158 mem.copy(u8, tmp_path_buf[0..], dn);
199 tmp_path_buf[dir.len] = path.sep;159 tmp_path_buf[dn.len] = path.sep;
200 }160 }
201161
202 tmp_path_buf[tmp_path_len] = 0;162 tmp_path_buf[tmp_path_len] = 0;
203 const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0];163 const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0];
204164
205 const my_cwd = cwd();
206
207 while (true) {165 while (true) {
208 try crypto.randomBytes(rand_buf[0..]);166 try crypto.randomBytes(rand_buf[0..]);
209 base64_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf);167 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(
212 tmp_path_slice,170 tmp_path_slice,
213 .{ .mode = mode, .exclusive = true },171 .{ .mode = mode, .exclusive = true },
214 ) catch |err| switch (err) {172 ) catch |err| switch (err) {
...@@ -220,33 +178,46 @@ pub const AtomicFile = struct {...@@ -220,33 +178,46 @@ pub const AtomicFile = struct {
220 .file = file,178 .file = file,
221 .tmp_path_buf = tmp_path_buf,179 .tmp_path_buf = tmp_path_buf,
222 .dest_path = dest_path,180 .dest_path = dest_path,
223 .finished = false,181 .file_open = true,
182 .file_exists = true,
183 .dir = dir,
224 };184 };
225 }185 }
226 }186 }
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
228 /// always call deinit, even after successful finish()193 /// always call deinit, even after successful finish()
229 pub fn deinit(self: *AtomicFile) void {194 pub fn deinit(self: *AtomicFile) void {
230 if (!self.finished) {195 if (self.file_open) {
231 self.file.close();196 self.file.close();
232 cwd().deleteFileC(@ptrCast([*:0]u8, &self.tmp_path_buf)) catch {};197 self.file_open = false;
233 self.finished = true;198 }
199 if (self.file_exists) {
200 self.dir.deleteFileC(&self.tmp_path_buf) catch {};
201 self.file_exists = false;
234 }202 }
203 self.* = undefined;
235 }204 }
236205
237 pub fn finish(self: *AtomicFile) !void {206 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 }
239 if (std.Target.current.os.tag == .windows) {212 if (std.Target.current.os.tag == .windows) {
240 const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);213 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));214 const tmp_path_w = try os.windows.cStrToPrefixedFileW(&self.tmp_path_buf);
242 self.file.close();215 try os.renameatW(self.dir.fd, &tmp_path_w, self.dir.fd, &dest_path_w, os.windows.TRUE);
243 self.finished = true;216 self.file_exists = false;
244 return os.renameW(&tmp_path_w, &dest_path_w);
245 } else {217 } else {
246 const dest_path_c = try os.toPosixPath(self.dest_path);218 const dest_path_c = try os.toPosixPath(self.dest_path);
247 self.file.close();219 try os.renameatZ(self.dir.fd, &self.tmp_path_buf, self.dir.fd, &dest_path_c);
248 self.finished = true;220 self.file_exists = false;
249 return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c);
250 }221 }
251 }222 }
252};223};
...@@ -694,7 +665,10 @@ pub const Dir = struct {...@@ -694,7 +665,10 @@ pub const Dir = struct {
694 const access_mask = w.SYNCHRONIZE |665 const access_mask = w.SYNCHRONIZE |
695 (if (flags.read) @as(u32, w.GENERIC_READ) else 0) |666 (if (flags.read) @as(u32, w.GENERIC_READ) else 0) |
696 (if (flags.write) @as(u32, w.GENERIC_WRITE) else 0);667 (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 });
698 }672 }
699673
700 /// Creates, opens, or overwrites a file with write access.674 /// Creates, opens, or overwrites a file with write access.
...@@ -739,7 +713,10 @@ pub const Dir = struct {...@@ -739,7 +713,10 @@ pub const Dir = struct {
739 @as(u32, w.FILE_OVERWRITE_IF)713 @as(u32, w.FILE_OVERWRITE_IF)
740 else714 else
741 @as(u32, w.FILE_OPEN_IF);715 @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 });
743 }720 }
744721
745 /// Deprecated; call `openFile` directly.722 /// Deprecated; call `openFile` directly.
...@@ -757,72 +734,6 @@ pub const Dir = struct {...@@ -757,72 +734,6 @@ pub const Dir = struct {
757 return self.openFileW(sub_path, .{});734 return self.openFileW(sub_path, .{});
758 }735 }
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
826 pub fn makeDir(self: Dir, sub_path: []const u8) !void {737 pub fn makeDir(self: Dir, sub_path: []const u8) !void {
827 try os.mkdirat(self.fd, sub_path, default_new_dir_mode);738 try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
828 }739 }
...@@ -898,6 +809,7 @@ pub const Dir = struct {...@@ -898,6 +809,7 @@ pub const Dir = struct {
898 /// Call `close` on the result when done.809 /// Call `close` on the result when done.
899 ///810 ///
900 /// Asserts that the path parameter has no null bytes.811 /// Asserts that the path parameter has no null bytes.
812 /// TODO collapse this and `openDirList` into one function with an options parameter
901 pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir {813 pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir {
902 if (builtin.os.tag == .windows) {814 if (builtin.os.tag == .windows) {
903 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);815 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
...@@ -915,6 +827,7 @@ pub const Dir = struct {...@@ -915,6 +827,7 @@ pub const Dir = struct {
915 /// Call `close` on the result when done.827 /// Call `close` on the result when done.
916 ///828 ///
917 /// Asserts that the path parameter has no null bytes.829 /// Asserts that the path parameter has no null bytes.
830 /// TODO collapse this and `openDirTraverse` into one function with an options parameter
918 pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir {831 pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir {
919 if (builtin.os.tag == .windows) {832 if (builtin.os.tag == .windows) {
920 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);833 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
...@@ -1370,6 +1283,70 @@ pub const Dir = struct {...@@ -1370,6 +1283,70 @@ pub const Dir = struct {
1370 pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {1283 pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
1371 return os.faccessatW(self.fd, sub_path_w, 0, 0);1284 return os.faccessatW(self.fd, sub_path_w, 0, 0);
1372 }1285 }
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 }
1373};1350};
13741351
1375/// Returns an handle to the current working directory that is open for traversal.1352/// 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 {...@@ -461,13 +461,11 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
461 );461 );
462462
463 switch (rc) {463 switch (rc) {
464 .SUCCESS => {},464 .SUCCESS => return,
465 .INVALID_HANDLE => unreachable, // Handle not open for writing465 .INVALID_HANDLE => unreachable, // Handle not open for writing
466 .ACCESS_DENIED => return error.CannotTruncate,466 .ACCESS_DENIED => return error.CannotTruncate,
467 else => return windows.unexpectedStatus(rc),467 else => return windows.unexpectedStatus(rc),
468 }468 }
469
470 return;
471 }469 }
472470
473 while (true) {471 while (true) {
...@@ -852,6 +850,7 @@ pub const OpenError = error{...@@ -852,6 +850,7 @@ pub const OpenError = error{
852850
853/// Open and possibly create a file. Keeps trying if it gets interrupted.851/// Open and possibly create a file. Keeps trying if it gets interrupted.
854/// See also `openC`.852/// See also `openC`.
853/// TODO support windows
855pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {854pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
856 const file_path_c = try toPosixPath(file_path);855 const file_path_c = try toPosixPath(file_path);
857 return openC(&file_path_c, flags, perm);856 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 {...@@ -859,6 +858,7 @@ pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
859858
860/// Open and possibly create a file. Keeps trying if it gets interrupted.859/// Open and possibly create a file. Keeps trying if it gets interrupted.
861/// See also `open`.860/// See also `open`.
861/// TODO support windows
862pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {862pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
863 while (true) {863 while (true) {
864 const rc = system.open(file_path, flags, perm);864 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 {...@@ -892,6 +892,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
892/// Open and possibly create a file. Keeps trying if it gets interrupted.892/// Open and possibly create a file. Keeps trying if it gets interrupted.
893/// `file_path` is relative to the open directory handle `dir_fd`.893/// `file_path` is relative to the open directory handle `dir_fd`.
894/// See also `openatC`.894/// See also `openatC`.
895/// TODO support windows
895pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {896pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
896 const file_path_c = try toPosixPath(file_path);897 const file_path_c = try toPosixPath(file_path);
897 return openatC(dir_fd, &file_path_c, flags, mode);898 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...@@ -900,6 +901,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope
900/// Open and possibly create a file. Keeps trying if it gets interrupted.901/// Open and possibly create a file. Keeps trying if it gets interrupted.
901/// `file_path` is relative to the open directory handle `dir_fd`.902/// `file_path` is relative to the open directory handle `dir_fd`.
902/// See also `openat`.903/// See also `openat`.
904/// TODO support windows
903pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {905pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
904 while (true) {906 while (true) {
905 const rc = system.openat(dir_fd, file_path, flags, mode);907 const rc = system.openat(dir_fd, file_path, flags, mode);
...@@ -1527,6 +1529,9 @@ const RenameError = error{...@@ -1527,6 +1529,9 @@ const RenameError = error{
1527 RenameAcrossMountPoints,1529 RenameAcrossMountPoints,
1528 InvalidUtf8,1530 InvalidUtf8,
1529 BadPathName,1531 BadPathName,
1532 NoDevice,
1533 SharingViolation,
1534 PipeBusy,
1530} || UnexpectedError;1535} || UnexpectedError;
15311536
1532/// Change the name or location of a file.1537/// 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...@@ -1580,6 +1585,108 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v
1580 return windows.MoveFileExW(old_path, new_path, flags);1585 return windows.MoveFileExW(old_path, new_path, flags);
1581}1586}
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
1583pub const MakeDirError = error{1690pub const MakeDirError = error{
1584 AccessDenied,1691 AccessDenied,
1585 DiskQuota,1692 DiskQuota,
...@@ -3125,6 +3232,7 @@ pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP...@@ -3125,6 +3232,7 @@ pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
3125}3232}
31263233
3127/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.3234/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
3235/// TODO use ntdll for better semantics
3128pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {3236pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
3129 const h_file = try windows.CreateFileW(3237 const h_file = try windows.CreateFileW(
3130 pathname,3238 pathname,
lib/std/os/linux.zig+4-4
...@@ -465,17 +465,17 @@ pub fn renameat(oldfd: i32, oldpath: [*]const u8, newfd: i32, newpath: [*]const...@@ -465,17 +465,17 @@ pub fn renameat(oldfd: i32, oldpath: [*]const u8, newfd: i32, newpath: [*]const
465 return syscall4(465 return syscall4(
466 SYS_renameat,466 SYS_renameat,
467 @bitCast(usize, @as(isize, oldfd)),467 @bitCast(usize, @as(isize, oldfd)),
468 @ptrToInt(old),468 @ptrToInt(oldpath),
469 @bitCast(usize, @as(isize, newfd)),469 @bitCast(usize, @as(isize, newfd)),
470 @ptrToInt(new),470 @ptrToInt(newpath),
471 );471 );
472 } else {472 } else {
473 return syscall5(473 return syscall5(
474 SYS_renameat2,474 SYS_renameat2,
475 @bitCast(usize, @as(isize, oldfd)),475 @bitCast(usize, @as(isize, oldfd)),
476 @ptrToInt(old),476 @ptrToInt(oldpath),
477 @bitCast(usize, @as(isize, newfd)),477 @bitCast(usize, @as(isize, newfd)),
478 @ptrToInt(new),478 @ptrToInt(newpath),
479 0,479 0,
480 );480 );
481 }481 }
lib/std/os/windows.zig+76
...@@ -88,6 +88,82 @@ pub fn CreateFileW(...@@ -88,6 +88,82 @@ pub fn CreateFileW(
88 return result;88 return result;
89}89}
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
91pub const CreatePipeError = error{Unexpected};167pub const CreatePipeError = error{Unexpected};
92168
93pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {169pub 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 {...@@ -242,6 +242,13 @@ pub const FILE_NAME_INFORMATION = extern struct {
242 FileName: [1]WCHAR,242 FileName: [1]WCHAR,
243};243};
244244
245pub const FILE_RENAME_INFORMATION = extern struct {
246 ReplaceIfExists: BOOLEAN,
247 RootDirectory: ?HANDLE,
248 FileNameLength: ULONG,
249 FileName: [1]WCHAR,
250};
251
245pub const IO_STATUS_BLOCK = extern struct {252pub const IO_STATUS_BLOCK = extern struct {
246 // "DUMMYUNIONNAME" expands to "u"253 // "DUMMYUNIONNAME" expands to "u"
247 u: extern union {254 u: extern union {
src/main.cpp+2-1
...@@ -1290,6 +1290,7 @@ static int main0(int argc, char **argv) {...@@ -1290,6 +1290,7 @@ static int main0(int argc, char **argv) {
1290 if (g->enable_cache) {1290 if (g->enable_cache) {
1291#if defined(ZIG_OS_WINDOWS)1291#if defined(ZIG_OS_WINDOWS)
1292 buf_replace(&g->bin_file_output_path, '/', '\\');1292 buf_replace(&g->bin_file_output_path, '/', '\\');
1293 buf_replace(g->output_dir, '/', '\\');
1293#endif1294#endif
1294 if (final_output_dir_step != nullptr) {1295 if (final_output_dir_step != nullptr) {
1295 Buf *dest_basename = buf_alloc();1296 Buf *dest_basename = buf_alloc();
...@@ -1303,7 +1304,7 @@ static int main0(int argc, char **argv) {...@@ -1303,7 +1304,7 @@ static int main0(int argc, char **argv) {
1303 return main_exit(root_progress_node, EXIT_FAILURE);1304 return main_exit(root_progress_node, EXIT_FAILURE);
1304 }1305 }
1305 } else {1306 } 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)
1307 return main_exit(root_progress_node, EXIT_FAILURE);1308 return main_exit(root_progress_node, EXIT_FAILURE);
1308 }1309 }
1309 }1310 }