Current section
Files
Jump to
Current section
Files
lib/archive/nif.ex
defmodule Archive.Setup do
@moduledoc false
@ext (case :os.type() do
{:win32, _} -> ".dll"
{:unix, :darwin} -> ".dylib"
{:unix, _} -> ".so"
end)
@base_path "#{:code.priv_dir(:archive)}/#{Mix.target()}"
@include_path "#{@base_path}/include/"
@library_dir "#{@base_path}/lib/"
@link_path "#{@library_dir}libarchive#{@ext}"
defmacro __using__(_opts) do
quote do
use Zig,
otp_app: :archive,
c: [
include_dirs: [unquote(@include_path)],
link_lib: unquote(@link_path),
library_dirs: [unquote(@library_dir)]
],
resources: [:ArchiveReaderResource, :ArchiveWriterResource, :ArchiveEntryResource],
dump_build_zig: true
end
end
end
defmodule Archive.Nif do
@moduledoc """
> #### Proceed With Caution {: .error}
>
> The `Archive.Nif` module contains the functions that directly interact with `libarchive`.
> It is generally discouraged to interact with this library directly. If you find you need
> functionality not supported in the higher-level APIs, consider opening an issue first
> if the feature would be generally applicable or desireable.
`Archive.Nif` provides direct `Elixir` bindings to the `libarchive` C API. Most of the functions
in this module are 1-to-1 mappings to the C API equivalent functions.
These functions work directly with references, rather than `Archive` or `Archive.Entry` structs.
As long as you use the `ArchiveReaderResource` and `ArchiveEntryResource` reference types, they will
be managed and garbage-collected by this module once created.
Most functions in this module may raise `ErlangError` on failure. You should invoke **most** of
these functions using `Archive.Nif.safe_call/2` to catch the errors and return them as `:ok` or
an `{:error, reason}` tuple
## `libarchive` APIs
`libarchive` distinguishes the following APIs:
<!-- tabs-open -->
### `archive_read`
> #### Reading an Archive {: .info}
>
> Although `Archive`'s high-level API takes care of all of the resource management for you, it can still be useful to understand how it works:
> 1) Create new archive reader object
> 2) Update any global reader properties as appropriate. These properties determine supported compressions, formats, etc.
> 3) Open the archive
> 4) Repeatedly call archive_read_next_header to get information about
> successive archive entries. Call archive_read_data to extract
> data for entries of interest.
> 5) Cleanup archive reader object
### `archive-write`
> #### Creating an Archive {: .info}
> To create an archive:
> 1) Ask archive_write_new for an archive writer object.
> 2) Set any global properties. In particular, you should set
> the compression and format to use.
> 3) Call archive_write_open to open the file (most people
> will use archive_write_open_file or archive_write_open_fd,
> which provide convenient canned I/O callbacks for you).
> 4) For each entry:
> - construct an appropriate struct archive_entry structure
> - archive_write_header to write the header
> - archive_write_data to write the entry data
> 5) archive_write_close to close the output
> 6) archive_write_free to cleanup the writer and release resources
### `archive_write_disk`
> #### Writing to Disk {: .info}
To create objects on disk:
> 1) Ask archive_write_disk_new for a new archive_write_disk object.
> 2) Set any global properties. In particular, you probably
> want to set the options.
> 3) For each entry:
> - construct an appropriate struct archive_entry structure
> - archive_write_header to create the file/dir/etc on disk
> - archive_write_data to write the entry data
> 4) archive_write_free to cleanup the writer and release resources
>s
> In particular, you can use this in conjunction with archive_read()
> to pull entries out of an archive and create them on disk.
<!-- tabs-close -->
It also has `archive_read_extract` functions that combine reading with writing to disk.
"""
require Logger
use Archive.Setup
~Z"""
const std = @import("std");
const c = @cImport({
if (@import("builtin").abi == .musl) {
@cDefine("__DEFINED_struct_timespec", "");
}
@cInclude("archive.h");
@cInclude("archive_entry.h");
});
const ArchiveError = error{ ArchiveFailed, ArchiveRetry, ArchiveWarn, ArchiveFatal, ArchiveEof };
const CAllocError = error{ archiveEntry, archive };
const beam = @import("beam");
const enif = @import("erl_nif");
const root = @import("root");
pub const ArchiveReaderResource = beam.Resource(*c.archive, root, .{ .Callbacks = ArchiveReaderResourceCallbacks });
pub const ArchiveWriterResource = beam.Resource(*c.archive, root, .{ .Callbacks = ArchiveWriterResourceCallbacks });
pub const ArchiveEntryResource = beam.Resource(*c.archive_entry, root, .{ .Callbacks = ArchiveEntryResourceCallbacks });
pub const ArchiveFilter = enum(c_int) {
none = c.ARCHIVE_FILTER_NONE,
gzip = c.ARCHIVE_FILTER_GZIP,
bzip2 = c.ARCHIVE_FILTER_BZIP2,
compress = c.ARCHIVE_FILTER_COMPRESS,
program = c.ARCHIVE_FILTER_PROGRAM,
lzma = c.ARCHIVE_FILTER_LZMA,
xz = c.ARCHIVE_FILTER_XZ,
uu = c.ARCHIVE_FILTER_UU,
rpm = c.ARCHIVE_FILTER_RPM,
lzip = c.ARCHIVE_FILTER_LZIP,
lrzip = c.ARCHIVE_FILTER_LRZIP,
lzop = c.ARCHIVE_FILTER_LZOP,
grzip = c.ARCHIVE_FILTER_GRZIP,
lz4 = c.ARCHIVE_FILTER_LZ4,
zstd = c.ARCHIVE_FILTER_ZSTD,
};
pub const ArchiveFormat = enum(c_int) {
cpio = c.ARCHIVE_FORMAT_CPIO,
cpio_posix = c.ARCHIVE_FORMAT_CPIO_POSIX,
cpio_bin_le = c.ARCHIVE_FORMAT_CPIO_BIN_LE,
cpio_bin_be = c.ARCHIVE_FORMAT_CPIO_BIN_BE,
cpio_svr4_nocrc = c.ARCHIVE_FORMAT_CPIO_SVR4_NOCRC,
cpio_svr4_crc = c.ARCHIVE_FORMAT_CPIO_SVR4_CRC,
cpio_afio_large = c.ARCHIVE_FORMAT_CPIO_AFIO_LARGE,
cpio_pwb = c.ARCHIVE_FORMAT_CPIO_PWB,
shar = c.ARCHIVE_FORMAT_SHAR,
shar_base = c.ARCHIVE_FORMAT_SHAR_BASE,
shar_dump = c.ARCHIVE_FORMAT_SHAR_DUMP,
tar = c.ARCHIVE_FORMAT_TAR,
tar_ustar = c.ARCHIVE_FORMAT_TAR_USTAR,
tar_pax_interchange = c.ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE,
tar_pax_restricted = c.ARCHIVE_FORMAT_TAR_PAX_RESTRICTED,
tar_gnutar = c.ARCHIVE_FORMAT_TAR_GNUTAR,
iso9660 = c.ARCHIVE_FORMAT_ISO9660,
iso9660_rockridge = c.ARCHIVE_FORMAT_ISO9660_ROCKRIDGE,
zip = c.ARCHIVE_FORMAT_ZIP,
empty = c.ARCHIVE_FORMAT_EMPTY,
ar = c.ARCHIVE_FORMAT_AR,
ar_gnu = c.ARCHIVE_FORMAT_AR_GNU,
ar_bsd = c.ARCHIVE_FORMAT_AR_BSD,
mtree = c.ARCHIVE_FORMAT_MTREE,
raw = c.ARCHIVE_FORMAT_RAW,
xar = c.ARCHIVE_FORMAT_XAR,
lha = c.ARCHIVE_FORMAT_LHA,
cab = c.ARCHIVE_FORMAT_CAB,
rar = c.ARCHIVE_FORMAT_RAR,
sevenz = c.ARCHIVE_FORMAT_7ZIP,
warc = c.ARCHIVE_FORMAT_WARC,
rar_v5 = c.ARCHIVE_FORMAT_RAR_V5,
};
pub fn isSubFormatOf(self: ArchiveFormat, parent: ArchiveFormat) bool {
return (@intFromEnum(self) & c.ARCHIVE_FORMAT_BASE_MASK) == @intFromEnum(parent);
}
pub const ExtractFlag = enum(c_int) {
owner = c.ARCHIVE_EXTRACT_OWNER,
perm = c.ARCHIVE_EXTRACT_PERM,
time = c.ARCHIVE_EXTRACT_TIME,
no_overwrite = c.ARCHIVE_EXTRACT_NO_OVERWRITE,
unlink = c.ARCHIVE_EXTRACT_UNLINK,
acl = c.ARCHIVE_EXTRACT_ACL,
fflags = c.ARCHIVE_EXTRACT_FFLAGS,
xattr = c.ARCHIVE_EXTRACT_XATTR,
secure_symlinks = c.ARCHIVE_EXTRACT_SECURE_SYMLINKS,
secure_nodotdot = c.ARCHIVE_EXTRACT_SECURE_NODOTDOT,
no_autodir = c.ARCHIVE_EXTRACT_NO_AUTODIR,
no_overwrite_newer = c.ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER,
sparse = c.ARCHIVE_EXTRACT_SPARSE,
mac_metadata = c.ARCHIVE_EXTRACT_MAC_METADATA,
no_hfs_compression = c.ARCHIVE_EXTRACT_NO_HFS_COMPRESSION,
hfs_compression_forced = c.ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED,
secure_noabsolutepaths = c.ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS,
clear_nochange_fflags = c.ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS,
safe_writes = c.ARCHIVE_EXTRACT_SAFE_WRITES,
};
const ExtractFlagDocs = struct {
flag: ExtractFlag,
default_doc: []const u8,
};
const extractFlagDocumentation = [_]ExtractFlagDocs{
.{ .flag = .owner, .default_doc = "Do not try to set owner/group." },
.{ .flag = .perm, .default_doc = "Do obey umask, do not restore SUID/SGID/SVTX bits." },
.{ .flag = .time, .default_doc = "Do not restore mtime/atime." },
.{ .flag = .no_overwrite, .default_doc = "Replace existing files." },
.{ .flag = .unlink, .default_doc = "Try create first, unlink only if create fails with EEXIST." },
.{ .flag = .acl, .default_doc = "Do not restore ACLs." },
.{ .flag = .fflags, .default_doc = "Do not restore fflags." },
.{ .flag = .xattr, .default_doc = "Do not restore xattrs." },
.{ .flag = .secure_symlinks, .default_doc = "Do not try to guard against extracts redirected by symlinks. Note: With ARCHIVE_EXTRACT_UNLINK, will remove any intermediate symlink." },
.{ .flag = .secure_nodotdot, .default_doc = "Do not reject entries with '..' as path elements." },
.{ .flag = .no_autodir, .default_doc = "Create parent directories as needed." },
.{ .flag = .no_overwrite_newer, .default_doc = "Overwrite files, even if one on disk is newer." },
.{ .flag = .sparse, .default_doc = "Detect blocks of 0 and write holes instead." },
.{ .flag = .mac_metadata, .default_doc = "Do not restore Mac extended metadata. This has no effect except on Mac OS." },
.{ .flag = .no_hfs_compression, .default_doc = "Use HFS+ compression if it was compressed. This has no effect except on Mac OS v10.6 or later." },
.{ .flag = .hfs_compression_forced, .default_doc = "Do not use HFS+ compression if it was not compressed. This has no effect except on Mac OS v10.6 or later." },
.{ .flag = .secure_noabsolutepaths, .default_doc = "Do not reject entries with absolute paths" },
.{ .flag = .clear_nochange_fflags, .default_doc = "Do not clear no-change flags when unlinking object" },
.{ .flag = .safe_writes, .default_doc = "Do not extract atomically (using rename)" },
};
pub const Allow = enum {
Read,
Write,
ReadWrite,
};
pub const ArchiveFormatInfo = struct {
format: ArchiveFormat,
allow: Allow,
};
pub const ArchiveFilterInfo = struct {
filter: ArchiveFilter,
allow: Allow,
};
pub const archive_format_info = [_]ArchiveFormatInfo{
.{ .format = .cpio, .allow = .ReadWrite },
.{ .format = .cpio_posix, .allow = .Write },
.{ .format = .cpio_bin_le, .allow = .Write },
.{ .format = .cpio_bin_be, .allow = .Write },
.{ .format = .cpio_svr4_nocrc, .allow = .Write },
.{ .format = .cpio_svr4_crc, .allow = .Write },
.{ .format = .cpio_afio_large, .allow = .Write },
.{ .format = .cpio_pwb, .allow = .Write },
.{ .format = .shar, .allow = .Write },
.{ .format = .shar_base, .allow = .Write },
.{ .format = .shar_dump, .allow = .Write },
.{ .format = .tar, .allow = .ReadWrite },
.{ .format = .tar_ustar, .allow = .Write },
.{ .format = .tar_pax_interchange, .allow = .Write },
.{ .format = .tar_pax_restricted, .allow = .Write },
.{ .format = .tar_gnutar, .allow = .Write },
.{ .format = .iso9660, .allow = .ReadWrite },
.{ .format = .iso9660_rockridge, .allow = .Read },
.{ .format = .zip, .allow = .ReadWrite },
.{ .format = .empty, .allow = .Read },
.{ .format = .ar, .allow = .Read },
.{ .format = .ar_gnu, .allow = .Read },
.{ .format = .ar_bsd, .allow = .Read },
.{ .format = .mtree, .allow = .ReadWrite },
.{ .format = .raw, .allow = .ReadWrite },
.{ .format = .xar, .allow = .ReadWrite },
.{ .format = .lha, .allow = .Read },
.{ .format = .cab, .allow = .Read },
.{ .format = .rar, .allow = .Read },
.{ .format = .sevenz, .allow = .ReadWrite },
.{ .format = .warc, .allow = .ReadWrite },
.{ .format = .rar_v5, .allow = .Read },
};
pub const archive_filter_info = [_]ArchiveFilterInfo{
.{ .filter = .none, .allow = .ReadWrite },
.{ .filter = .gzip, .allow = .ReadWrite },
.{ .filter = .bzip2, .allow = .ReadWrite },
.{ .filter = .compress, .allow = .ReadWrite },
.{ .filter = .lzma, .allow = .ReadWrite },
.{ .filter = .xz, .allow = .ReadWrite },
.{ .filter = .uu, .allow = .ReadWrite },
.{ .filter = .lzip, .allow = .ReadWrite },
.{ .filter = .lrzip, .allow = .ReadWrite },
.{ .filter = .lzop, .allow = .ReadWrite },
.{ .filter = .grzip, .allow = .ReadWrite },
.{ .filter = .lz4, .allow = .ReadWrite },
.{ .filter = .zstd, .allow = .ReadWrite },
.{ .filter = .rpm, .allow = .Read },
};
fn TableNames(comptime table: anytype, comptime getFormat: fn (info: @TypeOf(table[0])) []const u8, comptime condition: fn (info: @TypeOf(table[0])) bool) []const []const u8 {
@setEvalBranchQuota(10000); // Increase if needed for larger arrays
return comptime blk: {
var result: []const []const u8 = &[_][]const u8{};
for (table) |info| {
if (condition(info)) {
result = result ++ [_][]const u8{getFormat(info)};
}
}
break :blk result;
};
}
pub const format_readable_names = TableNames(archive_format_info, struct {
fn getFormat(info: @TypeOf(archive_format_info[0])) []const u8 {
return @tagName(info.format);
}
}.getFormat, struct {
fn isReadable(info: @TypeOf(archive_format_info[0])) bool {
return info.allow == .Read or info.allow == .ReadWrite;
}
}.isReadable);
pub const format_writable_names = TableNames(archive_format_info, struct {
fn getFormat(info: @TypeOf(archive_format_info[0])) []const u8 {
return @tagName(info.format);
}
}.getFormat, struct {
fn isWritable(info: @TypeOf(archive_format_info[0])) bool {
return info.allow == .Write or info.allow == .ReadWrite;
}
}.isWritable);
pub const filter_readable_names = TableNames(archive_filter_info, struct {
fn getFormat(info: @TypeOf(archive_filter_info[0])) []const u8 {
return @tagName(info.filter);
}
}.getFormat, struct {
fn isReadable(info: @TypeOf(archive_filter_info[0])) bool {
return info.allow == .Read or info.allow == .ReadWrite;
}
}.isReadable);
pub const filter_writable_names = TableNames(archive_filter_info, struct {
fn getFormat(info: @TypeOf(archive_filter_info[0])) []const u8 {
return @tagName(info.filter);
}
}.getFormat, struct {
fn isWritable(info: @TypeOf(archive_filter_info[0])) bool {
return info.allow == .Write or info.allow == .ReadWrite;
}
}.isWritable);
pub fn listReadableFormats() []const []const u8 {
return format_readable_names;
}
pub fn listReadableFilters() []const []const u8 {
return filter_readable_names;
}
pub fn listWritableFormats() []const []const u8 {
return format_writable_names;
}
pub fn listWritableFilters() []const []const u8 {
return filter_writable_names;
}
pub const ArchiveReaderResourceCallbacks = struct {
pub fn dtor(a: **c.archive) void {
_ = c.archive_read_free(a.*);
}
};
pub const ArchiveWriterResourceCallbacks = struct {
pub fn dtor(a: **c.archive) void {
_ = c.archive_write_free(a.*);
}
};
pub const ArchiveEntryResourceCallbacks = struct {
pub fn dtor(a: **c.archive_entry) void {
_ = c.archive_entry_free(a.*);
}
};
pub fn fileKinds() struct {
mask: c_uint,
block_device: c_uint,
character_device: c_uint,
directory: c_uint,
named_pipe: c_uint,
sym_link: c_uint,
file: c_uint,
unix_domain_socket: c_uint,
unknown: c_uint,
} {
return .{
.mask = c.S_IFMT,
.block_device = c.S_IFBLK,
.character_device = c.S_IFCHR,
.directory = c.S_IFDIR,
.named_pipe = c.S_IFIFO,
.sym_link = c.S_IFLNK,
.file = c.S_IFREG,
.unix_domain_socket = c.S_IFSOCK,
.unknown = 0,
};
}
const FileMode = enum(c_uint) {
irwxu = c.S_IRWXU,
irusr = c.S_IRUSR,
iwusr = c.S_IWUSR,
ixusr = c.S_IXUSR,
irwxg = c.S_IRWXG,
irgrp = c.S_IRGRP,
iwgrp = c.S_IWGRP,
ixgrp = c.S_IXGRP,
irwxo = c.S_IRWXO,
iroth = c.S_IROTH,
iwoth = c.S_IWOTH,
ixoth = c.S_IXOTH,
isuid = c.S_ISUID,
isgid = c.S_ISGID,
isvtx = c.S_ISVTX,
};
pub fn archive_entry_copy_stat(e: ArchiveEntryResource, stat: std.c.Stat) !void {
const c_ino: c.la_int64_t = @intCast(stat.ino);
c.archive_entry_set_ino(e.unpack(), c_ino);
c.archive_entry_set_size(e.unpack(), stat.size);
c.archive_entry_set_mode(e.unpack(), stat.mode);
const atime = stat.atime();
const mtime = stat.mtime();
const ctime = stat.ctime();
if (@hasField(@TypeOf(atime), "tv_sec") and @hasField(@TypeOf(atime), "tv_nsec")) {
c.archive_entry_set_atime(e.unpack(), atime.tv_sec, atime.tv_nsec);
c.archive_entry_set_mtime(e.unpack(), mtime.tv_sec, mtime.tv_nsec);
c.archive_entry_set_ctime(e.unpack(), ctime.tv_sec, ctime.tv_nsec);
} else if (@hasField(@TypeOf(atime), "sec") and @hasField(@TypeOf(atime), "nsec")) {
c.archive_entry_set_atime(e.unpack(), atime.sec, atime.nsec);
c.archive_entry_set_mtime(e.unpack(), mtime.sec, mtime.nsec);
c.archive_entry_set_ctime(e.unpack(), ctime.sec, ctime.nsec);
}
const c_nlink: u32 = @intCast(stat.ino);
c.archive_entry_set_nlink(e.unpack(), c_nlink);
c.archive_entry_set_dev(e.unpack(), stat.dev);
c.archive_entry_set_rdev(e.unpack(), stat.rdev);
c.archive_entry_set_gid(e.unpack(), stat.gid);
c.archive_entry_set_uid(e.unpack(), stat.uid);
}
pub fn archive_entry_stat(e: ArchiveEntryResource) !std.c.Stat {
const c_stat = c.archive_entry_stat(e.unpack());
if (c_stat == null) {
return error.StatError;
}
var stat: std.c.Stat = @bitCast(c_stat.*);
const nlinks: @TypeOf(stat.nlink) = @intCast(c.archive_entry_nlink(e.unpack()));
const newino: @TypeOf(stat.ino) = @intCast(c.archive_entry_ino(e.unpack()));
const newgid: @TypeOf(stat.gid) = @intCast(c.archive_entry_gid(e.unpack()));
const newuid: @TypeOf(stat.uid) = @intCast(c.archive_entry_uid(e.unpack()));
stat.ino = newino;
stat.nlink = nlinks;
stat.dev = c.archive_entry_dev(e.unpack());
stat.gid = newgid;
stat.uid = newuid;
return stat;
}
pub fn archiveFilterToInt(filter: ArchiveFilter) c_int {
return @intFromEnum(filter);
}
pub fn archiveFilterToAtom(filter: i32) ArchiveFilter {
return @enumFromInt(filter);
}
pub fn archiveFormatToInt(format: ArchiveFormat) c_int {
return @intFromEnum(format);
}
pub fn archiveFormatToAtom(format: i32) ArchiveFormat {
return @enumFromInt(format);
}
fn checkArchiveResult(result: c_int) !void {
switch (result) {
c.ARCHIVE_OK => return,
c.ARCHIVE_RETRY => return ArchiveError.ArchiveRetry,
c.ARCHIVE_WARN => return ArchiveError.ArchiveWarn,
c.ARCHIVE_FATAL => return ArchiveError.ArchiveFatal,
c.ARCHIVE_EOF => return ArchiveError.ArchiveEof,
else => return ArchiveError.ArchiveFailed,
}
}
pub fn archive_read_new() !ArchiveReaderResource {
const a: ?*c.archive = c.archive_read_new();
return if (a) |non_null_a|
ArchiveReaderResource.create(non_null_a, .{})
else
error.archiveEntry;
}
pub fn archive_write_new() !ArchiveWriterResource {
const a: ?*c.archive = c.archive_write_new();
return if (a) |non_null_a|
ArchiveWriterResource.create(non_null_a, .{})
else
error.archiveEntry;
}
pub fn archive_read_support_filter_all(a: ArchiveReaderResource) !void {
try checkArchiveResult(c.archive_read_support_filter_all(a.unpack()));
}
pub fn archive_read_support_format_all(a: ArchiveReaderResource) !void {
try checkArchiveResult(c.archive_read_support_format_all(a.unpack()));
}
pub fn archive_read_open_filename(a: ArchiveReaderResource, filename: []u8, block_size: usize) !void {
// Have to manually null-terminate this for the C API
// Zig's []u8 hold length information, so they aren't necessarily null-terminated
var slice = try beam.allocator.alloc(u8, filename.len + 1);
defer beam.allocator.free(slice);
@memcpy(slice[0..filename.len], filename);
slice[filename.len] = 0;
const result = c.archive_read_open_filename(a.unpack(), slice.ptr, block_size);
try checkArchiveResult(result);
}
pub fn archive_write_open_filename(a: ArchiveWriterResource, filename: []u8) !void {
var slice = try beam.allocator.alloc(u8, filename.len + 1);
defer beam.allocator.free(slice);
@memcpy(slice[0..filename.len], filename);
slice[filename.len] = 0;
const result = c.archive_write_open_filename(a.unpack(), slice.ptr);
try checkArchiveResult(result);
}
pub fn archive_read_open_memory(a: ArchiveReaderResource, buf: []const u8) !void {
const result = c.archive_read_open_memory(a.unpack(), buf.ptr, buf.len);
try checkArchiveResult(result);
}
pub fn archive_format_name(a: ArchiveReaderResource) [*c]u8 {
return @constCast(c.archive_format_name(a.unpack()));
}
pub fn archive_file_count(a: ArchiveReaderResource) i32 {
return c.archive_file_count(a.unpack());
}
pub fn archive_entry_new() !ArchiveEntryResource {
const e: ?*c.archive_entry = c.archive_entry_new();
return if (e) |non_null_e|
ArchiveEntryResource.create(non_null_e, .{})
else
error.archiveEntry;
}
pub fn archive_read_next_header(a: ArchiveReaderResource, e: ArchiveEntryResource) !void {
try checkArchiveResult(c.archive_read_next_header2(a.unpack(), e.unpack()));
}
pub fn archive_write_header(a: ArchiveWriterResource, e: ArchiveEntryResource) !void {
try checkArchiveResult(c.archive_write_header(a.unpack(), e.unpack()));
}
pub fn archive_entry_pathname(e: ArchiveEntryResource) [*c]u8 {
return @constCast(c.archive_entry_pathname(e.unpack()));
}
pub fn archive_entry_set_pathname(e: ArchiveEntryResource, pathname: [*c]const u8) void {
return c.archive_entry_set_pathname(e.unpack(), pathname);
}
fn getArchive(term: beam.term) !*c.archive {
if (beam.get(ArchiveReaderResource, term, .{})) |reader| {
return reader.unpack();
} else |_| {
if (beam.get(ArchiveWriterResource, term, .{})) |writer| {
return writer.unpack();
} else |_| {
return error.InvalidArchiveType;
}
}
}
pub fn archive_error_string(term: beam.term) ![*c]u8 {
const archive = try getArchive(term);
return @constCast(c.archive_error_string(archive));
}
pub fn archive_clear_error(term: beam.term) !void {
const archive = try getArchive(term);
c.archive_clear_error(archive);
}
pub fn archive_format(a: ArchiveReaderResource) i32 {
return c.archive_format(a.unpack());
}
pub fn archive_entry_size(e: ArchiveEntryResource) i64 {
return c.archive_entry_size(e.unpack());
}
pub fn archive_read_close(a: ArchiveReaderResource) !void {
try checkArchiveResult(c.archive_read_close(a.unpack()));
}
pub fn archive_write_close(a: ArchiveWriterResource) !void {
try checkArchiveResult(c.archive_write_close(a.unpack()));
}
pub fn archive_read_support_format_raw(a: ArchiveReaderResource) !void {
try checkArchiveResult(c.archive_read_support_format_raw(a.unpack()));
}
pub fn archive_read_support_compression_all(a: ArchiveReaderResource) !void {
try checkArchiveResult(c.archive_read_support_compression_all(a.unpack()));
}
pub fn archive_read_data(a: ArchiveReaderResource, size: usize) !beam.term {
const data = try beam.allocator.alloc(u8, size);
defer beam.allocator.free(data);
const num_read: isize = c.archive_read_data(a.unpack(), data.ptr, size);
if (size != num_read) {
return CAllocError.archive;
} else {
return beam.make(data, .{});
}
}
pub fn archive_read_support_format_by_code(a: ArchiveReaderResource, code: c_int) !void {
try checkArchiveResult(c.archive_read_support_format_by_code(a.unpack(), code));
}
pub fn archive_read_support_filter_by_code(a: ArchiveReaderResource, code: c_int) !void {
try checkArchiveResult(c.archive_read_support_filter_by_code(a.unpack(), code));
}
pub fn archive_read_format_capabilities(a: ArchiveReaderResource) i32 {
return c.archive_read_format_capabilities(a.unpack());
}
pub fn archive_write_add_filter(a: ArchiveWriterResource, code: c_int) !void {
try checkArchiveResult(c.archive_write_add_filter(a.unpack(), code));
}
pub fn archive_write_set_format(a: ArchiveWriterResource, code: c_int) !void {
try checkArchiveResult(c.archive_write_set_format(a.unpack(), code));
}
pub fn archive_entry_clear(e: ArchiveEntryResource) !void {
const orig = e.unpack();
const cleared = c.archive_entry_clear(orig) orelse return error.ArchiveEntryClearFailed;
e.update(cleared);
}
pub fn archive_read_refresh(a: ArchiveReaderResource) !void {
const orig = a.unpack();
_ = c.archive_read_free(orig);
const new_archive = c.archive_read_new() orelse return error.ArchiveCreationFailed;
a.update(new_archive);
}
pub fn archive_write_refresh(a: ArchiveWriterResource) !void {
const orig = a.unpack();
_ = c.archive_write_free(orig);
const new_archive = c.archive_write_new() orelse return error.ArchiveCreationFailed;
a.update(new_archive);
}
pub fn archive_compression(a: ArchiveReaderResource) i32 {
return c.archive_compression(a.unpack());
}
pub fn archive_compression_name(a: ArchiveReaderResource) [*c]u8 {
return @constCast(c.archive_compression_name(a.unpack()));
}
pub fn archive_version_string() [*c]u8 {
return @constCast(c.archive_version_string());
}
pub fn archive_version_details() [*c]u8 {
return @constCast(c.archive_version_details());
}
pub fn archive_zlib_version() [*c]u8 {
return @constCast(c.archive_zlib_version());
}
pub fn archive_liblzma_version() [*c]u8 {
return @constCast(c.archive_liblzma_version());
}
pub fn archive_bzlib_version() [*c]u8 {
return @constCast(c.archive_bzlib_version());
}
pub fn archive_liblz4_version() [*c]u8 {
return @constCast(c.archive_liblz4_version());
}
pub fn archive_libzstd_version() [*c]u8 {
return @constCast(c.archive_libzstd_version());
}
pub fn archive_read_extract(a: ArchiveReaderResource, e: ArchiveEntryResource, flags: i32) !void {
try checkArchiveResult(c.archive_read_extract(a.unpack(), e.unpack(), flags));
}
pub fn getExtractInfo() [extractFlagDocumentation.len]ExtractFlagDocs {
return extractFlagDocumentation;
}
pub fn extractFlagToInt(f: ExtractFlag) i32 {
return @intFromEnum(f);
}
"""
@errors [:ArchiveEof, :ArchiveFailed, :ArchiveWarn, :ArchiveFatal]
def get_error_string(ref) when is_reference(ref) do
err_string = archive_error_string(ref)
err_string =
unless is_nil(err_string) do
err_string
|> :binary.bin_to_list()
|> Enum.filter(&(&1 < 128))
|> List.to_string()
end
archive_clear_error(ref)
err_string
end
def safe_call(fun, ref \\ nil) do
try do
case fun.() do
:ok -> :ok
{:error, reason} -> {:error, reason}
other -> {:ok, other}
end
rescue
e in [ErlangError] ->
error_string =
if ref do
get_error_string(ref)
end
case e do
%{original: :ArchiveWarn} ->
if error_string, do: Logger.info(error_string)
:ok
%{original: error} when error in @errors ->
{:error, error_string || error}
%{reason: reason} ->
{:error, error_string || reason}
%{message: message} ->
{:error, error_string || message}
end
end
end
defmacro call(func) do
quote do
safe_call(fn -> unquote(func) end)
end
end
defmacro call(func, ref) do
quote do
safe_call(fn -> unquote(func) end, unquote(ref))
end
end
@doc false
def unwrap!(:ok), do: :ok
def unwrap!({:ok, value}), do: value
def unwrap!({:error, reason}), do: raise(reason)
def unwrap(_), do: raise("Bad value")
def list_all(:formats, :read), do: listReadableFormats() |> Enum.map(&String.to_atom/1)
def list_all(:filters, :read), do: listReadableFilters() |> Enum.map(&String.to_atom/1)
def list_all(:formats, :write), do: listWritableFormats() |> Enum.map(&String.to_atom/1)
def list_all(:filters, :write), do: listWritableFilters() |> Enum.map(&String.to_atom/1)
defmacro get_file_kinds() do
file_types = fileKinds()
quote do
unquote(Macro.escape(file_types))
end
end
defmacro __using__(_opts) do
read_formats = list_all(:formats, :read)
read_filters = list_all(:filters, :read)
write_formats = list_all(:formats, :write)
write_filters = list_all(:filters, :write)
extract_info = getExtractInfo()
quote do
alias Archive.Nif
import Archive.Nif, only: [unwrap!: 1, call: 1, call: 2, safe_call: 2, safe_call: 1]
@read_formats unquote(read_formats)
@read_filters unquote(read_filters)
@write_formats unquote(write_formats)
@write_filters unquote(write_filters)
@extract_info unquote(Macro.escape(extract_info))
end
end
end