diff --git a/lib/std/fs/AtomicFile.zig b/lib/std/fs/AtomicFile.zig index 20b782d72a..96793aec72 100644 --- a/lib/std/fs/AtomicFile.zig +++ b/lib/std/fs/AtomicFile.zig @@ -7,8 +7,7 @@ const assert = std.debug.assert; const posix = std.posix; file_writer: File.Writer, -// TODO either replace this with rand_buf or use []u16 on Windows -tmp_path_buf: [tmp_path_len:0]u8, +random_integer: u64, dest_basename: []const u8, file_open: bool, file_exists: bool, @@ -17,9 +16,6 @@ dir: Dir, pub const InitError = File.OpenError; -pub const random_bytes_len = 12; -const tmp_path_len = fs.base64_encoder.calcSize(random_bytes_len); - /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. pub fn init( dest_basename: []const u8, @@ -28,22 +24,16 @@ pub fn init( close_dir_on_deinit: bool, write_buffer: []u8, ) InitError!AtomicFile { - var rand_buf: [random_bytes_len]u8 = undefined; - var tmp_path_buf: [tmp_path_len:0]u8 = undefined; - while (true) { - std.crypto.random.bytes(rand_buf[0..]); - const tmp_path = fs.base64_encoder.encode(&tmp_path_buf, &rand_buf); - tmp_path_buf[tmp_path.len] = 0; - - const file = dir.createFile(tmp_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { + const random_integer = std.crypto.random.int(u64); + const tmp_sub_path = std.fmt.hex(random_integer); + const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { error.PathAlreadyExists => continue, else => |e| return e, }; - return .{ .file_writer = file.writer(write_buffer), - .tmp_path_buf = tmp_path_buf, + .random_integer = random_integer, .dest_basename = dest_basename, .file_open = true, .file_exists = true, @@ -54,33 +44,51 @@ pub fn init( } /// Always call deinit, even after a successful finish(). -pub fn deinit(self: *AtomicFile) void { - if (self.file_open) { - self.file_writer.file.close(); - self.file_open = false; +pub fn deinit(af: *AtomicFile) void { + if (af.file_open) { + af.file_writer.file.close(); + af.file_open = false; } - if (self.file_exists) { - self.dir.deleteFile(&self.tmp_path_buf) catch {}; - self.file_exists = false; + if (af.file_exists) { + const tmp_sub_path = std.fmt.hex(af.random_integer); + af.dir.deleteFile(&tmp_sub_path) catch {}; + af.file_exists = false; } - if (self.close_dir_on_deinit) { - self.dir.close(); + if (af.close_dir_on_deinit) { + af.dir.close(); } - self.* = undefined; + af.* = undefined; } -pub const FinishError = posix.RenameError; +pub const FlushError = File.WriteError; + +pub fn flush(af: *AtomicFile) FlushError!void { + af.file_writer.interface.flush() catch |err| switch (err) { + error.WriteFailed => return af.file_writer.err.?, + }; +} + +pub const RenameIntoPlaceError = posix.RenameError; /// 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 finish(self: *AtomicFile) FinishError!void { - assert(self.file_exists); - if (self.file_open) { - self.file_writer.file.close(); - self.file_open = false; +pub fn renameIntoPlace(af: *AtomicFile) RenameIntoPlaceError!void { + assert(af.file_exists); + if (af.file_open) { + af.file_writer.file.close(); + af.file_open = false; } - try posix.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename); - self.file_exists = false; + const tmp_sub_path = std.fmt.hex(af.random_integer); + try posix.renameat(af.dir.fd, &tmp_sub_path, af.dir.fd, af.dest_basename); + af.file_exists = false; +} + +pub const FinishError = FlushError || RenameIntoPlaceError; + +/// Combination of `flush` followed by `renameIntoPlace`. +pub fn finish(af: *AtomicFile) FinishError!void { + try af.flush(); + try af.renameIntoPlace(); } diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 21926991a8..35f0663fcd 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -2667,12 +2667,11 @@ pub fn copyFile( }); defer atomic_file.deinit(); - const size = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { + _ = atomic_file.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.?, }; try atomic_file.finish(); - _ = size; } pub const AtomicFileOptions = struct {