Skip to documentation
SLOP

tiny.cook.Store

Reference tiny.cook Store

Defined in tiny.cook.

API (7)

Actions

Public operations.

Fields and members

Public fields and members.

No direct callersNo direct callstiny.cookStore
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/cook/src/cache.zig:106

zig
pub const Store = struct {    gpa: Allocator,    io: std.Io,    path: []const u8,    limits: Limits,    pub fn init(gpa: Allocator, io: std.Io, path: []const u8, limits: Limits) !Store {        if (!std.fs.path.isAbsolute(path)) return error.CachePathNotAbsolute;        if (limits.max_entry_bytes == 0 or limits.max_cache_bytes < limits.max_entry_bytes or            limits.max_entries == 0 or limits.max_files == 0 or            limits.max_directories == 0 or limits.max_depth == 0 or            limits.max_path_bytes < 64) return error.InvalidLimits;        return .{ .gpa = gpa, .io = io, .path = path, .limits = limits };    }    /// The callback writes ordinary files below an empty directory. The    /// returned entry holds its manifest lease until deinit.    pub fn getOrDerive(        store: Store,        key: Key,        context: anytype,        comptime derive: fn (@TypeOf(context), std.Io, std.Io.Dir) anyerror!void,    ) !Entry {        try std.Io.Dir.cwd().createDirPath(store.io, store.path);        const hex = key.hex();        const prefix = try std.fs.path.join(store.gpa, &.{ store.path, "v2", hex[0..2] });        defer store.gpa.free(prefix);        try std.Io.Dir.cwd().createDirPath(store.io, prefix);        var key_lock = try store.keyLock(prefix, key);        defer key_lock.close(store.io);        try key_lock.lock(store.io, .shared);        const path = try std.fmt.allocPrint(store.gpa, "{s}/{s}", .{ prefix, hex[2..] });        errdefer store.gpa.free(path);        if (try store.exists(path)) return store.load(&key_lock, key, path, true, null);        key_lock.unlock(store.io);        try key_lock.lock(store.io, .exclusive);        if (try store.exists(path)) return store.load(&key_lock, key, path, true, null);        const summary = try store.publish(prefix, path, key, context, derive);        return store.load(&key_lock, key, path, false, summary);    }    pub fn open(store: Store, key: Key) !Entry {        const hex = key.hex();        const prefix = try std.fs.path.join(store.gpa, &.{ store.path, "v2", hex[0..2] });        defer store.gpa.free(prefix);        if (!try store.exists(prefix)) return error.CacheMiss;        const path = try std.fmt.allocPrint(store.gpa, "{s}/v2/{s}/{s}", .{ store.path, hex[0..2], hex[2..] });        errdefer store.gpa.free(path);        if (!try store.exists(path)) return error.CacheMiss;        var key_lock = try store.keyLock(prefix, key);        defer key_lock.close(store.io);        try key_lock.lock(store.io, .shared);        if (!try store.exists(path)) return error.CacheMiss;        return store.load(&key_lock, key, path, true, null);    }    fn keyLock(store: Store, prefix: []const u8, key: Key) !std.Io.File {        const hex = key.hex();        const lock_path = try std.fmt.allocPrint(store.gpa, "{s}/.key-{s}", .{ prefix, &hex });        defer store.gpa.free(lock_path);        return std.Io.Dir.createFileAbsolute(store.io, lock_path, .{            .truncate = false,            .read = true,            .permissions = .fromMode(0o600),        });    }    fn load(store: Store, key_lock: *std.Io.File, key: Key, path: []u8, hit: bool, published: ?Summary) !Entry {        var dir = try std.Io.Dir.openDirAbsolute(store.io, path, .{ .iterate = true, .follow_symlinks = false });        errdefer dir.close(store.io);        var manifest = dir.openFile(store.io, ".cook", .{ .mode = .read_write }) catch return error.CorruptEntry;        errdefer manifest.close(store.io);        try manifest.lock(store.io, .shared);        key_lock.unlock(store.io);        const summary = published orelse try store.verify(dir, key);        try store.touch(manifest);        return .{ .io = store.io, .gpa = store.gpa, .path = path, .lock = manifest, .dir = dir, .summary = summary, .hit = hit };    }    fn exists(store: Store, path: []const u8) !bool {        _ = std.Io.Dir.cwd().statFile(store.io, path, .{ .follow_symlinks = false }) catch |err| switch (err) {            error.FileNotFound => return false,            else => return err,        };        return true;    }    fn publish(        store: Store,        prefix: []const u8,        path: []const u8,        key: Key,        context: anytype,        comptime derive: fn (@TypeOf(context), std.Io, std.Io.Dir) anyerror!void,    ) !Summary {        const hex = key.hex();        const stage_path = try std.fmt.allocPrint(store.gpa, "{s}/.stage-{s}", .{ prefix, &hex });        defer store.gpa.free(stage_path);        try std.Io.Dir.cwd().deleteTree(store.io, stage_path);        try std.Io.Dir.cwd().createDir(store.io, stage_path, .default_dir);        errdefer std.Io.Dir.cwd().deleteTree(store.io, stage_path) catch {};        var stage = try std.Io.Dir.openDirAbsolute(store.io, stage_path, .{ .iterate = true, .follow_symlinks = false });        defer stage.close(store.io);        try derive(context, store.io, stage);        const tree = try store.snapshot(stage, true);        const encoded = encodeManifest(key, tree);        var manifest = try stage.createFile(store.io, ".cook", .{ .exclusive = true, .permissions = .fromMode(0o600) });        defer manifest.close(store.io);        try manifest.writePositionalAll(store.io, &encoded, 0);        try manifest.sync(store.io);        try syncDirectory(store.io, stage);        const lock_path = try std.fs.path.join(store.gpa, &.{ store.path, ".lock" });        defer store.gpa.free(lock_path);        var root_lock = try std.Io.Dir.createFileAbsolute(store.io, lock_path, .{            .truncate = false,            .read = true,            .permissions = .fromMode(0o600),        });        defer root_lock.close(store.io);        try root_lock.lock(store.io, .exclusive);        try store.prune(tree.summary);        var parent = try std.Io.Dir.openDirAbsolute(store.io, prefix, .{});        defer parent.close(store.io);        try std.Io.Dir.cwd().rename(stage_path, std.Io.Dir.cwd(), path, store.io);        try syncDirectory(store.io, parent);        return tree.summary;    }    fn verify(store: Store, dir: std.Io.Dir, key: Key) !Summary {        const encoded = dir.readFileAlloc(store.io, ".cook", store.gpa, .limited(85)) catch return error.CorruptEntry;        defer store.gpa.free(encoded);        if (encoded.len != 84 or !std.mem.eql(u8, encoded[0..8], "TCOOK001") or            !std.mem.eql(u8, encoded[8..40], &key.digest)) return error.CorruptEntry;        const tree = try store.snapshot(dir, false);        const expected = encodeManifest(key, tree);        if (!std.mem.eql(u8, encoded, &expected)) return error.CorruptEntry;        return tree.summary;    }    fn touch(store: Store, manifest: std.Io.File) !void {        try manifest.writePositionalAll(store.io, "T", 0);        try manifest.sync(store.io);    }    fn prune(store: Store, pending: Summary) !void {        const version = try std.fs.path.join(store.gpa, &.{ store.path, "v2" });        defer store.gpa.free(version);        var root = try std.Io.Dir.openDirAbsolute(store.io, version, .{ .iterate = true, .follow_symlinks = false });        defer root.close(store.io);        var stages: std.ArrayList(Candidate) = .empty;        defer {            for (stages.items) |item| store.gpa.free(item.path);            stages.deinit(store.gpa);        }        var candidates: std.ArrayList(Candidate) = .empty;        defer {            for (candidates.items) |item| store.gpa.free(item.path);            candidates.deinit(store.gpa);        }        var total_bytes: u64 = 0;        var prefixes = root.iterate();        while (try prefixes.next(store.io)) |prefix| {            if (prefix.kind != .directory or prefix.name.len != 2) continue;            const prefix_path = try std.fs.path.join(store.gpa, &.{ version, prefix.name });            defer store.gpa.free(prefix_path);            var shard = try root.openDir(store.io, prefix.name, .{ .iterate = true, .follow_symlinks = false });            defer shard.close(store.io);            var children = shard.iterate();            while (try children.next(store.io)) |child| {                if (child.kind != .directory) continue;                const path = try std.fs.path.join(store.gpa, &.{ prefix_path, child.name });                errdefer store.gpa.free(path);                if (std.mem.startsWith(u8, child.name, ".stage-")) {                    if (child.name.len != 71) {                        store.gpa.free(path);                        continue;                    }                    if (stages.items.len == 65536) return error.CacheInventoryExceeded;                    try stages.append(store.gpa, .{                        .path = path,                        .key = Key.parseHex(child.name[7..]) catch return error.CacheInventoryExceeded,                        .bytes = 0,                        .mtime = 0,                    });                    continue;                }                if (child.name.len != 62) {                    store.gpa.free(path);                    continue;                }                if (candidates.items.len == 65536) return error.CacheInventoryExceeded;                var hex: [64]u8 = undefined;                @memcpy(hex[0..2], prefix.name);                @memcpy(hex[2..], child.name);                const key = Key.parseHex(&hex) catch return error.CacheInventoryExceeded;                var entry_dir = try shard.openDir(store.io, child.name, .{ .follow_symlinks = false });                defer entry_dir.close(store.io);                const stat = try entry_dir.statFile(store.io, ".cook", .{ .follow_symlinks = false });                const encoded = try entry_dir.readFileAlloc(store.io, ".cook", store.gpa, .limited(85));                defer store.gpa.free(encoded);                const bytes = if (encoded.len == 84 and std.mem.eql(u8, encoded[0..8], "TCOOK001"))                    std.mem.readInt(u64, encoded[44..52], .little)                else                    store.limits.max_entry_bytes;                total_bytes = std.math.add(u64, total_bytes, bytes) catch return error.CacheInventoryExceeded;                try candidates.append(store.gpa, .{                    .path = path,                    .key = key,                    .bytes = bytes,                    .mtime = @intCast(stat.mtime.nanoseconds),                });            }        }        for (stages.items) |stage| {            const parent_path = std.fs.path.dirname(stage.path).?;            var key_lock = try store.keyLock(parent_path, stage.key);            defer key_lock.close(store.io);            if (!try key_lock.tryLock(store.io, .exclusive)) continue;            try std.Io.Dir.cwd().deleteTree(store.io, stage.path);            var parent = try std.Io.Dir.openDirAbsolute(store.io, parent_path, .{});            defer parent.close(store.io);            try syncDirectory(store.io, parent);        }        std.mem.sortUnstable(Candidate, candidates.items, {}, candidateLessThan);        var count: usize = candidates.items.len;        total_bytes = std.math.add(u64, total_bytes, pending.bytes) catch return error.CacheInventoryExceeded;        count += 1;        for (candidates.items) |candidate| {            if (count <= store.limits.max_entries and total_bytes <= store.limits.max_cache_bytes) break;            const parent_path = std.fs.path.dirname(candidate.path).?;            var key_lock = try store.keyLock(parent_path, candidate.key);            defer key_lock.close(store.io);            if (!try key_lock.tryLock(store.io, .exclusive)) continue;            const manifest_path = try std.fs.path.join(store.gpa, &.{ candidate.path, ".cook" });            defer store.gpa.free(manifest_path);            var manifest = try std.Io.Dir.openFileAbsolute(store.io, manifest_path, .{ .mode = .read_write });            defer manifest.close(store.io);            if (!try manifest.tryLock(store.io, .exclusive)) continue;            try std.Io.Dir.cwd().deleteTree(store.io, candidate.path);            var parent = try std.Io.Dir.openDirAbsolute(store.io, parent_path, .{});            defer parent.close(store.io);            try syncDirectory(store.io, parent);            total_bytes -= candidate.bytes;            count -= 1;        }        if (count > store.limits.max_entries or total_bytes > store.limits.max_cache_bytes) return error.CacheFull;    }    fn snapshot(store: Store, dir: std.Io.Dir, sync_files: bool) !Snapshot {        var records: std.ArrayList(Record) = .empty;        defer {            for (records.items) |record| store.gpa.free(record.path);            records.deinit(store.gpa);        }        var summary: Summary = .{ .files = 0, .bytes = 0 };        var directories: u32 = 0;        try store.walk(dir, "", 0, sync_files, &records, &summary, &directories);        std.mem.sortUnstable(Record, records.items, {}, recordLessThan);        var hasher = Sha256.init(.{});        for (records.items) |record| {            hashFramed(&hasher, record.path);            hasher.update(&.{record.kind});            var size: [8]u8 = undefined;            std.mem.writeInt(u64, &size, record.size, .little);            hasher.update(&size);            hasher.update(&record.digest);        }        var digest: [32]u8 = undefined;        hasher.final(&digest);        return .{ .summary = summary, .digest = digest };    }    fn walk(        store: Store,        dir: std.Io.Dir,        relative: []const u8,        depth: u8,        sync_files: bool,        records: *std.ArrayList(Record),        summary: *Summary,        directories: *u32,    ) !void {        var iterator = dir.iterate();        while (try iterator.next(store.io)) |item| {            if (depth == 0 and std.mem.eql(u8, item.name, ".cook")) {                if (sync_files) return error.ReservedPath;                continue;            }            const child_path = if (relative.len == 0)                try store.gpa.dupe(u8, item.name)            else                try std.fs.path.join(store.gpa, &.{ relative, item.name });            errdefer store.gpa.free(child_path);            if (child_path.len > store.limits.max_path_bytes) return error.PathLimitExceeded;            switch (item.kind) {                .directory => {                    if (depth == store.limits.max_depth) return error.DepthLimitExceeded;                    if (directories.* == store.limits.max_directories) return error.DirectoryLimitExceeded;                    directories.* += 1;                    var child = try dir.openDir(store.io, item.name, .{ .iterate = true, .follow_symlinks = false });                    defer child.close(store.io);                    try store.walk(child, child_path, depth + 1, sync_files, records, summary, directories);                    if (sync_files) try syncDirectory(store.io, child);                    try records.append(store.gpa, .{ .path = child_path, .kind = 0, .size = 0, .digest = @splat(0) });                },                .file => {                    if (summary.files == store.limits.max_files) return error.FileLimitExceeded;                    var file = try dir.openFile(store.io, item.name, .{});                    defer file.close(store.io);                    const stat = try file.stat(store.io);                    if (stat.size > store.limits.max_entry_bytes -| summary.bytes) return error.EntrySizeExceeded;                    const digest = try digestFile(store.io, file);                    if (sync_files) try file.sync(store.io);                    try records.append(store.gpa, .{ .path = child_path, .kind = 1, .size = stat.size, .digest = digest });                    summary.files += 1;                    summary.bytes += stat.size;                },                else => return error.UnsupportedFileType,            }        }    }};

Source: lib/cook/src/root.zig:5

zig
pub const Store = cache.Store;

Audit

Definitions4
Public names4
Members4
Version26.7.0
Revisiondaab053ee433