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