master
 1const AtomicFile = @This();
 2const std = @import("../std.zig");
 3const File = std.fs.File;
 4const Dir = std.fs.Dir;
 5const fs = std.fs;
 6const assert = std.debug.assert;
 7const posix = std.posix;
 8
 9file_writer: File.Writer,
10random_integer: u64,
11dest_basename: []const u8,
12file_open: bool,
13file_exists: bool,
14close_dir_on_deinit: bool,
15dir: Dir,
16
17pub const InitError = File.OpenError;
18
19/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
20pub fn init(
21    dest_basename: []const u8,
22    mode: File.Mode,
23    dir: Dir,
24    close_dir_on_deinit: bool,
25    write_buffer: []u8,
26) InitError!AtomicFile {
27    while (true) {
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) {
31            error.PathAlreadyExists => continue,
32            else => |e| return e,
33        };
34        return .{
35            .file_writer = file.writer(write_buffer),
36            .random_integer = random_integer,
37            .dest_basename = dest_basename,
38            .file_open = true,
39            .file_exists = true,
40            .close_dir_on_deinit = close_dir_on_deinit,
41            .dir = dir,
42        };
43    }
44}
45
46/// Always call deinit, even after a successful finish().
47pub fn deinit(af: *AtomicFile) void {
48    if (af.file_open) {
49        af.file_writer.file.close();
50        af.file_open = false;
51    }
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;
56    }
57    if (af.close_dir_on_deinit) {
58        af.dir.close();
59    }
60    af.* = undefined;
61}
62
63pub const FlushError = File.WriteError;
64
65pub 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
71pub const RenameIntoPlaceError = posix.RenameError;
72
73/// On Windows, this function introduces a period of time where some file
74/// system operations on the destination file will result in
75/// `error.AccessDenied`, including rename operations (such as the one used in
76/// this function).
77pub 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;
82    }
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;
86}
87
88pub const FinishError = FlushError || RenameIntoPlaceError;
89
90/// Combination of `flush` followed by `renameIntoPlace`.
91pub fn finish(af: *AtomicFile) FinishError!void {
92    try af.flush();
93    try af.renameIntoPlace();
94}