structstd.fs.AtomicFile[src]

Fields

file: File
tmp_path_buf: [tmp_path_len:0]u8
dest_basename: []const u8
file_open: bool
file_exists: bool
close_dir_on_deinit: bool
dir: Dir

Values

Constantrandom_bytes_len[src]

Source Code

Source code
pub const random_bytes_len = 12

Error Sets

Error SetOpenError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied OpenError

In WASI, this error may occur when the file descriptor does not hold the required rights to open a new resource relative to it.

AntivirusInterference

On Windows, antivirus software is enabled by default. It can be disabled, but Windows Update sometimes ignores the user's preference and re-enables it. When enabled, antivirus software on Windows intercepts file system operations and makes them significantly slower in addition to possibly failing with this error code.

BadPathName

On Windows, file paths cannot contain these characters: '/', '*', '?', '"', '<', '>', '|'

DeviceBusy OpenError
FileBusy OpenError

One of these three things:

  • pathname refers to an executable image which is currently being executed and write access was requested.
  • pathname refers to a file that is currently in use as a swap file, and the O_TRUNC flag was specified.
  • pathname refers to a file that is currently being read by the kernel (e.g., for module/firmware loading), and write access was requested.
FileLocksNotSupported OpenError

The underlying filesystem does not support file locks

FileNotFound OpenError

Either:

  • One of the path components does not exist.
  • Cwd was used, but cwd has been deleted.
  • The path associated with the open directory handle has been deleted.
  • On macOS, multiple processes or threads raced to create the same file with O.EXCL set to false.
FileTooBig OpenError

The file is too large to be opened. This error is unreachable for 64-bit targets, as well as when opening directories.

InvalidUtf8

WASI-only; file paths must be valid UTF-8.

InvalidWtf8

Windows-only; file paths provided by the user must be valid WTF-8. https://simonsapin.github.io/wtf-8/

IsDir OpenError

The path refers to directory but the DIRECTORY flag was not provided.

NameTooLong OpenError

The path exceeded max_path_bytes bytes.

NetworkNotFound

On Windows, \\server or \\server\share was not found.

NoDevice
NoSpaceLeft OpenError

A new path cannot be created because the device has no room for the new file. This error is only reachable when the CREAT flag is provided.

NotDir OpenError

A component used as a directory in the path was not, in fact, a directory, or DIRECTORY was specified and the path was not a directory.

PathAlreadyExists OpenError

The path already exists and the CREAT and EXCL flags were provided.

PipeBusy
ProcessFdQuotaExceeded OpenError
SharingViolation
SymLinkLoop OpenError
SystemFdQuotaExceeded OpenError
SystemResources OpenError

Insufficient kernel memory was available, or the named file is a FIFO and per-user hard limit on memory allocation for pipes has been reached.

Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

WouldBlock OpenError

Source Code

Source code
pub const OpenError = error{
    SharingViolation,
    PathAlreadyExists,
    FileNotFound,
    AccessDenied,
    PipeBusy,
    NoDevice,
    NameTooLong,
    /// WASI-only; file paths must be valid UTF-8.
    InvalidUtf8,
    /// Windows-only; file paths provided by the user must be valid WTF-8.
    /// https://simonsapin.github.io/wtf-8/
    InvalidWtf8,
    /// On Windows, file paths cannot contain these characters:
    /// '/', '*', '?', '"', '<', '>', '|'
    BadPathName,
    Unexpected,
    /// On Windows, `\\server` or `\\server\share` was not found.
    NetworkNotFound,
    /// On Windows, antivirus software is enabled by default. It can be
    /// disabled, but Windows Update sometimes ignores the user's preference
    /// and re-enables it. When enabled, antivirus software on Windows
    /// intercepts file system operations and makes them significantly slower
    /// in addition to possibly failing with this error code.
    AntivirusInterference,
} || posix.OpenError || posix.FlockError

Error SetRenameError[src]

Errors

anyerror means the error set is known only at runtime.

AccessDenied

In WASI, this error may occur when the file descriptor does not hold the required rights to rename a resource by path relative to it.

On Windows, this error may be returned instead of PathAlreadyExists when renaming a directory over an existing directory.

AntivirusInterference

On Windows, antivirus software is enabled by default. It can be disabled, but Windows Update sometimes ignores the user's preference and re-enables it. When enabled, antivirus software on Windows intercepts file system operations and makes them significantly slower in addition to possibly failing with this error code.

BadPathName
DiskQuota
FileBusy
FileNotFound
InvalidUtf8

WASI-only; file paths must be valid UTF-8.

InvalidWtf8

Windows-only; file paths provided by the user must be valid WTF-8. https://simonsapin.github.io/wtf-8/

IsDir
LinkQuotaExceeded
NameTooLong
NetworkNotFound

On Windows, \\server or \\server\share was not found.

NoDevice
NoSpaceLeft
NotDir
PathAlreadyExists
PipeBusy
ReadOnlyFileSystem
RenameAcrossMountPoints
SharingViolation
SymLinkLoop
SystemResources
Unexpected UnexpectedError

The Operating System returned an undocumented error code.

This error is in theory not possible, but it would be better to handle this error than to invoke undefined behavior.

When this error code is observed, it usually means the Zig Standard Library needs a small patch to add the error code to the error set for the respective function.

Source Code

Source code
pub const RenameError = error{
    /// In WASI, this error may occur when the file descriptor does
    /// not hold the required rights to rename a resource by path relative to it.
    ///
    /// On Windows, this error may be returned instead of PathAlreadyExists when
    /// renaming a directory over an existing directory.
    AccessDenied,
    FileBusy,
    DiskQuota,
    IsDir,
    SymLinkLoop,
    LinkQuotaExceeded,
    NameTooLong,
    FileNotFound,
    NotDir,
    SystemResources,
    NoSpaceLeft,
    PathAlreadyExists,
    ReadOnlyFileSystem,
    RenameAcrossMountPoints,
    /// WASI-only; file paths must be valid UTF-8.
    InvalidUtf8,
    /// Windows-only; file paths provided by the user must be valid WTF-8.
    /// https://simonsapin.github.io/wtf-8/
    InvalidWtf8,
    BadPathName,
    NoDevice,
    SharingViolation,
    PipeBusy,
    /// On Windows, `\\server` or `\\server\share` was not found.
    NetworkNotFound,
    /// On Windows, antivirus software is enabled by default. It can be
    /// disabled, but Windows Update sometimes ignores the user's preference
    /// and re-enables it. When enabled, antivirus software on Windows
    /// intercepts file system operations and makes them significantly slower
    /// in addition to possibly failing with this error code.
    AntivirusInterference,
} || UnexpectedError

Functions

Functioninit[src]

pub fn init( dest_basename: []const u8, mode: File.Mode, dir: Dir, close_dir_on_deinit: bool, ) InitError!AtomicFile

Note that the Dir.atomicFile API may be more handy than this lower-level function.

Parameters

dest_basename: []const u8
mode: File.Mode
dir: Dir
close_dir_on_deinit: bool

Source Code

Source code
pub fn init(
    dest_basename: []const u8,
    mode: File.Mode,
    dir: Dir,
    close_dir_on_deinit: bool,
) InitError!AtomicFile {
    var rand_buf: [random_bytes_len]u8 = undefined;
    var tmp_path_buf: [tmp_path_len:0]u8 = undefined;

    while (true) {
        std.crypto.random.bytes(rand_buf[0..]);
        const tmp_path = fs.base64_encoder.encode(&tmp_path_buf, &rand_buf);
        tmp_path_buf[tmp_path.len] = 0;

        const file = dir.createFile(
            tmp_path,
            .{ .mode = mode, .exclusive = true },
        ) catch |err| switch (err) {
            error.PathAlreadyExists => continue,
            else => |e| return e,
        };

        return AtomicFile{
            .file = file,
            .tmp_path_buf = tmp_path_buf,
            .dest_basename = dest_basename,
            .file_open = true,
            .file_exists = true,
            .close_dir_on_deinit = close_dir_on_deinit,
            .dir = dir,
        };
    }
}

Functiondeinit[src]

pub fn deinit(self: *AtomicFile) void

Always call deinit, even after a successful finish().

Parameters

self: *AtomicFile

Source Code

Source code
pub fn deinit(self: *AtomicFile) void {
    if (self.file_open) {
        self.file.close();
        self.file_open = false;
    }
    if (self.file_exists) {
        self.dir.deleteFile(&self.tmp_path_buf) catch {};
        self.file_exists = false;
    }
    if (self.close_dir_on_deinit) {
        self.dir.close();
    }
    self.* = undefined;
}

Functionfinish[src]

pub fn finish(self: *AtomicFile) FinishError!void

On Windows, this function introduces a period of time where some file system operations on the destination file will result in error.AccessDenied, including rename operations (such as the one used in this function).

Parameters

self: *AtomicFile

Source Code

Source code
pub fn finish(self: *AtomicFile) FinishError!void {
    assert(self.file_exists);
    if (self.file_open) {
        self.file.close();
        self.file_open = false;
    }
    try posix.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename);
    self.file_exists = false;
}

Source Code

Source code
file: File,
// TODO either replace this with rand_buf or use []u16 on Windows
tmp_path_buf: [tmp_path_len:0]u8,
dest_basename: []const u8,
file_open: bool,
file_exists: bool,
close_dir_on_deinit: bool,
dir: Dir,

pub const InitError = File.OpenError;

pub const random_bytes_len = 12;
const tmp_path_len = fs.base64_encoder.calcSize(random_bytes_len);

/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
pub fn init(
    dest_basename: []const u8,
    mode: File.Mode,
    dir: Dir,
    close_dir_on_deinit: bool,
) InitError!AtomicFile {
    var rand_buf: [random_bytes_len]u8 = undefined;
    var tmp_path_buf: [tmp_path_len:0]u8 = undefined;

    while (true) {
        std.crypto.random.bytes(rand_buf[0..]);
        const tmp_path = fs.base64_encoder.encode(&tmp_path_buf, &rand_buf);
        tmp_path_buf[tmp_path.len] = 0;

        const file = dir.createFile(
            tmp_path,
            .{ .mode = mode, .exclusive = true },
        ) catch |err| switch (err) {
            error.PathAlreadyExists => continue,
            else => |e| return e,
        };

        return AtomicFile{
            .file = file,
            .tmp_path_buf = tmp_path_buf,
            .dest_basename = dest_basename,
            .file_open = true,
            .file_exists = true,
            .close_dir_on_deinit = close_dir_on_deinit,
            .dir = dir,
        };
    }
}

/// Always call deinit, even after a successful finish().
pub fn deinit(self: *AtomicFile) void {
    if (self.file_open) {
        self.file.close();
        self.file_open = false;
    }
    if (self.file_exists) {
        self.dir.deleteFile(&self.tmp_path_buf) catch {};
        self.file_exists = false;
    }
    if (self.close_dir_on_deinit) {
        self.dir.close();
    }
    self.* = undefined;
}

pub const FinishError = posix.RenameError;

/// On Windows, this function introduces a period of time where some file
/// system operations on the destination file will result in
/// `error.AccessDenied`, including rename operations (such as the one used in
/// this function).
pub fn finish(self: *AtomicFile) FinishError!void {
    assert(self.file_exists);
    if (self.file_open) {
        self.file.close();
        self.file_open = false;
    }
    try posix.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename);
    self.file_exists = false;
}

const AtomicFile = @This();
const std = @import("../std.zig");
const File = std.fs.File;
const Dir = std.fs.Dir;
const fs = std.fs;
const assert = std.debug.assert;
const posix = std.posix;