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 }