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 #4733master
parent
eb4d313dbc
commit
66e76a0209
|
@ -2144,17 +2144,22 @@ pub const LibExeObjStep = struct {
|
||||||
try zig_args.append("--cache");
|
try zig_args.append("--cache");
|
||||||
try zig_args.append("on");
|
try zig_args.append("on");
|
||||||
|
|
||||||
const output_path_nl = try builder.execFromStep(zig_args.toSliceConst(), &self.step);
|
const output_dir_nl = try builder.execFromStep(zig_args.toSliceConst(), &self.step);
|
||||||
const output_path = mem.trimRight(u8, output_path_nl, "\r\n");
|
const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n");
|
||||||
|
|
||||||
if (self.output_dir) |output_dir| {
|
if (self.output_dir) |output_dir| {
|
||||||
const full_dest = try fs.path.join(builder.allocator, &[_][]const u8{
|
var src_dir = try std.fs.cwd().openDirTraverse(build_output_dir);
|
||||||
output_dir,
|
defer src_dir.close();
|
||||||
fs.path.basename(output_path),
|
|
||||||
});
|
var dest_dir = try std.fs.cwd().openDirList(output_dir);
|
||||||
try builder.updateFile(output_path, full_dest);
|
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 {
|
} else {
|
||||||
self.output_dir = fs.path.dirname(output_path).?;
|
self.output_dir = build_output_dir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 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 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 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 chdir(path: [*:0]const u8) c_int;
|
||||||
pub extern "c" fn fchdir(fd: fd_t) 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;
|
pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;
|
||||||
|
|
261
lib/std/fs.zig
261
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
|
pub const PrevStatus = enum {
|
||||||
const PrevStatus = enum {
|
|
||||||
stale,
|
stale,
|
||||||
fresh,
|
fresh,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Deprecated; use `Dir.updateFile`.
|
||||||
pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus {
|
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.
|
/// Deprecated; use `Dir.updateFile`.
|
||||||
/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
|
pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !Dir.PrevStatus {
|
||||||
/// 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();
|
const my_cwd = cwd();
|
||||||
|
return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, .{ .override_mode = mode });
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Guaranteed to be atomic.
|
/// 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();
|
return atomic_file.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO update this API to avoid a getrandom syscall for every operation. It
|
/// TODO update this API to avoid a getrandom syscall for every operation.
|
||||||
/// should accept a random interface.
|
|
||||||
/// TODO rework this to integrate with Dir
|
|
||||||
pub const AtomicFile = struct {
|
pub const AtomicFile = struct {
|
||||||
file: File,
|
file: File,
|
||||||
tmp_path_buf: [MAX_PATH_BYTES]u8,
|
tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8,
|
||||||
dest_path: []const u8,
|
dest_path: []const u8,
|
||||||
finished: bool,
|
file_open: bool,
|
||||||
|
file_exists: bool,
|
||||||
|
dir: Dir,
|
||||||
|
|
||||||
const InitError = File.OpenError;
|
const InitError = File.OpenError;
|
||||||
|
|
||||||
/// dest_path must remain valid for the lifetime of AtomicFile
|
/// TODO rename this. Callers should go through Dir API
|
||||||
/// call finish to atomically replace dest_path with contents
|
pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir) InitError!AtomicFile {
|
||||||
pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
|
|
||||||
const dirname = path.dirname(dest_path);
|
const dirname = path.dirname(dest_path);
|
||||||
var rand_buf: [12]u8 = undefined;
|
var rand_buf: [12]u8 = undefined;
|
||||||
const dirname_component_len = if (dirname) |d| d.len + 1 else 0;
|
const dirname_component_len = if (dirname) |d| d.len + 1 else 0;
|
||||||
const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);
|
const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);
|
||||||
const tmp_path_len = dirname_component_len + encoded_rand_len;
|
const tmp_path_len = dirname_component_len + encoded_rand_len;
|
||||||
var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined;
|
var tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8 = undefined;
|
||||||
if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong;
|
if (tmp_path_len > tmp_path_buf.len) return error.NameTooLong;
|
||||||
|
|
||||||
if (dirname) |dir| {
|
if (dirname) |dn| {
|
||||||
mem.copy(u8, tmp_path_buf[0..], dir);
|
mem.copy(u8, tmp_path_buf[0..], dn);
|
||||||
tmp_path_buf[dir.len] = path.sep;
|
tmp_path_buf[dn.len] = path.sep;
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp_path_buf[tmp_path_len] = 0;
|
tmp_path_buf[tmp_path_len] = 0;
|
||||||
const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0];
|
const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0];
|
||||||
|
|
||||||
const my_cwd = cwd();
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try crypto.randomBytes(rand_buf[0..]);
|
try crypto.randomBytes(rand_buf[0..]);
|
||||||
base64_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf);
|
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,
|
tmp_path_slice,
|
||||||
.{ .mode = mode, .exclusive = true },
|
.{ .mode = mode, .exclusive = true },
|
||||||
) catch |err| switch (err) {
|
) catch |err| switch (err) {
|
||||||
|
@ -220,33 +178,46 @@ pub const AtomicFile = struct {
|
||||||
.file = file,
|
.file = file,
|
||||||
.tmp_path_buf = tmp_path_buf,
|
.tmp_path_buf = tmp_path_buf,
|
||||||
.dest_path = dest_path,
|
.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()
|
/// always call deinit, even after successful finish()
|
||||||
pub fn deinit(self: *AtomicFile) void {
|
pub fn deinit(self: *AtomicFile) void {
|
||||||
if (!self.finished) {
|
if (self.file_open) {
|
||||||
self.file.close();
|
self.file.close();
|
||||||
cwd().deleteFileC(@ptrCast([*:0]u8, &self.tmp_path_buf)) catch {};
|
self.file_open = false;
|
||||||
self.finished = true;
|
|
||||||
}
|
}
|
||||||
|
if (self.file_exists) {
|
||||||
|
self.dir.deleteFileC(&self.tmp_path_buf) catch {};
|
||||||
|
self.file_exists = false;
|
||||||
|
}
|
||||||
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish(self: *AtomicFile) !void {
|
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) {
|
if (std.Target.current.os.tag == .windows) {
|
||||||
const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);
|
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));
|
const tmp_path_w = try os.windows.cStrToPrefixedFileW(&self.tmp_path_buf);
|
||||||
self.file.close();
|
try os.renameatW(self.dir.fd, &tmp_path_w, self.dir.fd, &dest_path_w, os.windows.TRUE);
|
||||||
self.finished = true;
|
self.file_exists = false;
|
||||||
return os.renameW(&tmp_path_w, &dest_path_w);
|
|
||||||
} else {
|
} else {
|
||||||
const dest_path_c = try os.toPosixPath(self.dest_path);
|
const dest_path_c = try os.toPosixPath(self.dest_path);
|
||||||
self.file.close();
|
try os.renameatZ(self.dir.fd, &self.tmp_path_buf, self.dir.fd, &dest_path_c);
|
||||||
self.finished = true;
|
self.file_exists = false;
|
||||||
return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -694,7 +665,10 @@ pub const Dir = struct {
|
||||||
const access_mask = w.SYNCHRONIZE |
|
const access_mask = w.SYNCHRONIZE |
|
||||||
(if (flags.read) @as(u32, w.GENERIC_READ) else 0) |
|
(if (flags.read) @as(u32, w.GENERIC_READ) else 0) |
|
||||||
(if (flags.write) @as(u32, w.GENERIC_WRITE) 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.
|
/// Creates, opens, or overwrites a file with write access.
|
||||||
|
@ -739,7 +713,10 @@ pub const Dir = struct {
|
||||||
@as(u32, w.FILE_OVERWRITE_IF)
|
@as(u32, w.FILE_OVERWRITE_IF)
|
||||||
else
|
else
|
||||||
@as(u32, w.FILE_OPEN_IF);
|
@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.
|
/// Deprecated; call `openFile` directly.
|
||||||
|
@ -757,72 +734,6 @@ pub const Dir = struct {
|
||||||
return self.openFileW(sub_path, .{});
|
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 {
|
pub fn makeDir(self: Dir, sub_path: []const u8) !void {
|
||||||
try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
|
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.
|
/// Call `close` on the result when done.
|
||||||
///
|
///
|
||||||
/// Asserts that the path parameter has no null bytes.
|
/// 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 {
|
pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir {
|
||||||
if (builtin.os.tag == .windows) {
|
if (builtin.os.tag == .windows) {
|
||||||
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
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.
|
/// Call `close` on the result when done.
|
||||||
///
|
///
|
||||||
/// Asserts that the path parameter has no null bytes.
|
/// 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 {
|
pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir {
|
||||||
if (builtin.os.tag == .windows) {
|
if (builtin.os.tag == .windows) {
|
||||||
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
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 {
|
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);
|
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.
|
/// Returns an handle to the current working directory that is open for traversal.
|
||||||
|
|
114
lib/std/os.zig
114
lib/std/os.zig
|
@ -461,13 +461,11 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (rc) {
|
switch (rc) {
|
||||||
.SUCCESS => {},
|
.SUCCESS => return,
|
||||||
.INVALID_HANDLE => unreachable, // Handle not open for writing
|
.INVALID_HANDLE => unreachable, // Handle not open for writing
|
||||||
.ACCESS_DENIED => return error.CannotTruncate,
|
.ACCESS_DENIED => return error.CannotTruncate,
|
||||||
else => return windows.unexpectedStatus(rc),
|
else => return windows.unexpectedStatus(rc),
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -852,6 +850,7 @@ pub const OpenError = error{
|
||||||
|
|
||||||
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||||||
/// See also `openC`.
|
/// See also `openC`.
|
||||||
|
/// TODO support windows
|
||||||
pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
|
pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
|
||||||
const file_path_c = try toPosixPath(file_path);
|
const file_path_c = try toPosixPath(file_path);
|
||||||
return openC(&file_path_c, flags, perm);
|
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.
|
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||||||
/// See also `open`.
|
/// See also `open`.
|
||||||
|
/// TODO support windows
|
||||||
pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
|
pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
|
||||||
while (true) {
|
while (true) {
|
||||||
const rc = system.open(file_path, flags, perm);
|
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.
|
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||||||
/// `file_path` is relative to the open directory handle `dir_fd`.
|
/// `file_path` is relative to the open directory handle `dir_fd`.
|
||||||
/// See also `openatC`.
|
/// 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 {
|
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);
|
const file_path_c = try toPosixPath(file_path);
|
||||||
return openatC(dir_fd, &file_path_c, flags, mode);
|
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.
|
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||||||
/// `file_path` is relative to the open directory handle `dir_fd`.
|
/// `file_path` is relative to the open directory handle `dir_fd`.
|
||||||
/// See also `openat`.
|
/// 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 {
|
pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
|
||||||
while (true) {
|
while (true) {
|
||||||
const rc = system.openat(dir_fd, file_path, flags, mode);
|
const rc = system.openat(dir_fd, file_path, flags, mode);
|
||||||
|
@ -1527,6 +1529,9 @@ const RenameError = error{
|
||||||
RenameAcrossMountPoints,
|
RenameAcrossMountPoints,
|
||||||
InvalidUtf8,
|
InvalidUtf8,
|
||||||
BadPathName,
|
BadPathName,
|
||||||
|
NoDevice,
|
||||||
|
SharingViolation,
|
||||||
|
PipeBusy,
|
||||||
} || UnexpectedError;
|
} || UnexpectedError;
|
||||||
|
|
||||||
/// Change the name or location of a file.
|
/// 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);
|
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{
|
pub const MakeDirError = error{
|
||||||
AccessDenied,
|
AccessDenied,
|
||||||
DiskQuota,
|
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.
|
/// 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 {
|
pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
|
||||||
const h_file = try windows.CreateFileW(
|
const h_file = try windows.CreateFileW(
|
||||||
pathname,
|
pathname,
|
||||||
|
|
|
@ -465,17 +465,17 @@ pub fn renameat(oldfd: i32, oldpath: [*]const u8, newfd: i32, newpath: [*]const
|
||||||
return syscall4(
|
return syscall4(
|
||||||
SYS_renameat,
|
SYS_renameat,
|
||||||
@bitCast(usize, @as(isize, oldfd)),
|
@bitCast(usize, @as(isize, oldfd)),
|
||||||
@ptrToInt(old),
|
@ptrToInt(oldpath),
|
||||||
@bitCast(usize, @as(isize, newfd)),
|
@bitCast(usize, @as(isize, newfd)),
|
||||||
@ptrToInt(new),
|
@ptrToInt(newpath),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return syscall5(
|
return syscall5(
|
||||||
SYS_renameat2,
|
SYS_renameat2,
|
||||||
@bitCast(usize, @as(isize, oldfd)),
|
@bitCast(usize, @as(isize, oldfd)),
|
||||||
@ptrToInt(old),
|
@ptrToInt(oldpath),
|
||||||
@bitCast(usize, @as(isize, newfd)),
|
@bitCast(usize, @as(isize, newfd)),
|
||||||
@ptrToInt(new),
|
@ptrToInt(newpath),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,82 @@ pub fn CreateFileW(
|
||||||
return result;
|
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 const CreatePipeError = error{Unexpected};
|
||||||
|
|
||||||
pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
|
pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
|
||||||
|
|
|
@ -242,6 +242,13 @@ pub const FILE_NAME_INFORMATION = extern struct {
|
||||||
FileName: [1]WCHAR,
|
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 {
|
pub const IO_STATUS_BLOCK = extern struct {
|
||||||
// "DUMMYUNIONNAME" expands to "u"
|
// "DUMMYUNIONNAME" expands to "u"
|
||||||
u: extern union {
|
u: extern union {
|
||||||
|
|
|
@ -1290,6 +1290,7 @@ static int main0(int argc, char **argv) {
|
||||||
if (g->enable_cache) {
|
if (g->enable_cache) {
|
||||||
#if defined(ZIG_OS_WINDOWS)
|
#if defined(ZIG_OS_WINDOWS)
|
||||||
buf_replace(&g->bin_file_output_path, '/', '\\');
|
buf_replace(&g->bin_file_output_path, '/', '\\');
|
||||||
|
buf_replace(g->output_dir, '/', '\\');
|
||||||
#endif
|
#endif
|
||||||
if (final_output_dir_step != nullptr) {
|
if (final_output_dir_step != nullptr) {
|
||||||
Buf *dest_basename = buf_alloc();
|
Buf *dest_basename = buf_alloc();
|
||||||
|
@ -1303,7 +1304,7 @@ static int main0(int argc, char **argv) {
|
||||||
return main_exit(root_progress_node, EXIT_FAILURE);
|
return main_exit(root_progress_node, EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
return main_exit(root_progress_node, EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue