diff --git a/build.sh b/build.sh index d88f5d3..bb9771f 100755 --- a/build.sh +++ b/build.sh @@ -20,7 +20,6 @@ zig build \ -Doptimize="$OPTIMIZE" \ -Dtarget="$target" \ -Dstrip=true \ - -Dbuild-squashfuse_tool=false \ -Denable-zlib=true \ -Duse-libdeflate=true \ -Denable-zstd=true \ @@ -36,7 +35,6 @@ zig build \ -Doptimize="$OPTIMIZE" \ -Dtarget="$target" \ -Dstrip=true \ - -Dbuild-squashfuse_tool=false \ -Denable-zlib=true \ -Duse-libdeflate=true \ -Denable-zstd=false \ @@ -52,7 +50,6 @@ zig build \ -Doptimize="$OPTIMIZE" \ -Dtarget="$target" \ -Dstrip=true \ - -Dbuild-squashfuse_tool=false \ -Denable-zlib=false \ -Denable-zstd=true \ -Denable-lz4=false \ @@ -67,7 +64,6 @@ zig build \ -Doptimize="$OPTIMIZE" \ -Dtarget="$target" \ -Dstrip=true \ - -Dbuild-squashfuse_tool=false \ -Denable-zlib=false \ -Denable-zstd=false \ -Denable-lz4=true \ @@ -82,7 +78,6 @@ zig build \ -Doptimize="$OPTIMIZE" \ -Dtarget="$target" \ -Dstrip=true \ - -Dbuild-squashfuse_tool=false \ -Denable-zlib=false \ -Denable-zstd=false \ -Denable-lz4=false \ @@ -97,7 +92,6 @@ zig build \ -Doptimize="$OPTIMIZE" \ -Dtarget="$target" \ -Dstrip=true \ - -Dbuild-squashfuse_tool=false \ -Denable-zlib=false \ -Denable-zstd=false \ -Denable-lz4=false \ diff --git a/build.zig b/build.zig index 51b08cf..e353877 100644 --- a/build.zig +++ b/build.zig @@ -16,8 +16,6 @@ fn initExecutable(b: *std.build.Builder, name: []const u8) !*std.Build.Step.Comp } pub fn build(b: *std.build.Builder) !void { - const allocator = b.allocator; - // TODO: add system flags for compression algos const strip = b.option( bool, @@ -73,32 +71,10 @@ pub fn build(b: *std.build.Builder) !void { "enable lzo decompression (default: true)", ) orelse true; - const build_squashfuse = b.option( - bool, - "build-squashfuse", - "whether or not to build main squashfuse executable (default: true)", - ) orelse true; - - const build_squashfuse_tool = b.option( - bool, - "build-squashfuse_tool", - "whether or not to build FUSEless squashfuse_tool executable (default: true)", - ) orelse true; - const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const executable_names = &[_]?[]const u8{ - if (build_squashfuse) "squashfuse" else null, - if (build_squashfuse_tool) "squashfuse_tool" else null, - }; - - var executable_list = std.ArrayList(*std.Build.Step.Compile).init(allocator); - for (executable_names) |executable| { - if (executable) |name| { - try executable_list.append(try initExecutable(b, name)); - } - } + var exe = try initExecutable(b, "squashfuse"); const exe_options = b.addOptions(); exe_options.addOption(bool, "enable_xz", enable_xz); @@ -122,49 +98,47 @@ pub fn build(b: *std.build.Builder) !void { }, }); - for (executable_list.items) |exe| { - exe.target = target; - exe.optimize = optimize; - exe.strip = strip; + exe.target = target; + exe.optimize = optimize; + exe.strip = strip; - const clap_dep = b.dependency("clap", .{ - .target = target, - .optimize = optimize, - }); + const clap_dep = b.dependency("clap", .{ + .target = target, + .optimize = optimize, + }); - const fuse_dep = b.dependency("fuse", .{ - .target = target, - .optimize = optimize, - }); + const fuse_dep = b.dependency("fuse", .{ + .target = target, + .optimize = optimize, + }); - const fuse_module = b.addModule( - "fuse", - .{ .source_file = fuse_dep.module("fuse").source_file }, - ); + const fuse_module = b.addModule( + "fuse", + .{ .source_file = fuse_dep.module("fuse").source_file }, + ); - const clap_module = b.addModule("clap", .{ - .source_file = clap_dep.path("clap.zig"), - }); + const clap_module = b.addModule("clap", .{ + .source_file = clap_dep.path("clap.zig"), + }); - link(exe, .{ - .enable_lz4 = enable_lz4, - .enable_lzo = enable_lzo, - .enable_zlib = enable_zlib, - .enable_zstd = enable_zstd, - .enable_xz = enable_xz, + link(exe, .{ + .enable_lz4 = enable_lz4, + .enable_lzo = enable_lzo, + .enable_zlib = enable_zlib, + .enable_zstd = enable_zstd, + .enable_xz = enable_xz, - .enable_fuse = std.mem.eql(u8, exe.name, "squashfuse"), - .use_system_fuse = use_system_fuse, + .enable_fuse = std.mem.eql(u8, exe.name, "squashfuse"), + .use_system_fuse = use_system_fuse, - .use_libdeflate = use_libdeflate, - }); + .use_libdeflate = use_libdeflate, + }); - exe.addModule("squashfuse", squashfuse_module); - exe.addModule("fuse", fuse_module); - exe.addModule("clap", clap_module); + exe.addModule("squashfuse", squashfuse_module); + exe.addModule("fuse", fuse_module); + exe.addModule("clap", clap_module); - b.installArtifact(exe); - } + b.installArtifact(exe); // TODO: create symlinks in install directory // if (build_squashfuse_tool) { @@ -174,7 +148,7 @@ pub fn build(b: *std.build.Builder) !void { // cwd.symLink("squashfuse_tool", "zig-out/bin/squashfuse_extract", .{}) catch {}; // } - const run_cmd = b.addRunArtifact(executable_list.items[0]); + const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); @@ -185,8 +159,8 @@ pub fn build(b: *std.build.Builder) !void { const unit_tests = b.addTest(.{ .root_source_file = .{ .path = "lib/test.zig" }, - .target = executable_list.items[0].target, - .optimize = executable_list.items[0].optimize, + .target = exe.target, + .optimize = exe.optimize, }); link(unit_tests, .{ diff --git a/src/squashfuse.zig b/src/squashfuse.zig index 91fbf88..c5b4191 100644 --- a/src/squashfuse.zig +++ b/src/squashfuse.zig @@ -1,5 +1,6 @@ const std = @import("std"); const fmt = std.fmt; +const io = std.io; const fuse = @import("fuse"); const clap = @import("clap"); @@ -16,8 +17,8 @@ var squash: Squash = undefined; const version = std.SemanticVersion{ .major = 0, - .minor = 0, - .patch = 43, + .minor = 1, + .patch = 0, }; pub fn main() !void { @@ -28,15 +29,18 @@ pub fn main() !void { var stdout = std.io.getStdOut().writer(); const params = comptime clap.parseParamsComptime( - \\-h, --help display this help and exit - \\-f, --foreground run in foreground - \\-d, --debug enable debug output (runs in foreground) - // TODO: \\-x, --extract extract the entire SquashFS image - \\-l, --list list file tree of SquashFS image - \\-o, --option ... use a mount option + \\-h, --help display this help and exit + \\-f, --foreground run in foreground + \\-d, --debug enable debug output (runs in foreground) + \\-x, --extract extract the SquashFS image + \\-l, --list list file tree of SquashFS image + \\-o, --option ... use a libFUSE mount option \\ \\ --offset mount at an offset + \\ --extract-src must be used with `--extract`; specify the source inode + \\ --extract-dest must be used with `--extract`; specify the destination name \\ --version print the current version + \\ --verbose enable verbose printing \\... ); @@ -58,16 +62,19 @@ pub fn main() !void { var light_green: []const u8 = "\x1b[0;92m"; var cyan: []const u8 = "\x1b[0;36m"; + // TODO: move formatting code into its own function or possibly new package if (res.args.help != 0 or res.positionals.len == 0) { // Obtain the longest argument length var longest_normal: usize = 0; var longest_long_only: usize = 0; for (params) |param| { if (param.names.long) |long_name| { + const suffix = if (param.id.val.len > 0) param.id.val.len + 3 else 0; + const new_len = long_name.len + suffix; if (param.names.short) |_| { - if (long_name.len > longest_normal) longest_normal = long_name.len; + if (new_len > longest_normal) longest_normal = new_len; } else { - if (long_name.len > longest_long_only) longest_long_only = long_name.len; + if (new_len > longest_long_only) longest_long_only = new_len; } } } @@ -110,13 +117,23 @@ pub fn main() !void { } if (param.names.long) |long_name| { - try stderr.print("{s}--{s}{s}:", .{ cyan, long_name, reset }); + var type_len: usize = 0; + + try stderr.print(" {s}--{s}{s}", .{ cyan, long_name, reset }); + if (param.id.val.len > 0) { + type_len = param.id.val.len + 3; + } + try stderr.print(":", .{}); // Pad all equal to the longest GNU-style flag - for (long_name.len..longest_normal) |_| { + for (long_name.len + type_len..longest_normal) |_| { try stderr.print(" ", .{}); } + if (param.id.val.len > 0) { + try stderr.print(" <{s}>", .{param.id.val}); + } + try stderr.print(" {s}\n", .{param.id.description()}); } } @@ -131,13 +148,23 @@ pub fn main() !void { if (param.names.long) |long_name| { if (param.names.short) |_| continue; - try stderr.print(" {s}--{s}{s}:", .{ cyan, long_name, reset }); + var type_len: usize = 0; + + try stderr.print(" {s}--{s}{s}", .{ cyan, long_name, reset }); + if (param.id.val.len > 0) { + type_len = param.id.val.len + 3; + } + try stderr.print(":", .{}); // Pad all equal to the longest GNU-style flag - for (long_name.len..longest_long_only) |_| { + for (long_name.len + type_len..longest_long_only) |_| { try stderr.print(" ", .{}); } + if (param.id.val.len > 0) { + try stderr.print(" <{s}>", .{param.id.val}); + } + try stderr.print(" {s}\n", .{param.id.description()}); } } @@ -221,6 +248,28 @@ pub fn main() !void { var walker = try root_inode.walk(allocator); defer walker.deinit(); + var extract_args_len: usize = 0; + for (res.args.extract) |_| { + extract_args_len += 1; + } + + const src = if (res.args.@"extract-src" != null) res.args.@"extract-src".? else "/"; + + // TODO: use basename of src if not `/` + const dest = if (res.args.@"extract-dest" != null) res.args.@"extract-dest".? else "squashfs-root"; + + if (res.args.extract != 0) { + try extractArchive( + allocator, + &sqfs, + src, + dest, + .{ .verbose = res.args.verbose != 0 }, + ); + + return; + } + if (res.args.list != 0) { while (try walker.next()) |entry| { try stdout.print("{s}\n", .{entry.path}); @@ -381,3 +430,87 @@ const FuseOperations = struct { }; } }; + +const ExtractArchiveOptions = struct { + verbose: bool = false, +}; + +fn extractArchive( + allocator: std.mem.Allocator, + sqfs: *SquashFs, + src: []const u8, + dest: []const u8, + opts: ExtractArchiveOptions, +) !void { + var stdout = io.getStdOut().writer(); + + var root_inode = sqfs.getRootInode(); + + var walker = try root_inode.walk(allocator); + defer walker.deinit(); + + // Remove slashes at the beginning and end of path + var real_src = if (src[0] == '/') blk: { + break :blk src[1..]; + } else blk: { + break :blk src; + }; + + if (real_src.len > 0 and real_src[real_src.len - 1] == '/') { + real_src.len -= 1; + } + + if (real_src.len == 0) { + const cwd = std.fs.cwd(); + try cwd.makeDir(dest); + } + + var file_found = false; + + // Iterate over the SquashFS image and extract each item + while (try walker.next()) |entry| { + // Skip if the path doesn't match our source + if (entry.path.len < real_src.len or !std.mem.eql(u8, real_src, entry.path[0..real_src.len])) { + continue; + } + + // Skip files that begin with the same path but aren't the exact same + // or a directory + // + // Example: + // `test/file` would pass this test + // `test` would also pass + // `test-file` would fail and get skipped + if (real_src.len > 0 and entry.path.len > real_src.len and entry.path[real_src.len] != '/') { + continue; + } + + file_found = true; + + var path_buf: [std.os.PATH_MAX]u8 = undefined; + const prefixed_dest = switch (real_src.len) { + 0 => try fmt.bufPrint(&path_buf, "{s}/{s}", .{ + dest, + entry.path, + }), + else => try fmt.bufPrint(&path_buf, "{s}{s}", .{ + dest, + entry.path[real_src.len..], + }), + }; + + if (opts.verbose) { + try stdout.print("{s}\n", .{prefixed_dest}); + } + + // TODO: flag to change buf size + var buf: [4096]u8 = undefined; + + var inode = entry.inode(); + try inode.extract(&buf, prefixed_dest); + } + + if (!file_found) { + std.debug.print("file ({s}) not found!\n", .{real_src}); + } +} diff --git a/src/squashfuse_tool.zig b/src/squashfuse_tool.zig deleted file mode 100644 index c74494a..0000000 --- a/src/squashfuse_tool.zig +++ /dev/null @@ -1,232 +0,0 @@ -// FUSE-less tool for reading SquashFS files - -const std = @import("std"); -const io = std.io; -const fmt = std.fmt; -const clap = @import("clap"); - -const SquashFs = @import("squashfuse").SquashFs; - -// TODO: import from build.zig.zon -const version = std.SemanticVersion{ - .major = 0, - .minor = 0, - .patch = 43, -}; - -pub fn main() !void { - const allocator = std.heap.c_allocator; - - var stderr = io.getStdErr().writer(); - var stdout = io.getStdOut().writer(); - - const params = comptime clap.parseParamsComptime( - \\-h, --help display this help and exit - \\ - \\ --extract extract contents of archive to path - \\ --list list out archive tree - \\ --verbose enable verbose printing - \\ - \\ --offset access at an offset - \\ --version print the current version - \\... - ); - - var diag = clap.Diagnostic{}; - - var res = clap.parse( - clap.Help, - ¶ms, - clap.parsers.default, - .{ .diagnostic = &diag }, - ) catch |err| { - // TODO: custom error reporting - try diag.report(io.getStdErr().writer(), err); - return; - }; - defer res.deinit(); - - var reset: []const u8 = "\x1b[0;0m"; - var orange: []const u8 = "\x1b[0;33m"; - var red: []const u8 = "\x1b[0;31m"; - var light_blue: []const u8 = "\x1b[0;94m"; - var light_green: []const u8 = "\x1b[0;92m"; - var cyan: []const u8 = "\x1b[0;36m"; - - if (res.args.help != 0 or res.positionals.len == 0) { - // Obtain the longest argument length - var longest_normal: usize = 0; - var longest_long_only: usize = 0; - for (params) |param| { - if (param.names.long) |long_name| { - if (param.names.short) |_| { - if (long_name.len > longest_normal) longest_normal = long_name.len; - } else { - if (long_name.len > longest_long_only) longest_long_only = long_name.len; - } - } - } - - const env_map = try std.process.getEnvMap(allocator); - - if (env_map.get("NO_COLOR")) |_| { - reset = ""; - orange = ""; - red = ""; - light_blue = ""; - light_green = ""; - cyan = ""; - } - - if (res.args.version != 0) { - try stderr.print("{d}.{d}.{d}\n", .{ - version.major, - version.minor, - version.patch, - }); - - return; - } - - try stderr.print( - \\{s}usage{s}: {s}{s} {s}[{s}archive{s}] [{s}option{s}]... - \\{s}description{s}: list all of the files in a SquashFS image - \\ - \\{s}normal options{s}: - \\ - , .{ orange, reset, light_blue, "squashfuse_ls", reset, light_blue, reset, cyan, reset, orange, reset, orange, reset }); - - // Print all normal arguments and their descriptions - for (params) |param| { - if (param.names.short) |short_name| { - try stderr.print(" {s}-{c}{s}, ", .{ cyan, short_name, reset }); - } else { - continue; - } - - if (param.names.long) |long_name| { - try stderr.print("{s}--{s}{s}:", .{ cyan, long_name, reset }); - - // Pad all equal to the longest GNU-style flag - for (long_name.len..longest_normal) |_| { - try stderr.print(" ", .{}); - } - - try stderr.print(" {s}\n", .{param.id.description()}); - } - } - - try stderr.print( - \\ - \\{s}long-only options{s}: - \\ - , .{ orange, reset }); - - for (params) |param| { - if (param.names.long) |long_name| { - if (param.names.short) |_| continue; - - try stderr.print(" {s}--{s}{s}:", .{ cyan, long_name, reset }); - - // Pad all equal to the longest GNU-style flag - for (long_name.len..longest_long_only) |_| { - try stderr.print(" ", .{}); - } - - try stderr.print(" {s}\n", .{param.id.description()}); - } - } - - try stderr.print( - \\ - \\{s}enviornment variables{s}: - \\ {s}NO_COLOR{s}: disable color - \\ - \\ - , .{ orange, reset, cyan, reset }); - - return; - } - - var offset: usize = 0; - - var sqfs: SquashFs = undefined; - - for (res.positionals, 0..) |arg, idx| { - if (idx > 0) { - try stderr.print("{s}::{s} failed to parse args: too many arguments\n", .{ red, reset }); - std.os.exit(1); - } - - // Open the SquashFS image in the first positional argument - if (res.args.offset) |o| { - offset = o; - } - - sqfs = SquashFs.init(allocator, arg, .{ - .offset = offset, - }) catch |err| { - try stderr.print("{s}::{s} failed to open image: {!}\n", .{ red, reset, err }); - std.os.exit(1); - }; - } - - var root_inode = sqfs.getRootInode(); - var walker = try root_inode.walk(allocator); - - defer walker.deinit(); - - if (res.args.extract) |dest| { - try extractArchive( - allocator, - &sqfs, - dest, - .{ .verbose = res.args.verbose != 0 }, - ); - } else if (res.args.list != 0) { - while (try walker.next()) |entry| { - try stdout.print("{s}\n", .{entry.path}); - } - } -} - -const ExtractArchiveOptions = struct { - verbose: bool = false, -}; - -fn extractArchive( - allocator: std.mem.Allocator, - sqfs: *SquashFs, - dest: []const u8, - opts: ExtractArchiveOptions, -) !void { - var stdout = io.getStdOut().writer(); - - var root_inode = sqfs.getRootInode(); - - var walker = try root_inode.walk(allocator); - defer walker.deinit(); - - const cwd = std.fs.cwd(); - - try cwd.makeDir(dest); - - // Iterate over the SquashFS image and extract each item - while (try walker.next()) |entry| { - var path_buf: [std.os.PATH_MAX]u8 = undefined; - const prefixed_dest = try fmt.bufPrint(&path_buf, "{s}/{s}", .{ - dest, - entry.path, - }); - - if (opts.verbose) { - try stdout.print("{s}\n", .{prefixed_dest}); - } - - // TODO: flag to change buf size - var buf: [4096]u8 = undefined; - - var inode = entry.inode(); - try inode.extract(&buf, prefixed_dest); - } -}