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