authorgravatar for gereeter+code@gmail.comJonathan S <gereeter+code@gmail.com> 2020-03-25 22:17:41-05:00
committergravatar for gereeter+code@gmail.comJonathan S <gereeter+code@gmail.com> 2020-03-25 23:22:36-05:00
loga779a96d38883c8d642fc9164c3a60165078c2d2
tree9e0e0f892f0a1304cfda7df53d8311e02cfc9ac7
parentf7f563ea53cf58c772003a46624b87dad9c4311d

In AtomicFile, work relative to the destination's parent directory. This is more robust against concurrent filesystem reorganization and avoids path length issues.


1 files changed, 21 insertions(+), 20 deletions(-)

lib/std/fs.zig+21-20
......@@ -118,38 +118,30 @@ pub fn copyFileAbsolute(source_path: []const u8, dest_path: []const u8, args: Co
118118/// TODO update this API to avoid a getrandom syscall for every operation.
119119pub const AtomicFile = struct {
120120 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,
122123 dest_path: []const u8,
123124 file_open: bool,
124125 file_exists: bool,
126 close_dir_on_deinit: bool,
125127 dir: Dir,
126128
127129 const InitError = File.OpenError;
128130
131 const TMP_PATH_LEN = base64.Base64Encoder.calcSize(12);
132
129133 /// TODO rename this. Callers should go through Dir API
130 pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir) InitError!AtomicFile {
131 const dirname = path.dirname(dest_path);
134 pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir, close_dir_on_deinit: bool) InitError!AtomicFile {
132135 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
144 tmp_path_buf[tmp_path_len] = 0;
145 const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0];
136 var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined;
137 tmp_path_buf[base64.Base64Encoder.calcSize(12)] = 0;
146138
147139 while (true) {
148140 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);
150142
151143 const file = dir.createFileC(
152 tmp_path_slice,
144 &tmp_path_buf,
153145 .{ .mode = mode, .exclusive = true },
154146 ) catch |err| switch (err) {
155147 error.PathAlreadyExists => continue,
......@@ -162,6 +154,7 @@ pub const AtomicFile = struct {
162154 .dest_path = dest_path,
163155 .file_open = true,
164156 .file_exists = true,
157 .close_dir_on_deinit = close_dir_on_deinit,
165158 .dir = dir,
166159 };
167160 }
......@@ -169,7 +162,7 @@ pub const AtomicFile = struct {
169162
170163 /// Deprecated. Use `Dir.atomicFile`.
171164 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 });
173166 }
174167
175168 /// always call deinit, even after successful finish()
......@@ -182,6 +175,9 @@ pub const AtomicFile = struct {
182175 self.dir.deleteFileC(&self.tmp_path_buf) catch {};
183176 self.file_exists = false;
184177 }
178 if (self.close_dir_on_deinit) {
179 self.dir.close();
180 }
185181 self.* = undefined;
186182 }
187183
......@@ -1281,7 +1277,12 @@ pub const Dir = struct {
12811277 /// `dest_path` must remain valid for the lifetime of `AtomicFile`.
12821278 /// Call `AtomicFile.finish` to atomically replace `dest_path` with contents.
12831279 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 }
12851286 }
12861287};
12871288