diff --git a/.zed/debug.json b/.zed/debug.json index 1bcdda2..d078561 100644 --- a/.zed/debug.json +++ b/.zed/debug.json @@ -11,7 +11,7 @@ "build": { "command": "zig", - "args": ["build", "-Duse_c_libs=true", "-Ddebug=true"], + "args": ["build", "-Ddebug=true"], }, "program": "zig-out/bin/unsquashfs", diff --git a/src/archive.zig b/src/archive.zig index 5717c46..3f96367 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -48,7 +48,7 @@ pub fn root(self: Archive, alloc: std.mem.Allocator, io: Io) !File { self.super.block_size, self.super.root_ref, ); - return .init(alloc, root_inode, ""); + return .init(alloc, self, root_inode, ""); } /// Opens a File within the archive. pub fn open(self: Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File { @@ -61,9 +61,16 @@ pub fn open(self: Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u } /// Extract the entire archive contents to the given directory. pub fn extract(self: Archive, alloc: std.mem.Allocator, io: Io, extract_dir: []const u8, options: ExtractionOptions) !void { - _ = self; - _ = alloc; - _ = io; + const root_inode = try Utils.inodeFromRef( + alloc, + io, + self.file, + &self.stateless_decomp, + self.super.inode_start, + self.super.block_size, + self.super.root_ref, + ); + _ = root_inode; _ = extract_dir; _ = options; return error.TODO; @@ -74,7 +81,15 @@ pub fn extract(self: Archive, alloc: std.mem.Allocator, io: Io, extract_dir: []c pub fn inode(self: Archive, alloc: std.mem.Allocator, io: Io, num: u32) !Inode { if (!self.super.flags.exportable) return error.NotExportable; - const ref = try LookupTable.lookupValue(Inode.Ref, alloc, io, &self.stateless_decomp, self.file, self.super.export_start, num + 1); + const ref = try LookupTable.lookupValue( + Inode.Ref, + alloc, + io, + &self.stateless_decomp, + self.file, + self.super.export_start, + num + 1, + ); return Utils.inodeFromRef( alloc, io, @@ -85,6 +100,18 @@ pub fn inode(self: Archive, alloc: std.mem.Allocator, io: Io, num: u32) !Inode { ref, ); } +/// Returns a value at the given index from the Archive's id (uid/gid) table. +pub fn idTable(self: Archive, alloc: std.mem.Allocator, io: Io, idx: u32) !u16 { + return LookupTable.lookupValue( + u16, + alloc, + io, + &self.stateless_decomp, + self.file, + self.super.id_start, + idx, + ); +} // Superblock @@ -98,7 +125,7 @@ const SuperblockError = error{ }; /// A squashfs Superblock -pub const Superblock = packed struct(u768) { +pub const Superblock = extern struct { magic: u32, inode_count: u32, mod_time: u32, @@ -113,7 +140,7 @@ pub const Superblock = packed struct(u768) { zstd, }, block_log: u16, - flags: packed struct { + flags: packed struct(u16) { inode_uncompressed: bool, data_uncompressed: bool, check: bool, diff --git a/src/decomp/zstd.zig b/src/decomp/zstd.zig index 7700044..0042c5d 100644 --- a/src/decomp/zstd.zig +++ b/src/decomp/zstd.zig @@ -57,7 +57,7 @@ fn decomp(d: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8 inline fn zstdDecomp(buffer: []u8, in: []u8, out: []u8) !usize { var rdr: Reader = .fixed(in); - var d = zstd.Decompress.init(&rdr, buffer, .{ .window_len = @truncate(in.len) }); + var d = zstd.Decompress.init(&rdr, buffer, .{ .window_len = @truncate(out.len) }); return d.reader.readSliceShort(out); } @@ -67,7 +67,7 @@ inline fn zstdDecomp(buffer: []u8, in: []u8, out: []u8) !usize { pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp }; fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize { - const buf = try alloc.alloc(u8, in.len + zstd.block_size_max); + const buf = try alloc.alloc(u8, out.len + zstd.block_size_max); defer alloc.free(buf); return zstdDecomp(buf, in, out); } diff --git a/src/directory.zig b/src/directory.zig new file mode 100644 index 0000000..9e4bbb7 --- /dev/null +++ b/src/directory.zig @@ -0,0 +1,67 @@ +const std = @import("std"); +const Reader = std.Io.Reader; + +const Inode = @import("inode.zig"); + +const DirEntry = @This(); + +block_start: u32, +block_offset: u16, +type: Inode.Type, +name: []const u8, + +pub fn deinit(self: DirEntry, alloc: std.mem.Allocator) void { + alloc.free(self.name); +} + +pub fn readDirectory(alloc: std.mem.Allocator, rdr: *Reader, size: u32) ![]DirEntry { + var hdr: Header = undefined; + var raw: RawEntry = undefined; + var out: std.ArrayList(DirEntry) = try .initCapacity(alloc, 30); + errdefer { + for (out.items) |ent| + alloc.free(ent.name); + out.deinit(alloc); + } + + var tot_red: u32 = 3; + while (tot_red < size) { + try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little); + try out.ensureUnusedCapacity(alloc, hdr.count + 1); + + tot_red += @sizeOf(Header); + + for (hdr.count + 1) |_| { + try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little); + + const new_name = try alloc.alloc(u8, raw.name_size + 1); + try rdr.readSliceEndian(u8, new_name, .little); + + const new = out.addOneAssumeCapacity(); + new.* = .{ + .block_start = hdr.block_start, + .block_offset = raw.block_offset, + .type = raw.type, + .name = new_name, + }; + + tot_red += @sizeOf(RawEntry) + raw.name_size + 1; + } + } + return out.toOwnedSlice(alloc); +} + +// Types + +const Header = extern struct { + count: u32, + block_start: u32, + num: u32, +}; + +const RawEntry = extern struct { + block_offset: u16, + num_offset: i16, + type: Inode.Type, + name_size: u16, +}; diff --git a/src/file.zig b/src/file.zig index 0c92802..b854552 100644 --- a/src/file.zig +++ b/src/file.zig @@ -3,6 +3,8 @@ const std = @import("std"); const Io = std.Io; +const Archive = @import("archive.zig"); +const DirEntry = @import("directory.zig"); const ExtractionOptions = @import("options.zig"); const Inode = @import("inode.zig"); @@ -10,17 +12,21 @@ const File = @This(); alloc: std.mem.Allocator, +archive: Archive, + inode: Inode, name: []const u8, /// Creates a new File from an inode. Takes ownership of the Inode and creates a copy of the given name. /// Requires the given allocator was used to create the Inode. -pub fn init(alloc: std.mem.Allocator, in: Inode, name: []const u8) !File { +pub fn init(alloc: std.mem.Allocator, archive: Archive, in: Inode, name: []const u8) !File { const new_name = try alloc.alloc(u8, name.len); @memcpy(new_name, name); return .{ .alloc = alloc, + .archive = archive, + .inode = in, .name = new_name, }; @@ -31,10 +37,10 @@ pub fn deinit(self: File) void { } pub fn open(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File { - _ = self; - _ = alloc; - _ = io; - _ = filepath; + switch (self.inode.hdr.inode_type) { + .dir, .ext_dir => {}, + else => return Error.NotDirectory, + } return error.TODO; } @@ -46,3 +52,12 @@ pub fn extract(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u _ = options; return error.TODO; } + +// Types + +pub const Error = error{ + NotDirectory, + NotRegularFile, + NotSymlink, + NotDevice, +}; diff --git a/src/inode.zig b/src/inode.zig index bcbe5d8..c0ad0f0 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -87,7 +87,7 @@ pub const Data = union(Type) { ext_socket: misc.ExtIPC, }; -pub const Header = packed struct { +pub const Header = extern struct { inode_type: Type, permissions: u16, uid_idx: u16, diff --git a/src/inode_data/dir.zig b/src/inode_data/dir.zig index aa0216e..6b45db0 100644 --- a/src/inode_data/dir.zig +++ b/src/inode_data/dir.zig @@ -1,6 +1,6 @@ const Reader = @import("std").Io.Reader; -pub const Dir = packed struct { +pub const Dir = extern struct { block_start: u32, hard_links: u32, size: u16, @@ -14,7 +14,7 @@ pub const Dir = packed struct { } }; -pub const ExtDir = packed struct { +pub const ExtDir = extern struct { hard_links: u32, size: u32, block_start: u32, @@ -26,7 +26,7 @@ pub const ExtDir = packed struct { pub fn read(rdr: *Reader) !ExtDir { var d: ExtDir = undefined; - try rdr.readSliceEndian(Dir, @ptrCast(&d), .little); + try rdr.readSliceEndian(ExtDir, @ptrCast(&d), .little); return d; } }; diff --git a/src/inode_data/file.zig b/src/inode_data/file.zig index 17afe70..11e7059 100644 --- a/src/inode_data/file.zig +++ b/src/inode_data/file.zig @@ -1,34 +1,43 @@ const std = @import("std"); const Reader = std.Io.Reader; -pub const BlockSize = packed struct { +pub const BlockSize = packed struct(u32) { size: u24, uncompressed: bool, _: u7, }; +const FileRawRead = extern struct { + block_start: u32, + frag_idx: u32, + frag_block_offset: u32, + size: u32, +}; + pub const File = struct { - block_start: u32, // bytes 0-3 - frag_idx: u32, // bytes 4-7 - frag_block_offset: u32, // bytes 8-11 - size: u32, // bytes 12-15 + block_start: u32, + frag_idx: u32, + frag_block_offset: u32, + size: u32, block_sizes: []BlockSize, pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File { - var start: [16]u8 = undefined; - try rdr.readSliceAll(&start); - const frag_idx: u32 = std.mem.readInt(u32, start[4..8], .little); - const size: u32 = std.mem.readInt(u32, start[12..16], .little); - var num_blocks: u32 = size / block_size; - if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1; + var raw: FileRawRead = undefined; + try rdr.readSliceEndian(FileRawRead, @ptrCast(&raw), .little); + + var num_blocks: u32 = raw.size / block_size; + if (raw.size % block_size != 0 and raw.frag_idx == 0xFFFFFFFF) + num_blocks += 1; + const sizes = try alloc.alloc(BlockSize, num_blocks); errdefer alloc.free(sizes); try rdr.readSliceEndian(BlockSize, sizes, .little); + return .{ - .block_start = std.mem.readInt(u32, start[0..4], .little), - .frag_idx = frag_idx, - .frag_block_offset = std.mem.readInt(u32, start[8..12], .little), - .size = size, + .block_start = raw.block_start, + .frag_idx = raw.frag_idx, + .frag_block_offset = raw.frag_block_offset, + .size = raw.size, .block_sizes = sizes, }; } @@ -38,34 +47,46 @@ pub const File = struct { } }; +const ExtFileRawRead = extern struct { + block_start: u64, + size: u64, + sparse: u64, + hard_links: u32, + frag_idx: u32, + frag_block_offset: u32, + xattr_idx: u32, +}; + pub const ExtFile = struct { - block_start: u64, // bytes 0-7 - size: u64, // bytes 8-15 - sparse: u64, // bytes 16-23 - hard_links: u32, // bytes 24-27 - frag_idx: u32, // bytes 28-31 - frag_block_offset: u32, // bytes 32-35 - xattr_idx: u32, // bytes 36-39 + block_start: u64, + size: u64, + sparse: u64, + hard_links: u32, + frag_idx: u32, + frag_block_offset: u32, + xattr_idx: u32, block_sizes: []BlockSize, pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile { - var start: [40]u8 = undefined; - try rdr.readSliceAll(&start); - const frag_idx: u32 = std.mem.readInt(u32, start[28..32], .little); - const size: u64 = std.mem.readInt(u64, start[8..16], .little); - var num_blocks: u32 = @truncate(size / block_size); - if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1; + var raw: ExtFileRawRead = undefined; + try rdr.readSliceEndian(ExtFileRawRead, @ptrCast(&raw), .little); + + var num_blocks: u32 = @truncate(raw.size / block_size); + if (raw.size % block_size != 0 and raw.frag_idx == 0xFFFFFFFF) + num_blocks += 1; + const sizes = try alloc.alloc(BlockSize, num_blocks); errdefer alloc.free(sizes); try rdr.readSliceEndian(BlockSize, sizes, .little); + return .{ - .block_start = std.mem.readInt(u64, start[0..8], .little), - .size = size, - .sparse = std.mem.readInt(u64, start[16..24], .little), - .hard_links = std.mem.readInt(u32, start[24..28], .little), - .frag_idx = frag_idx, - .frag_block_offset = std.mem.readInt(u32, start[32..36], .little), - .xattr_idx = std.mem.readInt(u32, start[36..40], .little), + .block_start = raw.block_start, + .size = raw.size, + .sparse = raw.sparse, + .hard_links = raw.hard_links, + .frag_idx = raw.frag_idx, + .frag_block_offset = raw.frag_block_offset, + .xattr_idx = raw.xattr_idx, .block_sizes = sizes, }; } diff --git a/src/inode_data/misc.zig b/src/inode_data/misc.zig index d18a837..615da1d 100644 --- a/src/inode_data/misc.zig +++ b/src/inode_data/misc.zig @@ -50,7 +50,7 @@ pub const ExtSymlink = struct { }; /// A block or character device. -pub const Dev = packed struct { +pub const Dev = extern struct { hard_links: u32, dev: u32, @@ -62,7 +62,7 @@ pub const Dev = packed struct { }; /// An extended block or character device. -pub const ExtDev = packed struct { +pub const ExtDev = extern struct { hard_links: u32, dev: u32, xattr_idx: u32, @@ -75,7 +75,7 @@ pub const ExtDev = packed struct { }; /// A socket or FIFO file. -pub const IPC = packed struct { +pub const IPC = extern struct { hard_links: u32, pub fn read(rdr: *Reader) !IPC { @@ -86,7 +86,7 @@ pub const IPC = packed struct { }; /// An extended socket or FIFO file. -pub const ExtIPC = packed struct { +pub const ExtIPC = extern struct { hard_links: u32, xattr_idx: u32, diff --git a/src/test.zig b/src/test.zig index 4e4d06c..ae74f1f 100644 --- a/src/test.zig +++ b/src/test.zig @@ -13,10 +13,7 @@ test "Basics" { var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{}); defer fil.close(io); var sfs: Archive = try .init(io, fil, 0); - if (sfs.super != LinuxPATestCorrectSuperblock) { - std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super }); - return error.BadSuperblock; - } + try std.testing.expectEqualDeep(sfs.super, LinuxPATestCorrectSuperblock); const root_file = try sfs.root(alloc, io); defer root_file.deinit(); } diff --git a/src/util/metadata.zig b/src/util/metadata.zig index 63c0955..285127f 100644 --- a/src/util/metadata.zig +++ b/src/util/metadata.zig @@ -6,7 +6,7 @@ const StreamError = std.Io.Reader.StreamError; const Decompressor = @import("decompressor.zig"); -const BlockHeader = packed struct { +const BlockHeader = packed struct(u16) { size: u15, uncompressed: bool, };