tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

io_v2_paths.zig (5821B)


      1 //! Private synchronous path operations. Observations do not pin subsequent path
      2 //! operations. OS process authority, no grants, sandbox or implicit rollback.
      3 const std = @import("std");
      4 const F = @import("io_v2_files.zig");
      5 const c = @cImport({
      6     @cUndef("_FORTIFY_SOURCE");
      7     @cDefine("_FORTIFY_SOURCE", "0");
      8     @cInclude("uv.h");
      9     @cInclude("sys/stat.h");
     10     @cInclude("fcntl.h");
     11 });
     12 pub const Error = F.Error || error{NotDirectory};
     13 pub const Info = union(enum) { regular: u64, directory, symlink, other };
     14 pub const Mutation = enum { create_directory, remove_directory, remove_file, rename };
     15 fn valid(path: [:0]const u8) Error!void {
     16     if (path.len == 0 or std.mem.indexOfScalar(u8, path, 0) != null or !std.unicode.utf8ValidateSlice(path)) return error.InvalidArgument;
     17 }
     18 fn checked(status: *F.Status, code: c_int) Error!void {
     19     if (code < 0) {
     20         status.code = code;
     21         return error.HostFailure;
     22     }
     23 }
     24 pub fn stat(status: *F.Status, path: [:0]const u8, follow: bool) Error!Info {
     25     status.* = .{};
     26     try valid(path);
     27     var req: c.uv_fs_t = undefined;
     28     const rc = if (follow) c.uv_fs_stat(null, &req, path.ptr, null) else c.uv_fs_lstat(null, &req, path.ptr, null);
     29     defer c.uv_fs_req_cleanup(&req);
     30     try checked(status, rc);
     31     return switch (req.statbuf.st_mode & c.S_IFMT) {
     32         c.S_IFREG => if (req.statbuf.st_size <= F.limit) .{ .regular = req.statbuf.st_size } else error.InvalidBound,
     33         c.S_IFDIR => .directory,
     34         c.S_IFLNK => .symlink,
     35         else => .other,
     36     };
     37 }
     38 pub fn mutate(status: *F.Status, op: Mutation, path: [:0]const u8, destination: ?[:0]const u8) Error!void {
     39     status.* = .{};
     40     try valid(path);
     41     if (op == .rename) try valid(destination orelse return error.InvalidArgument);
     42     var req: c.uv_fs_t = undefined;
     43     const rc = switch (op) {
     44         .create_directory => c.uv_fs_mkdir(null, &req, path.ptr, 0o777, null),
     45         .remove_directory => c.uv_fs_rmdir(null, &req, path.ptr, null),
     46         .remove_file => c.uv_fs_unlink(null, &req, path.ptr, null),
     47         .rename => c.uv_fs_rename(null, &req, path.ptr, destination.?.ptr, null),
     48     };
     49     defer c.uv_fs_req_cleanup(&req);
     50     try checked(status, rc);
     51 }
     52 pub fn syncDirectory(status: *F.Status, path: [:0]const u8) Error!void {
     53     status.* = .{};
     54     try valid(path);
     55     var req: c.uv_fs_t = undefined;
     56     // NONBLOCK plus O_DIRECTORY avoids opening a FIFO while waiting for peers.
     57     const fd = c.uv_fs_open(null, &req, path.ptr, c.O_RDONLY | c.O_NONBLOCK | c.O_DIRECTORY | c.O_CLOEXEC | c.O_NOCTTY, 0, null);
     58     c.uv_fs_req_cleanup(&req);
     59     if (fd == c.UV_ENOTDIR) return error.NotDirectory;
     60     try checked(status, fd);
     61     // Close exactly once on every path; retain a primary sync/fstat error.
     62     const rc = c.uv_fs_fstat(null, &req, fd, null);
     63     const is_directory = rc >= 0 and req.statbuf.st_mode & c.S_IFMT == c.S_IFDIR;
     64     c.uv_fs_req_cleanup(&req);
     65     const synced = if (is_directory) blk: {
     66         const result = c.uv_fs_fsync(null, &req, fd, null);
     67         c.uv_fs_req_cleanup(&req);
     68         break :blk result;
     69     } else 0;
     70     const closed = c.uv_fs_close(null, &req, fd, null);
     71     c.uv_fs_req_cleanup(&req);
     72     if (closed < 0) status.cleanup_code = closed;
     73     try checked(status, rc);
     74     if (!is_directory) return error.NotDirectory;
     75     try checked(status, synced);
     76     try checked(status, closed);
     77 }
     78 
     79 test "path observations, explicit symlink following, rename/unlink and directory durability" {
     80     const t = std.testing;
     81     var tmp = t.tmpDir(.{});
     82     defer tmp.cleanup();
     83     const base = try tmp.dir.realPathFileAlloc(t.io, ".", t.allocator);
     84     defer t.allocator.free(base);
     85     const dir = try std.fmt.allocPrintSentinel(t.allocator, "{s}/dir", .{base}, 0);
     86     defer t.allocator.free(dir);
     87     const source = try std.fmt.allocPrintSentinel(t.allocator, "{s}/file", .{base}, 0);
     88     defer t.allocator.free(source);
     89     const target = try std.fmt.allocPrintSentinel(t.allocator, "{s}/dir/file", .{base}, 0);
     90     defer t.allocator.free(target);
     91     const link = try std.fmt.allocPrintSentinel(t.allocator, "{s}/link", .{base}, 0);
     92     defer t.allocator.free(link);
     93     var status: F.Status = .{};
     94     try mutate(&status, .create_directory, dir, null);
     95     try t.expect(try stat(&status, dir, true) == .directory);
     96     try t.expectError(error.HostFailure, mutate(&status, .create_directory, dir, null));
     97     try t.expectEqualStrings("already-exists", F.errorCategory(status.code));
     98     var file = try F.open(&status, source, .writer);
     99     defer file.close(&status) catch {};
    100     _ = try file.write(&status, "abc");
    101     var req: c.uv_fs_t = undefined;
    102     const linked = c.uv_fs_symlink(null, &req, source.ptr, link.ptr, 0, null);
    103     c.uv_fs_req_cleanup(&req);
    104     try t.expectEqual(@as(c_int, 0), linked);
    105     try t.expect(try stat(&status, link, false) == .symlink);
    106     try t.expectEqual(@as(u64, 3), (try stat(&status, link, true)).regular);
    107     try mutate(&status, .rename, source, target);
    108     try t.expectError(error.HostFailure, stat(&status, source, true));
    109     try t.expectEqualStrings("not-found", F.errorCategory(status.code));
    110     try t.expectEqual(@as(u64, 3), (try stat(&status, target, true)).regular);
    111     try syncDirectory(&status, dir);
    112     try t.expectError(error.NotDirectory, syncDirectory(&status, target));
    113     try t.expectError(error.HostFailure, mutate(&status, .remove_directory, dir, null));
    114     try mutate(&status, .remove_file, target, null);
    115     try mutate(&status, .remove_directory, dir, null);
    116     try mutate(&status, .remove_file, link, null);
    117     try t.expectError(error.InvalidArgument, mutate(&status, .rename, source, null));
    118     try t.expectError(error.InvalidArgument, stat(&status, "x\x00y", true));
    119     try t.expectError(error.InvalidArgument, syncDirectory(&status, ""));
    120 }