mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 02:24:33 +01:00
Merge pull request 'std: rework atomic file / temp file API' (#30686) from std.Io.File.Atomic into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30686
This commit is contained in:
commit
c906f7d2e7
13 changed files with 449 additions and 206 deletions
|
|
@ -667,6 +667,7 @@ pub const VTable = struct {
|
|||
dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat,
|
||||
dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void,
|
||||
dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File,
|
||||
dirCreateFileAtomic: *const fn (?*anyopaque, Dir, []const u8, Dir.CreateFileAtomicOptions) Dir.CreateFileAtomicError!File.Atomic,
|
||||
dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File,
|
||||
dirClose: *const fn (?*anyopaque, []const Dir) void,
|
||||
dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize,
|
||||
|
|
@ -710,6 +711,7 @@ pub const VTable = struct {
|
|||
fileUnlock: *const fn (?*anyopaque, File) void,
|
||||
fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void,
|
||||
fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize,
|
||||
fileHardLink: *const fn (?*anyopaque, File, Dir, []const u8, File.HardLinkOptions) File.HardLinkError!void,
|
||||
|
||||
processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
|
||||
processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
|
||||
|
|
|
|||
|
|
@ -454,7 +454,6 @@ pub const OpenError = error{
|
|||
SystemFdQuotaExceeded,
|
||||
NoDevice,
|
||||
SystemResources,
|
||||
DeviceBusy,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
} || PathNameError || Io.Cancelable || Io.UnexpectedError;
|
||||
|
|
@ -598,30 +597,29 @@ pub fn updateFile(
|
|||
}
|
||||
}
|
||||
|
||||
if (path.dirname(dest_path)) |dirname| {
|
||||
try dest_dir.createDirPath(io, dirname);
|
||||
}
|
||||
|
||||
var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
|
||||
var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{
|
||||
.permissions = actual_permissions,
|
||||
.write_buffer = &buffer,
|
||||
.make_path = true,
|
||||
.replace = true,
|
||||
});
|
||||
defer atomic_file.deinit();
|
||||
defer atomic_file.deinit(io);
|
||||
|
||||
var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var file_writer = atomic_file.file.writer(io, &buffer);
|
||||
|
||||
var src_reader: File.Reader = .initSize(src_file, io, &.{}, src_stat.size);
|
||||
const dest_writer = &atomic_file.file_writer.interface;
|
||||
const dest_writer = &file_writer.interface;
|
||||
|
||||
_ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
|
||||
error.ReadFailed => return src_reader.err.?,
|
||||
error.WriteFailed => return atomic_file.file_writer.err.?,
|
||||
error.WriteFailed => return file_writer.err.?,
|
||||
};
|
||||
try atomic_file.flush();
|
||||
try atomic_file.file_writer.file.setTimestamps(io, .{
|
||||
try file_writer.flush();
|
||||
try file_writer.file.setTimestamps(io, .{
|
||||
.access_timestamp = .init(src_stat.atime),
|
||||
.modify_timestamp = .init(src_stat.mtime),
|
||||
});
|
||||
try atomic_file.renameIntoPlace();
|
||||
try atomic_file.replace(io);
|
||||
return .stale;
|
||||
}
|
||||
|
||||
|
|
@ -995,27 +993,9 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename
|
|||
return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);
|
||||
}
|
||||
|
||||
pub const HardLinkOptions = struct {
|
||||
follow_symlinks: bool = true,
|
||||
};
|
||||
pub const HardLinkOptions = File.HardLinkOptions;
|
||||
|
||||
pub const HardLinkError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
DiskQuota,
|
||||
PathAlreadyExists,
|
||||
HardwareFailure,
|
||||
/// Either the OS or the filesystem does not support hard links.
|
||||
OperationUnsupported,
|
||||
SymLinkLoop,
|
||||
LinkQuotaExceeded,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
NoSpaceLeft,
|
||||
ReadOnlyFileSystem,
|
||||
NotSameFileSystem,
|
||||
NotDir,
|
||||
} || Io.Cancelable || PathNameError || Io.UnexpectedError;
|
||||
pub const HardLinkError = File.HardLinkError;
|
||||
|
||||
pub fn hardLink(
|
||||
old_dir: Dir,
|
||||
|
|
@ -1251,7 +1231,6 @@ pub const DeleteTreeError = error{
|
|||
ReadOnlyFileSystem,
|
||||
FileSystem,
|
||||
FileBusy,
|
||||
DeviceBusy,
|
||||
/// One of the path components was not a directory.
|
||||
/// This error is unreachable if `sub_path` does not contain a path separator.
|
||||
NotDir,
|
||||
|
|
@ -1322,7 +1301,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
|
|||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
|
|
@ -1417,7 +1395,6 @@ pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
|
|||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
|
|
@ -1522,7 +1499,6 @@ fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8,
|
|||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
|
|
@ -1619,7 +1595,6 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin
|
|||
error.SystemResources,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.DeviceBusy,
|
||||
error.NetworkNotFound,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
|
|
@ -1658,15 +1633,18 @@ fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hin
|
|||
pub const CopyFileOptions = struct {
|
||||
/// When this is `null` the permissions are copied from the source file.
|
||||
permissions: ?File.Permissions = null,
|
||||
make_path: bool = false,
|
||||
replace: bool = true,
|
||||
};
|
||||
|
||||
pub const CopyFileError = File.OpenError || File.StatError ||
|
||||
File.Atomic.InitError || File.Atomic.FinishError ||
|
||||
CreateFileAtomicError || File.Atomic.ReplaceError || File.Atomic.LinkError ||
|
||||
File.Reader.Error || File.Writer.Error || error{InvalidFileName};
|
||||
|
||||
/// Atomically creates a new file at `dest_path` within `dest_dir` with the
|
||||
/// same contents as `source_path` within `source_dir`, overwriting any already
|
||||
/// existing file.
|
||||
/// same contents as `source_path` within `source_dir`.
|
||||
///
|
||||
/// Whether to overwrite the existing file is determined by `options`.
|
||||
///
|
||||
/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
|
||||
/// readily available, there is a possibility of power loss or application
|
||||
|
|
@ -1695,19 +1673,27 @@ pub fn copyFile(
|
|||
break :blk st.permissions;
|
||||
};
|
||||
|
||||
var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
|
||||
var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{
|
||||
.permissions = permissions,
|
||||
.write_buffer = &buffer,
|
||||
.make_path = options.make_path,
|
||||
.replace = options.replace,
|
||||
});
|
||||
defer atomic_file.deinit();
|
||||
defer atomic_file.deinit(io);
|
||||
|
||||
_ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
|
||||
var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var file_writer = atomic_file.file.writer(io, &buffer);
|
||||
|
||||
_ = file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
|
||||
error.ReadFailed => return file_reader.err.?,
|
||||
error.WriteFailed => return atomic_file.file_writer.err.?,
|
||||
error.WriteFailed => return file_writer.err.?,
|
||||
};
|
||||
|
||||
try atomic_file.finish();
|
||||
try file_writer.flush();
|
||||
|
||||
switch (options.replace) {
|
||||
true => try atomic_file.replace(io),
|
||||
false => try atomic_file.link(io),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `copyFile`, except asserts that both `source_path` and `dest_path`
|
||||
|
|
@ -1730,33 +1716,65 @@ pub fn copyFileAbsolute(
|
|||
|
||||
test copyFileAbsolute {}
|
||||
|
||||
pub const AtomicFileOptions = struct {
|
||||
pub const CreateFileAtomicOptions = struct {
|
||||
permissions: File.Permissions = .default_file,
|
||||
make_path: bool = false,
|
||||
write_buffer: []u8,
|
||||
/// Tells whether the unnamed file will be ultimately created with
|
||||
/// `File.Atomic.link` or `File.Atomic.replace`.
|
||||
///
|
||||
/// If this value is incorrect it will cause an assertion failure in
|
||||
/// `File.Atomic.replace`.
|
||||
replace: bool = false,
|
||||
};
|
||||
|
||||
/// Directly access the `.file` field, and then call `File.Atomic.finish` to
|
||||
/// atomically replace `dest_path` with contents.
|
||||
///
|
||||
/// Always call `File.Atomic.deinit` to clean up, regardless of whether
|
||||
/// `File.Atomic.finish` succeeded. `dest_path` must remain valid until
|
||||
/// `File.Atomic.deinit` is called.
|
||||
///
|
||||
/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `dest_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic {
|
||||
if (path.dirname(dest_path)) |dirname| {
|
||||
const dir = if (options.make_path)
|
||||
try parent.createDirPathOpen(io, dirname, .{})
|
||||
else
|
||||
try parent.openDir(io, dirname, .{});
|
||||
pub const CreateFileAtomicError = error{
|
||||
NoDevice,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
/// On Windows, antivirus software is enabled by default. It can be
|
||||
/// disabled, but Windows Update sometimes ignores the user's preference
|
||||
/// and re-enables it. When enabled, antivirus software on Windows
|
||||
/// intercepts file system operations and makes them significantly slower
|
||||
/// in addition to possibly failing with this error code.
|
||||
AntivirusInterference,
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to open a new resource relative to it.
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
SymLinkLoop,
|
||||
ProcessFdQuotaExceeded,
|
||||
SystemFdQuotaExceeded,
|
||||
/// Either:
|
||||
/// * One of the path components does not exist.
|
||||
/// * Cwd was used, but cwd has been deleted.
|
||||
/// * The path associated with the open directory handle has been deleted.
|
||||
FileNotFound,
|
||||
/// Insufficient kernel memory was available.
|
||||
SystemResources,
|
||||
/// A new path cannot be created because the device has no room for the new file.
|
||||
NoSpaceLeft,
|
||||
/// A component used as a directory in the path was not, in fact, a directory.
|
||||
NotDir,
|
||||
WouldBlock,
|
||||
ReadOnlyFileSystem,
|
||||
} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
return .init(io, path.basename(dest_path), options.permissions, dir, true, options.write_buffer);
|
||||
} else {
|
||||
return .init(io, dest_path, options.permissions, parent, false, options.write_buffer);
|
||||
}
|
||||
/// Create an unnamed ephemeral file that can eventually be atomically
|
||||
/// materialized into `sub_path`.
|
||||
///
|
||||
/// The returned `File.Atomic` provides API to emulate the behavior in case it
|
||||
/// is not directly supported by the underlying operating system.
|
||||
///
|
||||
/// * On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// * On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn createFileAtomic(
|
||||
dir: Dir,
|
||||
io: Io,
|
||||
sub_path: []const u8,
|
||||
options: CreateFileAtomicOptions,
|
||||
) CreateFileAtomicError!File.Atomic {
|
||||
return io.vtable.dirCreateFileAtomic(io.userdata, dir, sub_path, options);
|
||||
}
|
||||
|
||||
pub const SetPermissionsError = File.SetPermissionsError;
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ pub const OpenError = error{
|
|||
FileBusy,
|
||||
/// Non-blocking was requested and the operation cannot return immediately.
|
||||
WouldBlock,
|
||||
} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
|
||||
} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
pub fn close(file: File, io: Io) void {
|
||||
return io.vtable.fileClose(io.userdata, (&file)[0..1]);
|
||||
|
|
@ -708,6 +708,38 @@ pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize {
|
|||
return io.vtable.fileRealPath(io.userdata, file, out_buffer);
|
||||
}
|
||||
|
||||
pub const HardLinkOptions = struct {
|
||||
follow_symlinks: bool = true,
|
||||
};
|
||||
|
||||
pub const HardLinkError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
DiskQuota,
|
||||
PathAlreadyExists,
|
||||
HardwareFailure,
|
||||
/// Either the OS or the filesystem does not support hard links.
|
||||
OperationUnsupported,
|
||||
SymLinkLoop,
|
||||
LinkQuotaExceeded,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
NoSpaceLeft,
|
||||
ReadOnlyFileSystem,
|
||||
NotSameFileSystem,
|
||||
NotDir,
|
||||
} || Io.Cancelable || Dir.PathNameError || Io.UnexpectedError;
|
||||
|
||||
pub fn hardLink(
|
||||
file: File,
|
||||
io: Io,
|
||||
new_dir: Dir,
|
||||
new_sub_path: []const u8,
|
||||
options: HardLinkOptions,
|
||||
) HardLinkError!void {
|
||||
return io.vtable.fileHardLink(io.userdata, file, new_dir, new_sub_path, options);
|
||||
}
|
||||
|
||||
test {
|
||||
_ = Reader;
|
||||
_ = Writer;
|
||||
|
|
|
|||
|
|
@ -6,97 +6,78 @@ const File = std.Io.File;
|
|||
const Dir = std.Io.Dir;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
file_writer: File.Writer,
|
||||
random_integer: u64,
|
||||
dest_basename: []const u8,
|
||||
file: File,
|
||||
file_basename_hex: u64,
|
||||
file_open: bool,
|
||||
file_exists: bool,
|
||||
close_dir_on_deinit: bool,
|
||||
|
||||
dir: Dir,
|
||||
close_dir_on_deinit: bool,
|
||||
|
||||
dest_sub_path: []const u8,
|
||||
|
||||
pub const InitError = File.OpenError;
|
||||
|
||||
/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
|
||||
pub fn init(
|
||||
io: Io,
|
||||
dest_basename: []const u8,
|
||||
permissions: File.Permissions,
|
||||
dir: Dir,
|
||||
close_dir_on_deinit: bool,
|
||||
write_buffer: []u8,
|
||||
) InitError!Atomic {
|
||||
while (true) {
|
||||
const random_integer = std.crypto.random.int(u64);
|
||||
const tmp_sub_path = std.fmt.hex(random_integer);
|
||||
const file = dir.createFile(io, &tmp_sub_path, .{
|
||||
.permissions = permissions,
|
||||
.exclusive = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
else => |e| return e,
|
||||
};
|
||||
return .{
|
||||
.file_writer = file.writer(io, write_buffer),
|
||||
.random_integer = random_integer,
|
||||
.dest_basename = dest_basename,
|
||||
.file_open = true,
|
||||
.file_exists = true,
|
||||
.close_dir_on_deinit = close_dir_on_deinit,
|
||||
.dir = dir,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Always call deinit, even after a successful finish().
|
||||
pub fn deinit(af: *Atomic) void {
|
||||
const io = af.file_writer.io;
|
||||
|
||||
/// To release all resources, always call `deinit`, even after a successful
|
||||
/// `finish`.
|
||||
pub fn deinit(af: *Atomic, io: Io) void {
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close(io);
|
||||
af.file.close(io);
|
||||
af.file_open = false;
|
||||
}
|
||||
if (af.file_exists) {
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
|
||||
af.dir.deleteFile(io, &tmp_sub_path) catch {};
|
||||
af.file_exists = false;
|
||||
}
|
||||
if (af.close_dir_on_deinit) {
|
||||
af.dir.close(io);
|
||||
af.close_dir_on_deinit = false;
|
||||
}
|
||||
af.* = undefined;
|
||||
}
|
||||
|
||||
pub const FlushError = File.Writer.Error;
|
||||
pub const LinkError = Dir.HardLinkError;
|
||||
|
||||
pub fn flush(af: *Atomic) FlushError!void {
|
||||
af.file_writer.interface.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => return af.file_writer.err.?,
|
||||
};
|
||||
/// Atomically materializes the file into place, failing with
|
||||
/// `error.PathAlreadyExists` if something already exists there.
|
||||
pub fn link(af: *Atomic, io: Io) LinkError!void {
|
||||
if (af.file_exists) {
|
||||
if (af.file_open) {
|
||||
af.file.close(io);
|
||||
af.file_open = false;
|
||||
}
|
||||
const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
|
||||
try af.dir.hardLink(&tmp_sub_path, af.dir, af.dest_sub_path, io, .{});
|
||||
af.dir.deleteFile(io, &tmp_sub_path) catch {};
|
||||
af.file_exists = false;
|
||||
} else {
|
||||
assert(af.file_open);
|
||||
try af.file.hardLink(io, af.dir, af.dest_sub_path, .{});
|
||||
af.file.close(io);
|
||||
af.file_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub const RenameIntoPlaceError = Dir.RenameError;
|
||||
pub const ReplaceError = Dir.RenameError;
|
||||
|
||||
/// Atomically materializes the file into place, replacing any file that
|
||||
/// already exists there.
|
||||
///
|
||||
/// Calling this function requires setting `CreateFileAtomicOptions.replace` to
|
||||
/// `true`.
|
||||
///
|
||||
/// On Windows, this function introduces a period of time where some file
|
||||
/// system operations on the destination file will result in
|
||||
/// `error.AccessDenied`, including rename operations (such as the one used in
|
||||
/// this function).
|
||||
pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void {
|
||||
const io = af.file_writer.io;
|
||||
|
||||
assert(af.file_exists);
|
||||
pub fn replace(af: *Atomic, io: Io) ReplaceError!void {
|
||||
assert(af.file_exists); // Wrong value for `CreateFileAtomicOptions.replace`.
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close(io);
|
||||
af.file.close(io);
|
||||
af.file_open = false;
|
||||
}
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io);
|
||||
const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
|
||||
try af.dir.rename(&tmp_sub_path, af.dir, af.dest_sub_path, io);
|
||||
af.file_exists = false;
|
||||
}
|
||||
|
||||
pub const FinishError = FlushError || RenameIntoPlaceError;
|
||||
|
||||
/// Combination of `flush` followed by `renameIntoPlace`.
|
||||
pub fn finish(af: *Atomic) FinishError!void {
|
||||
try af.flush();
|
||||
try af.renameIntoPlace();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,3 +272,11 @@ pub fn end(w: *Writer) EndError!void {
|
|||
=> {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience method for calling `Io.Writer.flush` and returning the
|
||||
/// underlying error.
|
||||
pub fn flush(w: *Writer) Error!void {
|
||||
w.interface.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => return w.err.?,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1403,6 +1403,7 @@ pub fn io(t: *Threaded) Io {
|
|||
.dirStatFile = dirStatFile,
|
||||
.dirAccess = dirAccess,
|
||||
.dirCreateFile = dirCreateFile,
|
||||
.dirCreateFileAtomic = dirCreateFileAtomic,
|
||||
.dirOpenFile = dirOpenFile,
|
||||
.dirOpenDir = dirOpenDir,
|
||||
.dirClose = dirClose,
|
||||
|
|
@ -1445,6 +1446,7 @@ pub fn io(t: *Threaded) Io {
|
|||
.fileUnlock = fileUnlock,
|
||||
.fileDowngradeLock = fileDowngradeLock,
|
||||
.fileRealPath = fileRealPath,
|
||||
.fileHardLink = fileHardLink,
|
||||
|
||||
.processExecutableOpen = processExecutableOpen,
|
||||
.processExecutablePath = processExecutablePath,
|
||||
|
|
@ -1549,6 +1551,7 @@ pub fn ioBasic(t: *Threaded) Io {
|
|||
.dirStatFile = dirStatFile,
|
||||
.dirAccess = dirAccess,
|
||||
.dirCreateFile = dirCreateFile,
|
||||
.dirCreateFileAtomic = dirCreateFileAtomic,
|
||||
.dirOpenFile = dirOpenFile,
|
||||
.dirOpenDir = dirOpenDir,
|
||||
.dirClose = dirClose,
|
||||
|
|
@ -1591,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io {
|
|||
.fileUnlock = fileUnlock,
|
||||
.fileDowngradeLock = fileDowngradeLock,
|
||||
.fileRealPath = fileRealPath,
|
||||
.fileHardLink = fileHardLink,
|
||||
|
||||
.processExecutableOpen = processExecutableOpen,
|
||||
.processExecutablePath = processExecutablePath,
|
||||
|
|
@ -3413,6 +3417,170 @@ fn dirCreateFileWasi(
|
|||
}
|
||||
}
|
||||
|
||||
fn dirCreateFileAtomic(
|
||||
userdata: ?*anyopaque,
|
||||
dir: Dir,
|
||||
dest_path: []const u8,
|
||||
options: Dir.CreateFileAtomicOptions,
|
||||
) Dir.CreateFileAtomicError!File.Atomic {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const t_io = ioBasic(t);
|
||||
|
||||
// Linux has O_TMPFILE, but linkat() does not support AT_REPLACE, so it's
|
||||
// useless when we have to make up a bogus path name to do the rename()
|
||||
// anyway.
|
||||
if (native_os == .linux and !options.replace) tmpfile: {
|
||||
const flags: posix.O = if (@hasField(posix.O, "TMPFILE")) .{
|
||||
.ACCMODE = .RDWR,
|
||||
.TMPFILE = true,
|
||||
.DIRECTORY = true,
|
||||
.CLOEXEC = true,
|
||||
} else if (@hasField(posix.O, "TMPFILE0") and !@hasField(posix.O, "TMPFILE2")) .{
|
||||
.ACCMODE = .RDWR,
|
||||
.TMPFILE0 = true,
|
||||
.TMPFILE1 = true,
|
||||
.DIRECTORY = true,
|
||||
.CLOEXEC = true,
|
||||
} else break :tmpfile;
|
||||
|
||||
const dest_dirname = Dir.path.dirname(dest_path);
|
||||
if (dest_dirname) |dirname| {
|
||||
// This has a nice side effect of preemptively triggering EISDIR or
|
||||
// ENOENT, avoiding the ambiguity below.
|
||||
dir.createDirPath(t_io, dirname) catch |err| switch (err) {
|
||||
// None of these make sense in this context.
|
||||
error.IsDir,
|
||||
error.Streaming,
|
||||
error.DiskQuota,
|
||||
error.PathAlreadyExists,
|
||||
error.LinkQuotaExceeded,
|
||||
error.SharingViolation,
|
||||
error.PipeBusy,
|
||||
error.FileTooBig,
|
||||
error.DeviceBusy,
|
||||
error.FileLocksUnsupported,
|
||||
error.FileBusy,
|
||||
=> return error.Unexpected,
|
||||
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
var path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
const sub_path_posix = try pathToPosix(dest_dirname orelse ".", &path_buffer);
|
||||
|
||||
const syscall: Syscall = try .start();
|
||||
while (true) {
|
||||
const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode());
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => {
|
||||
syscall.finish();
|
||||
return .{
|
||||
.file = .{ .handle = @intCast(rc) },
|
||||
.file_basename_hex = 0,
|
||||
.dest_sub_path = dest_path,
|
||||
.file_open = true,
|
||||
.file_exists = false,
|
||||
.close_dir_on_deinit = false,
|
||||
.dir = dir,
|
||||
};
|
||||
},
|
||||
.INTR => {
|
||||
try syscall.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.ISDIR, .NOENT => {
|
||||
// Ambiguous error code. It might mean the file system
|
||||
// does not support O_TMPFILE. Therefore, we must fall
|
||||
// back to not using O_TMPFILE.
|
||||
syscall.finish();
|
||||
break :tmpfile;
|
||||
},
|
||||
.INVAL => return syscall.fail(error.BadPathName),
|
||||
.ACCES => return syscall.fail(error.AccessDenied),
|
||||
.LOOP => return syscall.fail(error.SymLinkLoop),
|
||||
.MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
|
||||
.NAMETOOLONG => return syscall.fail(error.NameTooLong),
|
||||
.NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
|
||||
.NODEV => return syscall.fail(error.NoDevice),
|
||||
.NOMEM => return syscall.fail(error.SystemResources),
|
||||
.NOSPC => return syscall.fail(error.NoSpaceLeft),
|
||||
.NOTDIR => return syscall.fail(error.NotDir),
|
||||
.PERM => return syscall.fail(error.PermissionDenied),
|
||||
.AGAIN => return syscall.fail(error.WouldBlock),
|
||||
.NXIO => return syscall.fail(error.NoDevice),
|
||||
.ILSEQ => return syscall.fail(error.BadPathName),
|
||||
else => |err| return syscall.unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Dir.path.dirname(dest_path)) |dirname| {
|
||||
const new_dir = if (options.make_path)
|
||||
dir.createDirPathOpen(t_io, dirname, .{}) catch |err| switch (err) {
|
||||
// None of these make sense in this context.
|
||||
error.IsDir,
|
||||
error.Streaming,
|
||||
error.DiskQuota,
|
||||
error.PathAlreadyExists,
|
||||
error.LinkQuotaExceeded,
|
||||
error.SharingViolation,
|
||||
error.PipeBusy,
|
||||
error.FileTooBig,
|
||||
error.FileLocksUnsupported,
|
||||
error.FileBusy,
|
||||
error.DeviceBusy,
|
||||
=> return error.Unexpected,
|
||||
|
||||
else => |e| return e,
|
||||
}
|
||||
else
|
||||
try dir.openDir(t_io, dirname, .{});
|
||||
|
||||
return atomicFileInit(t_io, Dir.path.basename(dest_path), options.permissions, new_dir, true);
|
||||
}
|
||||
|
||||
return atomicFileInit(t_io, dest_path, options.permissions, dir, false);
|
||||
}
|
||||
|
||||
fn atomicFileInit(
|
||||
t_io: Io,
|
||||
dest_basename: []const u8,
|
||||
permissions: File.Permissions,
|
||||
dir: Dir,
|
||||
close_dir_on_deinit: bool,
|
||||
) Dir.CreateFileAtomicError!File.Atomic {
|
||||
while (true) {
|
||||
const random_integer = std.crypto.random.int(u64);
|
||||
const tmp_sub_path = std.fmt.hex(random_integer);
|
||||
const file = dir.createFile(t_io, &tmp_sub_path, .{
|
||||
.permissions = permissions,
|
||||
.exclusive = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
error.DeviceBusy => continue,
|
||||
error.FileBusy => continue,
|
||||
error.SharingViolation => continue,
|
||||
|
||||
error.IsDir => return error.Unexpected, // No path components.
|
||||
error.FileTooBig => return error.Unexpected, // Creating, not opening.
|
||||
error.FileLocksUnsupported => return error.Unexpected, // Not asking for locks.
|
||||
error.PipeBusy => return error.Unexpected, // Not opening a pipe.
|
||||
|
||||
else => |e| return e,
|
||||
};
|
||||
return .{
|
||||
.file = file,
|
||||
.file_basename_hex = random_integer,
|
||||
.dest_sub_path = dest_basename,
|
||||
.file_open = true,
|
||||
.file_exists = true,
|
||||
.close_dir_on_deinit = close_dir_on_deinit,
|
||||
.dir = dir,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const dirOpenFile = switch (native_os) {
|
||||
.windows => dirOpenFileWindows,
|
||||
.wasi => dirOpenFileWasi,
|
||||
|
|
@ -3925,7 +4093,7 @@ fn dirOpenDirPosix(
|
|||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.DeviceBusy,
|
||||
.BUSY => |err| return errnoBug(err), // O_EXCL not passed
|
||||
.NXIO => return error.NoDevice,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
|
|
@ -4985,6 +5153,64 @@ fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
|
|||
comptime unreachable;
|
||||
}
|
||||
|
||||
fn fileHardLink(
|
||||
userdata: ?*anyopaque,
|
||||
file: File,
|
||||
new_dir: Dir,
|
||||
new_sub_path: []const u8,
|
||||
options: File.HardLinkOptions,
|
||||
) File.HardLinkError!void {
|
||||
_ = userdata;
|
||||
if (native_os != .linux) return error.OperationUnsupported;
|
||||
|
||||
var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
|
||||
|
||||
const flags: u32 = if (!options.follow_symlinks)
|
||||
posix.AT.SYMLINK_NOFOLLOW | posix.AT.EMPTY_PATH
|
||||
else
|
||||
posix.AT.EMPTY_PATH;
|
||||
|
||||
return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags);
|
||||
}
|
||||
|
||||
fn linkat(
|
||||
old_dir: posix.fd_t,
|
||||
old_path: [*:0]const u8,
|
||||
new_dir: posix.fd_t,
|
||||
new_path: [*:0]const u8,
|
||||
flags: u32,
|
||||
) File.HardLinkError!void {
|
||||
const syscall: Syscall = try .start();
|
||||
while (true) {
|
||||
switch (posix.errno(posix.system.linkat(old_dir, old_path, new_dir, new_path, flags))) {
|
||||
.SUCCESS => return syscall.finish(),
|
||||
.INTR => {
|
||||
try syscall.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.ACCES => return syscall.fail(error.AccessDenied),
|
||||
.DQUOT => return syscall.fail(error.DiskQuota),
|
||||
.EXIST => return syscall.fail(error.PathAlreadyExists),
|
||||
.IO => return syscall.fail(error.HardwareFailure),
|
||||
.LOOP => return syscall.fail(error.SymLinkLoop),
|
||||
.MLINK => return syscall.fail(error.LinkQuotaExceeded),
|
||||
.NAMETOOLONG => return syscall.fail(error.NameTooLong),
|
||||
.NOENT => return syscall.fail(error.FileNotFound),
|
||||
.NOMEM => return syscall.fail(error.SystemResources),
|
||||
.NOSPC => return syscall.fail(error.NoSpaceLeft),
|
||||
.NOTDIR => return syscall.fail(error.NotDir),
|
||||
.PERM => return syscall.fail(error.PermissionDenied),
|
||||
.ROFS => return syscall.fail(error.ReadOnlyFileSystem),
|
||||
.XDEV => return syscall.fail(error.NotSameFileSystem),
|
||||
.ILSEQ => return syscall.fail(error.BadPathName),
|
||||
.FAULT => |err| return syscall.errnoBug(err),
|
||||
.INVAL => |err| return syscall.errnoBug(err),
|
||||
else => |err| return syscall.unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dirDeleteFile = switch (native_os) {
|
||||
.windows => dirDeleteFileWindows,
|
||||
.wasi => dirDeleteFileWasi,
|
||||
|
|
@ -7325,7 +7551,6 @@ fn dirOpenDirWasi(
|
|||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.DeviceBusy,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
|
|
@ -7401,46 +7626,7 @@ fn dirHardLink(
|
|||
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
|
||||
|
||||
const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
|
||||
|
||||
const syscall: Syscall = try .start();
|
||||
while (true) {
|
||||
switch (posix.errno(posix.system.linkat(
|
||||
old_dir.handle,
|
||||
old_sub_path_posix,
|
||||
new_dir.handle,
|
||||
new_sub_path_posix,
|
||||
flags,
|
||||
))) {
|
||||
.SUCCESS => return syscall.finish(),
|
||||
.INTR => {
|
||||
try syscall.checkCancel();
|
||||
continue;
|
||||
},
|
||||
else => |e| {
|
||||
syscall.finish();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.IO => return error.HardwareFailure,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.MLINK => return error.LinkQuotaExceeded,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.XDEV => return error.NotSameFileSystem,
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags);
|
||||
}
|
||||
|
||||
fn fileClose(userdata: ?*anyopaque, files: []const File) void {
|
||||
|
|
@ -13831,7 +14017,6 @@ fn windowsCreateProcessPathExt(
|
|||
error.NetworkNotFound,
|
||||
error.NameTooLong,
|
||||
error.BadPathName,
|
||||
error.DeviceBusy,
|
||||
=> return error.FileNotFound,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8427,6 +8427,7 @@ pub const O = switch (native_os) {
|
|||
CLOEXEC: bool = false,
|
||||
SYNC: bool = false,
|
||||
PATH: bool = false,
|
||||
/// This is typically invalid without also setting `DIRECTORY`.
|
||||
TMPFILE: bool = false,
|
||||
_: u9 = 0,
|
||||
},
|
||||
|
|
@ -8615,6 +8616,7 @@ pub const O = switch (native_os) {
|
|||
_19: u1 = 0,
|
||||
CLOEXEC: bool = false,
|
||||
PATH: bool = false,
|
||||
/// This is typically invalid without also setting `DIRECTORY`.
|
||||
TMPFILE: bool = false,
|
||||
_: u9 = 0,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1652,11 +1652,10 @@ test "AtomicFile" {
|
|||
;
|
||||
|
||||
{
|
||||
var buffer: [100]u8 = undefined;
|
||||
var af = try ctx.dir.atomicFile(io, test_out_file, .{ .write_buffer = &buffer });
|
||||
defer af.deinit();
|
||||
try af.file_writer.interface.writeAll(test_content);
|
||||
try af.finish();
|
||||
var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = true });
|
||||
defer af.deinit(io);
|
||||
try af.file.writeStreamingAll(io, test_content);
|
||||
try af.replace(io);
|
||||
}
|
||||
const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999));
|
||||
try expectEqualStrings(test_content, content);
|
||||
|
|
|
|||
|
|
@ -324,6 +324,7 @@ pub const O = switch (native_arch) {
|
|||
CLOEXEC: bool = false,
|
||||
SYNC: bool = false,
|
||||
PATH: bool = false,
|
||||
/// This is typically invalid without also setting `DIRECTORY`.
|
||||
TMPFILE: bool = false,
|
||||
_23: u9 = 0,
|
||||
},
|
||||
|
|
@ -346,6 +347,7 @@ pub const O = switch (native_arch) {
|
|||
CLOEXEC: bool = false,
|
||||
SYNC: bool = false,
|
||||
PATH: bool = false,
|
||||
/// This is typically invalid without also setting `DIRECTORY`.
|
||||
TMPFILE: bool = false,
|
||||
_23: u9 = 0,
|
||||
},
|
||||
|
|
@ -368,6 +370,7 @@ pub const O = switch (native_arch) {
|
|||
CLOEXEC: bool = false,
|
||||
SYNC: bool = false,
|
||||
PATH: bool = false,
|
||||
/// This is typically invalid without also setting `DIRECTORY`.
|
||||
TMPFILE: bool = false,
|
||||
_23: u9 = 0,
|
||||
},
|
||||
|
|
@ -393,6 +396,7 @@ pub const O = switch (native_arch) {
|
|||
CLOEXEC: bool = false,
|
||||
SYNC: bool = false,
|
||||
PATH: bool = false,
|
||||
/// This is typically invalid without also setting `DIRECTORY`.
|
||||
TMPFILE: bool = false,
|
||||
_27: u6 = 0,
|
||||
},
|
||||
|
|
@ -417,6 +421,7 @@ pub const O = switch (native_arch) {
|
|||
CLOEXEC: bool = false,
|
||||
_20: u1 = 0,
|
||||
PATH: bool = false,
|
||||
/// This is typically invalid without also setting `DIRECTORY`.
|
||||
TMPFILE: bool = false,
|
||||
_23: u9 = 0,
|
||||
},
|
||||
|
|
@ -439,6 +444,7 @@ pub const O = switch (native_arch) {
|
|||
CLOEXEC: bool = false,
|
||||
SYNC: bool = false,
|
||||
PATH: bool = false,
|
||||
/// This is typically invalid without also setting `DIRECTORY`.
|
||||
TMPFILE: bool = false,
|
||||
_23: u9 = 0,
|
||||
},
|
||||
|
|
@ -459,13 +465,16 @@ pub const O = switch (native_arch) {
|
|||
NOFOLLOW: bool = false,
|
||||
NOATIME: bool = false,
|
||||
CLOEXEC: bool = false,
|
||||
_20: u1 = 0,
|
||||
/// This is typically invalid without also setting `TMPFILE1` and `DIRECTORY`.
|
||||
TMPFILE0: bool = false,
|
||||
PATH: bool = false,
|
||||
_22: u10 = 0,
|
||||
_22: u4 = 0,
|
||||
/// This is typically invalid without also setting `TMPFILE0` and `DIRECTORY`.
|
||||
TMPFILE1: bool = false,
|
||||
_27: u5 = 0,
|
||||
|
||||
// #define O_RSYNC 04010000
|
||||
// #define O_SYNC 04010000
|
||||
// #define O_TMPFILE 020200000
|
||||
// #define O_NDELAY O_NONBLOCK
|
||||
},
|
||||
.m68k => packed struct(u32) {
|
||||
|
|
|
|||
|
|
@ -793,7 +793,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
|
|||
var dir = cwd.openDir(io, rpath, .{}) catch |err| switch (err) {
|
||||
error.NameTooLong => return error.Unexpected,
|
||||
error.BadPathName => return error.Unexpected,
|
||||
error.DeviceBusy => return error.Unexpected,
|
||||
error.NetworkNotFound => return error.Unexpected, // Windows-only
|
||||
|
||||
error.FileNotFound => return error.GLibCNotFound,
|
||||
|
|
|
|||
|
|
@ -343,10 +343,10 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {
|
|||
}
|
||||
|
||||
// `make_path` matters because the dir hasn't actually been created yet.
|
||||
var af = try root_dir.atomicFile(io, sub_path, .{ .make_path = true, .write_buffer = &.{} });
|
||||
defer af.deinit();
|
||||
try af.file_writer.interface.writeAll(file.source.?);
|
||||
af.finish() catch |err| switch (err) {
|
||||
var af = try root_dir.createFileAtomic(io, sub_path, .{ .make_path = true, .replace = true });
|
||||
defer af.deinit(io);
|
||||
try af.file.writeStreamingAll(io, file.source.?);
|
||||
af.replace(io) catch |err| switch (err) {
|
||||
error.AccessDenied => switch (builtin.os.tag) {
|
||||
.windows => {
|
||||
// Very likely happened due to another process or thread
|
||||
|
|
|
|||
|
|
@ -3916,11 +3916,14 @@ pub fn saveState(comp: *Compilation) !void {
|
|||
|
||||
// Using an atomic file prevents a crash or power failure from corrupting
|
||||
// the previous incremental compilation state.
|
||||
var af = try lf.emit.root_dir.handle.createFileAtomic(io, basename, .{ .replace = true });
|
||||
defer af.deinit(io);
|
||||
|
||||
var write_buffer: [1024]u8 = undefined;
|
||||
var af = try lf.emit.root_dir.handle.atomicFile(io, basename, .{ .write_buffer = &write_buffer });
|
||||
defer af.deinit();
|
||||
try af.file_writer.interface.writeVecAll(bufs.items);
|
||||
try af.finish();
|
||||
var file_writer = af.file.writer(io, &write_buffer);
|
||||
try file_writer.interface.writeVecAll(bufs.items);
|
||||
try file_writer.interface.flush();
|
||||
try af.replace(io);
|
||||
}
|
||||
|
||||
fn addBuf(list: *std.array_list.Managed([]const u8), buf: []const u8) void {
|
||||
|
|
@ -5244,26 +5247,31 @@ fn processOneJob(
|
|||
}
|
||||
}
|
||||
|
||||
fn createDepFile(comp: *Compilation, depfile: []const u8, binfile: Cache.Path) anyerror!void {
|
||||
fn createDepFile(comp: *Compilation, dep_file: []const u8, bin_file: Cache.Path) anyerror!void {
|
||||
const io = comp.io;
|
||||
|
||||
var af = try Io.Dir.cwd().createFileAtomic(io, dep_file, .{ .replace = true });
|
||||
defer af.deinit(io);
|
||||
|
||||
var buf: [4096]u8 = undefined;
|
||||
var af = try Io.Dir.cwd().atomicFile(io, depfile, .{ .write_buffer = &buf });
|
||||
defer af.deinit();
|
||||
var file_writer = af.file.writer(io, &buf);
|
||||
|
||||
comp.writeDepFile(binfile, &af.file_writer.interface) catch return af.file_writer.err.?;
|
||||
|
||||
try af.finish();
|
||||
comp.writeDepFile(bin_file, &file_writer.interface) catch |err| switch (err) {
|
||||
error.WriteFailed => return file_writer.err.?,
|
||||
};
|
||||
try file_writer.flush();
|
||||
try af.replace(io);
|
||||
}
|
||||
|
||||
fn writeDepFile(
|
||||
comp: *Compilation,
|
||||
binfile: Cache.Path,
|
||||
bin_file: Cache.Path,
|
||||
w: *std.Io.Writer,
|
||||
) std.Io.Writer.Error!void {
|
||||
const prefixes = comp.cache_parent.prefixes();
|
||||
const fsi = comp.file_system_inputs.?.items;
|
||||
|
||||
try w.print("{f}:", .{binfile});
|
||||
try w.print("{f}:", .{bin_file});
|
||||
|
||||
{
|
||||
var it = std.mem.splitScalar(u8, fsi, 0);
|
||||
|
|
|
|||
|
|
@ -355,11 +355,11 @@ fn fmtPathFile(
|
|||
try fmt.stdout_writer.interface.print("{s}\n", .{file_path});
|
||||
fmt.any_error = true;
|
||||
} else {
|
||||
var af = try dir.atomicFile(io, sub_path, .{ .permissions = stat.permissions, .write_buffer = &.{} });
|
||||
defer af.deinit();
|
||||
var af = try dir.createFileAtomic(io, sub_path, .{ .permissions = stat.permissions, .replace = true });
|
||||
defer af.deinit(io);
|
||||
|
||||
try af.file_writer.interface.writeAll(fmt.out_buffer.written());
|
||||
try af.finish();
|
||||
try af.file.writeStreamingAll(io, fmt.out_buffer.written());
|
||||
try af.replace(io);
|
||||
try fmt.stdout_writer.interface.print("{s}\n", .{file_path});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue