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
master
Andrew Kelley 2020-03-13 21:06:07 -04:00
parent eb4d313dbc
commit 66e76a0209
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
8 changed files with 333 additions and 158 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}
/// 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 {
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);
return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, .{});
}
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;
/// Deprecated; use `Dir.updateFile`.
pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !Dir.PrevStatus {
const my_cwd = cwd();
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.

View File

@ -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,

View File

@ -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,
);
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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);
}
}