PranitInitial commit439f83c3 months agoHistory
1676 lines · 58.4 KB · Zig
RawDownload
const std = @import("std");const io_mod = @import("../shared/io.zig");const profile_paths = @import("../shared/profile_paths.zig");const generation_fact_codec = @import("generation_fact_codec.zig");const usage_report = @import("usage_report.zig"); const Allocator = std.mem.Allocator;const usage_file = profile_paths.usage_file_name;const usage_lock_file = "usage.lock";const lock_deadline_ms: u64 = 2000;const max_file_bytes: usize = 32 * 1024 * 1024;const max_record_bytes: usize = 16 * 1024;const max_records: usize = 200_000;const compaction_threshold_bytes: u64 = 8 * 1024 * 1024;const retention_ms: i64 = std.time.ms_per_day * 35;const private_dir_permissions = std.Io.Dir.Permissions.fromMode(0o700);const private_file_permissions = std.Io.File.Permissions.fromMode(0o600); pub const AppendOutcome = enum {    appended,    duplicate,    conflict,}; pub const Loaded = struct {    coverage_started_at_ms: ?i64,    facts: []usage_report.GenerationFact,    pending: []usage_report.PendingMarker,    incidents: []usage_report.Incident,    record_count: usize,     pub fn deinit(self: *Loaded, alloc: Allocator) void {        for (self.facts) |*fact| fact.deinit(alloc);        alloc.free(self.facts);        for (self.pending) |*marker| marker.deinit(alloc);        alloc.free(self.pending);        alloc.free(self.incidents);        self.* = undefined;    }}; const ParsedRecord = union(enum) {    coverage: i64,    generation: usage_report.GenerationFact,    pending: usage_report.PendingMarker,    incident: usage_report.Incident,     fn deinit(self: *ParsedRecord, alloc: Allocator) void {        switch (self.*) {            .generation => |*fact| fact.deinit(alloc),            .pending => |*marker| marker.deinit(alloc),            .coverage, .incident => {},        }        self.* = undefined;    }}; const AppendDecision = struct {    outcome: AppendOutcome,    write: bool,}; const VariantIndexes = struct {    first: usize,    second: ?usize = null,}; const TailBoundary = struct {    length: u64,    incomplete: bool,}; pub const Store = struct {    home_path: []u8,    durable_home: ?io_mod.VerifiedDir = null,    lock_ops: io_mod.LockOps = .{},     pub fn initFromHome(alloc: Allocator, home_path: []const u8) !Store {        const owned_home = try alloc.dupe(u8, home_path);        errdefer alloc.free(owned_home);        return .{            .home_path = owned_home,            .durable_home = try openExistingDurableHome(home_path),        };    }     pub fn deinit(self: *Store, alloc: Allocator) void {        if (self.durable_home) |*dir| dir.close();        alloc.free(self.home_path);        self.* = undefined;    }     fn appendFact(        self: *Store,        alloc: Allocator,        fact: usage_report.GenerationFact,    ) !AppendOutcome {        return self.appendEvent(alloc, .{ .generation = fact });    }     pub fn appendEvent(        self: *Store,        alloc: Allocator,        event: usage_report.ProfileEvent,    ) !AppendOutcome {        try validateEvent(event);        const event_line = try serializeEvent(alloc, event);        defer alloc.free(event_line);        if (event_line.len > max_record_bytes) return error.UsageRecordTooLarge;         try self.ensureWritable();        var lock = self.acquireLock() catch |err| switch (err) {            error.LockBusy => return error.UsageLockBusy,            error.LockUnsupported => return error.UsageLockUnsupported,            else => return err,        };        defer lock.release();         var file: ?std.Io.File = (try self.openUsage(true, true)).?;        defer if (file) |open_file| open_file.close(io_mod.getIo());        const tail = try inspectTail(file.?);        const has_incomplete_tail = tail.incomplete;        var boundary = tail.length;        var loaded = try self.loadFromFile(alloc, file.?, boundary);        defer loaded.deinit(alloc);         const decision = classifyEvent(loaded, event);         const now_ms = @max(io_mod.milliTimestamp(), 0);        var append_bytes: std.Io.Writer.Allocating = .init(alloc);        defer append_bytes.deinit();        var append_record_count: usize = 0;        if (loaded.coverage_started_at_ms == null) {            try writeCoverage(&append_bytes.writer, now_ms);            append_record_count += 1;        }        if (has_incomplete_tail) {            try writeIncident(&append_bytes.writer, .{                .occurred_at_ms = now_ms,                .completeness = .incomplete,            });            append_record_count += 1;        }        if (decision.write) {            try append_bytes.writer.writeAll(event_line);            append_record_count += 1;        }        if (append_bytes.written().len == 0) return decision.outcome;         var next_length = std.math.add(            u64,            boundary,            std.math.cast(u64, append_bytes.written().len) orelse                return error.UsageCapacityExceeded,        ) catch return error.UsageCapacityExceeded;        var compacted_before_append = false;        var append_committed = false;        var base_record_count = loaded.record_count;        if (shouldCompactBeforeAppend(            next_length,            base_record_count,            append_record_count,            loaded,            now_ms,        )) {            compacted_before_append = true;            base_record_count = retainedRecordCount(loaded, now_ms);            if (has_incomplete_tail) {                var replacement: std.Io.Writer.Allocating = .init(alloc);                defer replacement.deinit();                try writeRetainedRecords(&replacement.writer, loaded, now_ms);                try replacement.writer.writeAll(append_bytes.written());                next_length = std.math.cast(u64, replacement.written().len) orelse                    return error.UsageCapacityExceeded;                if (next_length > max_file_bytes) return error.UsageCapacityExceeded;                _ = try checkedRecordCount(base_record_count, append_record_count);                try self.replaceUsageLocked(alloc, replacement.written());                file.?.close(io_mod.getIo());                file = null;                append_committed = true;            } else {                file.?.close(io_mod.getIo());                file = null;                try self.compactLocked(alloc, now_ms);                file = (try self.openUsage(true, false)) orelse                    return error.UsageReadFailed;                boundary = try file.?.length(io_mod.getIo());                next_length = std.math.add(                    u64,                    boundary,                    std.math.cast(u64, append_bytes.written().len) orelse                        return error.UsageCapacityExceeded,                ) catch return error.UsageCapacityExceeded;            }        }        if (next_length > max_file_bytes) return error.UsageCapacityExceeded;        _ = try checkedRecordCount(base_record_count, append_record_count);        if (!append_committed) {            if (has_incomplete_tail) {                var replacement: std.Io.Writer.Allocating = .init(alloc);                defer replacement.deinit();                try copyFilePrefix(                    &replacement.writer,                    file.?,                    boundary,                );                try replacement.writer.writeAll(append_bytes.written());                try self.replaceUsageLocked(alloc, replacement.written());                file.?.close(io_mod.getIo());                file = null;            } else {                try file.?.writePositionalAll(io_mod.getIo(), append_bytes.written(), boundary);                file.?.sync(io_mod.getIo()) catch return error.UsageWriteFailed;            }        }         if (shouldCompactAfterAppend(            next_length,            loaded,            eventTimestamp(event),            now_ms,            compacted_before_append,        )) {            if (file) |open_file| {                open_file.close(io_mod.getIo());                file = null;            }            try self.compactLocked(alloc, now_ms);        }        return decision.outcome;    }     /// Loads one stable read-only boundary without creating profile state.    pub fn load(self: *Store, alloc: Allocator) !Loaded {        if (self.durable_home == null) {            self.durable_home = try openExistingDurableHome(self.home_path);        }        try self.validateReadable();        while (true) {            const existing_lock = self.acquireExistingLock() catch |err| switch (err) {                error.LockBusy => return error.UsageLockBusy,                error.LockUnsupported => return error.UsageLockUnsupported,                else => return err,            };            if (existing_lock) |held| {                var lock = held;                defer lock.release();                return self.loadUnlocked(alloc);            }             var loaded = try self.loadUnlocked(alloc);            if (!try self.lockFileExists()) return loaded;            loaded.deinit(alloc);        }    }     fn loadUnlocked(self: *Store, alloc: Allocator) !Loaded {        var file = (try self.openUsage(false, false)) orelse return .{            .coverage_started_at_ms = null,            .facts = try alloc.alloc(usage_report.GenerationFact, 0),            .pending = try alloc.alloc(usage_report.PendingMarker, 0),            .incidents = try alloc.alloc(usage_report.Incident, 0),            .record_count = 0,        };        defer file.close(io_mod.getIo());        const boundary = try file.length(io_mod.getIo());        return self.loadFromFile(alloc, file, boundary);    }     fn validateReadable(self: *Store) !void {        const durable_home = self.durable_home orelse return;        const stat = try durable_home.dir.stat(io_mod.getIo());        if (stat.kind != .directory) return error.DurablePathUnsafe;        if (stat.permissions.toMode() & 0o777 != 0o700) {            return error.PrivateStatePermissionsUnsupported;        }    }     fn ensureWritable(self: *Store) !void {        if (self.durable_home == null) {            const zio = io_mod.getIo();            var home = io_mod.VerifiedDir{                .dir = std.Io.Dir.openDirAbsolute(zio, self.home_path, .{                    .iterate = true,                }) catch return error.DurableLayoutFailed,            };            defer home.close();            self.durable_home = io_mod.openOrCreateVerifiedPrivateDir(                &home,                profile_paths.root_dir_name,            ) catch |err| switch (err) {                error.PrivateStatePermissionsUnsupported, error.DurablePathUnsafe => return err,                else => return error.DurableLayoutFailed,            };        }        self.durable_home.?.dir.setPermissions(            io_mod.getIo(),            private_dir_permissions,        ) catch return error.PrivateStatePermissionsUnsupported;        const stat = try self.durable_home.?.dir.stat(io_mod.getIo());        if (stat.kind != .directory) return error.DurablePathUnsafe;        if (stat.permissions.toMode() & 0o777 != 0o700) {            return error.PrivateStatePermissionsUnsupported;        }    }     fn acquireLock(self: *Store) !io_mod.TimedAdvisoryLock {        return io_mod.acquireTimedAdvisoryLockWithOps(            &self.durable_home.?,            usage_lock_file,            lock_deadline_ms,            self.lock_ops,        );    }     fn acquireExistingLock(self: *Store) !?io_mod.TimedAdvisoryLock {        const durable_home = self.durable_home orelse return null;        const zio = io_mod.getIo();        const file = durable_home.dir.openFile(zio, usage_lock_file, .{            .mode = .read_write,            .allow_directory = false,            .follow_symlinks = false,            .resolve_beneath = true,        }) catch |err| switch (err) {            error.FileNotFound => return null,            error.SymLinkLoop, error.IsDir, error.NotDir => return error.DurablePathUnsafe,            else => return err,        };        errdefer file.close(zio);        const stat = try file.stat(zio);        if (stat.kind != .file or stat.nlink != 1) {            return error.DurablePathUnsafe;        }        if (stat.permissions.toMode() & 0o777 != 0o600) {            return error.PrivateStatePermissionsUnsupported;        }         const started = self.lock_ops.now_ms(self.lock_ops.ctx);        const deadline = started + @as(i64, @intCast(lock_deadline_ms));        while (true) {            const locked = self.lock_ops.try_lock(                self.lock_ops.ctx,                file,            ) catch |err| switch (err) {                error.FileLocksUnsupported => return error.LockUnsupported,                else => return err,            };            if (locked) return .{ .file = file };            if (self.lock_ops.now_ms(self.lock_ops.ctx) >= deadline) {                return error.LockBusy;            }            self.lock_ops.sleep_ms(                self.lock_ops.ctx,                @min(@as(u64, 10), lock_deadline_ms),            );        }    }     fn lockFileExists(self: *Store) !bool {        const durable_home = self.durable_home orelse return false;        const stat = durable_home.dir.statFile(io_mod.getIo(), usage_lock_file, .{            .follow_symlinks = false,        }) catch |err| switch (err) {            error.FileNotFound => return false,            error.SymLinkLoop, error.NotDir => return error.DurablePathUnsafe,            else => return err,        };        if (stat.kind != .file or stat.nlink != 1) {            return error.DurablePathUnsafe;        }        if (stat.permissions.toMode() & 0o777 != 0o600) {            return error.PrivateStatePermissionsUnsupported;        }        return true;    }     fn openUsage(        self: *Store,        writable: bool,        create: bool,    ) !?std.Io.File {        if (self.durable_home == null) return null;        const zio = io_mod.getIo();        var created = false;        var file = openExistingUsageFile(            self.durable_home.?.dir,            if (writable) .read_write else .read_only,        ) catch |err| switch (err) {            error.FileNotFound => create_file: {                if (!create) return null;                const new_file = self.durable_home.?.dir.createFile(zio, usage_file, .{                    .read = true,                    .truncate = false,                    .exclusive = true,                    .permissions = private_file_permissions,                    .resolve_beneath = true,                }) catch |create_err| switch (create_err) {                    error.PathAlreadyExists => return self.openUsage(writable, false),                    else => return create_err,                };                created = true;                break :create_file new_file;            },            error.SymLinkLoop, error.IsDir, error.NotDir => return error.DurablePathUnsafe,            else => return err,        };        errdefer file.close(zio);         const initial = try file.stat(zio);        try io_mod.verifyOpenedRegularFile(            initial,            if (writable) .read_write else .read_only,        );        if (writable) {            file.setPermissions(zio, private_file_permissions) catch                return error.PrivateStatePermissionsUnsupported;        }        const verified = if (writable) try file.stat(zio) else initial;        if (verified.permissions.toMode() & 0o777 != 0o600) {            return error.PrivateStatePermissionsUnsupported;        }        if (created) {            io_mod.syncVerifiedDir(self.durable_home.?.dir) catch                return error.DurableLayoutFailed;        }        return file;    }     fn loadFromFile(        self: *Store,        alloc: Allocator,        file: std.Io.File,        boundary: u64,    ) !Loaded {        _ = self;        if (boundary > max_file_bytes) return error.UsageCapacityExceeded;        if (boundary == 0) {            return .{                .coverage_started_at_ms = null,                .facts = try alloc.alloc(usage_report.GenerationFact, 0),                .pending = try alloc.alloc(usage_report.PendingMarker, 0),                .incidents = try alloc.alloc(usage_report.Incident, 0),                .record_count = 0,            };        }        const byte_len = std.math.cast(usize, boundary) orelse            return error.UsageCapacityExceeded;        const bytes = try alloc.alloc(u8, byte_len);        defer alloc.free(bytes);        const read_count = try file.readPositionalAll(io_mod.getIo(), bytes, 0);        if (read_count != byte_len) return error.UsageReadFailed;        if (bytes[bytes.len - 1] != '\n') return error.UsageStoreIncomplete;         var facts: std.ArrayList(usage_report.GenerationFact) = .empty;        errdefer {            for (facts.items) |*fact| fact.deinit(alloc);            facts.deinit(alloc);        }        var pending: std.ArrayList(usage_report.PendingMarker) = .empty;        errdefer {            for (pending.items) |*marker| marker.deinit(alloc);            pending.deinit(alloc);        }        var incidents: std.ArrayList(usage_report.Incident) = .empty;        errdefer incidents.deinit(alloc);        var fact_indexes: std.StringHashMapUnmanaged(VariantIndexes) = .empty;        defer fact_indexes.deinit(alloc);        var pending_indexes: std.StringHashMapUnmanaged(VariantIndexes) = .empty;        defer pending_indexes.deinit(alloc);        var coverage_started_at_ms: ?i64 = null;        var record_count: usize = 0;        var lines = std.mem.splitScalar(u8, bytes, '\n');        while (lines.next()) |line| {            if (line.len == 0) continue;            record_count += 1;            if (record_count > max_records or line.len > max_record_bytes) {                return error.UsageCapacityExceeded;            }            var record = try parseRecord(alloc, line);            defer record.deinit(alloc);            switch (record) {                .coverage => |started_at_ms| {                    if (coverage_started_at_ms) |existing| {                        if (existing != started_at_ms) return error.InvalidUsageStore;                    } else {                        coverage_started_at_ms = started_at_ms;                    }                },                .generation => |fact| {                    if (coverage_started_at_ms == null) return error.InvalidUsageStore;                    if (fact_indexes.getPtr(fact.id)) |indexes| {                        if (usage_report.GenerationFact.eql(                            facts.items[indexes.first],                            fact,                        ) or (indexes.second != null and                            usage_report.GenerationFact.eql(                                facts.items[indexes.second.?],                                fact,                            )))                        {

Lines 1–500 of 1676.