Commit 66e76a0209
Changed files (8)
lib/std/os/windows/bits.zig
@@ -242,6 +242,13 @@ pub const FILE_NAME_INFORMATION = extern struct {
FileName: [1]WCHAR,
};
+pub const FILE_RENAME_INFORMATION = extern struct {
+ ReplaceIfExists: BOOLEAN,
+ RootDirectory: ?HANDLE,
+ FileNameLength: ULONG,
+ FileName: [1]WCHAR,
+};
+
pub const IO_STATUS_BLOCK = extern struct {
// "DUMMYUNIONNAME" expands to "u"
u: extern union {
lib/std/os/linux.zig
@@ -465,17 +465,17 @@ pub fn renameat(oldfd: i32, oldpath: [*]const u8, newfd: i32, newpath: [*]const
return syscall4(
SYS_renameat,
@bitCast(usize, @as(isize, oldfd)),
- @ptrToInt(old),
+ @ptrToInt(oldpath),
@bitCast(usize, @as(isize, newfd)),
- @ptrToInt(new),
+ @ptrToInt(newpath),
);
} else {
return syscall5(
SYS_renameat2,
@bitCast(usize, @as(isize, oldfd)),
- @ptrToInt(old),
+ @ptrToInt(oldpath),
@bitCast(usize, @as(isize, newfd)),
- @ptrToInt(new),
+ @ptrToInt(newpath),
0,
);
}
lib/std/os/windows.zig
@@ -88,6 +88,82 @@ pub fn CreateFileW(
return result;
}
+pub const OpenError = error{
+ IsDir,
+ FileNotFound,
+ NoDevice,
+ SharingViolation,
+ AccessDenied,
+ PipeBusy,
+ PathAlreadyExists,
+ Unexpected,
+ NameTooLong,
+};
+
+/// TODO rename to CreateFileW
+/// TODO actually we don't need the path parameter to be null terminated
+pub fn OpenFileW(
+ dir: ?HANDLE,
+ sub_path_w: [*:0]const u16,
+ sa: ?*SECURITY_ATTRIBUTES,
+ access_mask: ACCESS_MASK,
+ creation: ULONG,
+) OpenError!HANDLE {
+ if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
+ return error.IsDir;
+ }
+ if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
+ return error.IsDir;
+ }
+
+ var result: HANDLE = undefined;
+
+ const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
+ error.Overflow => return error.NameTooLong,
+ };
+ var nt_name = UNICODE_STRING{
+ .Length = path_len_bytes,
+ .MaximumLength = path_len_bytes,
+ .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
+ };
+ var attr = OBJECT_ATTRIBUTES{
+ .Length = @sizeOf(OBJECT_ATTRIBUTES),
+ .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
+ .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
+ .ObjectName = &nt_name,
+ .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null,
+ .SecurityQualityOfService = null,
+ };
+ var io: IO_STATUS_BLOCK = undefined;
+ const rc = ntdll.NtCreateFile(
+ &result,
+ access_mask,
+ &attr,
+ &io,
+ null,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
+ creation,
+ FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
+ null,
+ 0,
+ );
+ switch (rc) {
+ .SUCCESS => return result,
+ .OBJECT_NAME_INVALID => unreachable,
+ .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+ .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
+ .NO_MEDIA_IN_DEVICE => return error.NoDevice,
+ .INVALID_PARAMETER => unreachable,
+ .SHARING_VIOLATION => return error.SharingViolation,
+ .ACCESS_DENIED => return error.AccessDenied,
+ .PIPE_BUSY => return error.PipeBusy,
+ .OBJECT_PATH_SYNTAX_BAD => unreachable,
+ .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
+ else => return unexpectedStatus(rc),
+ }
+}
+
pub const CreatePipeError = error{Unexpected};
pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
lib/std/build.zig
@@ -2144,17 +2144,22 @@ pub const LibExeObjStep = struct {
try zig_args.append("--cache");
try zig_args.append("on");
- const output_path_nl = try builder.execFromStep(zig_args.toSliceConst(), &self.step);
- const output_path = mem.trimRight(u8, output_path_nl, "\r\n");
+ const output_dir_nl = try builder.execFromStep(zig_args.toSliceConst(), &self.step);
+ const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n");
if (self.output_dir) |output_dir| {
- const full_dest = try fs.path.join(builder.allocator, &[_][]const u8{
- output_dir,
- fs.path.basename(output_path),
- });
- try builder.updateFile(output_path, full_dest);
+ var src_dir = try std.fs.cwd().openDirTraverse(build_output_dir);
+ defer src_dir.close();
+
+ var dest_dir = try std.fs.cwd().openDirList(output_dir);
+ defer dest_dir.close();
+
+ var it = src_dir.iterate();
+ while (try it.next()) |entry| {
+ _ = try src_dir.updateFile(entry.name, dest_dir, entry.name, .{});
+ }
} else {
- self.output_dir = fs.path.dirname(output_path).?;
+ self.output_dir = build_output_dir;
}
}
lib/std/c.zig
@@ -106,6 +106,7 @@ pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int;
pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int;
pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int;
pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int;
+pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, new: [*:0]const u8) c_int;
pub extern "c" fn chdir(path: [*:0]const u8) c_int;
pub extern "c" fn fchdir(fd: fd_t) c_int;
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
@@ -81,60 +81,21 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
}
}
-// TODO fix enum literal not casting to error union
-const PrevStatus = enum {
+pub const PrevStatus = enum {
stale,
fresh,
};
+/// Deprecated; use `Dir.updateFile`.
pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus {
- return updateFileMode(source_path, dest_path, null);
+ const my_cwd = cwd();
+ return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, .{});
}
-/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
-/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
-/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
-/// Returns the previous status of the file before updating.
-/// If any of the directories do not exist for dest_path, they are created.
-/// TODO rework this to integrate with Dir
-pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !PrevStatus {
+/// Deprecated; use `Dir.updateFile`.
+pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !Dir.PrevStatus {
const my_cwd = cwd();
-
- var src_file = try my_cwd.openFile(source_path, .{});
- defer src_file.close();
-
- const src_stat = try src_file.stat();
- check_dest_stat: {
- const dest_stat = blk: {
- var dest_file = my_cwd.openFile(dest_path, .{}) catch |err| switch (err) {
- error.FileNotFound => break :check_dest_stat,
- else => |e| return e,
- };
- defer dest_file.close();
-
- break :blk try dest_file.stat();
- };
-
- if (src_stat.size == dest_stat.size and
- src_stat.mtime == dest_stat.mtime and
- src_stat.mode == dest_stat.mode)
- {
- return PrevStatus.fresh;
- }
- }
- const actual_mode = mode orelse src_stat.mode;
-
- if (path.dirname(dest_path)) |dirname| {
- try cwd().makePath(dirname);
- }
-
- var atomic_file = try AtomicFile.init(dest_path, actual_mode);
- defer atomic_file.deinit();
-
- try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
- try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
- try atomic_file.finish();
- return PrevStatus.stale;
+ return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, .{ .override_mode = mode });
}
/// Guaranteed to be atomic.
@@ -172,43 +133,40 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M
return atomic_file.finish();
}
-/// TODO update this API to avoid a getrandom syscall for every operation. It
-/// should accept a random interface.
-/// TODO rework this to integrate with Dir
+/// TODO update this API to avoid a getrandom syscall for every operation.
pub const AtomicFile = struct {
file: File,
- tmp_path_buf: [MAX_PATH_BYTES]u8,
+ tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8,
dest_path: []const u8,
- finished: bool,
+ file_open: bool,
+ file_exists: bool,
+ dir: Dir,
const InitError = File.OpenError;
- /// dest_path must remain valid for the lifetime of AtomicFile
- /// call finish to atomically replace dest_path with contents
- pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
+ /// TODO rename this. Callers should go through Dir API
+ pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir) InitError!AtomicFile {
const dirname = path.dirname(dest_path);
var rand_buf: [12]u8 = undefined;
const dirname_component_len = if (dirname) |d| d.len + 1 else 0;
const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);
const tmp_path_len = dirname_component_len + encoded_rand_len;
- var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined;
- if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong;
+ var tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8 = undefined;
+ if (tmp_path_len > tmp_path_buf.len) return error.NameTooLong;
- if (dirname) |dir| {
- mem.copy(u8, tmp_path_buf[0..], dir);
- tmp_path_buf[dir.len] = path.sep;
+ if (dirname) |dn| {
+ mem.copy(u8, tmp_path_buf[0..], dn);
+ tmp_path_buf[dn.len] = path.sep;
}
tmp_path_buf[tmp_path_len] = 0;
const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0];
- const my_cwd = cwd();
-
while (true) {
try crypto.randomBytes(rand_buf[0..]);
base64_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf);
- const file = my_cwd.createFileC(
+ const file = dir.createFileC(
tmp_path_slice,
.{ .mode = mode, .exclusive = true },
) catch |err| switch (err) {
@@ -220,33 +178,46 @@ pub const AtomicFile = struct {
.file = file,
.tmp_path_buf = tmp_path_buf,
.dest_path = dest_path,
- .finished = false,
+ .file_open = true,
+ .file_exists = true,
+ .dir = dir,
};
}
}
+ /// Deprecated. Use `Dir.atomicFile`.
+ pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
+ return init2(dest_path, mode, cwd());
+ }
+
/// always call deinit, even after successful finish()
pub fn deinit(self: *AtomicFile) void {
- if (!self.finished) {
+ if (self.file_open) {
self.file.close();
- cwd().deleteFileC(@ptrCast([*:0]u8, &self.tmp_path_buf)) catch {};
- self.finished = true;
+ self.file_open = false;
+ }
+ if (self.file_exists) {
+ self.dir.deleteFileC(&self.tmp_path_buf) catch {};
+ self.file_exists = false;
}
+ self.* = undefined;
}
pub fn finish(self: *AtomicFile) !void {
- assert(!self.finished);
+ assert(self.file_exists);
+ if (self.file_open) {
+ self.file.close();
+ self.file_open = false;
+ }
if (std.Target.current.os.tag == .windows) {
const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);
- const tmp_path_w = try os.windows.cStrToPrefixedFileW(@ptrCast([*:0]u8, &self.tmp_path_buf));
- self.file.close();
- self.finished = true;
- return os.renameW(&tmp_path_w, &dest_path_w);
+ const tmp_path_w = try os.windows.cStrToPrefixedFileW(&self.tmp_path_buf);
+ try os.renameatW(self.dir.fd, &tmp_path_w, self.dir.fd, &dest_path_w, os.windows.TRUE);
+ self.file_exists = false;
} else {
const dest_path_c = try os.toPosixPath(self.dest_path);
- self.file.close();
- self.finished = true;
- return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c);
+ try os.renameatZ(self.dir.fd, &self.tmp_path_buf, self.dir.fd, &dest_path_c);
+ self.file_exists = false;
}
}
};
@@ -694,7 +665,10 @@ pub const Dir = struct {
const access_mask = w.SYNCHRONIZE |
(if (flags.read) @as(u32, w.GENERIC_READ) else 0) |
(if (flags.write) @as(u32, w.GENERIC_WRITE) else 0);
- return self.openFileWindows(sub_path_w, access_mask, w.FILE_OPEN);
+ return @as(File, .{
+ .handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, w.FILE_OPEN),
+ .io_mode = .blocking,
+ });
}
/// Creates, opens, or overwrites a file with write access.
@@ -739,7 +713,10 @@ pub const Dir = struct {
@as(u32, w.FILE_OVERWRITE_IF)
else
@as(u32, w.FILE_OPEN_IF);
- return self.openFileWindows(sub_path_w, access_mask, creation);
+ return @as(File, .{
+ .handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, creation),
+ .io_mode = .blocking,
+ });
}
/// Deprecated; call `openFile` directly.
@@ -757,72 +734,6 @@ pub const Dir = struct {
return self.openFileW(sub_path, .{});
}
- pub fn openFileWindows(
- self: Dir,
- sub_path_w: [*:0]const u16,
- access_mask: os.windows.ACCESS_MASK,
- creation: os.windows.ULONG,
- ) File.OpenError!File {
- const w = os.windows;
-
- if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
- return error.IsDir;
- }
- if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
- return error.IsDir;
- }
-
- var result = File{
- .handle = undefined,
- .io_mode = .blocking,
- };
-
- const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
- error.Overflow => return error.NameTooLong,
- };
- var nt_name = w.UNICODE_STRING{
- .Length = path_len_bytes,
- .MaximumLength = path_len_bytes,
- .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
- };
- var attr = w.OBJECT_ATTRIBUTES{
- .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
- .RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
- .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- };
- var io: w.IO_STATUS_BLOCK = undefined;
- const rc = w.ntdll.NtCreateFile(
- &result.handle,
- access_mask,
- &attr,
- &io,
- null,
- w.FILE_ATTRIBUTE_NORMAL,
- w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
- creation,
- w.FILE_NON_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT,
- null,
- 0,
- );
- switch (rc) {
- .SUCCESS => return result,
- .OBJECT_NAME_INVALID => unreachable,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NO_MEDIA_IN_DEVICE => return error.NoDevice,
- .INVALID_PARAMETER => unreachable,
- .SHARING_VIOLATION => return error.SharingViolation,
- .ACCESS_DENIED => return error.AccessDenied,
- .PIPE_BUSY => return error.PipeBusy,
- .OBJECT_PATH_SYNTAX_BAD => unreachable,
- .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
- else => return w.unexpectedStatus(rc),
- }
- }
-
pub fn makeDir(self: Dir, sub_path: []const u8) !void {
try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
}
@@ -898,6 +809,7 @@ pub const Dir = struct {
/// Call `close` on the result when done.
///
/// Asserts that the path parameter has no null bytes.
+ /// TODO collapse this and `openDirList` into one function with an options parameter
pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
@@ -915,6 +827,7 @@ pub const Dir = struct {
/// Call `close` on the result when done.
///
/// Asserts that the path parameter has no null bytes.
+ /// TODO collapse this and `openDirTraverse` into one function with an options parameter
pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
@@ -1370,6 +1283,70 @@ pub const Dir = struct {
pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
return os.faccessatW(self.fd, sub_path_w, 0, 0);
}
+
+ pub const UpdateFileOptions = struct {
+ override_mode: ?File.Mode = null,
+ };
+
+ /// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
+ /// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
+ /// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
+ /// Returns the previous status of the file before updating.
+ /// If any of the directories do not exist for dest_path, they are created.
+ /// If `override_mode` is provided, then that value is used rather than the source path's mode.
+ pub fn updateFile(
+ source_dir: Dir,
+ source_path: []const u8,
+ dest_dir: Dir,
+ dest_path: []const u8,
+ options: UpdateFileOptions,
+ ) !PrevStatus {
+ var src_file = try source_dir.openFile(source_path, .{});
+ defer src_file.close();
+
+ const src_stat = try src_file.stat();
+ const actual_mode = options.override_mode orelse src_stat.mode;
+ check_dest_stat: {
+ const dest_stat = blk: {
+ var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
+ error.FileNotFound => break :check_dest_stat,
+ else => |e| return e,
+ };
+ defer dest_file.close();
+
+ break :blk try dest_file.stat();
+ };
+
+ if (src_stat.size == dest_stat.size and
+ src_stat.mtime == dest_stat.mtime and
+ actual_mode == dest_stat.mode)
+ {
+ return PrevStatus.fresh;
+ }
+ }
+
+ if (path.dirname(dest_path)) |dirname| {
+ try dest_dir.makePath(dirname);
+ }
+
+ var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = actual_mode });
+ defer atomic_file.deinit();
+
+ try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
+ try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
+ try atomic_file.finish();
+ return PrevStatus.stale;
+ }
+
+ pub const AtomicFileOptions = struct {
+ mode: File.Mode = File.default_mode,
+ };
+
+ /// `dest_path` must remain valid for the lifetime of `AtomicFile`.
+ /// Call `AtomicFile.finish` to atomically replace `dest_path` with contents.
+ pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
+ return AtomicFile.init2(dest_path, options.mode, self);
+ }
};
/// Returns an handle to the current working directory that is open for traversal.
lib/std/os.zig
@@ -461,13 +461,11 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
);
switch (rc) {
- .SUCCESS => {},
+ .SUCCESS => return,
.INVALID_HANDLE => unreachable, // Handle not open for writing
.ACCESS_DENIED => return error.CannotTruncate,
else => return windows.unexpectedStatus(rc),
}
-
- return;
}
while (true) {
@@ -852,6 +850,7 @@ pub const OpenError = error{
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// See also `openC`.
+/// TODO support windows
pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
const file_path_c = try toPosixPath(file_path);
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 {
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// See also `open`.
+/// TODO support windows
pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
while (true) {
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 {
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openatC`.
+/// TODO support windows
pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
const file_path_c = try toPosixPath(file_path);
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
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openat`.
+/// TODO support windows
pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
while (true) {
const rc = system.openat(dir_fd, file_path, flags, mode);
@@ -1527,6 +1529,9 @@ const RenameError = error{
RenameAcrossMountPoints,
InvalidUtf8,
BadPathName,
+ NoDevice,
+ SharingViolation,
+ PipeBusy,
} || UnexpectedError;
/// 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
return windows.MoveFileExW(old_path, new_path, flags);
}
+/// Change the name or location of a file based on an open directory handle.
+pub fn renameat(
+ old_dir_fd: fd_t,
+ old_path: []const u8,
+ new_dir_fd: fd_t,
+ new_path: []const u8,
+) RenameError!void {
+ if (builtin.os.tag == .windows) {
+ const old_path_w = try windows.sliceToPrefixedFileW(old_path);
+ const new_path_w = try windows.sliceToPrefixedFileW(new_path);
+ return renameatW(old_dir_fd, &old_path_w, new_dir_fd, &new_path_w, windows.TRUE);
+ } else {
+ const old_path_c = try toPosixPath(old_path);
+ const new_path_c = try toPosixPath(new_path);
+ return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c);
+ }
+}
+
+/// Same as `renameat` except the parameters are null-terminated byte arrays.
+pub fn renameatZ(
+ old_dir_fd: fd_t,
+ old_path: [*:0]const u8,
+ new_dir_fd: fd_t,
+ new_path: [*:0]const u8,
+) RenameError!void {
+ if (builtin.os.tag == .windows) {
+ const old_path_w = try windows.cStrToPrefixedFileW(old_path);
+ const new_path_w = try windows.cStrToPrefixedFileW(new_path);
+ return renameatW(old_dir_fd, &old_path_w, new_dir_fd, &new_path_w, windows.TRUE);
+ }
+
+ switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
+ 0 => return,
+ EACCES => return error.AccessDenied,
+ EPERM => return error.AccessDenied,
+ EBUSY => return error.FileBusy,
+ EDQUOT => return error.DiskQuota,
+ EFAULT => unreachable,
+ EINVAL => unreachable,
+ EISDIR => return error.IsDir,
+ ELOOP => return error.SymLinkLoop,
+ EMLINK => return error.LinkQuotaExceeded,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOTDIR => return error.NotDir,
+ ENOMEM => return error.SystemResources,
+ ENOSPC => return error.NoSpaceLeft,
+ EEXIST => return error.PathAlreadyExists,
+ ENOTEMPTY => return error.PathAlreadyExists,
+ EROFS => return error.ReadOnlyFileSystem,
+ EXDEV => return error.RenameAcrossMountPoints,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+/// Same as `renameat` except the parameters are null-terminated UTF16LE encoded byte arrays.
+/// Assumes target is Windows.
+/// TODO these args can actually be slices when using ntdll. audit the rest of the W functions too.
+pub fn renameatW(
+ old_dir_fd: fd_t,
+ old_path: [*:0]const u16,
+ new_dir_fd: fd_t,
+ new_path_w: [*:0]const u16,
+ ReplaceIfExists: windows.BOOLEAN,
+) RenameError!void {
+ const access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE;
+ const src_fd = try windows.OpenFileW(old_dir_fd, old_path, null, access_mask, windows.FILE_OPEN);
+ defer windows.CloseHandle(src_fd);
+
+ const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (MAX_PATH_BYTES - 1);
+ var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined;
+ const new_path = mem.span(new_path_w);
+ const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path.len * 2;
+ if (struct_len > struct_buf_len) return error.NameTooLong;
+
+ const rename_info = @ptrCast(*windows.FILE_RENAME_INFORMATION, &rename_info_buf);
+
+ rename_info.* = .{
+ .ReplaceIfExists = ReplaceIfExists,
+ .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(new_path_w)) null else new_dir_fd,
+ .FileNameLength = @intCast(u32, new_path.len * 2), // already checked error.NameTooLong
+ .FileName = undefined,
+ };
+ std.mem.copy(u16, @as([*]u16, &rename_info.FileName)[0..new_path.len], new_path);
+
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
+
+ const rc = windows.ntdll.NtSetInformationFile(
+ src_fd,
+ &io_status_block,
+ rename_info,
+ @intCast(u32, struct_len), // already checked for error.NameTooLong
+ .FileRenameInformation,
+ );
+
+ switch (rc) {
+ .SUCCESS => return,
+ .INVALID_HANDLE => unreachable,
+ else => return windows.unexpectedStatus(rc),
+ }
+}
+
pub const MakeDirError = error{
AccessDenied,
DiskQuota,
@@ -3125,6 +3232,7 @@ pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
}
/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
+/// TODO use ntdll for better semantics
pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
const h_file = try windows.CreateFileW(
pathname,
src/main.cpp
@@ -1290,6 +1290,7 @@ static int main0(int argc, char **argv) {
if (g->enable_cache) {
#if defined(ZIG_OS_WINDOWS)
buf_replace(&g->bin_file_output_path, '/', '\\');
+ buf_replace(g->output_dir, '/', '\\');
#endif
if (final_output_dir_step != nullptr) {
Buf *dest_basename = buf_alloc();
@@ -1303,7 +1304,7 @@ static int main0(int argc, char **argv) {
return main_exit(root_progress_node, EXIT_FAILURE);
}
} else {
- if (g->emit_bin && printf("%s\n", buf_ptr(&g->bin_file_output_path)) < 0)
+ if (printf("%s\n", buf_ptr(g->output_dir)) < 0)
return main_exit(root_progress_node, EXIT_FAILURE);
}
}