2024-07-27 08:40:48 -08:00
|
|
|
// SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
|
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
|
|
const std = @import("std");
|
|
|
|
|
const main = @import("main.zig");
|
2024-08-01 04:20:34 -08:00
|
|
|
const model = @import("model.zig");
|
2024-07-27 08:40:48 -08:00
|
|
|
const sink = @import("sink.zig");
|
|
|
|
|
const util = @import("util.zig");
|
|
|
|
|
const ui = @import("ui.zig");
|
2024-08-03 03:16:44 -08:00
|
|
|
|
|
|
|
|
extern fn ZSTD_compress(dst: ?*anyopaque, dstCapacity: usize, src: ?*const anyopaque, srcSize: usize, compressionLevel: c_int) usize;
|
|
|
|
|
extern fn ZSTD_isError(code: usize) c_uint;
|
2024-07-27 08:40:48 -08:00
|
|
|
|
|
|
|
|
pub const global = struct {
|
|
|
|
|
var fd: std.fs.File = undefined;
|
|
|
|
|
var index = std.ArrayList(u8).init(main.allocator);
|
|
|
|
|
var file_off: u64 = 0;
|
|
|
|
|
var lock: std.Thread.Mutex = .{};
|
|
|
|
|
var root_itemref: u64 = 0;
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-03 03:16:44 -08:00
|
|
|
const BLOCK_SIZE: usize = 64*1024;
|
2024-07-27 08:40:48 -08:00
|
|
|
|
2024-08-02 03:17:42 -08:00
|
|
|
pub const SIGNATURE = "\xbfncduEX1";
|
|
|
|
|
|
|
|
|
|
pub const ItemKey = enum(u5) {
|
2024-07-27 08:40:48 -08:00
|
|
|
// all items
|
2024-08-01 04:20:34 -08:00
|
|
|
type = 0, // EType
|
2024-07-27 08:40:48 -08:00
|
|
|
name = 1, // bytes
|
|
|
|
|
prev = 2, // itemref
|
|
|
|
|
// Only for non-specials
|
|
|
|
|
asize = 3, // u64
|
|
|
|
|
dsize = 4, // u64
|
|
|
|
|
// Only for .dir
|
|
|
|
|
dev = 5, // u64 only if different from parent dir
|
|
|
|
|
rderr = 6, // bool true = error reading directory list, false = error in sub-item, absent = no error
|
|
|
|
|
cumasize = 7, // u64
|
|
|
|
|
cumdsize = 8, // u64
|
|
|
|
|
shrasize = 9, // u64
|
|
|
|
|
shrdsize = 10, // u64
|
2024-08-08 01:37:53 -08:00
|
|
|
items = 11, // u64
|
2024-07-27 08:40:48 -08:00
|
|
|
sub = 12, // itemref only if dir is not empty
|
|
|
|
|
// Only for .link
|
|
|
|
|
ino = 13, // u64
|
|
|
|
|
nlink = 14, // u32
|
|
|
|
|
// Extended mode
|
2024-07-31 09:32:49 -08:00
|
|
|
uid = 15, // u32
|
|
|
|
|
gid = 16, // u32
|
|
|
|
|
mode = 17, // u16
|
|
|
|
|
mtime = 18, // u64
|
2024-08-02 03:17:42 -08:00
|
|
|
_,
|
2024-07-27 08:40:48 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Pessimistic upper bound on the encoded size of an item, excluding the name field.
|
2024-07-31 01:46:15 -08:00
|
|
|
// 2 bytes for map start/end, 11 per field (2 for the key, 9 for a full u64).
|
2024-07-27 08:40:48 -08:00
|
|
|
const MAX_ITEM_LEN = 2 + 11 * @typeInfo(ItemKey).Enum.fields.len;
|
|
|
|
|
|
2024-08-02 03:17:42 -08:00
|
|
|
pub const CborMajor = enum(u3) { pos, neg, bytes, text, array, map, tag, simple };
|
2024-07-27 08:40:48 -08:00
|
|
|
|
|
|
|
|
inline fn bigu16(v: u16) [2]u8 { return @bitCast(std.mem.nativeToBig(u16, v)); }
|
|
|
|
|
inline fn bigu32(v: u32) [4]u8 { return @bitCast(std.mem.nativeToBig(u32, v)); }
|
|
|
|
|
inline fn bigu64(v: u64) [8]u8 { return @bitCast(std.mem.nativeToBig(u64, v)); }
|
|
|
|
|
|
2024-08-08 23:29:26 -08:00
|
|
|
inline fn blockHeader(id: u4, len: u28) [4]u8 { return bigu32((@as(u32, id) << 28) | len); }
|
2024-07-27 08:40:48 -08:00
|
|
|
|
|
|
|
|
inline fn cborByte(major: CborMajor, arg: u5) u8 { return (@as(u8, @intFromEnum(major)) << 5) | arg; }
|
|
|
|
|
|
2024-08-11 00:56:41 -08:00
|
|
|
// ZSTD_COMPRESSBOUND(), assuming input does not exceed ZSTD_MAX_INPUT_SIZE
|
|
|
|
|
fn compressBound(size: usize) usize { return size + (size>>8) + (if (size < (128<<10)) ((128<<10) - size) >> 11 else 0); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// (Uncompressed) data block size.
|
|
|
|
|
// Start with 64k, then use increasingly larger block sizes as the export file
|
|
|
|
|
// grows. This is both to stay within the block number limit of the index block
|
|
|
|
|
// and because, with a larger index block, the reader will end up using more
|
|
|
|
|
// memory anyway.
|
|
|
|
|
fn blockSize(num: u32) usize {
|
|
|
|
|
// block size uncompressed data in this num range
|
|
|
|
|
// # mil # KiB # GiB
|
|
|
|
|
return if (num < ( 1<<20)) 64<<10 // 64
|
|
|
|
|
else if (num < ( 2<<20)) 128<<10 // 128
|
|
|
|
|
else if (num < ( 4<<20)) 256<<10 // 512
|
|
|
|
|
else if (num < ( 8<<20)) 512<<10 // 2048
|
|
|
|
|
else if (num < (16<<20)) 1024<<10 // 8192
|
|
|
|
|
else 2048<<10; // 32768
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 08:40:48 -08:00
|
|
|
|
|
|
|
|
pub const Thread = struct {
|
2024-08-03 03:16:44 -08:00
|
|
|
buf: []u8 = undefined,
|
2024-08-11 00:56:41 -08:00
|
|
|
off: usize = std.math.maxInt(usize) - (1<<10), // large number to trigger a flush() for the first write
|
2024-07-27 08:40:48 -08:00
|
|
|
block_num: u32 = std.math.maxInt(u32),
|
|
|
|
|
itemref: u64 = 0, // ref of item currently being written
|
|
|
|
|
|
2024-08-03 03:16:44 -08:00
|
|
|
// unused, but kept around for easy debugging
|
2024-07-31 01:46:15 -08:00
|
|
|
fn compressNone(in: []const u8, out: []u8) usize {
|
|
|
|
|
@memcpy(out[0..in.len], in);
|
|
|
|
|
return in.len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn compressZstd(in: []const u8, out: []u8) usize {
|
2024-08-03 03:16:44 -08:00
|
|
|
while (true) {
|
|
|
|
|
const r = ZSTD_compress(out.ptr, out.len, in.ptr, in.len, main.config.complevel);
|
|
|
|
|
if (ZSTD_isError(r) == 0) return r;
|
|
|
|
|
ui.oom(); // That *ought* to be the only reason the above call can fail.
|
|
|
|
|
}
|
2024-07-31 01:46:15 -08:00
|
|
|
}
|
|
|
|
|
|
2024-08-11 00:56:41 -08:00
|
|
|
fn createBlock(t: *Thread) std.ArrayList(u8) {
|
|
|
|
|
var out = std.ArrayList(u8).init(main.allocator);
|
|
|
|
|
if (t.block_num == std.math.maxInt(u32) or t.off == 0) return out;
|
2024-07-27 08:40:48 -08:00
|
|
|
|
2024-08-11 05:56:12 -08:00
|
|
|
out.ensureTotalCapacityPrecise(12 + compressBound(t.off)) catch unreachable;
|
2024-08-11 00:56:41 -08:00
|
|
|
out.items.len = out.capacity;
|
2024-08-11 05:56:12 -08:00
|
|
|
const bodylen = compressZstd(t.buf[0..t.off], out.items[8..]);
|
|
|
|
|
out.items.len = 12 + bodylen;
|
2024-08-11 00:56:41 -08:00
|
|
|
|
|
|
|
|
out.items[0..4].* = blockHeader(0, @intCast(out.items.len));
|
|
|
|
|
out.items[4..8].* = bigu32(t.block_num);
|
2024-08-11 05:56:12 -08:00
|
|
|
out.items[8+bodylen..][0..4].* = blockHeader(0, @intCast(out.items.len));
|
2024-08-11 00:56:41 -08:00
|
|
|
return out;
|
2024-07-27 08:40:48 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn flush(t: *Thread, expected_len: usize) void {
|
|
|
|
|
@setCold(true);
|
|
|
|
|
const block = createBlock(t);
|
2024-08-11 00:56:41 -08:00
|
|
|
defer block.deinit();
|
2024-07-27 08:40:48 -08:00
|
|
|
|
|
|
|
|
global.lock.lock();
|
|
|
|
|
defer global.lock.unlock();
|
|
|
|
|
// This can only really happen when the root path exceeds BLOCK_SIZE,
|
|
|
|
|
// in which case we would probably have error'ed out earlier anyway.
|
|
|
|
|
if (expected_len > t.buf.len) ui.die("Error writing data: path too long.\n", .{});
|
|
|
|
|
|
2024-08-11 00:56:41 -08:00
|
|
|
if (block.items.len > 0) {
|
2024-08-09 08:24:59 -08:00
|
|
|
if (global.file_off >= (1<<40)) ui.die("Export data file has grown too large, please report a bug.\n", .{});
|
2024-08-11 00:56:41 -08:00
|
|
|
global.index.items[4..][t.block_num*8..][0..8].* = bigu64((global.file_off << 24) + block.items.len);
|
|
|
|
|
global.file_off += block.items.len;
|
|
|
|
|
global.fd.writeAll(block.items) catch |e|
|
2024-07-27 08:40:48 -08:00
|
|
|
ui.die("Error writing to file: {s}.\n", .{ ui.errorString(e) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.off = 0;
|
|
|
|
|
t.block_num = @intCast((global.index.items.len - 4) / 8);
|
|
|
|
|
global.index.appendSlice(&[1]u8{0}**8) catch unreachable;
|
2024-08-09 08:24:59 -08:00
|
|
|
if (global.index.items.len + 12 >= (1<<28)) ui.die("Too many data blocks, please report a bug.\n", .{});
|
2024-08-11 00:56:41 -08:00
|
|
|
|
|
|
|
|
const newsize = blockSize(t.block_num);
|
|
|
|
|
if (t.buf.len != newsize) t.buf = main.allocator.realloc(t.buf, newsize) catch unreachable;
|
2024-07-27 08:40:48 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn cborHead(t: *Thread, major: CborMajor, arg: u64) void {
|
|
|
|
|
if (arg <= 23) {
|
|
|
|
|
t.buf[t.off] = cborByte(major, @intCast(arg));
|
|
|
|
|
t.off += 1;
|
|
|
|
|
} else if (arg <= std.math.maxInt(u8)) {
|
|
|
|
|
t.buf[t.off] = cborByte(major, 24);
|
|
|
|
|
t.buf[t.off+1] = @truncate(arg);
|
|
|
|
|
t.off += 2;
|
|
|
|
|
} else if (arg <= std.math.maxInt(u16)) {
|
|
|
|
|
t.buf[t.off] = cborByte(major, 25);
|
|
|
|
|
t.buf[t.off+1..][0..2].* = bigu16(@intCast(arg));
|
|
|
|
|
t.off += 3;
|
|
|
|
|
} else if (arg <= std.math.maxInt(u32)) {
|
|
|
|
|
t.buf[t.off] = cborByte(major, 26);
|
|
|
|
|
t.buf[t.off+1..][0..4].* = bigu32(@intCast(arg));
|
|
|
|
|
t.off += 5;
|
|
|
|
|
} else {
|
|
|
|
|
t.buf[t.off] = cborByte(major, 27);
|
|
|
|
|
t.buf[t.off+1..][0..8].* = bigu64(arg);
|
|
|
|
|
t.off += 9;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn cborIndef(t: *Thread, major: CborMajor) void {
|
|
|
|
|
t.buf[t.off] = cborByte(major, 31);
|
|
|
|
|
t.off += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn itemKey(t: *Thread, key: ItemKey) void {
|
|
|
|
|
t.cborHead(.pos, @intFromEnum(key));
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 23:19:25 -08:00
|
|
|
fn itemRef(t: *Thread, key: ItemKey, ref: ?u64) void {
|
|
|
|
|
const r = ref orelse return;
|
2024-07-27 08:40:48 -08:00
|
|
|
t.itemKey(key);
|
|
|
|
|
// Full references compress like shit and most of the references point
|
|
|
|
|
// into the same block, so optimize that case by using a negative
|
|
|
|
|
// offset instead.
|
2024-08-08 23:19:25 -08:00
|
|
|
if ((r >> 24) == t.block_num) t.cborHead(.neg, t.itemref - r - 1)
|
|
|
|
|
else t.cborHead(.pos, r);
|
2024-07-27 08:40:48 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reserve space for a new item, write out the type, prev and name fields and return the itemref.
|
2024-08-08 23:19:25 -08:00
|
|
|
fn itemStart(t: *Thread, itype: model.EType, prev_item: ?u64, name: []const u8) u64 {
|
2024-07-27 08:40:48 -08:00
|
|
|
const min_len = name.len + MAX_ITEM_LEN;
|
2024-08-03 03:16:44 -08:00
|
|
|
if (t.off + min_len > t.buf.len) t.flush(min_len);
|
2024-07-27 08:40:48 -08:00
|
|
|
|
|
|
|
|
t.itemref = (@as(u64, t.block_num) << 24) | t.off;
|
|
|
|
|
t.cborIndef(.map);
|
|
|
|
|
t.itemKey(.type);
|
|
|
|
|
if (@intFromEnum(itype) >= 0) t.cborHead(.pos, @intCast(@intFromEnum(itype)))
|
|
|
|
|
else t.cborHead(.neg, @intCast(-1 - @intFromEnum(itype)));
|
|
|
|
|
t.itemKey(.name);
|
|
|
|
|
t.cborHead(.bytes, name.len);
|
|
|
|
|
@memcpy(t.buf[t.off..][0..name.len], name);
|
|
|
|
|
t.off += name.len;
|
|
|
|
|
t.itemRef(.prev, prev_item);
|
|
|
|
|
return t.itemref;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn itemExt(t: *Thread, stat: *const sink.Stat) void {
|
|
|
|
|
if (!main.config.extended) return;
|
2024-08-09 08:24:59 -08:00
|
|
|
if (stat.ext.pack.hasuid) {
|
|
|
|
|
t.itemKey(.uid);
|
|
|
|
|
t.cborHead(.pos, stat.ext.uid);
|
|
|
|
|
}
|
|
|
|
|
if (stat.ext.pack.hasgid) {
|
|
|
|
|
t.itemKey(.gid);
|
|
|
|
|
t.cborHead(.pos, stat.ext.gid);
|
|
|
|
|
}
|
|
|
|
|
if (stat.ext.pack.hasmode) {
|
|
|
|
|
t.itemKey(.mode);
|
|
|
|
|
t.cborHead(.pos, stat.ext.mode);
|
|
|
|
|
}
|
|
|
|
|
if (stat.ext.pack.hasmtime) {
|
|
|
|
|
t.itemKey(.mtime);
|
|
|
|
|
t.cborHead(.pos, stat.ext.mtime);
|
|
|
|
|
}
|
2024-07-27 08:40:48 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn itemEnd(t: *Thread) void {
|
|
|
|
|
t.cborIndef(.simple);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub const Dir = struct {
|
|
|
|
|
// TODO: When items are written out into blocks depth-first, parent dirs
|
|
|
|
|
// will end up getting their items distributed over many blocks, which will
|
|
|
|
|
// significantly slow down reading that dir's listing. It may be worth
|
|
|
|
|
// buffering some items at the Dir level before flushing them out to the
|
|
|
|
|
// Thread buffer.
|
|
|
|
|
|
|
|
|
|
// The lock protects all of the below, and is necessary because final()
|
|
|
|
|
// accesses the parent dir and may be called from other threads.
|
|
|
|
|
// I'm not expecting much lock contention, but it's possible to turn
|
|
|
|
|
// last_item into an atomic integer and other fields could be split up for
|
|
|
|
|
// subdir use.
|
|
|
|
|
lock: std.Thread.Mutex = .{},
|
2024-08-08 23:19:25 -08:00
|
|
|
last_sub: ?u64 = null,
|
2024-07-27 08:40:48 -08:00
|
|
|
stat: sink.Stat,
|
2024-08-08 01:37:53 -08:00
|
|
|
items: u64 = 0,
|
2024-07-27 08:40:48 -08:00
|
|
|
size: u64 = 0,
|
|
|
|
|
blocks: u64 = 0,
|
|
|
|
|
err: bool = false,
|
|
|
|
|
suberr: bool = false,
|
2024-07-31 09:32:49 -08:00
|
|
|
shared_size: u64 = 0,
|
|
|
|
|
shared_blocks: u64 = 0,
|
|
|
|
|
inodes: Inodes = Inodes.init(main.allocator),
|
|
|
|
|
|
|
|
|
|
const Inodes = std.AutoHashMap(u64, Inode);
|
|
|
|
|
const Inode = struct {
|
|
|
|
|
size: u64,
|
|
|
|
|
blocks: u64,
|
|
|
|
|
nlink: u32,
|
|
|
|
|
nfound: u32,
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-27 08:40:48 -08:00
|
|
|
|
2024-08-01 04:20:34 -08:00
|
|
|
pub fn addSpecial(d: *Dir, t: *Thread, name: []const u8, sp: model.EType) void {
|
2024-07-27 08:40:48 -08:00
|
|
|
d.lock.lock();
|
|
|
|
|
defer d.lock.unlock();
|
|
|
|
|
d.items += 1;
|
|
|
|
|
if (sp == .err) d.suberr = true;
|
2024-08-01 04:20:34 -08:00
|
|
|
d.last_sub = t.itemStart(sp, d.last_sub, name);
|
2024-07-27 08:40:48 -08:00
|
|
|
t.itemEnd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn addStat(d: *Dir, t: *Thread, name: []const u8, stat: *const sink.Stat) void {
|
|
|
|
|
d.lock.lock();
|
|
|
|
|
defer d.lock.unlock();
|
|
|
|
|
d.items += 1;
|
2024-08-01 04:20:34 -08:00
|
|
|
if (stat.etype != .link) {
|
2024-07-27 08:40:48 -08:00
|
|
|
d.size +|= stat.size;
|
|
|
|
|
d.blocks +|= stat.blocks;
|
|
|
|
|
}
|
2024-08-01 04:20:34 -08:00
|
|
|
d.last_sub = t.itemStart(stat.etype, d.last_sub, name);
|
2024-07-27 08:40:48 -08:00
|
|
|
t.itemKey(.asize);
|
|
|
|
|
t.cborHead(.pos, stat.size);
|
|
|
|
|
t.itemKey(.dsize);
|
|
|
|
|
t.cborHead(.pos, util.blocksToSize(stat.blocks));
|
2024-07-31 09:32:49 -08:00
|
|
|
|
2024-08-01 04:20:34 -08:00
|
|
|
if (stat.etype == .link) {
|
2024-07-31 09:32:49 -08:00
|
|
|
const lnk = d.inodes.getOrPut(stat.ino) catch unreachable;
|
|
|
|
|
if (!lnk.found_existing) lnk.value_ptr.* = .{
|
|
|
|
|
.size = stat.size,
|
|
|
|
|
.blocks = stat.blocks,
|
|
|
|
|
.nlink = stat.nlink,
|
|
|
|
|
.nfound = 1,
|
|
|
|
|
} else lnk.value_ptr.nfound += 1;
|
|
|
|
|
t.itemKey(.ino);
|
|
|
|
|
t.cborHead(.pos, stat.ino);
|
|
|
|
|
t.itemKey(.nlink);
|
|
|
|
|
t.cborHead(.pos, stat.nlink);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 08:40:48 -08:00
|
|
|
t.itemExt(stat);
|
|
|
|
|
t.itemEnd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn addDir(d: *Dir, stat: *const sink.Stat) Dir {
|
|
|
|
|
d.lock.lock();
|
|
|
|
|
defer d.lock.unlock();
|
|
|
|
|
d.items += 1;
|
|
|
|
|
d.size +|= stat.size;
|
|
|
|
|
d.blocks +|= stat.blocks;
|
|
|
|
|
return .{ .stat = stat.* };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn setReadError(d: *Dir) void {
|
|
|
|
|
d.lock.lock();
|
|
|
|
|
defer d.lock.unlock();
|
|
|
|
|
d.err = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-31 09:32:49 -08:00
|
|
|
// XXX: older JSON exports did not include the nlink count and have
|
|
|
|
|
// this field set to '0'. We can deal with that when importing to
|
|
|
|
|
// mem_sink, but the hardlink counting algorithm used here really does need
|
|
|
|
|
// that information. Current code makes sure to count such links only once
|
|
|
|
|
// per dir, but does not count them towards the shared_* fields. That
|
|
|
|
|
// behavior is similar to ncdu 1.x, but the difference between memory
|
|
|
|
|
// import and this file export might be surprising.
|
|
|
|
|
fn countLinks(d: *Dir, parent: ?*Dir) void {
|
|
|
|
|
var parent_new: u32 = 0;
|
|
|
|
|
var it = d.inodes.iterator();
|
|
|
|
|
while (it.next()) |kv| {
|
|
|
|
|
const v = kv.value_ptr;
|
|
|
|
|
d.size +|= v.size;
|
|
|
|
|
d.blocks +|= v.blocks;
|
2024-08-06 04:51:42 -08:00
|
|
|
if (v.nlink > 1 and v.nfound < v.nlink) {
|
2024-07-31 09:32:49 -08:00
|
|
|
d.shared_size +|= v.size;
|
|
|
|
|
d.shared_blocks +|= v.blocks;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const p = parent orelse continue;
|
|
|
|
|
// All contained in this dir, no need to keep this entry around
|
|
|
|
|
if (v.nlink > 0 and v.nfound >= v.nlink) {
|
|
|
|
|
p.size +|= v.size;
|
|
|
|
|
p.blocks +|= v.blocks;
|
|
|
|
|
_ = d.inodes.remove(kv.key_ptr.*);
|
|
|
|
|
} else if (!p.inodes.contains(kv.key_ptr.*))
|
|
|
|
|
parent_new += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Merge remaining inodes into parent
|
|
|
|
|
const p = parent orelse return;
|
|
|
|
|
if (d.inodes.count() == 0) return;
|
|
|
|
|
|
|
|
|
|
// If parent is empty, just transfer
|
|
|
|
|
if (p.inodes.count() == 0) {
|
|
|
|
|
p.inodes.deinit();
|
|
|
|
|
p.inodes = d.inodes;
|
|
|
|
|
d.inodes = Inodes.init(main.allocator); // So we can deinit() without affecting parent
|
|
|
|
|
// Otherwise, merge
|
|
|
|
|
} else {
|
|
|
|
|
p.inodes.ensureUnusedCapacity(parent_new) catch unreachable;
|
|
|
|
|
it = d.inodes.iterator();
|
|
|
|
|
while (it.next()) |kv| {
|
|
|
|
|
const v = kv.value_ptr;
|
|
|
|
|
const plnk = p.inodes.getOrPutAssumeCapacity(kv.key_ptr.*);
|
|
|
|
|
if (!plnk.found_existing) plnk.value_ptr.* = v.*
|
|
|
|
|
else plnk.value_ptr.*.nfound += v.nfound;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 08:40:48 -08:00
|
|
|
pub fn final(d: *Dir, t: *Thread, name: []const u8, parent: ?*Dir) void {
|
|
|
|
|
if (parent) |p| p.lock.lock();
|
|
|
|
|
defer if (parent) |p| p.lock.unlock();
|
|
|
|
|
|
|
|
|
|
if (parent) |p| {
|
2024-07-31 09:32:49 -08:00
|
|
|
// Different dev? Don't merge the 'inodes' sets, just count the
|
|
|
|
|
// links here first so the sizes get added to the parent.
|
|
|
|
|
if (p.stat.dev != d.stat.dev) d.countLinks(null);
|
|
|
|
|
|
2024-07-27 08:40:48 -08:00
|
|
|
p.items += d.items;
|
|
|
|
|
p.size +|= d.size;
|
|
|
|
|
p.blocks +|= d.blocks;
|
|
|
|
|
if (d.suberr or d.err) p.suberr = true;
|
|
|
|
|
|
2024-07-31 09:32:49 -08:00
|
|
|
// Same dir, merge inodes
|
|
|
|
|
if (p.stat.dev == d.stat.dev) d.countLinks(p);
|
|
|
|
|
|
2024-07-27 08:40:48 -08:00
|
|
|
p.last_sub = t.itemStart(.dir, p.last_sub, name);
|
2024-07-31 09:32:49 -08:00
|
|
|
} else {
|
|
|
|
|
d.countLinks(null);
|
2024-07-27 08:40:48 -08:00
|
|
|
global.root_itemref = t.itemStart(.dir, 0, name);
|
2024-07-31 09:32:49 -08:00
|
|
|
}
|
|
|
|
|
d.inodes.deinit();
|
2024-07-27 08:40:48 -08:00
|
|
|
|
|
|
|
|
t.itemKey(.asize);
|
|
|
|
|
t.cborHead(.pos, d.stat.size);
|
|
|
|
|
t.itemKey(.dsize);
|
|
|
|
|
t.cborHead(.pos, util.blocksToSize(d.stat.blocks));
|
|
|
|
|
if (parent == null or parent.?.stat.dev != d.stat.dev) {
|
|
|
|
|
t.itemKey(.dev);
|
|
|
|
|
t.cborHead(.pos, d.stat.dev);
|
|
|
|
|
}
|
|
|
|
|
if (d.err or d.suberr) {
|
|
|
|
|
t.itemKey(.rderr);
|
|
|
|
|
t.cborHead(.simple, if (d.err) 21 else 20);
|
|
|
|
|
}
|
|
|
|
|
t.itemKey(.cumasize);
|
2024-07-31 09:32:49 -08:00
|
|
|
t.cborHead(.pos, d.size +| d.stat.size);
|
2024-07-27 08:40:48 -08:00
|
|
|
t.itemKey(.cumdsize);
|
2024-07-31 09:32:49 -08:00
|
|
|
t.cborHead(.pos, util.blocksToSize(d.blocks +| d.stat.blocks));
|
|
|
|
|
if (d.shared_size > 0) {
|
|
|
|
|
t.itemKey(.shrasize);
|
|
|
|
|
t.cborHead(.pos, d.shared_size);
|
|
|
|
|
}
|
|
|
|
|
if (d.shared_blocks > 0) {
|
|
|
|
|
t.itemKey(.shrdsize);
|
|
|
|
|
t.cborHead(.pos, util.blocksToSize(d.shared_blocks));
|
|
|
|
|
}
|
2024-07-27 08:40:48 -08:00
|
|
|
t.itemKey(.items);
|
|
|
|
|
t.cborHead(.pos, d.items);
|
|
|
|
|
t.itemRef(.sub, d.last_sub);
|
|
|
|
|
t.itemExt(&d.stat);
|
|
|
|
|
t.itemEnd();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2024-08-03 03:16:44 -08:00
|
|
|
pub fn createRoot(stat: *const sink.Stat, threads: []sink.Thread) Dir {
|
|
|
|
|
for (threads) |*t| {
|
|
|
|
|
t.sink.bin.buf = main.allocator.alloc(u8, BLOCK_SIZE) catch unreachable;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 08:40:48 -08:00
|
|
|
return .{ .stat = stat.* };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn done(threads: []sink.Thread) void {
|
2024-08-03 03:16:44 -08:00
|
|
|
for (threads) |*t| {
|
|
|
|
|
t.sink.bin.flush(0);
|
|
|
|
|
main.allocator.free(t.sink.bin.buf);
|
|
|
|
|
}
|
2024-07-27 08:40:48 -08:00
|
|
|
|
|
|
|
|
while (std.mem.endsWith(u8, global.index.items, &[1]u8{0}**8))
|
|
|
|
|
global.index.shrinkRetainingCapacity(global.index.items.len - 8);
|
|
|
|
|
global.index.appendSlice(&bigu64(global.root_itemref)) catch unreachable;
|
2024-08-08 23:29:26 -08:00
|
|
|
global.index.appendSlice(&blockHeader(1, @intCast(global.index.items.len + 4))) catch unreachable;
|
|
|
|
|
global.index.items[0..4].* = blockHeader(1, @intCast(global.index.items.len));
|
2024-07-27 08:40:48 -08:00
|
|
|
global.fd.writeAll(global.index.items) catch |e|
|
|
|
|
|
ui.die("Error writing to file: {s}.\n", .{ ui.errorString(e) });
|
|
|
|
|
global.index.clearAndFree();
|
|
|
|
|
|
|
|
|
|
global.fd.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn setupOutput(fd: std.fs.File) void {
|
|
|
|
|
global.fd = fd;
|
2024-08-02 03:17:42 -08:00
|
|
|
fd.writeAll(SIGNATURE) catch |e|
|
2024-07-27 08:40:48 -08:00
|
|
|
ui.die("Error writing to file: {s}.\n", .{ ui.errorString(e) });
|
|
|
|
|
global.file_off = 8;
|
|
|
|
|
|
|
|
|
|
// Placeholder for the index block header.
|
|
|
|
|
global.index.appendSlice("aaaa") catch unreachable;
|
|
|
|
|
}
|