Page MenuHomePhorge

lockfile.zig
No OneTemporary

Size
13 KB
Referenced Files
None
Subscribers
None

lockfile.zig

const std = @import("std");
const builtin = @import("builtin");
pub const MAGIC: u32 = 0x504B474C;
pub const VERSION: u32 = 1;
pub const StringRef = extern struct {
offset: u32,
len: u32,
pub fn slice(self: StringRef, string_table: []const u8) []const u8 {
const offset: usize = self.offset;
const len: usize = self.len;
if (offset >= string_table.len) return "";
const end = @min(offset + len, string_table.len);
return string_table[offset..end];
}
pub const empty: StringRef = .{ .offset = 0, .len = 0 };
};
pub const Header = extern struct {
magic: u32 = MAGIC,
version: u32 = VERSION,
package_count: u32 = 0,
dependency_count: u32 = 0,
string_table_offset: u32 = 0,
string_table_size: u32 = 0,
package_array_offset: u32 = 0,
dependency_array_offset: u32 = 0,
hash_table_offset: u32 = 0,
hash_table_size: u32 = 0,
_reserved: [24]u8 = [_]u8{0} ** 24,
pub fn validate(self: *const Header) bool {
return self.magic == MAGIC and self.version == VERSION;
}
};
pub const PackageFlags = packed struct(u8) {
dev: bool = false,
optional: bool = false,
peer: bool = false,
bundled: bool = false,
has_bin: bool = false,
has_scripts: bool = false,
direct: bool = false,
_reserved: u1 = 0,
};
pub const Package = extern struct {
name: StringRef,
version_major: u64,
version_minor: u64,
version_patch: u64,
prerelease: StringRef,
integrity: [64]u8,
tarball_url: StringRef,
parent_path: StringRef,
deps_start: u32,
deps_count: u32,
flags: PackageFlags,
_padding: [3]u8 = .{ 0, 0, 0 },
comptime {
std.debug.assert(@sizeOf(Package) == 136);
}
pub fn versionString(self: *const Package, allocator: std.mem.Allocator, string_table: []const u8) ![]u8 {
const prerelease_str = self.prerelease.slice(string_table);
if (prerelease_str.len > 0) {
return std.fmt.allocPrint(allocator, "{d}.{d}.{d}-{s}", .{
self.version_major,
self.version_minor,
self.version_patch,
prerelease_str,
});
}
return std.fmt.allocPrint(allocator, "{d}.{d}.{d}", .{
self.version_major,
self.version_minor,
self.version_patch,
});
}
pub fn integrityHex(self: *const Package) [128]u8 {
return std.fmt.bytesToHex(self.integrity, .lower);
}
pub fn installPath(self: *const Package, allocator: std.mem.Allocator, string_table: []const u8) ![]u8 {
const parent = self.parent_path.slice(string_table);
const name = self.name.slice(string_table);
if (parent.len > 0) return std.fmt.allocPrint(allocator, "{s}/node_modules/{s}", .{ parent, name });
return allocator.dupe(u8, name);
}
pub fn isNested(self: *const Package) bool {
return self.parent_path.len > 0;
}
};
pub const DependencyFlags = packed struct(u8) {
peer: bool = false,
dev: bool = false,
optional: bool = false,
_reserved: u5 = 0,
};
pub const Dependency = extern struct {
package_index: u32,
constraint: StringRef,
flags: DependencyFlags = .{},
_padding: [3]u8 = .{ 0, 0, 0 },
};
pub const HashBucket = extern struct {
name_hash: u32,
package_index: u32,
pub const empty: HashBucket = .{
.name_hash = 0,
.package_index = std.math.maxInt(u32)
};
};
pub fn djb2Hash(str: []const u8) u32 {
var hash: u32 = 5381;
for (str) |c| hash = ((hash << 5) +% hash) +% c;
return hash;
}
pub const Lockfile = struct {
data:
if (builtin.os.tag == .windows) []align(@alignOf(Header)) u8
else []align(std.heap.page_size_min) const u8,
header: *const Header,
string_table: []const u8,
packages: []const Package,
dependencies: []const Dependency,
hash_table: []const HashBucket,
pub fn open(path: []const u8) !Lockfile {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const stat = try file.stat();
if (stat.size < @sizeOf(Header)) {
return error.InvalidLockfile;
}
if (comptime builtin.os.tag == .windows) {
const data = try std.heap.c_allocator.alignedAlloc(u8, std.mem.Alignment.fromByteUnits(@alignOf(Header)), stat.size);
errdefer std.heap.c_allocator.free(data);
const bytes_read = try file.readAll(data);
if (bytes_read != stat.size) {
std.heap.c_allocator.free(data);
return error.InvalidLockfile;
}
return initFromDataWindows(data);
} else {
const data = try std.posix.mmap(
null, stat.size,
std.posix.PROT.READ,
.{ .TYPE = .PRIVATE },
file.handle, 0,
);
return initFromData(data);
}
}
fn initFromDataWindows(data: []align(@alignOf(Header)) u8) !Lockfile {
if (data.len < @sizeOf(Header)) return error.InvalidLockfile;
const header: *const Header = @ptrCast(@alignCast(data.ptr));
if (!header.validate()) return error.InvalidLockfile;
if (header.string_table_offset + header.string_table_size > data.len or
header.package_array_offset + header.package_count * @sizeOf(Package) > data.len or
header.dependency_array_offset + header.dependency_count * @sizeOf(Dependency) > data.len or
header.hash_table_offset + header.hash_table_size * @sizeOf(HashBucket) > data.len)
{ return error.InvalidLockfile; }
return .{
.data = data,
.header = header,
.string_table = data[header.string_table_offset..][0..header.string_table_size],
.packages = @as([*]const Package, @ptrCast(@alignCast(data.ptr + header.package_array_offset)))[0..header.package_count],
.dependencies = @as([*]const Dependency, @ptrCast(@alignCast(data.ptr + header.dependency_array_offset)))[0..header.dependency_count],
.hash_table = @as([*]const HashBucket, @ptrCast(@alignCast(data.ptr + header.hash_table_offset)))[0..header.hash_table_size],
};
}
pub fn initFromData(data: []align(std.heap.page_size_min) const u8) !Lockfile {
if (data.len < @sizeOf(Header)) {
return error.InvalidLockfile;
}
const header: *const Header = @ptrCast(@alignCast(data.ptr));
if (!header.validate()) {
return error.InvalidLockfile;
}
if (header.string_table_offset + header.string_table_size > data.len or
header.package_array_offset + header.package_count * @sizeOf(Package) > data.len or
header.dependency_array_offset + header.dependency_count * @sizeOf(Dependency) > data.len or
header.hash_table_offset + header.hash_table_size * @sizeOf(HashBucket) > data.len)
{ return error.InvalidLockfile; }
const string_table = data[header.string_table_offset..][0..header.string_table_size];
const packages_ptr: [*]const Package = @ptrCast(@alignCast(data.ptr + header.package_array_offset));
const packages = packages_ptr[0..header.package_count];
const deps_ptr: [*]const Dependency = @ptrCast(@alignCast(data.ptr + header.dependency_array_offset));
const dependencies = deps_ptr[0..header.dependency_count];
const hash_ptr: [*]const HashBucket = @ptrCast(@alignCast(data.ptr + header.hash_table_offset));
const hash_table = hash_ptr[0..header.hash_table_size];
return .{
.data = data,
.header = header,
.string_table = string_table,
.packages = packages,
.dependencies = dependencies,
.hash_table = hash_table,
};
}
pub fn close(self: *Lockfile) void {
if (comptime builtin.os.tag == .windows) {
std.heap.c_allocator.free(self.data);
} else std.posix.munmap(self.data);
self.* = undefined;
}
pub fn lookupPackage(self: *const Lockfile, name: []const u8) ?*const Package {
if (self.hash_table.len == 0) return null;
const hash = djb2Hash(name);
var index = hash % @as(u32, @intCast(self.hash_table.len));
var probes: u32 = 0;
while (probes < self.hash_table.len) : (probes += 1) {
const bucket = &self.hash_table[index];
if (bucket.package_index == std.math.maxInt(u32)) return null;
if (bucket.name_hash == hash) {
const pkg = &self.packages[bucket.package_index];
const pkg_name = pkg.name.slice(self.string_table);
if (std.mem.eql(u8, pkg_name, name)) return pkg;
}
index = (index + 1) % @as(u32, @intCast(self.hash_table.len));
}
return null;
}
pub fn getPackageDeps(self: *const Lockfile, pkg: *const Package) []const Dependency {
if (pkg.deps_count == 0) return &[_]Dependency{};
if (pkg.deps_start >= self.dependencies.len or
pkg.deps_start + pkg.deps_count > self.dependencies.len) return &[_]Dependency{};
return self.dependencies[pkg.deps_start..][0..pkg.deps_count];
}
};
pub const LockfileWriter = struct {
allocator: std.mem.Allocator,
packages: std.ArrayListUnmanaged(Package),
dependencies: std.ArrayListUnmanaged(Dependency),
string_builder: std.ArrayListUnmanaged(u8),
string_offsets: std.StringHashMap(StringRef),
pub fn init(allocator: std.mem.Allocator) LockfileWriter {
return .{
.allocator = allocator,
.packages = .{},
.dependencies = .{},
.string_builder = .{},
.string_offsets = std.StringHashMap(StringRef).init(allocator),
};
}
pub fn deinit(self: *LockfileWriter) void {
self.packages.deinit(self.allocator);
self.dependencies.deinit(self.allocator);
self.string_builder.deinit(self.allocator);
var key_iter = self.string_offsets.keyIterator();
while (key_iter.next()) |key| {
self.allocator.free(key.*);
}
self.string_offsets.deinit();
}
pub fn internString(self: *LockfileWriter, str: []const u8) !StringRef {
if (str.len == 0) return StringRef.empty;
if (self.string_offsets.get(str)) |ref| return ref;
if (self.string_builder.items.len > std.math.maxInt(u32)) return error.StringTableTooLarge;
if (str.len > std.math.maxInt(u32)) return error.StringTooLarge;
const offset: u32 = @intCast(self.string_builder.items.len);
try self.string_builder.appendSlice(self.allocator, str);
const ref = StringRef{ .offset = offset, .len = @intCast(str.len) };
const stored_str = try self.allocator.dupe(u8, str);
errdefer self.allocator.free(stored_str);
try self.string_offsets.put(stored_str, ref);
return ref;
}
pub fn addPackage(self: *LockfileWriter, pkg: Package) !u32 {
const index: u32 = @intCast(self.packages.items.len);
try self.packages.append(self.allocator, pkg);
return index;
}
pub fn addDependency(self: *LockfileWriter, dep: Dependency) !void {
try self.dependencies.append(self.allocator, dep);
}
fn alignOffset(offset: u32, alignment: u32) u32 {
const rem = offset % alignment;
return if (rem == 0) offset else offset + (alignment - rem);
}
pub fn write(self: *LockfileWriter, path: []const u8) !void {
const file = try std.fs.cwd().createFile(path, .{});
defer file.close();
const header_size: u32 = @sizeOf(Header);
const string_table_offset = header_size;
const string_table_size: u32 = @intCast(self.string_builder.items.len);
const package_array_offset = alignOffset(string_table_offset + string_table_size, @alignOf(Package));
const package_pad_size = package_array_offset - (string_table_offset + string_table_size);
const package_array_size: u32 = @intCast(self.packages.items.len * @sizeOf(Package));
const dependency_array_offset = alignOffset(package_array_offset + package_array_size, @alignOf(Dependency));
const dep_pad_size = dependency_array_offset - (package_array_offset + package_array_size);
const dependency_array_size: u32 = @intCast(self.dependencies.items.len * @sizeOf(Dependency));
const hash_table_size: u32 = @intCast(@max(1, self.packages.items.len * 10 / 7));
var hash_table = try self.allocator.alloc(HashBucket, hash_table_size);
defer self.allocator.free(hash_table);
for (hash_table) |*bucket| {
bucket.* = HashBucket.empty;
}
for (self.packages.items, 0..) |pkg, i| {
const name = pkg.name.slice(self.string_builder.items);
const hash = djb2Hash(name);
var index = hash % hash_table_size;
var probes: u32 = 0;
while (hash_table[index].package_index != std.math.maxInt(u32) and probes < hash_table_size) : (probes += 1) {
index = (index + 1) % hash_table_size;
}
if (probes >= hash_table_size) return error.HashTableFull;
hash_table[index] = .{
.name_hash = hash,
.package_index = @intCast(i),
};
}
const hash_table_offset = alignOffset(dependency_array_offset + dependency_array_size, @alignOf(HashBucket));
const hash_pad_size = hash_table_offset - (dependency_array_offset + dependency_array_size);
const header = Header{
.package_count = @intCast(self.packages.items.len),
.dependency_count = @intCast(self.dependencies.items.len),
.string_table_offset = string_table_offset,
.string_table_size = string_table_size,
.package_array_offset = package_array_offset,
.dependency_array_offset = dependency_array_offset,
.hash_table_offset = hash_table_offset,
.hash_table_size = hash_table_size,
};
try file.writeAll(std.mem.asBytes(&header));
try file.writeAll(self.string_builder.items);
if (package_pad_size > 0) {
const padding = [_]u8{0} ** 8;
try file.writeAll(padding[0..package_pad_size]);
} try file.writeAll(std.mem.sliceAsBytes(self.packages.items));
if (dep_pad_size > 0) {
const padding = [_]u8{0} ** 8;
try file.writeAll(padding[0..dep_pad_size]);
} try file.writeAll(std.mem.sliceAsBytes(self.dependencies.items));
if (hash_pad_size > 0) {
const padding = [_]u8{0} ** 8;
try file.writeAll(padding[0..hash_pad_size]);
} try file.writeAll(std.mem.sliceAsBytes(hash_table));
}
};

File Metadata

Mime Type
text/plain
Expires
Sun, May 3, 7:58 AM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
533615
Default Alt Text
lockfile.zig (13 KB)

Event Timeline