libarchive
| Language | C (usable from C++) |
|---|---|
| License | BSD 2-clause (some files under other permissive licenses) |
| Repository | github.com/libarchive/libarchive |
| Documentation | libarchive wiki, man pages archive_read(3), archive_write(3) |
| Depends on | optional liblzma, zlib, bzip2, zstd (needed for the matching 7z methods) |
| Read / write | yes / yes; no encryption (encrypted entries are detected but can't be extracted) |
libarchive is a C library for reading and writing archives with one streaming API: you open an archive, loop over entries and read or write each entry’s data. It handles 7z, TAR, ZIP, CPIO, ISO 9660, RAR (read) and more, and it’s the engine behind bsdtar, the tar.exe in Windows and macOS, and Windows 11’s built-in archive support.
For 7z it reads LZMA, LZMA2, PPMd, BZip2, Deflate, Zstandard, Delta and the BCJ/BCJ2/ARM/ARM64/RISC-V filters, solid archives and compressed headers. It writes 7z with Copy, Deflate, BZip2, LZMA1, LZMA2 (default), PPMd or Zstandard. It doesn’t support AES encryption in 7z, neither reading nor writing. Because 7z keeps its header at the end, libarchive needs a seekable file to read 7z (not a pipe).
Installation#
# Debian / Ubuntu
sudo apt install libarchive-dev
# Fedora
sudo dnf install libarchive-devel
# macOS (the system copy has no headers)
brew install libarchive
# Windows (vcpkg)
vcpkg install libarchive
With CMake:
find_package(LibArchive REQUIRED)
target_link_libraries(myapp PRIVATE LibArchive::LibArchive)
Basic usage: list a 7z archive and read file contents#
#include <archive.h>
#include <archive_entry.h>
#include <stdio.h>
int main(void) {
struct archive *a = archive_read_new();
archive_read_support_format_7zip(a); // or archive_read_support_format_all(a)
archive_read_support_filter_all(a);
if (archive_read_open_filename(a, "archive.7z", 10240) != ARCHIVE_OK) {
fprintf(stderr, "open: %s\n", archive_error_string(a));
archive_read_free(a);
return 1;
}
struct archive_entry *entry;
while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
printf("%s %lld bytes\n", archive_entry_pathname(entry),
(long long)archive_entry_size(entry));
// read the data of this entry in blocks
char buf[16384];
la_ssize_t n;
while ((n = archive_read_data(a, buf, sizeof(buf))) > 0) {
// process n bytes of buf
}
if (n < 0) {
fprintf(stderr, "read: %s\n", archive_error_string(a));
break;
}
}
archive_read_free(a); // also closes
return 0;
}
archive_read_data_skip(a) skips an entry’s data. In a solid 7z archive skipping still decompresses the data, so reading entries in archive order is fastest.
How to extract a 7z archive to a folder#
archive_write_disk creates files, directories and timestamps from the entries:
#include <archive.h>
#include <archive_entry.h>
#include <stdio.h>
int extract(const char *path) {
struct archive *a = archive_read_new();
archive_read_support_format_7zip(a);
struct archive *out = archive_write_disk_new();
archive_write_disk_set_options(out, ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM |
ARCHIVE_EXTRACT_SECURE_NODOTDOT | ARCHIVE_EXTRACT_SECURE_SYMLINKS);
archive_write_disk_set_standard_lookup(out);
if (archive_read_open_filename(a, path, 10240) != ARCHIVE_OK) return 1;
struct archive_entry *entry;
int r;
while ((r = archive_read_next_header(a, &entry)) == ARCHIVE_OK) {
if (archive_write_header(out, entry) == ARCHIVE_OK) {
const void *buf;
size_t size;
la_int64_t offset;
while (archive_read_data_block(a, &buf, &size, &offset) == ARCHIVE_OK)
archive_write_data_block(out, buf, size, offset);
}
archive_write_finish_entry(out);
}
if (r != ARCHIVE_EOF) fprintf(stderr, "%s\n", archive_error_string(a));
archive_read_free(a);
archive_write_free(out);
return r == ARCHIVE_EOF ? 0 : 1;
}
Files are written relative to the current directory; chdir to the target folder first, or change the path with archive_entry_set_pathname before archive_write_header.
How to create a 7z archive#
#include <archive.h>
#include <archive_entry.h>
#include <string.h>
#include <time.h>
int write_7z(const char *path) {
struct archive *a = archive_write_new();
archive_write_set_format_7zip(a);
archive_write_set_format_option(a, "7zip", "compression", "lzma2"); // copy, deflate, bzip2, lzma1, lzma2, ppmd, zstd
archive_write_set_format_option(a, "7zip", "compression-level", "9");
if (archive_write_open_filename(a, path) != ARCHIVE_OK) return 1;
const char *text = "hello, world\n";
struct archive_entry *e = archive_entry_new();
archive_entry_set_pathname(e, "docs/hello.txt");
archive_entry_set_size(e, (la_int64_t)strlen(text));
archive_entry_set_filetype(e, AE_IFREG);
archive_entry_set_perm(e, 0644);
archive_entry_set_mtime(e, time(NULL), 0);
archive_write_header(a, e);
archive_write_data(a, text, strlen(text));
archive_entry_free(e);
archive_write_close(a); // the 7z header is written here
archive_write_free(a);
return 0;
}
libarchive compresses all entries into one solid block. Set the size before writing the header and write exactly that many bytes.
How to detect encrypted 7z entries#
libarchive can’t decrypt 7z, but it tells you when an entry or the header is encrypted, so you can show a clear error:
int r = archive_read_next_header(a, &entry);
if (r != ARCHIVE_OK) {
// an encrypted header fails here
fprintf(stderr, "%s\n", archive_error_string(a));
} else if (archive_entry_is_data_encrypted(entry)) {
fprintf(stderr, "%s is encrypted, use 7-Zip to extract it\n", archive_entry_pathname(entry));
}
archive_read_has_encrypted_entries(a) returns 1, 0 or ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW for the archive as a whole.
Tips#
- Use
archive_read_support_format_allandarchive_read_support_filter_allto accept any archive the user drops on your program;archive_format_name(a)tells you what it was (“7-Zip”). - Multi-volume
.7z.001,.7z.002files are byte splits of one archive: concatenate them into one.7zfile before opening. - For a C++ wrapper with RAII, wrap
archive *in astd::unique_ptrwitharchive_read_freeas the deleter.