| ... | @@ -118,38 +118,30 @@ pub fn copyFileAbsolute(source_path: []const u8, dest_path: []const u8, args: Co | ... | @@ -118,38 +118,30 @@ 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 | |
| | 131 | const TMP_PATH_LEN = base64.Base64Encoder.calcSize(12); |
| | 132 | |
| 129 | /// TODO rename this. Callers should go through Dir API | 133 | /// TODO rename this. Callers should go through Dir API |
| 130 | pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir) InitError!AtomicFile { | 134 | pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir, close_dir_on_deinit: bool) InitError!AtomicFile { |
| 131 | const dirname = path.dirname(dest_path); | | |
| 132 | var rand_buf: [12]u8 = undefined; | 135 | var rand_buf: [12]u8 = undefined; |
| 133 | const dirname_component_len = if (dirname) |d| d.len + 1 else 0; | 136 | var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined; |
| 134 | const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len); | 137 | tmp_path_buf[base64.Base64Encoder.calcSize(12)] = 0; |
| 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 | | | |
| 144 | tmp_path_buf[tmp_path_len] = 0; | | |
| 145 | const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0]; | | |
| 146 | | 138 | |
| 147 | while (true) { | 139 | while (true) { |
| 148 | try crypto.randomBytes(rand_buf[0..]); | 140 | try crypto.randomBytes(rand_buf[0..]); |
| 149 | base64_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf); | 141 | base64_encoder.encode(&tmp_path_buf, &rand_buf); |
| 150 | | 142 | |
| 151 | const file = dir.createFileC( | 143 | const file = dir.createFileC( |
| 152 | tmp_path_slice, | 144 | &tmp_path_buf, |
| 153 | .{ .mode = mode, .exclusive = true }, | 145 | .{ .mode = mode, .exclusive = true }, |
| 154 | ) catch |err| switch (err) { | 146 | ) catch |err| switch (err) { |
| 155 | error.PathAlreadyExists => continue, | 147 | error.PathAlreadyExists => continue, |
| ... | @@ -162,6 +154,7 @@ pub const AtomicFile = struct { | ... | @@ -162,6 +154,7 @@ pub const AtomicFile = struct { |
| 162 | .dest_path = dest_path, | 154 | .dest_path = dest_path, |
| 163 | .file_open = true, | 155 | .file_open = true, |
| 164 | .file_exists = true, | 156 | .file_exists = true, |
| | 157 | .close_dir_on_deinit = close_dir_on_deinit, |
| 165 | .dir = dir, | 158 | .dir = dir, |
| 166 | }; | 159 | }; |
| 167 | } | 160 | } |
| ... | @@ -169,7 +162,7 @@ pub const AtomicFile = struct { | ... | @@ -169,7 +162,7 @@ pub const AtomicFile = struct { |
| 169 | | 162 | |
| 170 | /// Deprecated. Use `Dir.atomicFile`. | 163 | /// Deprecated. Use `Dir.atomicFile`. |
| 171 | pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile { | 164 | pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile { |
| 172 | return init2(dest_path, mode, cwd()); | 165 | return cwd().atomicFile(dest_path, .{ .mode = mode }); |
| 173 | } | 166 | } |
| 174 | | 167 | |
| 175 | /// always call deinit, even after successful finish() | 168 | /// always call deinit, even after successful finish() |
| ... | @@ -182,6 +175,9 @@ pub const AtomicFile = struct { | ... | @@ -182,6 +175,9 @@ pub const AtomicFile = struct { |
| 182 | self.dir.deleteFileC(&self.tmp_path_buf) catch {}; | 175 | self.dir.deleteFileC(&self.tmp_path_buf) catch {}; |
| 183 | self.file_exists = false; | 176 | self.file_exists = false; |
| 184 | } | 177 | } |
| | 178 | if (self.close_dir_on_deinit) { |
| | 179 | self.dir.close(); |
| | 180 | } |
| 185 | self.* = undefined; | 181 | self.* = undefined; |
| 186 | } | 182 | } |
| 187 | | 183 | |
| ... | @@ -1281,7 +1277,12 @@ pub const Dir = struct { | ... | @@ -1281,7 +1277,12 @@ pub const Dir = struct { |
| 1281 | /// `dest_path` must remain valid for the lifetime of `AtomicFile`. | 1277 | /// `dest_path` must remain valid for the lifetime of `AtomicFile`. |
| 1282 | /// Call `AtomicFile.finish` to atomically replace `dest_path` with contents. | 1278 | /// Call `AtomicFile.finish` to atomically replace `dest_path` with contents. |
| 1283 | pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile { | 1279 | pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile { |
| 1284 | return AtomicFile.init2(dest_path, options.mode, self); | 1280 | if (path.dirname(dest_path)) |dirname| { |
| | 1281 | const dir = try self.openDir(dirname, .{}); |
| | 1282 | return AtomicFile.init2(path.basename(dest_path), options.mode, dir, true); |
| | 1283 | } else { |
| | 1284 | return AtomicFile.init2(dest_path, options.mode, self, false); |
| | 1285 | } |
| 1285 | } | 1286 | } |
| 1286 | }; | 1287 | }; |
| 1287 | | 1288 | |