| ... | @@ -63,7 +63,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: | ... | @@ -63,7 +63,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: |
| 63 | | 63 | |
| 64 | const dirname = path.dirname(new_path) orelse "."; | 64 | const dirname = path.dirname(new_path) orelse "."; |
| 65 | | 65 | |
| 66 | var rand_buf: [12]u8 = undefined; | 66 | var rand_buf: [AtomicFile.RANDOM_BYTES]u8 = undefined; |
| 67 | const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len)); | 67 | const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len)); |
| 68 | defer allocator.free(tmp_path); | 68 | defer allocator.free(tmp_path); |
| 69 | mem.copy(u8, tmp_path[0..], dirname); | 69 | mem.copy(u8, tmp_path[0..], dirname); |
| ... | @@ -118,38 +118,31 @@ pub fn copyFileAbsolute(source_path: []const u8, dest_path: []const u8, args: Co | ... | @@ -118,38 +118,31 @@ pub fn copyFileAbsolute(source_path: []const u8, dest_path: []const u8, args: Co |
| 118 | /// TODO update this API to avoid a getrandom syscall for every operation. | 118 | /// TODO update this API to avoid a getrandom syscall for every operation. |
| 119 | pub const AtomicFile = struct { | 119 | pub const AtomicFile = struct { |
| 120 | file: File, | 120 | file: File, |
| 121 | tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8, | 121 | // TODO either replace this with rand_buf or use []u16 on Windows |
| | 122 | tmp_path_buf: [TMP_PATH_LEN:0]u8, |
| 122 | dest_path: []const u8, | 123 | dest_path: []const u8, |
| 123 | file_open: bool, | 124 | file_open: bool, |
| 124 | file_exists: bool, | 125 | file_exists: bool, |
| | 126 | close_dir_on_deinit: bool, |
| 125 | dir: Dir, | 127 | dir: Dir, |
| 126 | | 128 | |
| 127 | const InitError = File.OpenError; | 129 | const InitError = File.OpenError; |
| 128 | | 130 | |
| 129 | /// TODO rename this. Callers should go through Dir API | 131 | const RANDOM_BYTES = 12; |
| 130 | pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir) InitError!AtomicFile { | 132 | const TMP_PATH_LEN = base64.Base64Encoder.calcSize(RANDOM_BYTES); |
| 131 | const dirname = path.dirname(dest_path); | | |
| 132 | var rand_buf: [12]u8 = undefined; | | |
| 133 | const dirname_component_len = if (dirname) |d| d.len + 1 else 0; | | |
| 134 | const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len); | | |
| 135 | const tmp_path_len = dirname_component_len + encoded_rand_len; | | |
| 136 | var tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8 = undefined; | | |
| 137 | if (tmp_path_len > tmp_path_buf.len) return error.NameTooLong; | | |
| 138 | | | |
| 139 | if (dirname) |dn| { | | |
| 140 | mem.copy(u8, tmp_path_buf[0..], dn); | | |
| 141 | tmp_path_buf[dn.len] = path.sep; | | |
| 142 | } | | |
| 143 | | 133 | |
| 144 | tmp_path_buf[tmp_path_len] = 0; | 134 | /// TODO rename this. Callers should go through Dir API |
| 145 | const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0]; | 135 | pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir, close_dir_on_deinit: bool) InitError!AtomicFile { |
| | 136 | var rand_buf: [RANDOM_BYTES]u8 = undefined; |
| | 137 | var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined; |
| | 138 | tmp_path_buf[base64.Base64Encoder.calcSize(RANDOM_BYTES)] = 0; |
| 146 | | 139 | |
| 147 | while (true) { | 140 | while (true) { |
| 148 | try crypto.randomBytes(rand_buf[0..]); | 141 | try crypto.randomBytes(rand_buf[0..]); |
| 149 | base64_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf); | 142 | base64_encoder.encode(&tmp_path_buf, &rand_buf); |
| 150 | | 143 | |
| 151 | const file = dir.createFileC( | 144 | const file = dir.createFileC( |
| 152 | tmp_path_slice, | 145 | &tmp_path_buf, |
| 153 | .{ .mode = mode, .exclusive = true }, | 146 | .{ .mode = mode, .exclusive = true }, |
| 154 | ) catch |err| switch (err) { | 147 | ) catch |err| switch (err) { |
| 155 | error.PathAlreadyExists => continue, | 148 | error.PathAlreadyExists => continue, |
| ... | @@ -162,6 +155,7 @@ pub const AtomicFile = struct { | ... | @@ -162,6 +155,7 @@ pub const AtomicFile = struct { |
| 162 | .dest_path = dest_path, | 155 | .dest_path = dest_path, |
| 163 | .file_open = true, | 156 | .file_open = true, |
| 164 | .file_exists = true, | 157 | .file_exists = true, |
| | 158 | .close_dir_on_deinit = close_dir_on_deinit, |
| 165 | .dir = dir, | 159 | .dir = dir, |
| 166 | }; | 160 | }; |
| 167 | } | 161 | } |
| ... | @@ -169,7 +163,7 @@ pub const AtomicFile = struct { | ... | @@ -169,7 +163,7 @@ pub const AtomicFile = struct { |
| 169 | | 163 | |
| 170 | /// Deprecated. Use `Dir.atomicFile`. | 164 | /// Deprecated. Use `Dir.atomicFile`. |
| 171 | pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile { | 165 | pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile { |
| 172 | return init2(dest_path, mode, cwd()); | 166 | return cwd().atomicFile(dest_path, .{ .mode = mode }); |
| 173 | } | 167 | } |
| 174 | | 168 | |
| 175 | /// always call deinit, even after successful finish() | 169 | /// always call deinit, even after successful finish() |
| ... | @@ -182,6 +176,9 @@ pub const AtomicFile = struct { | ... | @@ -182,6 +176,9 @@ pub const AtomicFile = struct { |
| 182 | self.dir.deleteFileC(&self.tmp_path_buf) catch {}; | 176 | self.dir.deleteFileC(&self.tmp_path_buf) catch {}; |
| 183 | self.file_exists = false; | 177 | self.file_exists = false; |
| 184 | } | 178 | } |
| | 179 | if (self.close_dir_on_deinit) { |
| | 180 | self.dir.close(); |
| | 181 | } |
| 185 | self.* = undefined; | 182 | self.* = undefined; |
| 186 | } | 183 | } |
| 187 | | 184 | |
| ... | @@ -1281,7 +1278,12 @@ pub const Dir = struct { | ... | @@ -1281,7 +1278,12 @@ pub const Dir = struct { |
| 1281 | /// `dest_path` must remain valid for the lifetime of `AtomicFile`. | 1278 | /// `dest_path` must remain valid for the lifetime of `AtomicFile`. |
| 1282 | /// Call `AtomicFile.finish` to atomically replace `dest_path` with contents. | 1279 | /// Call `AtomicFile.finish` to atomically replace `dest_path` with contents. |
| 1283 | pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile { | 1280 | pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile { |
| 1284 | return AtomicFile.init2(dest_path, options.mode, self); | 1281 | if (path.dirname(dest_path)) |dirname| { |
| | 1282 | const dir = try self.openDir(dirname, .{}); |
| | 1283 | return AtomicFile.init2(path.basename(dest_path), options.mode, dir, true); |
| | 1284 | } else { |
| | 1285 | return AtomicFile.init2(dest_path, options.mode, self, false); |
| | 1286 | } |
| 1285 | } | 1287 | } |
| 1286 | }; | 1288 | }; |
| 1287 | | 1289 | |