tricu

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

io_v2_snapshot.zig (2868B)


      1 //! Synchronous private anonymous snapshot. The host registers the descriptor
      2 //! before copy; authentication completes before any format decoding. Not durable
      3 //! publication, confinement, a deadline, or an aggregate disk/heap quota.
      4 const std = @import("std");
      5 const F = @import("io_v2_files.zig");
      6 const Indexed = @import("indexed.zig");
      7 const c = @cImport({
      8     @cUndef("_FORTIFY_SOURCE");
      9     @cDefine("_FORTIFY_SOURCE", "0");
     10     @cInclude("uv.h");
     11 });
     12 pub fn create(allocator: std.mem.Allocator, status: *F.Status, directory: [:0]const u8) !F.File {
     13     if (directory.len == 0 or std.mem.indexOfScalar(u8, directory, 0) != null) return error.InvalidArgument;
     14     const template = try std.fmt.allocPrintSentinel(allocator, "{s}/.tricu-snapshot-XXXXXX", .{directory}, 0);
     15     defer allocator.free(template);
     16     var req: c.uv_fs_t = undefined;
     17     const fd = c.uv_fs_mkstemp(null, &req, template.ptr, null);
     18     defer c.uv_fs_req_cleanup(&req);
     19     if (fd < 0) {
     20         status.code = fd;
     21         return error.HostFailure;
     22     }
     23     var file = F.File{ .fd = fd, .mode = .update };
     24     var unlink_req: c.uv_fs_t = undefined;
     25     const removed = c.uv_fs_unlink(null, &unlink_req, req.path, null);
     26     c.uv_fs_req_cleanup(&unlink_req);
     27     if (removed < 0) {
     28         var cleanup: F.Status = .{};
     29         file.close(&cleanup) catch {};
     30         status.* = .{ .code = removed, .cleanup_code = cleanup.code };
     31         return error.HostFailure;
     32     }
     33     return file;
     34 }
     35 pub const Snapshot = struct {
     36     file: *F.File,
     37     status: *F.Status,
     38     length: u64,
     39     pub fn readAt(self: *@This(), offset: u64, bytes: []u8) !void {
     40         if (bytes.len > 4096 or offset > self.length or bytes.len > self.length - offset) return error.InvalidData;
     41         try self.file.seek(offset);
     42         var used: usize = 0;
     43         while (used < bytes.len) {
     44             const n = try self.file.read(self.status, bytes[used..]);
     45             if (n == 0) return error.InvalidData;
     46             used += n;
     47         }
     48     }
     49 };
     50 pub fn authenticate(file: *F.File, source: *F.File, status: *F.Status, size: u64, expected: *const [64]u8) !void {
     51     var hash = std.crypto.hash.sha2.Sha256.init(.{});
     52     hash.update(Indexed.domain ++ "\x00");
     53     var remaining = size;
     54     var bytes: [4096]u8 = undefined;
     55     while (remaining != 0) {
     56         const wanted: usize = @intCast(@min(remaining, bytes.len));
     57         const count = try source.read(status, bytes[0..wanted]);
     58         if (count == 0) return error.SnapshotTruncated;
     59         var sent: usize = 0;
     60         while (sent < count) sent += try file.write(status, bytes[sent..count]);
     61         hash.update(bytes[0..count]);
     62         remaining -= count;
     63     }
     64     var raw: [32]u8 = undefined;
     65     hash.final(&raw);
     66     const actual = std.fmt.bytesToHex(raw, .lower);
     67     if (!std.mem.eql(u8, &actual, expected)) return error.SnapshotDigestMismatch;
     68 }