Skip to documentation
SLOP

tiny.sandbox.layer

Reference tiny.sandbox layer

Defined in tiny.sandbox.

API (3)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallstest sourcelib.sandbox.src.layertest: layer copies source tree and cl...test sourcelib.sandbox.src.layertest: layer preserves source file per...test sourcelib.sandbox.src.layertest: layer preserves source symlinkstest sourcelib.sandbox.src.layertest: layer rejects unsupported sourc...test sourcelib.sandbox.src.layertest: layer skips oversized source fi...private sourcelib.sandbox.src.layercopyTreeprivate sourcelib.sandbox.src.layercreateEmptylayercreate
Static calls · unresolved targets: 0 · external targets: 1.

Source: lib/sandbox/src/layer.zig

zig
const std = @import("std");const builtin = @import("builtin");const sys = @import("sys");const change = @import("change.zig");const scan = @import("scan.zig");const Allocator = std.mem.Allocator;const fs_io = sys.fs.debugIo();const root_dir_name = "root";pub const Options = struct {    scratch: sys.fs.Dir,    source: ?sys.fs.Dir = null,    prefix: []const u8 = "sandbox",    max_file_bytes: usize = 16 * 1024 * 1024,};pub const Layer = struct {    allocator: Allocator,    scratch: sys.fs.Dir,    root: sys.fs.Dir,    path: []u8,    cleanup: bool = true,    skipped: std.ArrayList([]u8) = .empty,    pub fn deinit(self: *Layer) void {        self.root.close(fs_io);        if (self.cleanup) self.scratch.deleteTree(fs_io, self.path) catch {};        for (self.skipped.items) |item| self.allocator.free(item);        self.skipped.deinit(self.allocator);        self.allocator.free(self.path);        self.* = undefined;    }    pub fn readFileAlloc(self: *const Layer, allocator: Allocator, path: []const u8, limit: usize) ![]u8 {        return try self.root.readFileAlloc(fs_io, path, allocator, .limited(limit));    }};pub fn create(allocator: Allocator, options: Options) !Layer {    var layer = try createEmpty(allocator, options.scratch, options.prefix);    errdefer layer.deinit();    if (options.source) |source| try copyTree(allocator, source, layer.root, options.max_file_bytes, &layer.skipped);    return layer;}fn createEmpty(allocator: Allocator, scratch: sys.fs.Dir, prefix: []const u8) !Layer {    var attempt: usize = 0;    while (attempt < 128) : (attempt += 1) {        const name = try uniqueNameAlloc(allocator, prefix, attempt);        errdefer allocator.free(name);        scratch.createDir(fs_io, name, .default_dir) catch |err| switch (err) {            error.PathAlreadyExists => {                allocator.free(name);                continue;            },            else => return err,        };        errdefer scratch.deleteTree(fs_io, name) catch {};        var container = try scratch.openDir(fs_io, name, .{});        defer container.close(fs_io);        try container.createDir(fs_io, root_dir_name, .default_dir);        const root = try container.openDir(fs_io, root_dir_name, .{ .iterate = true });        return .{            .allocator = allocator,            .scratch = scratch,            .root = root,            .path = name,        };    }    return error.UniquePathExhausted;}fn uniqueNameAlloc(allocator: Allocator, prefix: []const u8, attempt: usize) Allocator.Error![]u8 {    const pid = sys.process.currentProcessId() catch 0;    const now = sys.time.nanoTimestamp();    return try std.fmt.allocPrint(allocator, "{s}-{x}-{x}-{d}", .{ prefix, pid, now, attempt });}fn copyTree(allocator: Allocator, source: sys.fs.Dir, dest: sys.fs.Dir, max_file_bytes: usize, skipped: *std.ArrayList([]u8)) !void {    var dir = try source.openDir(fs_io, ".", .{ .iterate = true });    defer dir.close(fs_io);    var walker = try dir.walk(allocator);    defer walker.deinit();    while (try walker.next(fs_io)) |entry| {        switch (entry.kind) {            .directory => try dest.createDirPath(fs_io, entry.path),            .file => switch (try copyFile(source, dest, entry.path, max_file_bytes)) {                .copied => {},                .skipped => try skipped.append(allocator, try allocator.dupe(u8, entry.path)),            },            .sym_link => try copyLink(source, dest, entry.path),            else => return error.UnsupportedSourceEntry,        }    }}const CopyOutcome = enum { copied, skipped };fn copyFile(source: sys.fs.Dir, dest: sys.fs.Dir, path: []const u8, max_file_bytes: usize) !CopyOutcome {    var input = try source.openFile(fs_io, path, .{});    defer input.close(fs_io);    const source_stat = try input.stat(fs_io);    if (source_stat.size > max_file_bytes) return .skipped;    if (std.fs.path.dirname(path)) |parent| {        if (parent.len != 0) try dest.createDirPath(fs_io, parent);    }    var output = try dest.createFile(fs_io, path, .{        .truncate = true,        .permissions = source_stat.permissions,    });    defer output.close(fs_io);    var reader_buffer: [16 * 1024]u8 = undefined;    var reader = input.reader(fs_io, &reader_buffer);    var writer_buffer: [16 * 1024]u8 = undefined;    var writer = output.writer(fs_io, &writer_buffer);    var chunk: [16 * 1024]u8 = undefined;    while (true) {        const count = reader.interface.readSliceShort(&chunk) catch |err| switch (err) {            error.ReadFailed => return reader.err orelse error.ReadFailed,        };        if (count == 0) break;        try writer.interface.writeAll(chunk[0..count]);    }    try writer.interface.flush();    try output.setPermissions(fs_io, source_stat.permissions);    return .copied;}fn copyLink(source: sys.fs.Dir, dest: sys.fs.Dir, path: []const u8) !void {    if (std.fs.path.dirname(path)) |parent| {        if (parent.len != 0) try dest.createDirPath(fs_io, parent);    }    var target_buffer: [std.fs.max_path_bytes]u8 = undefined;    const target_len = try source.readLink(fs_io, path, &target_buffer);    try dest.symLink(fs_io, target_buffer[0..target_len], path, .{ .is_directory = linkPointsToDirectory(source, path) });}fn linkPointsToDirectory(source: sys.fs.Dir, path: []const u8) bool {    const stat = source.statFile(fs_io, path, .{}) catch return false;    return stat.kind == .directory;}fn createFifo(dir: sys.fs.Dir, path: [:0]const u8) !void {    if (builtin.os.tag != .linux) return error.SkipZigTest;    return sys.fs.createNamedPipe(dir, path, 0o600) catch |err| switch (err) {        error.AccessDenied, error.UnsupportedPlatform => error.SkipZigTest,        error.CreateFailed => error.CreateFifoFailed,    };}fn scratchEntryCount(dir: sys.fs.Dir, allocator: Allocator) !usize {    var opened = try dir.openDir(fs_io, ".", .{ .iterate = true });    defer opened.close(fs_io);    var walker = try opened.walk(allocator);    defer walker.deinit();    var count: usize = 0;    while (try walker.next(fs_io)) |_| count += 1;    return count;}test "layer copies source tree and cleans up" {    var source = std.testing.tmpDir(.{});    defer source.cleanup();    var scratch = std.testing.tmpDir(.{});    defer scratch.cleanup();    try source.dir.createDirPath(fs_io, "dir");    try source.dir.writeFile(fs_io, .{ .sub_path = "dir/a.txt", .data = "alpha" });    var value = try create(std.testing.allocator, .{        .scratch = scratch.dir,        .source = source.dir,        .prefix = "layer-test",    });    const path = try std.testing.allocator.dupe(u8, value.path);    defer std.testing.allocator.free(path);    const content = try value.readFileAlloc(std.testing.allocator, "dir/a.txt", 1024);    defer std.testing.allocator.free(content);    try std.testing.expectEqualStrings("alpha", content);    value.deinit();    try std.testing.expectError(error.FileNotFound, scratch.dir.statFile(fs_io, path, .{}));}test "layer preserves source symlinks" {    var source = std.testing.tmpDir(.{});    defer source.cleanup();    var scratch = std.testing.tmpDir(.{});    defer scratch.cleanup();    try source.dir.writeFile(fs_io, .{ .sub_path = "target.txt", .data = "target" });    source.dir.symLink(fs_io, "target.txt", "link", .{}) catch |err| switch (err) {        error.AccessDenied, error.PermissionDenied => return error.SkipZigTest,        else => return err,    };    var value = try create(std.testing.allocator, .{        .scratch = scratch.dir,        .source = source.dir,        .prefix = "layer-link",    });    defer value.deinit();    var target_buffer: [std.fs.max_path_bytes]u8 = undefined;    const target_len = try value.root.readLink(fs_io, "link", &target_buffer);    try std.testing.expectEqualStrings("target.txt", target_buffer[0..target_len]);    var snapshot = try scan.capture(value.root, std.testing.allocator);    defer snapshot.deinit();    try std.testing.expectEqual(change.Kind.sym_link, snapshot.find("link").?.kind);}test "layer preserves source file permissions" {    if (comptime !sys.fs.FilePermissions.has_executable_bit) return error.SkipZigTest;    var source = std.testing.tmpDir(.{});    defer source.cleanup();    var scratch = std.testing.tmpDir(.{});    defer scratch.cleanup();    try source.dir.writeFile(fs_io, .{ .sub_path = "tool.sh", .data = "#!/bin/sh\n" });    try source.dir.setFilePermissions(fs_io, "tool.sh", .executable_file, .{});    var value = try create(std.testing.allocator, .{        .scratch = scratch.dir,        .source = source.dir,        .prefix = "layer-mode",    });    defer value.deinit();    const stat = try value.root.statFile(fs_io, "tool.sh", .{});    try std.testing.expect((stat.permissions.toMode() & 0o111) != 0);}test "layer rejects unsupported source entries and cleans up" {    var source = std.testing.tmpDir(.{});    defer source.cleanup();    var scratch = std.testing.tmpDir(.{});    defer scratch.cleanup();    try createFifo(source.dir, "pipe");    try std.testing.expectError(error.UnsupportedSourceEntry, create(std.testing.allocator, .{        .scratch = scratch.dir,        .source = source.dir,        .prefix = "layer-fifo",    }));    try std.testing.expectEqual(@as(usize, 0), try scratchEntryCount(scratch.dir, std.testing.allocator));}test "layer skips oversized source files and records them" {    var source = std.testing.tmpDir(.{});    defer source.cleanup();    var scratch = std.testing.tmpDir(.{});    defer scratch.cleanup();    try source.dir.writeFile(fs_io, .{ .sub_path = "huge.txt", .data = "abcdef" });    try source.dir.writeFile(fs_io, .{ .sub_path = "small.txt", .data = "ok" });    var value = try create(std.testing.allocator, .{        .scratch = scratch.dir,        .source = source.dir,        .prefix = "layer-size",        .max_file_bytes = 2,    });    defer value.deinit();    try std.testing.expectEqual(@as(usize, 1), value.skipped.items.len);    try std.testing.expectEqualStrings("huge.txt", value.skipped.items[0]);    try std.testing.expectError(error.FileNotFound, value.root.statFile(fs_io, "huge.txt", .{}));    const small = try value.readFileAlloc(std.testing.allocator, "small.txt", 1024);    defer std.testing.allocator.free(small);    try std.testing.expectEqualStrings("ok", small);}

Source: lib/sandbox/src/root.zig:36

zig
pub const layer = @import("layer.zig");

Audit

Definitions3
Public names3
Members4
Version26.7.0
Revisiondaab053ee433