authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-22 13:54:16-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-22 15:24:57-07:00
loge00e9c0fbf0913154f0f0de95e8d28a6d0ace95a
tree8738fb312c4f8286344493d7f69ca75ba85334e4
parentc95e2e65fac6afec5c57f6716b1f4e8e7a7292fb

std.fs: extract AtomicFile to separate file


3 files changed, 86 insertions(+), 80 deletions(-)

CMakeLists.txt+1
...@@ -247,6 +247,7 @@ set(ZIG_STAGE2_SOURCES...@@ -247,6 +247,7 @@ set(ZIG_STAGE2_SOURCES
247 "${CMAKE_SOURCE_DIR}/lib/std/fmt/errol/lookup.zig"247 "${CMAKE_SOURCE_DIR}/lib/std/fmt/errol/lookup.zig"
248 "${CMAKE_SOURCE_DIR}/lib/std/fmt/parse_float.zig"248 "${CMAKE_SOURCE_DIR}/lib/std/fmt/parse_float.zig"
249 "${CMAKE_SOURCE_DIR}/lib/std/fs.zig"249 "${CMAKE_SOURCE_DIR}/lib/std/fs.zig"
250 "${CMAKE_SOURCE_DIR}/lib/std/fs/AtomicFile.zig"
250 "${CMAKE_SOURCE_DIR}/lib/std/fs/Dir.zig"251 "${CMAKE_SOURCE_DIR}/lib/std/fs/Dir.zig"
251 "${CMAKE_SOURCE_DIR}/lib/std/fs/file.zig"252 "${CMAKE_SOURCE_DIR}/lib/std/fs/file.zig"
252 "${CMAKE_SOURCE_DIR}/lib/std/fs/get_app_data_dir.zig"253 "${CMAKE_SOURCE_DIR}/lib/std/fs/get_app_data_dir.zig"
lib/std/fs.zig+1-80
...@@ -7,11 +7,11 @@ const base64 = std.base64;...@@ -7,11 +7,11 @@ const base64 = std.base64;
7const crypto = std.crypto;7const crypto = std.crypto;
8const Allocator = std.mem.Allocator;8const Allocator = std.mem.Allocator;
9const assert = std.debug.assert;9const assert = std.debug.assert;
10const math = std.math;
1110
12const is_darwin = builtin.os.tag.isDarwin();11const is_darwin = builtin.os.tag.isDarwin();
1312
14pub const Dir = @import("fs/Dir.zig");13pub const Dir = @import("fs/Dir.zig");
14pub const AtomicFile = @import("fs/AtomicFile.zig");
1515
16pub const has_executable_bit = switch (builtin.os.tag) {16pub const has_executable_bit = switch (builtin.os.tag) {
17 .windows, .wasi => false,17 .windows, .wasi => false,
...@@ -150,85 +150,6 @@ pub fn copyFileAbsolute(...@@ -150,85 +150,6 @@ pub fn copyFileAbsolute(
150 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);150 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
151}151}
152152
153pub const AtomicFile = struct {
154 file: File,
155 // TODO either replace this with rand_buf or use []u16 on Windows
156 tmp_path_buf: [TMP_PATH_LEN:0]u8,
157 dest_basename: []const u8,
158 file_open: bool,
159 file_exists: bool,
160 close_dir_on_deinit: bool,
161 dir: Dir,
162
163 pub const InitError = File.OpenError;
164
165 const RANDOM_BYTES = 12;
166 const TMP_PATH_LEN = base64_encoder.calcSize(RANDOM_BYTES);
167
168 /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
169 pub fn init(
170 dest_basename: []const u8,
171 mode: File.Mode,
172 dir: Dir,
173 close_dir_on_deinit: bool,
174 ) InitError!AtomicFile {
175 var rand_buf: [RANDOM_BYTES]u8 = undefined;
176 var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined;
177
178 while (true) {
179 crypto.random.bytes(rand_buf[0..]);
180 const tmp_path = base64_encoder.encode(&tmp_path_buf, &rand_buf);
181 tmp_path_buf[tmp_path.len] = 0;
182
183 const file = dir.createFile(
184 tmp_path,
185 .{ .mode = mode, .exclusive = true },
186 ) catch |err| switch (err) {
187 error.PathAlreadyExists => continue,
188 else => |e| return e,
189 };
190
191 return AtomicFile{
192 .file = file,
193 .tmp_path_buf = tmp_path_buf,
194 .dest_basename = dest_basename,
195 .file_open = true,
196 .file_exists = true,
197 .close_dir_on_deinit = close_dir_on_deinit,
198 .dir = dir,
199 };
200 }
201 }
202
203 /// Always call deinit, even after a successful finish().
204 pub fn deinit(self: *AtomicFile) void {
205 if (self.file_open) {
206 self.file.close();
207 self.file_open = false;
208 }
209 if (self.file_exists) {
210 self.dir.deleteFile(&self.tmp_path_buf) catch {};
211 self.file_exists = false;
212 }
213 if (self.close_dir_on_deinit) {
214 self.dir.close();
215 }
216 self.* = undefined;
217 }
218
219 pub const FinishError = std.os.RenameError;
220
221 pub fn finish(self: *AtomicFile) FinishError!void {
222 assert(self.file_exists);
223 if (self.file_open) {
224 self.file.close();
225 self.file_open = false;
226 }
227 try os.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename);
228 self.file_exists = false;
229 }
230};
231
232/// Create a new directory, based on an absolute path.153/// Create a new directory, based on an absolute path.
233/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates154/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
234/// on both absolute and relative paths.155/// on both absolute and relative paths.
lib/std/fs/AtomicFile.zig created+84
...@@ -0,0 +1,84 @@
1file: File,
2// TODO either replace this with rand_buf or use []u16 on Windows
3tmp_path_buf: [TMP_PATH_LEN:0]u8,
4dest_basename: []const u8,
5file_open: bool,
6file_exists: bool,
7close_dir_on_deinit: bool,
8dir: Dir,
9
10pub const InitError = File.OpenError;
11
12const RANDOM_BYTES = 12;
13const TMP_PATH_LEN = fs.base64_encoder.calcSize(RANDOM_BYTES);
14
15/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
16pub fn init(
17 dest_basename: []const u8,
18 mode: File.Mode,
19 dir: Dir,
20 close_dir_on_deinit: bool,
21) InitError!AtomicFile {
22 var rand_buf: [RANDOM_BYTES]u8 = undefined;
23 var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined;
24
25 while (true) {
26 std.crypto.random.bytes(rand_buf[0..]);
27 const tmp_path = fs.base64_encoder.encode(&tmp_path_buf, &rand_buf);
28 tmp_path_buf[tmp_path.len] = 0;
29
30 const file = dir.createFile(
31 tmp_path,
32 .{ .mode = mode, .exclusive = true },
33 ) catch |err| switch (err) {
34 error.PathAlreadyExists => continue,
35 else => |e| return e,
36 };
37
38 return AtomicFile{
39 .file = file,
40 .tmp_path_buf = tmp_path_buf,
41 .dest_basename = dest_basename,
42 .file_open = true,
43 .file_exists = true,
44 .close_dir_on_deinit = close_dir_on_deinit,
45 .dir = dir,
46 };
47 }
48}
49
50/// Always call deinit, even after a successful finish().
51pub fn deinit(self: *AtomicFile) void {
52 if (self.file_open) {
53 self.file.close();
54 self.file_open = false;
55 }
56 if (self.file_exists) {
57 self.dir.deleteFile(&self.tmp_path_buf) catch {};
58 self.file_exists = false;
59 }
60 if (self.close_dir_on_deinit) {
61 self.dir.close();
62 }
63 self.* = undefined;
64}
65
66pub const FinishError = posix.RenameError;
67
68pub fn finish(self: *AtomicFile) FinishError!void {
69 assert(self.file_exists);
70 if (self.file_open) {
71 self.file.close();
72 self.file_open = false;
73 }
74 try posix.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename);
75 self.file_exists = false;
76}
77
78const AtomicFile = @This();
79const std = @import("../std.zig");
80const File = std.fs.File;
81const Dir = std.fs.Dir;
82const fs = std.fs;
83const assert = std.debug.assert;
84const posix = std.os;