diff --git a/.gitignore b/.gitignore index a66975c..4018da0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ .idea/ .godot/ +/result + # apple .DS_Store .AppleDouble diff --git a/Cargo.toml b/Cargo.toml index 780d38a..6c4a442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,11 @@ name = "yaar" version = "0.1.0" edition = "2024" +description = "Yet another archiver — a CLI tool for creating, extracting, compressing, and inspecting archives using libarchive" +license = "GPL-3.0-only" +repository = "https://git.kp2pml30.moe/ya/ya-ar" +keywords = ["archive", "tar", "zip", "compression", "libarchive"] +categories = ["command-line-utilities", "compression"] [dependencies] clap = { version = "4", features = ["derive"] } diff --git a/flake.nix b/flake.nix index 8d92471..9eb2477 100644 --- a/flake.nix +++ b/flake.nix @@ -36,6 +36,7 @@ packages.x86_64-linux = { yaar-amd64-linux = mkYaar "amd64-linux"; yaar-arm64-linux = mkYaar "arm64-linux"; + gen-libarchive-sys = import ./support/gen-libarchive-sys.nix { inherit pkgs zig; }; }; devShells = forAllSystems (system: diff --git a/gen_libarchive_sys.zig b/gen_libarchive_sys.zig new file mode 100644 index 0000000..adb9006 --- /dev/null +++ b/gen_libarchive_sys.zig @@ -0,0 +1,272 @@ +const std = @import("std"); +const c = @cImport({ + @cInclude("archive.h"); + @cInclude("archive_entry.h"); +}); + +const W = *std.Io.Writer; + +fn writeRustType(w: W, comptime T: type) std.Io.Writer.Error!void { + if (T == c_int) return w.writeAll("c_int"); + if (T == c_uint) return w.writeAll("c_uint"); + if (T == c_long) return w.writeAll("c_long"); + if (T == c_ulong) return w.writeAll("c_ulong"); + if (T == usize) return w.writeAll("usize"); + if (T == isize) return w.writeAll("isize"); + if (T == i64) return w.writeAll("i64"); + if (T == u64) return w.writeAll("u64"); + if (T == i32) return w.writeAll("i32"); + if (T == u32) return w.writeAll("u32"); + if (T == c.struct_archive) return w.writeAll("archive"); + if (T == c.struct_archive_entry) return w.writeAll("archive_entry"); + if (T == anyopaque) return w.writeAll("c_void"); + if (T == u8) return w.writeAll("c_char"); + + switch (@typeInfo(T)) { + .optional => |o| return writeRustType(w, o.child), + .pointer => |p| { + try w.writeAll(if (p.is_const) "*const " else "*mut "); + return writeRustType(w, p.child); + }, + else => @compileError("unhandled type: " ++ @typeName(T)), + } +} + +fn FnInfo(comptime name: []const u8) type { + const F = @TypeOf(@field(c, name)); + const raw = @typeInfo(F); + return switch (raw) { + .@"fn" => F, + .pointer => |p| p.child, + else => @compileError("expected fn for " ++ name), + }; +} + +fn writeFn(w: W, comptime name: []const u8) std.Io.Writer.Error!void { + const fi = @typeInfo(FnInfo(name)).@"fn"; + + try w.writeAll(" pub fn " ++ name ++ "("); + inline for (fi.params, 0..) |param, i| { + if (i > 0) try w.writeAll(", "); + try w.print("_{d}: ", .{i}); + try writeRustType(w, param.type.?); + } + try w.writeAll(")"); + + if (fi.return_type) |ret| { + if (ret != void) { + try w.writeAll(" -> "); + try writeRustType(w, ret); + } + } + try w.writeAll(";\n"); +} + +pub fn main() !void { + var buf: [4096]u8 = undefined; + var fw = std.fs.File.stdout().writerStreaming(&buf); + const w: W = &fw.interface; + + try w.writeAll( + \\#![allow(non_camel_case_types, dead_code, unused_imports)] + \\ + \\use std::os::raw::{c_char, c_int, c_long, c_uint, c_void}; + \\ + \\// Opaque types + \\#[repr(C)] + \\pub struct archive { + \\ _opaque: [u8; 0], + \\} + \\ + \\#[repr(C)] + \\pub struct archive_entry { + \\ _opaque: [u8; 0], + \\} + \\ + \\ + ); + + // Return codes + try w.writeAll("// Return codes\n"); + inline for (.{ "ARCHIVE_EOF", "ARCHIVE_OK", "ARCHIVE_WARN" }) |name| { + try w.print("pub const {s}: c_int = {d};\n", .{ name, @as(c_int, @field(c, name)) }); + } + + // Format codes + try w.writeAll("\n// Format codes\n"); + inline for (.{ + "ARCHIVE_FORMAT_BASE_MASK", + "ARCHIVE_FORMAT_CPIO", + "ARCHIVE_FORMAT_TAR", + "ARCHIVE_FORMAT_ISO9660", + "ARCHIVE_FORMAT_ZIP", + "ARCHIVE_FORMAT_EMPTY", + "ARCHIVE_FORMAT_AR", + "ARCHIVE_FORMAT_MTREE", + "ARCHIVE_FORMAT_RAW", + "ARCHIVE_FORMAT_XAR", + "ARCHIVE_FORMAT_LHA", + "ARCHIVE_FORMAT_CAB", + "ARCHIVE_FORMAT_RAR", + "ARCHIVE_FORMAT_7ZIP", + "ARCHIVE_FORMAT_WARC", + "ARCHIVE_FORMAT_RAR_V5", + }) |name| { + try w.print("pub const {s}: u32 = 0x{x};\n", .{ name, @as(c_uint, @field(c, name)) }); + } + + // Filter codes + try w.writeAll("\n// Filter codes\n"); + inline for (.{ + "ARCHIVE_FILTER_NONE", + "ARCHIVE_FILTER_GZIP", + "ARCHIVE_FILTER_BZIP2", + "ARCHIVE_FILTER_COMPRESS", + "ARCHIVE_FILTER_LZMA", + "ARCHIVE_FILTER_XZ", + "ARCHIVE_FILTER_UU", + "ARCHIVE_FILTER_RPM", + "ARCHIVE_FILTER_LZIP", + "ARCHIVE_FILTER_LRZIP", + "ARCHIVE_FILTER_LZOP", + "ARCHIVE_FILTER_GRZIP", + "ARCHIVE_FILTER_LZ4", + "ARCHIVE_FILTER_ZSTD", + }) |name| { + try w.print("pub const {s}: u32 = {d};\n", .{ name, @as(c_uint, @field(c, name)) }); + } + + // Extract flags + try w.writeAll("\n// Extract flags\n"); + inline for (.{ + "ARCHIVE_EXTRACT_TIME", + "ARCHIVE_EXTRACT_PERM", + "ARCHIVE_EXTRACT_ACL", + "ARCHIVE_EXTRACT_FFLAGS", + "ARCHIVE_EXTRACT_SECURE_SYMLINKS", + "ARCHIVE_EXTRACT_SECURE_NODOTDOT", + "ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS", + }) |name| { + try w.print("pub const {s}: u32 = 0x{x:0>4};\n", .{ name, @as(c_uint, @field(c, name)) }); + } + + // File type constants (from POSIX stat) — hardcoded, zig cImport can't translate octal macros + try w.writeAll("\n// File type constants (from POSIX stat)\n"); + try w.writeAll("pub const AE_IFREG: u32 = 0o100000;\n"); + try w.writeAll("pub const AE_IFDIR: u32 = 0o040000;\n"); + try w.writeAll("pub const AE_IFLNK: u32 = 0o120000;\n"); + + // Functions + try w.writeAll( + \\ + \\unsafe extern "C" { + \\ + ); + + // Reader + try w.writeAll(" // Reader\n"); + inline for (.{ + "archive_read_new", + "archive_read_free", + "archive_read_support_format_all", + "archive_read_support_format_raw", + "archive_read_support_filter_all", + "archive_read_open_filename", + "archive_read_open_memory", + "archive_read_next_header", + "archive_read_next_header2", + "archive_read_data", + "archive_read_data_block", + }) |name| { + try writeFn(w, name); + } + + // Writer + try w.writeAll("\n // Writer\n"); + inline for (.{ + "archive_write_new", + "archive_write_free", + "archive_write_set_format", + "archive_write_set_format_raw", + "archive_write_set_format_pax_restricted", + "archive_write_set_format_zip", + "archive_write_set_format_7zip", + "archive_write_set_format_ar_svr4", + "archive_write_set_format_cpio_newc", + "archive_write_add_filter", + "archive_write_add_filter_none", + "archive_write_open_filename", + "archive_write_open_fd", + "archive_write_header", + "archive_write_data", + "archive_write_finish_entry", + "archive_write_close", + }) |name| { + try writeFn(w, name); + } + + // Disk reader + try w.writeAll("\n // Disk reader\n"); + inline for (.{ + "archive_read_disk_new", + "archive_read_disk_set_standard_lookup", + "archive_read_disk_open", + "archive_read_disk_descend", + }) |name| { + try writeFn(w, name); + } + + // Disk writer + try w.writeAll("\n // Disk writer\n"); + inline for (.{ + "archive_write_disk_new", + "archive_write_disk_set_options", + "archive_write_disk_set_standard_lookup", + }) |name| { + try writeFn(w, name); + } + + // Entry + try w.writeAll("\n // Entry\n"); + inline for (.{ + "archive_entry_new", + "archive_entry_free", + "archive_entry_clear", + "archive_entry_pathname", + "archive_entry_pathname_utf8", + "archive_entry_set_pathname_utf8", + "archive_entry_size", + "archive_entry_set_size", + "archive_entry_filetype", + "archive_entry_set_filetype", + "archive_entry_perm", + "archive_entry_set_perm", + "archive_entry_set_mtime", + "archive_entry_set_atime", + "archive_entry_set_ctime", + "archive_entry_set_uid", + "archive_entry_set_gid", + "archive_entry_set_uname_utf8", + "archive_entry_set_gname_utf8", + "archive_entry_hardlink_utf8", + "archive_entry_symlink_utf8", + }) |name| { + try writeFn(w, name); + } + + // Metadata + try w.writeAll("\n // Metadata\n"); + inline for (.{ + "archive_format", + "archive_filter_count", + "archive_filter_code", + "archive_filter_name", + "archive_format_name", + "archive_error_string", + }) |name| { + try writeFn(w, name); + } + + try w.writeAll("}\n"); + try w.flush(); +} diff --git a/src/cli.rs b/src/cli.rs index 5c052f7..6e53309 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -39,7 +39,7 @@ impl fmt::Display for OutputFormat { } #[derive(Parser)] -#[command(name = "yaar", about = "Yet another archiver")] +#[command(name = "yaar", about = "Yet Another ARchiver")] pub struct Cli { #[command(subcommand)] pub command: Command, diff --git a/src/libarchive_sys.rs b/src/libarchive_sys.rs index 4f49155..5d2c7fe 100644 --- a/src/libarchive_sys.rs +++ b/src/libarchive_sys.rs @@ -1,6 +1,6 @@ -#![allow(non_camel_case_types, dead_code)] +#![allow(non_camel_case_types, dead_code, unused_imports)] -use std::os::raw::{c_char, c_int, c_long, c_void}; +use std::os::raw::{c_char, c_int, c_long, c_uint, c_void}; // Opaque types #[repr(C)] @@ -28,12 +28,12 @@ pub const ARCHIVE_FORMAT_EMPTY: u32 = 0x60000; pub const ARCHIVE_FORMAT_AR: u32 = 0x70000; pub const ARCHIVE_FORMAT_MTREE: u32 = 0x80000; pub const ARCHIVE_FORMAT_RAW: u32 = 0x90000; -pub const ARCHIVE_FORMAT_XAR: u32 = 0xA0000; -pub const ARCHIVE_FORMAT_LHA: u32 = 0xB0000; -pub const ARCHIVE_FORMAT_CAB: u32 = 0xC0000; -pub const ARCHIVE_FORMAT_RAR: u32 = 0xD0000; -pub const ARCHIVE_FORMAT_7ZIP: u32 = 0xE0000; -pub const ARCHIVE_FORMAT_WARC: u32 = 0xF0000; +pub const ARCHIVE_FORMAT_XAR: u32 = 0xa0000; +pub const ARCHIVE_FORMAT_LHA: u32 = 0xb0000; +pub const ARCHIVE_FORMAT_CAB: u32 = 0xc0000; +pub const ARCHIVE_FORMAT_RAR: u32 = 0xd0000; +pub const ARCHIVE_FORMAT_7ZIP: u32 = 0xe0000; +pub const ARCHIVE_FORMAT_WARC: u32 = 0xf0000; pub const ARCHIVE_FORMAT_RAR_V5: u32 = 0x100000; // Filter codes @@ -69,75 +69,75 @@ pub const AE_IFLNK: u32 = 0o120000; unsafe extern "C" { // Reader pub fn archive_read_new() -> *mut archive; - pub fn archive_read_free(a: *mut archive) -> c_int; - pub fn archive_read_support_format_all(a: *mut archive) -> c_int; - pub fn archive_read_support_format_raw(a: *mut archive) -> c_int; - pub fn archive_read_support_filter_all(a: *mut archive) -> c_int; - pub fn archive_read_open_filename(a: *mut archive, filename: *const c_char, block_size: usize) -> c_int; - pub fn archive_read_open_memory(a: *mut archive, buff: *const c_void, size: usize) -> c_int; - pub fn archive_read_next_header(a: *mut archive, entry: *mut *mut archive_entry) -> c_int; - pub fn archive_read_next_header2(a: *mut archive, entry: *mut archive_entry) -> c_int; - pub fn archive_read_data(a: *mut archive, buff: *mut c_void, size: usize) -> isize; - pub fn archive_read_data_block(a: *mut archive, buff: *mut *const c_void, size: *mut usize, offset: *mut i64) -> c_int; + pub fn archive_read_free(_0: *mut archive) -> c_int; + pub fn archive_read_support_format_all(_0: *mut archive) -> c_int; + pub fn archive_read_support_format_raw(_0: *mut archive) -> c_int; + pub fn archive_read_support_filter_all(_0: *mut archive) -> c_int; + pub fn archive_read_open_filename(_0: *mut archive, _1: *const c_char, _2: usize) -> c_int; + pub fn archive_read_open_memory(_0: *mut archive, _1: *const c_void, _2: usize) -> c_int; + pub fn archive_read_next_header(_0: *mut archive, _1: *mut *mut archive_entry) -> c_int; + pub fn archive_read_next_header2(_0: *mut archive, _1: *mut archive_entry) -> c_int; + pub fn archive_read_data(_0: *mut archive, _1: *mut c_void, _2: usize) -> isize; + pub fn archive_read_data_block(_0: *mut archive, _1: *mut *const c_void, _2: *mut usize, _3: *mut i64) -> c_int; // Writer pub fn archive_write_new() -> *mut archive; - pub fn archive_write_free(a: *mut archive) -> c_int; - pub fn archive_write_set_format(a: *mut archive, format_code: c_int) -> c_int; - pub fn archive_write_set_format_raw(a: *mut archive) -> c_int; - pub fn archive_write_set_format_pax_restricted(a: *mut archive) -> c_int; - pub fn archive_write_set_format_zip(a: *mut archive) -> c_int; - pub fn archive_write_set_format_7zip(a: *mut archive) -> c_int; - pub fn archive_write_set_format_ar_svr4(a: *mut archive) -> c_int; - pub fn archive_write_set_format_cpio_newc(a: *mut archive) -> c_int; - pub fn archive_write_add_filter(a: *mut archive, filter_code: c_int) -> c_int; - pub fn archive_write_add_filter_none(a: *mut archive) -> c_int; - pub fn archive_write_open_filename(a: *mut archive, filename: *const c_char) -> c_int; - pub fn archive_write_open_fd(a: *mut archive, fd: c_int) -> c_int; - pub fn archive_write_header(a: *mut archive, entry: *mut archive_entry) -> c_int; - pub fn archive_write_data(a: *mut archive, data: *const c_void, size: usize) -> isize; - pub fn archive_write_finish_entry(a: *mut archive) -> c_int; - pub fn archive_write_close(a: *mut archive) -> c_int; + pub fn archive_write_free(_0: *mut archive) -> c_int; + pub fn archive_write_set_format(_0: *mut archive, _1: c_int) -> c_int; + pub fn archive_write_set_format_raw(_0: *mut archive) -> c_int; + pub fn archive_write_set_format_pax_restricted(_0: *mut archive) -> c_int; + pub fn archive_write_set_format_zip(_0: *mut archive) -> c_int; + pub fn archive_write_set_format_7zip(_0: *mut archive) -> c_int; + pub fn archive_write_set_format_ar_svr4(_0: *mut archive) -> c_int; + pub fn archive_write_set_format_cpio_newc(_0: *mut archive) -> c_int; + pub fn archive_write_add_filter(_0: *mut archive, _1: c_int) -> c_int; + pub fn archive_write_add_filter_none(_0: *mut archive) -> c_int; + pub fn archive_write_open_filename(_0: *mut archive, _1: *const c_char) -> c_int; + pub fn archive_write_open_fd(_0: *mut archive, _1: c_int) -> c_int; + pub fn archive_write_header(_0: *mut archive, _1: *mut archive_entry) -> c_int; + pub fn archive_write_data(_0: *mut archive, _1: *const c_void, _2: usize) -> isize; + pub fn archive_write_finish_entry(_0: *mut archive) -> c_int; + pub fn archive_write_close(_0: *mut archive) -> c_int; // Disk reader pub fn archive_read_disk_new() -> *mut archive; - pub fn archive_read_disk_set_standard_lookup(a: *mut archive) -> c_int; - pub fn archive_read_disk_open(a: *mut archive, path: *const c_char) -> c_int; - pub fn archive_read_disk_descend(a: *mut archive) -> c_int; + pub fn archive_read_disk_set_standard_lookup(_0: *mut archive) -> c_int; + pub fn archive_read_disk_open(_0: *mut archive, _1: *const c_char) -> c_int; + pub fn archive_read_disk_descend(_0: *mut archive) -> c_int; // Disk writer pub fn archive_write_disk_new() -> *mut archive; - pub fn archive_write_disk_set_options(a: *mut archive, flags: c_int) -> c_int; - pub fn archive_write_disk_set_standard_lookup(a: *mut archive) -> c_int; + pub fn archive_write_disk_set_options(_0: *mut archive, _1: c_int) -> c_int; + pub fn archive_write_disk_set_standard_lookup(_0: *mut archive) -> c_int; // Entry pub fn archive_entry_new() -> *mut archive_entry; - pub fn archive_entry_free(entry: *mut archive_entry); - pub fn archive_entry_clear(entry: *mut archive_entry) -> *mut archive_entry; - pub fn archive_entry_pathname(entry: *mut archive_entry) -> *const c_char; - pub fn archive_entry_pathname_utf8(entry: *mut archive_entry) -> *const c_char; - pub fn archive_entry_set_pathname_utf8(entry: *mut archive_entry, name: *const c_char); - pub fn archive_entry_size(entry: *mut archive_entry) -> i64; - pub fn archive_entry_set_size(entry: *mut archive_entry, size: i64); - pub fn archive_entry_filetype(entry: *mut archive_entry) -> c_int; - pub fn archive_entry_set_filetype(entry: *mut archive_entry, ftype: c_int); - pub fn archive_entry_perm(entry: *mut archive_entry) -> c_int; - pub fn archive_entry_set_perm(entry: *mut archive_entry, perm: c_int); - pub fn archive_entry_set_mtime(entry: *mut archive_entry, sec: i64, nsec: c_long); - pub fn archive_entry_set_atime(entry: *mut archive_entry, sec: i64, nsec: c_long); - pub fn archive_entry_set_ctime(entry: *mut archive_entry, sec: i64, nsec: c_long); - pub fn archive_entry_set_uid(entry: *mut archive_entry, uid: i64); - pub fn archive_entry_set_gid(entry: *mut archive_entry, gid: i64); - pub fn archive_entry_set_uname_utf8(entry: *mut archive_entry, name: *const c_char); - pub fn archive_entry_set_gname_utf8(entry: *mut archive_entry, name: *const c_char); - pub fn archive_entry_hardlink_utf8(entry: *mut archive_entry) -> *const c_char; - pub fn archive_entry_symlink_utf8(entry: *mut archive_entry) -> *const c_char; + pub fn archive_entry_free(_0: *mut archive_entry); + pub fn archive_entry_clear(_0: *mut archive_entry) -> *mut archive_entry; + pub fn archive_entry_pathname(_0: *mut archive_entry) -> *const c_char; + pub fn archive_entry_pathname_utf8(_0: *mut archive_entry) -> *const c_char; + pub fn archive_entry_set_pathname_utf8(_0: *mut archive_entry, _1: *const c_char); + pub fn archive_entry_size(_0: *mut archive_entry) -> i64; + pub fn archive_entry_set_size(_0: *mut archive_entry, _1: i64); + pub fn archive_entry_filetype(_0: *mut archive_entry) -> c_uint; + pub fn archive_entry_set_filetype(_0: *mut archive_entry, _1: c_uint); + pub fn archive_entry_perm(_0: *mut archive_entry) -> c_uint; + pub fn archive_entry_set_perm(_0: *mut archive_entry, _1: c_uint); + pub fn archive_entry_set_mtime(_0: *mut archive_entry, _1: c_long, _2: c_long); + pub fn archive_entry_set_atime(_0: *mut archive_entry, _1: c_long, _2: c_long); + pub fn archive_entry_set_ctime(_0: *mut archive_entry, _1: c_long, _2: c_long); + pub fn archive_entry_set_uid(_0: *mut archive_entry, _1: i64); + pub fn archive_entry_set_gid(_0: *mut archive_entry, _1: i64); + pub fn archive_entry_set_uname_utf8(_0: *mut archive_entry, _1: *const c_char); + pub fn archive_entry_set_gname_utf8(_0: *mut archive_entry, _1: *const c_char); + pub fn archive_entry_hardlink_utf8(_0: *mut archive_entry) -> *const c_char; + pub fn archive_entry_symlink_utf8(_0: *mut archive_entry) -> *const c_char; // Metadata - pub fn archive_format(a: *mut archive) -> c_int; - pub fn archive_filter_count(a: *mut archive) -> c_int; - pub fn archive_filter_code(a: *mut archive, index: c_int) -> c_int; - pub fn archive_filter_name(a: *mut archive, index: c_int) -> *const c_char; - pub fn archive_format_name(a: *mut archive) -> *const c_char; - pub fn archive_error_string(a: *mut archive) -> *const c_char; + pub fn archive_format(_0: *mut archive) -> c_int; + pub fn archive_filter_count(_0: *mut archive) -> c_int; + pub fn archive_filter_code(_0: *mut archive, _1: c_int) -> c_int; + pub fn archive_filter_name(_0: *mut archive, _1: c_int) -> *const c_char; + pub fn archive_format_name(_0: *mut archive) -> *const c_char; + pub fn archive_error_string(_0: *mut archive) -> *const c_char; } diff --git a/support/gen-libarchive-sys.nix b/support/gen-libarchive-sys.nix new file mode 100644 index 0000000..148c3dd --- /dev/null +++ b/support/gen-libarchive-sys.nix @@ -0,0 +1,19 @@ +{ pkgs, zig }: +pkgs.stdenvNoCC.mkDerivation { + name = "libarchive-sys-rs"; + src = ../gen_libarchive_sys.zig; + dontUnpack = true; + nativeBuildInputs = [ zig ]; + ZIG_GLOBAL_CACHE_DIR = "/build/.zig-cache"; + ZIG_LOCAL_CACHE_DIR = "/build/.zig-cache-local"; + buildPhase = '' + ${zig}/zig run "$src" \ + -target x86_64-linux-musl \ + -lc -OReleaseFast -fstrip \ + -I${pkgs.libarchive.dev}/include \ + > libarchive_sys.rs + ''; + installPhase = '' + cp libarchive_sys.rs "$out" + ''; +}