| ... | ... | @@ -7,8 +7,7 @@ const assert = std.debug.assert; |
| 7 | 7 | const posix = std.posix; |
| 8 | 8 | |
| 9 | 9 | file_writer: File.Writer, |
| 10 | | // TODO either replace this with rand_buf or use []u16 on Windows |
| 11 | | tmp_path_buf: [tmp_path_len:0]u8, |
| 10 | random_integer: u64, |
| 12 | 11 | dest_basename: []const u8, |
| 13 | 12 | file_open: bool, |
| 14 | 13 | file_exists: bool, |
| ... | ... | @@ -17,9 +16,6 @@ dir: Dir, |
| 17 | 16 | |
| 18 | 17 | pub const InitError = File.OpenError; |
| 19 | 18 | |
| 20 | | pub const random_bytes_len = 12; |
| 21 | | const tmp_path_len = fs.base64_encoder.calcSize(random_bytes_len); |
| 22 | | |
| 23 | 19 | /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. |
| 24 | 20 | pub fn init( |
| 25 | 21 | dest_basename: []const u8, |
| ... | ... | @@ -28,22 +24,16 @@ pub fn init( |
| 28 | 24 | close_dir_on_deinit: bool, |
| 29 | 25 | write_buffer: []u8, |
| 30 | 26 | ) InitError!AtomicFile { |
| 31 | | var rand_buf: [random_bytes_len]u8 = undefined; |
| 32 | | var tmp_path_buf: [tmp_path_len:0]u8 = undefined; |
| 33 | | |
| 34 | 27 | while (true) { |
| 35 | | std.crypto.random.bytes(rand_buf[0..]); |
| 36 | | const tmp_path = fs.base64_encoder.encode(&tmp_path_buf, &rand_buf); |
| 37 | | tmp_path_buf[tmp_path.len] = 0; |
| 38 | | |
| 39 | | const file = dir.createFile(tmp_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { |
| 28 | const random_integer = std.crypto.random.int(u64); |
| 29 | const tmp_sub_path = std.fmt.hex(random_integer); |
| 30 | const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) { |
| 40 | 31 | error.PathAlreadyExists => continue, |
| 41 | 32 | else => |e| return e, |
| 42 | 33 | }; |
| 43 | | |
| 44 | 34 | return .{ |
| 45 | 35 | .file_writer = file.writer(write_buffer), |
| 46 | | .tmp_path_buf = tmp_path_buf, |
| 36 | .random_integer = random_integer, |
| 47 | 37 | .dest_basename = dest_basename, |
| 48 | 38 | .file_open = true, |
| 49 | 39 | .file_exists = true, |
| ... | ... | @@ -54,33 +44,51 @@ pub fn init( |
| 54 | 44 | } |
| 55 | 45 | |
| 56 | 46 | /// Always call deinit, even after a successful finish(). |
| 57 | | pub fn deinit(self: *AtomicFile) void { |
| 58 | | if (self.file_open) { |
| 59 | | self.file_writer.file.close(); |
| 60 | | self.file_open = false; |
| 47 | pub fn deinit(af: *AtomicFile) void { |
| 48 | if (af.file_open) { |
| 49 | af.file_writer.file.close(); |
| 50 | af.file_open = false; |
| 61 | 51 | } |
| 62 | | if (self.file_exists) { |
| 63 | | self.dir.deleteFile(&self.tmp_path_buf) catch {}; |
| 64 | | self.file_exists = false; |
| 52 | if (af.file_exists) { |
| 53 | const tmp_sub_path = std.fmt.hex(af.random_integer); |
| 54 | af.dir.deleteFile(&tmp_sub_path) catch {}; |
| 55 | af.file_exists = false; |
| 65 | 56 | } |
| 66 | | if (self.close_dir_on_deinit) { |
| 67 | | self.dir.close(); |
| 57 | if (af.close_dir_on_deinit) { |
| 58 | af.dir.close(); |
| 68 | 59 | } |
| 69 | | self.* = undefined; |
| 60 | af.* = undefined; |
| 61 | } |
| 62 | |
| 63 | pub const FlushError = File.WriteError; |
| 64 | |
| 65 | pub fn flush(af: *AtomicFile) FlushError!void { |
| 66 | af.file_writer.interface.flush() catch |err| switch (err) { |
| 67 | error.WriteFailed => return af.file_writer.err.?, |
| 68 | }; |
| 70 | 69 | } |
| 71 | 70 | |
| 72 | | pub const FinishError = posix.RenameError; |
| 71 | pub const RenameIntoPlaceError = posix.RenameError; |
| 73 | 72 | |
| 74 | 73 | /// On Windows, this function introduces a period of time where some file |
| 75 | 74 | /// system operations on the destination file will result in |
| 76 | 75 | /// `error.AccessDenied`, including rename operations (such as the one used in |
| 77 | 76 | /// this function). |
| 78 | | pub fn finish(self: *AtomicFile) FinishError!void { |
| 79 | | assert(self.file_exists); |
| 80 | | if (self.file_open) { |
| 81 | | self.file_writer.file.close(); |
| 82 | | self.file_open = false; |
| 77 | pub fn renameIntoPlace(af: *AtomicFile) RenameIntoPlaceError!void { |
| 78 | assert(af.file_exists); |
| 79 | if (af.file_open) { |
| 80 | af.file_writer.file.close(); |
| 81 | af.file_open = false; |
| 83 | 82 | } |
| 84 | | try posix.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename); |
| 85 | | self.file_exists = false; |
| 83 | const tmp_sub_path = std.fmt.hex(af.random_integer); |
| 84 | try posix.renameat(af.dir.fd, &tmp_sub_path, af.dir.fd, af.dest_basename); |
| 85 | af.file_exists = false; |
| 86 | } |
| 87 | |
| 88 | pub const FinishError = FlushError || RenameIntoPlaceError; |
| 89 | |
| 90 | /// Combination of `flush` followed by `renameIntoPlace`. |
| 91 | pub fn finish(af: *AtomicFile) FinishError!void { |
| 92 | try af.flush(); |
| 93 | try af.renameIntoPlace(); |
| 86 | 94 | } |