| author | |
| committer | |
| log | a2432b6755c3f5c7f05ed219aefbf290aeb358cb |
| tree | fa1bd38109ba5ecc932d4766da2b4b25b5303b5c |
| parent | 0a69a10f2a1997edaeae047c44eca7b260ab72b9 |
| parent | a27a8561e9387b22d7c694af924e8f2ed0f17290 |
| signature |
zig build system: correctly handle multiple output artifacts12 files changed, 407 insertions(+), 176 deletions(-)
doc/docgen.zig+18-2| ... | @@ -1096,6 +1096,9 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var | ... | @@ -1096,6 +1096,9 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var |
| 1096 | try build_args.append("-lc"); | 1096 | try build_args.append("-lc"); |
| 1097 | try out.print(" -lc", .{}); | 1097 | try out.print(" -lc", .{}); |
| 1098 | } | 1098 | } |
| 1099 | const target = try std.zig.CrossTarget.parse(.{ | ||
| 1100 | .arch_os_abi = code.target_str orelse "native", | ||
| 1101 | }); | ||
| 1099 | if (code.target_str) |triple| { | 1102 | if (code.target_str) |triple| { |
| 1100 | try build_args.appendSlice(&[_][]const u8{ "-target", triple }); | 1103 | try build_args.appendSlice(&[_][]const u8{ "-target", triple }); |
| 1101 | if (!code.is_inline) { | 1104 | if (!code.is_inline) { |
| ... | @@ -1150,7 +1153,15 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var | ... | @@ -1150,7 +1153,15 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var |
| 1150 | } | 1153 | } |
| 1151 | } | 1154 | } |
| 1152 | 1155 | ||
| 1153 | const path_to_exe = mem.trim(u8, exec_result.stdout, " \r\n"); | 1156 | const path_to_exe_dir = mem.trim(u8, exec_result.stdout, " \r\n"); |
| 1157 | const path_to_exe_basename = try std.fmt.allocPrint(allocator, "{}{}", .{ | ||
| 1158 | code.name, | ||
| 1159 | target.exeFileExt(), | ||
| 1160 | }); | ||
| 1161 | const path_to_exe = try fs.path.join(allocator, &[_][]const u8{ | ||
| 1162 | path_to_exe_dir, | ||
| 1163 | path_to_exe_basename, | ||
| 1164 | }); | ||
| 1154 | const run_args = &[_][]const u8{path_to_exe}; | 1165 | const run_args = &[_][]const u8{path_to_exe}; |
| 1155 | 1166 | ||
| 1156 | var exited_with_signal = false; | 1167 | var exited_with_signal = false; |
| ... | @@ -1486,7 +1497,12 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var | ... | @@ -1486,7 +1497,12 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var |
| 1486 | } | 1497 | } |
| 1487 | 1498 | ||
| 1488 | fn exec(allocator: *mem.Allocator, env_map: *std.BufMap, args: []const []const u8) !ChildProcess.ExecResult { | 1499 | fn exec(allocator: *mem.Allocator, env_map: *std.BufMap, args: []const []const u8) !ChildProcess.ExecResult { |
| 1489 | const result = try ChildProcess.exec(allocator, args, null, env_map, max_doc_file_size); | 1500 | const result = try ChildProcess.exec2(.{ |
| 1501 | .allocator = allocator, | ||
| 1502 | .argv = args, | ||
| 1503 | .env_map = env_map, | ||
| 1504 | .max_output_bytes = max_doc_file_size, | ||
| 1505 | }); | ||
| 1490 | switch (result.term) { | 1506 | switch (result.term) { |
| 1491 | .Exited => |exit_code| { | 1507 | .Exited => |exit_code| { |
| 1492 | if (exit_code != 0) { | 1508 | if (exit_code != 0) { |
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"); |
| 2146 | 2146 | ||
| 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"); |
| 2149 | 2149 | ||
| 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 | } |
| 2160 | 2165 |
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; |
| 106 | pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int; | 106 | pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int; |
| 107 | pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int; | 107 | pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int; |
| 108 | pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int; | 108 | pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int; |
| 109 | pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, new: [*:0]const u8) c_int; | ||
| 109 | pub extern "c" fn chdir(path: [*:0]const u8) c_int; | 110 | pub extern "c" fn chdir(path: [*:0]const u8) c_int; |
| 110 | pub extern "c" fn fchdir(fd: fd_t) c_int; | 111 | pub extern "c" fn fchdir(fd: fd_t) c_int; |
| 111 | pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int; | 112 | pub 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 | } |
| 83 | 83 | ||
| 84 | // TODO fix enum literal not casting to error union | 84 | pub const PrevStatus = enum { |
| 85 | const PrevStatus = enum { | ||
| 86 | stale, | 85 | stale, |
| 87 | fresh, | 86 | fresh, |
| 88 | }; | 87 | }; |
| 89 | 88 | ||
| 89 | /// Deprecated; use `Dir.updateFile`. | ||
| 90 | pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus { | 90 | pub 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 | } |
| 93 | 94 | ||
| 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, | 96 | pub 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 | ||
| 100 | pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !PrevStatus { | ||
| 101 | const my_cwd = cwd(); | 97 | const my_cwd = cwd(); |
| 102 | 98 | 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 | } |
| 139 | 100 | ||
| 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 | } |
| 174 | 135 | ||
| 175 | /// TODO update this API to avoid a getrandom syscall for every operation. It | 136 | /// 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 | ||
| 178 | pub const AtomicFile = struct { | 137 | pub 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, | ||
| 183 | 144 | ||
| 184 | const InitError = File.OpenError; | 145 | const InitError = File.OpenError; |
| 185 | 146 | ||
| 186 | /// dest_path must remain valid for the lifetime of AtomicFile | 147 | /// TODO rename this. Callers should go through Dir API |
| 187 | /// call finish to atomically replace dest_path with contents | 148 | 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; |
| 196 | 156 | ||
| 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 | } |
| 201 | 161 | ||
| 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]; |
| 204 | 164 | ||
| 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); |
| 210 | 168 | ||
| 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 | } |
| 227 | 187 | ||
| 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 | } |
| 236 | 205 | ||
| 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 | } |
| 699 | 673 | ||
| 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 | else | 714 | 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 | } |
| 744 | 721 | ||
| 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 | } |
| 759 | 736 | ||
| 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 | }; |
| 1374 | 1351 | ||
| 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/mem.zig+17-3| ... | @@ -116,7 +116,7 @@ pub const Allocator = struct { | ... | @@ -116,7 +116,7 @@ pub const Allocator = struct { |
| 116 | pub fn allocSentinel(self: *Allocator, comptime Elem: type, n: usize, comptime sentinel: Elem) Error![:sentinel]Elem { | 116 | pub fn allocSentinel(self: *Allocator, comptime Elem: type, n: usize, comptime sentinel: Elem) Error![:sentinel]Elem { |
| 117 | var ptr = try self.alloc(Elem, n + 1); | 117 | var ptr = try self.alloc(Elem, n + 1); |
| 118 | ptr[n] = sentinel; | 118 | ptr[n] = sentinel; |
| 119 | return ptr[0 .. n :sentinel]; | 119 | return ptr[0..n :sentinel]; |
| 120 | } | 120 | } |
| 121 | 121 | ||
| 122 | pub fn alignedAlloc( | 122 | pub fn alignedAlloc( |
| ... | @@ -567,12 +567,20 @@ test "span" { | ... | @@ -567,12 +567,20 @@ test "span" { |
| 567 | 567 | ||
| 568 | /// Takes a pointer to an array, an array, a sentinel-terminated pointer, | 568 | /// Takes a pointer to an array, an array, a sentinel-terminated pointer, |
| 569 | /// or a slice, and returns the length. | 569 | /// or a slice, and returns the length. |
| 570 | /// In the case of a sentinel-terminated array, it scans the array | ||
| 571 | /// for a sentinel and uses that for the length, rather than using the array length. | ||
| 570 | pub fn len(ptr: var) usize { | 572 | pub fn len(ptr: var) usize { |
| 571 | return switch (@typeInfo(@TypeOf(ptr))) { | 573 | return switch (@typeInfo(@TypeOf(ptr))) { |
| 572 | .Array => |info| info.len, | 574 | .Array => |info| if (info.sentinel) |sentinel| |
| 575 | indexOfSentinel(info.child, sentinel, &ptr) | ||
| 576 | else | ||
| 577 | info.len, | ||
| 573 | .Pointer => |info| switch (info.size) { | 578 | .Pointer => |info| switch (info.size) { |
| 574 | .One => switch (@typeInfo(info.child)) { | 579 | .One => switch (@typeInfo(info.child)) { |
| 575 | .Array => |x| x.len, | 580 | .Array => |x| if (x.sentinel) |sentinel| |
| 581 | indexOfSentinel(x.child, sentinel, ptr) | ||
| 582 | else | ||
| 583 | ptr.len, | ||
| 576 | else => @compileError("invalid type given to std.mem.length"), | 584 | else => @compileError("invalid type given to std.mem.length"), |
| 577 | }, | 585 | }, |
| 578 | .Many => if (info.sentinel) |sentinel| | 586 | .Many => if (info.sentinel) |sentinel| |
| ... | @@ -597,6 +605,12 @@ test "len" { | ... | @@ -597,6 +605,12 @@ test "len" { |
| 597 | const ptr = array[0..2 :0].ptr; | 605 | const ptr = array[0..2 :0].ptr; |
| 598 | testing.expect(len(ptr) == 2); | 606 | testing.expect(len(ptr) == 2); |
| 599 | } | 607 | } |
| 608 | { | ||
| 609 | var array: [5:0]u16 = [_:0]u16{ 1, 2, 3, 4, 5 }; | ||
| 610 | testing.expect(len(&array) == 5); | ||
| 611 | array[2] = 0; | ||
| 612 | testing.expect(len(&array) == 2); | ||
| 613 | } | ||
| 600 | } | 614 | } |
| 601 | 615 | ||
| 602 | pub fn indexOfSentinel(comptime Elem: type, comptime sentinel: Elem, ptr: [*:sentinel]const Elem) usize { | 616 | pub fn indexOfSentinel(comptime Elem: type, comptime sentinel: Elem, ptr: [*:sentinel]const Elem) usize { |
lib/std/os.zig+116-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 | ); |
| 462 | 462 | ||
| 463 | switch (rc) { | 463 | switch (rc) { |
| 464 | .SUCCESS => {}, | 464 | .SUCCESS => return, |
| 465 | .INVALID_HANDLE => unreachable, // Handle not open for writing | 465 | .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 | } |
| 472 | 470 | ||
| 473 | while (true) { | 471 | while (true) { |
| ... | @@ -852,6 +850,7 @@ pub const OpenError = error{ | ... | @@ -852,6 +850,7 @@ pub const OpenError = error{ |
| 852 | 850 | ||
| 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 | ||
| 855 | pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t { | 854 | pub 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 { |
| 859 | 858 | ||
| 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 | ||
| 862 | pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t { | 862 | pub 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 | ||
| 895 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t { | 896 | pub 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 | ||
| 903 | pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t { | 905 | pub 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; |
| 1531 | 1536 | ||
| 1532 | /// Change the name or location of a file. | 1537 | /// Change the name or location of a file. |
| ... | @@ -1580,6 +1585,113 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v | ... | @@ -1580,6 +1585,113 @@ 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 | } |
| 1582 | 1587 | ||
| 1588 | /// Change the name or location of a file based on an open directory handle. | ||
| 1589 | pub 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. | ||
| 1607 | pub 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. | ||
| 1646 | pub 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 | windows.DELETE; | ||
| 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 | .INVALID_PARAMETER => unreachable, | ||
| 1687 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | ||
| 1688 | .ACCESS_DENIED => return error.AccessDenied, | ||
| 1689 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | ||
| 1690 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | ||
| 1691 | else => return windows.unexpectedStatus(rc), | ||
| 1692 | } | ||
| 1693 | } | ||
| 1694 | |||
| 1583 | pub const MakeDirError = error{ | 1695 | pub const MakeDirError = error{ |
| 1584 | AccessDenied, | 1696 | AccessDenied, |
| 1585 | DiskQuota, | 1697 | DiskQuota, |
| ... | @@ -3125,6 +3237,7 @@ pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP | ... | @@ -3125,6 +3237,7 @@ pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP |
| 3125 | } | 3237 | } |
| 3126 | 3238 | ||
| 3127 | /// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded. | 3239 | /// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded. |
| 3240 | /// TODO use ntdll for better semantics | ||
| 3128 | pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { | 3241 | pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 3129 | const h_file = try windows.CreateFileW( | 3242 | const h_file = try windows.CreateFileW( |
| 3130 | pathname, | 3243 | 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 | } |
| 90 | 90 | ||
| 91 | pub 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 | ||
| 105 | pub 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 | |||
| 91 | pub const CreatePipeError = error{Unexpected}; | 167 | pub const CreatePipeError = error{Unexpected}; |
| 92 | 168 | ||
| 93 | pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void { | 169 | pub 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 | }; |
| 244 | 244 | ||
| 245 | pub const FILE_RENAME_INFORMATION = extern struct { | ||
| 246 | ReplaceIfExists: BOOLEAN, | ||
| 247 | RootDirectory: ?HANDLE, | ||
| 248 | FileNameLength: ULONG, | ||
| 249 | FileName: [1]WCHAR, | ||
| 250 | }; | ||
| 251 | |||
| 245 | pub const IO_STATUS_BLOCK = extern struct { | 252 | pub 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/codegen.cpp+33-13| ... | @@ -9650,6 +9650,21 @@ Error create_c_object_cache(CodeGen *g, CacheHash **out_cache_hash, bool verbose | ... | @@ -9650,6 +9650,21 @@ Error create_c_object_cache(CodeGen *g, CacheHash **out_cache_hash, bool verbose |
| 9650 | return ErrorNone; | 9650 | return ErrorNone; |
| 9651 | } | 9651 | } |
| 9652 | 9652 | ||
| 9653 | static bool need_llvm_module(CodeGen *g) { | ||
| 9654 | return buf_len(&g->main_pkg->root_src_path) != 0; | ||
| 9655 | } | ||
| 9656 | |||
| 9657 | // before gen_c_objects | ||
| 9658 | static bool main_output_dir_is_just_one_c_object_pre(CodeGen *g) { | ||
| 9659 | return g->enable_cache && g->c_source_files.length == 1 && !need_llvm_module(g) && | ||
| 9660 | g->out_type == OutTypeObj && g->link_objects.length == 0; | ||
| 9661 | } | ||
| 9662 | |||
| 9663 | // after gen_c_objects | ||
| 9664 | static bool main_output_dir_is_just_one_c_object_post(CodeGen *g) { | ||
| 9665 | return g->enable_cache && g->link_objects.length == 1 && !need_llvm_module(g) && g->out_type == OutTypeObj; | ||
| 9666 | } | ||
| 9667 | |||
| 9653 | // returns true if it was a cache miss | 9668 | // returns true if it was a cache miss |
| 9654 | static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) { | 9669 | static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) { |
| 9655 | Error err; | 9670 | Error err; |
| ... | @@ -9667,7 +9682,12 @@ static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) { | ... | @@ -9667,7 +9682,12 @@ static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) { |
| 9667 | buf_len(c_source_basename), 0); | 9682 | buf_len(c_source_basename), 0); |
| 9668 | 9683 | ||
| 9669 | Buf *final_o_basename = buf_alloc(); | 9684 | Buf *final_o_basename = buf_alloc(); |
| 9670 | os_path_extname(c_source_basename, final_o_basename, nullptr); | 9685 | // We special case when doing build-obj for just one C file |
| 9686 | if (main_output_dir_is_just_one_c_object_pre(g)) { | ||
| 9687 | buf_init_from_buf(final_o_basename, g->root_out_name); | ||
| 9688 | } else { | ||
| 9689 | os_path_extname(c_source_basename, final_o_basename, nullptr); | ||
| 9690 | } | ||
| 9671 | buf_append_str(final_o_basename, target_o_file_ext(g->zig_target)); | 9691 | buf_append_str(final_o_basename, target_o_file_ext(g->zig_target)); |
| 9672 | 9692 | ||
| 9673 | CacheHash *cache_hash; | 9693 | CacheHash *cache_hash; |
| ... | @@ -10467,10 +10487,6 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) { | ... | @@ -10467,10 +10487,6 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) { |
| 10467 | return ErrorNone; | 10487 | return ErrorNone; |
| 10468 | } | 10488 | } |
| 10469 | 10489 | ||
| 10470 | static bool need_llvm_module(CodeGen *g) { | ||
| 10471 | return buf_len(&g->main_pkg->root_src_path) != 0; | ||
| 10472 | } | ||
| 10473 | |||
| 10474 | static void resolve_out_paths(CodeGen *g) { | 10490 | static void resolve_out_paths(CodeGen *g) { |
| 10475 | assert(g->output_dir != nullptr); | 10491 | assert(g->output_dir != nullptr); |
| 10476 | assert(g->root_out_name != nullptr); | 10492 | assert(g->root_out_name != nullptr); |
| ... | @@ -10482,10 +10498,6 @@ static void resolve_out_paths(CodeGen *g) { | ... | @@ -10482,10 +10498,6 @@ static void resolve_out_paths(CodeGen *g) { |
| 10482 | case OutTypeUnknown: | 10498 | case OutTypeUnknown: |
| 10483 | zig_unreachable(); | 10499 | zig_unreachable(); |
| 10484 | case OutTypeObj: | 10500 | case OutTypeObj: |
| 10485 | if (g->enable_cache && g->link_objects.length == 1 && !need_llvm_module(g)) { | ||
| 10486 | buf_init_from_buf(&g->bin_file_output_path, g->link_objects.at(0)); | ||
| 10487 | return; | ||
| 10488 | } | ||
| 10489 | if (need_llvm_module(g) && g->link_objects.length != 0 && !g->enable_cache && | 10501 | if (need_llvm_module(g) && g->link_objects.length != 0 && !g->enable_cache && |
| 10490 | buf_eql_buf(o_basename, out_basename)) | 10502 | buf_eql_buf(o_basename, out_basename)) |
| 10491 | { | 10503 | { |
| ... | @@ -10580,6 +10592,16 @@ static void output_type_information(CodeGen *g) { | ... | @@ -10580,6 +10592,16 @@ static void output_type_information(CodeGen *g) { |
| 10580 | } | 10592 | } |
| 10581 | } | 10593 | } |
| 10582 | 10594 | ||
| 10595 | static void init_output_dir(CodeGen *g, Buf *digest) { | ||
| 10596 | if (main_output_dir_is_just_one_c_object_post(g)) { | ||
| 10597 | g->output_dir = buf_alloc(); | ||
| 10598 | os_path_dirname(g->link_objects.at(0), g->output_dir); | ||
| 10599 | } else { | ||
| 10600 | g->output_dir = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR OS_SEP "%s", | ||
| 10601 | buf_ptr(g->cache_dir), buf_ptr(digest)); | ||
| 10602 | } | ||
| 10603 | } | ||
| 10604 | |||
| 10583 | void codegen_build_and_link(CodeGen *g) { | 10605 | void codegen_build_and_link(CodeGen *g) { |
| 10584 | Error err; | 10606 | Error err; |
| 10585 | assert(g->out_type != OutTypeUnknown); | 10607 | assert(g->out_type != OutTypeUnknown); |
| ... | @@ -10622,8 +10644,7 @@ void codegen_build_and_link(CodeGen *g) { | ... | @@ -10622,8 +10644,7 @@ void codegen_build_and_link(CodeGen *g) { |
| 10622 | } | 10644 | } |
| 10623 | 10645 | ||
| 10624 | if (g->enable_cache && buf_len(&digest) != 0) { | 10646 | if (g->enable_cache && buf_len(&digest) != 0) { |
| 10625 | g->output_dir = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR OS_SEP "%s", | 10647 | init_output_dir(g, &digest); |
| 10626 | buf_ptr(g->cache_dir), buf_ptr(&digest)); | ||
| 10627 | resolve_out_paths(g); | 10648 | resolve_out_paths(g); |
| 10628 | } else { | 10649 | } else { |
| 10629 | if (need_llvm_module(g)) { | 10650 | if (need_llvm_module(g)) { |
| ... | @@ -10644,8 +10665,7 @@ void codegen_build_and_link(CodeGen *g) { | ... | @@ -10644,8 +10665,7 @@ void codegen_build_and_link(CodeGen *g) { |
| 10644 | exit(1); | 10665 | exit(1); |
| 10645 | } | 10666 | } |
| 10646 | } | 10667 | } |
| 10647 | g->output_dir = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR OS_SEP "%s", | 10668 | init_output_dir(g, &digest); |
| 10648 | buf_ptr(g->cache_dir), buf_ptr(&digest)); | ||
| 10649 | 10669 | ||
| 10650 | if ((err = os_make_path(g->output_dir))) { | 10670 | if ((err = os_make_path(g->output_dir))) { |
| 10651 | fprintf(stderr, "Unable to create output directory: %s\n", err_str(err)); | 10671 | fprintf(stderr, "Unable to create output directory: %s\n", err_str(err)); |
src/link.cpp+1| ... | @@ -566,6 +566,7 @@ static const char *build_libc_object(CodeGen *parent_gen, const char *name, CFil | ... | @@ -566,6 +566,7 @@ static const char *build_libc_object(CodeGen *parent_gen, const char *name, CFil |
| 566 | Stage2ProgressNode *progress_node) | 566 | Stage2ProgressNode *progress_node) |
| 567 | { | 567 | { |
| 568 | CodeGen *child_gen = create_child_codegen(parent_gen, nullptr, OutTypeObj, nullptr, name, progress_node); | 568 | CodeGen *child_gen = create_child_codegen(parent_gen, nullptr, OutTypeObj, nullptr, name, progress_node); |
| 569 | child_gen->root_out_name = buf_create_from_str(name); | ||
| 569 | ZigList<CFile *> c_source_files = {0}; | 570 | ZigList<CFile *> c_source_files = {0}; |
| 570 | c_source_files.append(c_file); | 571 | c_source_files.append(c_file); |
| 571 | child_gen->c_source_files = c_source_files; | 572 | child_gen->c_source_files = c_source_files; |
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 | #endif | 1294 | #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 | } |