Compare commits

..

No commits in common. "main" and "1.6.0" have entirely different histories.
main ... 1.6.0

10 changed files with 18 additions and 381 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.8..3.22)
project(minitar LANGUAGES C VERSION 1.7.6)
project(minitar LANGUAGES C VERSION 1.6.0)
option(MINITAR_IGNORE_UNSUPPORTED_TYPES "Skip past entries that have unsupported types instead of panicking (deprecated)" OFF)
@ -31,4 +31,4 @@ endif()
install(TARGETS minitar DESTINATION lib)
install(FILES minitar.h DESTINATION include)
add_subdirectory(examples)
add_subdirectory(examples)

View File

@ -1,10 +1,10 @@
# minitar
Tiny and easy-to-use C library to read/write tar (specifically, the [ustar](https://www.ibm.com/docs/en/zos/2.3.0?topic=formats-tar-format-tar-archives#taf) variant, which is a bit old but simple, and newer tar variants (pax, GNU tar) are mostly backwards-compatible with it) archives.
Tiny and easy-to-use C library to parse tar (specifically, the newer [USTAR](https://www.ibm.com/docs/en/zos/2.3.0?topic=formats-tar-format-tar-archives#taf) variant, which is the one pretty much everybody uses) archives.
No third-party dependencies, only a minimally capable standard C library (pretty much only requires a basic subset of the C FILE API, apart from other simple functions).
Aims to be bloat-free (currently a bit above 500 LoC), fast and optimized, and as portable between systems as possible (has its own implementation of some non-standard functions, such as [strlcpy](https://linux.die.net/man/3/strlcpy) or [basename](https://linux.die.net/man/3/basename)).
Aims to be bloat-free (currently just above 500 LoC), fast and optimized, and as portable between systems as possible (has its own implementation of some non-standard functions, such as [strlcpy](https://linux.die.net/man/3/strlcpy) or [basename](https://linux.die.net/man/3/basename)).
Does not include support for compressed archives. You'll have to pass those through another program or library to decompress them before minitar can handle them.
@ -43,7 +43,7 @@ See [examples](examples/) for more examples using minitar.
## Project structure
The user-facing API (functions defined in `minitar.h` and documented in [API.md](docs/API.md)) is implemented in `src/tar.c`. Utility and internally-used functions live in `src/util.c`.
The user-facing API (functions defined in `minitar.h` and documented in this README) is implemented in `src/tar.c`. Utility and internally-used functions live in `src/util.c`.
## Documentation
@ -75,4 +75,4 @@ pub extern "C" fn minitar_handle_panic(message: *const u8) -> !
## License
`minitar` is free and open-source software under the [BSD-2-Clause](LICENSE) license.
`minitar` is free and open-source software under the [BSD-2-Clause](LICENSE) license.

View File

@ -1,17 +1,10 @@
# minitar API documentation
Functions/types suffixed with `_w` or that contain `write` in their names are part of the newer API for writing to archives. Other types/functions are part of the (older) API for reading archives.
## Functions
### minitar_open
`int minitar_open(const char* pathname, struct minitar* mp)`
Initializes the caller-provided `mp` [handle](API.md#minitar) by opening the archive pointed to by `pathname` for reading. Returns 0 on success, anything else is failure.
### minitar_open_w
`int minitar_open_w(const char* pathname, struct minitar_w* mp, enum minitar_write_mode mode)`
Initializes the caller-provided `mp` [handle](API.md#minitar_w) by opening the archive pointed to by `pathname` for writing (in case `pathname` already exists, mode selects if the existing file is overwritten or if new entries are appended to it). Returns 0 on success, anything else is failure.
Initializes the caller-provided `mp` structure by opening the archive pointed to by `pathname` for reading. Returns 0 on success, anything else is failure.
### minitar_read_entry
`int minitar_read_entry(struct minitar* mp, struct minitar_entry* out)`
@ -24,30 +17,6 @@ To read the contents of an entry, you should allocate a buffer large enough to h
This function returns 0 on success and -1 on end-of-file (when all entries have been read).
### minitar_write_file_entry
`int minitar_write_file_entry(struct minitar_w* mp, const struct minitar_entry_metadata* metadata, char* buf)`
Writes a regular file entry into a `struct minitar_w` which should be initialized by a previous call to `minitar_open_w()`.
This function writes both a header (generated from the metadata) and the file contents in `buf`, which should be `metadata.size` bytes long.
This function will only write entries for regular files (metadata.type == `MTAR_REGULAR`). It will ignore the `type` field and write the "regular file" type into the tar archive.
To write any other kind of entry (directories, special files), use `minitar_write_special_entry`.
This function returns 0 on success.
### minitar_write_special_entry
`int minitar_write_special_entry(struct minitar_w* mp, const struct minitar_entry_metadata* metadata)`
Writes a special file entry (anything that does not have contents, so directories or special files) into a `struct minitar_w` which should be initialized by a previous call to `minitar_open_w()`.
This function only writes a header (generated from the metadata). The `size` field is written as 0, no matter what its original value was.
This function does not write entries for regular files (metadata.type == `MTAR_REGULAR`). Trying to do so will result in minitar panicking (see [error handling](../README.md#error-handling)). To write regular files, use `minitar_write_file_entry`.
This function returns 0 on success.
### minitar_rewind
`void minitar_rewind(struct minitar* mp)`
@ -76,7 +45,7 @@ Same as `minitar_find_by_name()`, but matches the full path inside the archive i
Same as `minitar_find_by_name()`, but matches the file type instead of the name. As with `minitar_find_by_name()`, this function starts searching from the current archive position and calling it in a loop until it returns -1 will find all matching entries.
### minitar_read_contents
`size_t minitar_read_contents(struct minitar* mp, const struct minitar_entry* entry, char* buf, size_t max)`
`size_t minitar_read_contents(struct minitar* mp, struct minitar_entry* entry, char* buf, size_t max)`
Reads up to `max` bytes of an entry's contents from the archive stream `mp` and stores them into `buf`.
@ -84,7 +53,7 @@ This function can be called as many times as desired, and at any given point in
This function returns the number of bytes read, or 0 on error. 0 might also be a successful return value (if `max` is 0 or the entry's size is 0, for example), which means `errno` should be checked to see if 0 means error or simply 0 bytes read.
`minitar_read_contents()` will never read more than `metadata.size`, regardless of the value in `max`. (so, if `max == SIZE_MAX`, `minitar_read_contents()` will always read `metadata.size` bytes).
`minitar_read_contents()` only reads up to `metadata.size`, regardless of the value in `max`.
The contents are not null-terminated. If you want null-termination (keep in mind the contents might not be ASCII and might contain null bytes before the end), just do `buf[nread] = 0;`. In that case, the value of `max` should be one less than the size of the buffer, to make sure the zero byte is not written past the end of `buf` if `max` bytes are read.
@ -95,13 +64,6 @@ Closes the tar archive file `mp` points to. The pointer passed to `minitar_close
Returns 0 on success, everything else is failure and you should check `errno`.
### minitar_close_w
`int minitar_close_w(struct minitar_w* mp)`
Closes the tar archive file `mp` points to. The pointer passed to `minitar_close_w()` should be initialized by a previous call to `minitar_open_w()`.
Returns 0 on success, everything else is failure and you should check `errno`.
## Types
### minitar_file_type
@ -119,18 +81,7 @@ This enum lists all supported file types:
`MTAR_FIFO`: FIFO special files
`MTAR_BLKDEV`: Block devices
`MTAR_CHRDEV`: Character devices
### minitar_write_mode
`enum minitar_write_mode`
This enum tells `minitar_open_w` what to do if the chosen archive path already exists:
`MTAR_OVERWRITE`: Overwrite the archive
`MTAR_APPEND`: Add new entries to the end of it
Other file types supported in tar archives, such as block/character devices or FIFOs, are not supported and minitar will throw an error when encountering one of them. This behavior can be controlled by passing `-DMINITAR_IGNORE_UNSUPPORTED_TYPES=ON` to CMake when configuring, which will make minitar silently ignore such entries instead of panicking.
### minitar_entry_metadata
`struct minitar_entry_metadata`
@ -159,10 +110,6 @@ This structure represents an entry's metadata, with the following fields:
`gname`: A string representing the group name of the entry's owner. (`char[]`)
`devmajor`: An integer representing the major number of a device. (`unsigned int`)
`devminor`: An integer representing the minor number of a device. (`unsigned int`)
### minitar_entry
`struct minitar_entry`
@ -170,14 +117,4 @@ An entry in a tar archive. Fields:
`metadata`: The entry's metadata. (`struct minitar_entry_metadata`)
`_internal`: Reserved for internal use. (`struct minitar_entry_internal`)
### minitar
`struct minitar`
An archive handle for the "reading" API. To write to an archive, use `struct minitar_w` and `minitar_open_w()` instead.
### minitar_w
`struct minitar_w`
An archive handle for the "writing" API. To read from an archive, use `struct minitar` and `minitar_open()` instead.
`_internal`: Reserved for internal use. (`struct minitar_entry_internal`)

View File

@ -4,9 +4,4 @@ target_link_libraries(list PRIVATE minitar)
add_executable(untar EXCLUDE_FROM_ALL untar.c)
target_link_libraries(untar PRIVATE minitar)
add_executable(pack EXCLUDE_FROM_ALL pack.c)
target_link_libraries(pack PRIVATE minitar)
add_custom_target(examples DEPENDS list untar pack)
add_custom_target(examples-posix DEPENDS list untar pack)
add_custom_target(examples-windows DEPENDS list)
add_custom_target(examples DEPENDS list untar)

View File

@ -24,9 +24,9 @@ int main(int argc, char** argv)
}
struct minitar_entry entry;
do {
if (minitar_read_entry(&mp, &entry) == 0) { printf("%s (%s, %zu bytes, mode %o)\n", entry.metadata.path, entry.metadata.name, entry.metadata.size, entry.metadata.mode); }
if (minitar_read_entry(&mp, &entry) == 0) { printf("%s\n", entry.metadata.path); }
else
break;
} while (1);
minitar_close(&mp);
}
}

View File

@ -1,100 +0,0 @@
/*
* Copyright (c) 2023, apio.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* pack.c: Example utility which creates a tar archive (POSIX only).
*/
#define _XOPEN_SOURCE 700
#include <minitar.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
int main(int argc, char** argv)
{
if (argc < 3)
{
fprintf(stderr, "Usage: %s [output] files\n", argv[0]);
return 1;
}
struct minitar_w mp;
if (minitar_open_w(argv[1], &mp, MTAR_OVERWRITE) != 0)
{
perror(argv[1]);
return 1;
}
int exit_status = 0;
int arg = 2;
while (arg < argc)
{
FILE* fp = fopen(argv[arg], "r");
if (!fp)
{
perror("fopen");
exit_status = 1;
break;
}
// Get the file length.
fseek(fp, 0, SEEK_END);
size_t length = ftell(fp);
fseek(fp, 0, SEEK_SET);
char* buf = malloc(length);
if (!buf)
{
perror("malloc");
fclose(fp);
exit_status = 1;
break;
}
fread(buf, 1, length, fp);
if (ferror(fp))
{
perror("fread");
goto err;
}
struct stat st;
int rc = fstat(fileno(fp), &st);
if (rc < 0)
{
perror("fstat");
goto err;
}
struct minitar_entry_metadata metadata;
strncpy(metadata.path, argv[arg], sizeof(metadata.path));
metadata.uid = st.st_uid;
metadata.gid = st.st_gid;
metadata.mtime = st.st_mtime;
metadata.size = length;
metadata.type = MTAR_REGULAR;
metadata.mode = st.st_mode & ~S_IFMT;
rc = minitar_write_file_entry(&mp, &metadata, buf);
free(buf);
fclose(fp);
if (rc != 0)
{
perror("write entry failed");
exit_status = 1;
break;
}
arg++;
continue;
err:
free(buf);
fclose(fp);
exit_status = 1;
break;
}
minitar_close_w(&mp);
return exit_status;
}

View File

@ -9,7 +9,6 @@
#define _XOPEN_SOURCE 700
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <minitar.h>
#include <stdio.h>
#include <stdlib.h>
@ -18,37 +17,8 @@
#include <sys/sysmacros.h>
#include <unistd.h>
static int create_parent_recursively(const char* path)
{
char* path_copy = strdup(path);
if (!path_copy) return -1;
char* parent = dirname(path_copy);
create:
if (mkdir(parent, 0755) < 0)
{
if (errno == ENOENT)
{
create_parent_recursively(parent);
goto create;
}
if (errno == EEXIST) goto success;
free(path_copy);
return -1;
}
success:
free(path_copy);
return 0;
}
static int untar_file(const struct minitar_entry* entry, const void* buf)
{
if (create_parent_recursively(entry->metadata.path) < 0) return 1;
int fd = open(entry->metadata.path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
if (fd < 0) return 1;
@ -61,8 +31,6 @@ static int untar_file(const struct minitar_entry* entry, const void* buf)
static int untar_directory(const struct minitar_entry* entry)
{
if (create_parent_recursively(entry->metadata.path) < 0) return 1;
if (mkdir(entry->metadata.path, entry->metadata.mode) < 0) return 1;
return 0;
@ -88,7 +56,6 @@ int main(int argc, char** argv)
{
if (entry.metadata.type == MTAR_DIRECTORY)
{
if (!strcmp(entry.metadata.name, ".") || !strcmp(entry.metadata.name, "..")) continue;
int status = untar_directory(&entry);
if (status != 0)
{
@ -126,13 +93,10 @@ int main(int argc, char** argv)
}
else if (entry.metadata.type == MTAR_SYMLINK)
{
if (create_parent_recursively(entry.metadata.path) < 0) goto symlink_err;
int status = symlink(entry.metadata.link, entry.metadata.path);
if (status != 0)
{
symlink_err:
fprintf(stderr, "Failed to create symlink %s: %s\n", entry.metadata.path, strerror(errno));
exit_status = 1;
break;
@ -142,13 +106,10 @@ int main(int argc, char** argv)
}
else if (entry.metadata.type == MTAR_HARDLINK)
{
if (create_parent_recursively(entry.metadata.path) < 0) goto hardlink_err;
int status = link(entry.metadata.link, entry.metadata.path);
if (status != 0)
{
hardlink_err:
fprintf(stderr, "Failed to create hard link %s: %s\n", entry.metadata.path, strerror(errno));
exit_status = 1;
break;
@ -158,57 +119,42 @@ int main(int argc, char** argv)
}
else if (entry.metadata.type == MTAR_FIFO)
{
#ifndef __luna__
if (create_parent_recursively(entry.metadata.path) < 0) goto fifo_err;
int status = mknod(entry.metadata.path, entry.metadata.mode | S_IFIFO, 0);
if (status != 0)
{
fifo_err:
fprintf(stderr, "Failed to create FIFO %s: %s\n", entry.metadata.path, strerror(errno));
exit_status = 1;
break;
}
#endif
printf("fifo %s\n", entry.metadata.path);
}
else if (entry.metadata.type == MTAR_BLKDEV)
{
#ifndef __luna__
if (create_parent_recursively(entry.metadata.path) < 0) goto blkdev_err;
int status = mknod(entry.metadata.path, entry.metadata.mode | S_IFBLK,
makedev(entry.metadata.devmajor, entry.metadata.devminor));
if (status != 0)
{
blkdev_err:
fprintf(stderr, "Failed to create block device %s: %s\n", entry.metadata.path, strerror(errno));
exit_status = 1;
break;
}
#endif
printf("blkdev %s (%u:%u)\n", entry.metadata.path, entry.metadata.devmajor, entry.metadata.devminor);
}
else if (entry.metadata.type == MTAR_CHRDEV)
{
#ifndef __luna__
if (create_parent_recursively(entry.metadata.path) < 0) goto chrdev_err;
int status = mknod(entry.metadata.path, entry.metadata.mode | S_IFCHR,
makedev(entry.metadata.devmajor, entry.metadata.devminor));
if (status != 0)
{
chrdev_err:
fprintf(stderr, "Failed to create character device %s: %s\n", entry.metadata.path, strerror(errno));
exit_status = 1;
break;
}
#endif
printf("chrdev %s (%u:%u)\n", entry.metadata.path, entry.metadata.devmajor, entry.metadata.devminor);
}
@ -224,4 +170,4 @@ int main(int argc, char** argv)
} while (1);
minitar_close(&mp);
return exit_status;
}
}

View File

@ -10,32 +10,13 @@
#define MINITAR_H
#include <stddef.h>
#include <stdio.h>
#include <time.h>
#ifdef _WIN32
typedef unsigned int mode_t;
typedef unsigned int gid_t;
typedef unsigned int uid_t;
#else
#include <sys/types.h>
#endif
struct minitar
{
FILE* stream;
};
struct minitar_w
{
FILE* stream;
};
enum minitar_write_mode
{
MTAR_APPEND,
MTAR_OVERWRITE
};
enum minitar_file_type
{
MTAR_REGULAR,
@ -55,7 +36,7 @@ struct minitar_entry_internal
struct minitar_entry_metadata
{
char path[257];
char name[101];
char name[128];
char link[101];
mode_t mode;
uid_t uid;
@ -81,17 +62,13 @@ extern "C"
#endif
int minitar_open(const char* pathname, struct minitar* out);
int minitar_open_w(const char* pathname, struct minitar_w* out, enum minitar_write_mode mode);
int minitar_read_entry(struct minitar* mp, struct minitar_entry* out);
int minitar_write_file_entry(struct minitar_w* mp, const struct minitar_entry_metadata* metadata, char* buf);
int minitar_write_special_entry(struct minitar_w* mp, const struct minitar_entry_metadata* metadata);
void minitar_rewind(struct minitar* mp);
int minitar_find_by_name(struct minitar* mp, const char* name, struct minitar_entry* out);
int minitar_find_by_path(struct minitar* mp, const char* path, struct minitar_entry* out);
int minitar_find_any_of(struct minitar* mp, enum minitar_file_type type, struct minitar_entry* out);
size_t minitar_read_contents(struct minitar* mp, const struct minitar_entry* entry, char* buf, size_t max);
int minitar_close(struct minitar* mp);
int minitar_close_w(struct minitar_w* mp);
#ifdef __cplusplus
}

View File

@ -11,18 +11,10 @@
#include <stdio.h>
#include <string.h>
#ifndef __TINYC__
#include <stdnoreturn.h>
#else
#define noreturn _Noreturn
#endif
// all of these are defined in util.c
int minitar_read_header(struct minitar*, struct tar_header*);
noreturn void minitar_handle_panic(const char*);
int minitar_validate_header(const struct tar_header*);
void minitar_parse_metadata_from_tar_header(const struct tar_header*, struct minitar_entry_metadata*);
void minitar_construct_header_from_metadata(struct tar_header*, const struct minitar_entry_metadata*);
size_t minitar_align_up_to_block_size(size_t);
int minitar_open(const char* pathname, struct minitar* out)
@ -35,31 +27,11 @@ int minitar_open(const char* pathname, struct minitar* out)
return 0;
}
int minitar_open_w(const char* pathname, struct minitar_w* out, enum minitar_write_mode mode)
{
const char* mode_string;
switch (mode)
{
case MTAR_APPEND: mode_string = "ab"; break;
case MTAR_OVERWRITE: mode_string = "wb"; break;
default: minitar_handle_panic("mode passed to minitar_open_w is not supported");
}
FILE* fp = fopen(pathname, mode_string);
if (!fp) return -1;
out->stream = fp;
return 0;
}
int minitar_close(struct minitar* mp)
{
return fclose(mp->stream);
}
int minitar_close_w(struct minitar_w* mp)
{
return fclose(mp->stream);
}
// Try to read a valid header, and construct an entry from it. If the 512-byte block at the current read offset is not a
// valid header, valid is set to 0 so we can try again with the next block. In any other case, valid is set to 1. This
// helps distinguish valid return values, EOF, and invalid headers that we should just skip.
@ -102,47 +74,6 @@ int minitar_read_entry(struct minitar* mp, struct minitar_entry* out)
return result;
}
int minitar_write_file_entry(struct minitar_w* mp, const struct minitar_entry_metadata* metadata, char* buf)
{
struct minitar_entry_metadata meta = *metadata;
meta.type = MTAR_REGULAR;
struct tar_header hdr;
minitar_construct_header_from_metadata(&hdr, &meta);
// Write the header.
size_t nwrite = fwrite(&hdr, sizeof(hdr), 1, mp->stream);
if (nwrite == 0 && ferror(mp->stream)) return -1;
// Write the file data.
nwrite = fwrite(buf, 1, meta.size, mp->stream);
if (nwrite == 0 && ferror(mp->stream)) return -1;
char zeroes[512];
memset(zeroes, 0, sizeof(zeroes));
// Pad with zeroes to finish a block (512 bytes).
size_t nzero = minitar_align_up_to_block_size(meta.size) - meta.size;
nwrite = fwrite(zeroes, 1, nzero, mp->stream);
if (nwrite == 0 && ferror(mp->stream)) return -1;
return 0;
}
int minitar_write_special_entry(struct minitar_w* mp, const struct minitar_entry_metadata* metadata)
{
struct minitar_entry_metadata meta = *metadata;
if (meta.type == MTAR_REGULAR)
minitar_handle_panic("Trying to write a special entry, yet MTAR_REGULAR passed as the entry type");
meta.size = 0;
struct tar_header hdr;
minitar_construct_header_from_metadata(&hdr, &meta);
size_t nwrite = fwrite(&hdr, sizeof(hdr), 1, mp->stream);
if (nwrite == 0 && ferror(mp->stream)) return -1;
return 0;
}
void minitar_rewind(struct minitar* mp)
{
rewind(mp->stream);

View File

@ -13,6 +13,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#ifndef __TINYC__
#include <stdnoreturn.h>
@ -20,7 +21,7 @@
#define noreturn _Noreturn
#endif
#if !defined(_WIN32) && !defined(__TINYC__)
#if !defined(_MSC_VER) && !defined(__TINYC__)
#define WEAK __attribute__((weak))
#else
#define WEAK
@ -233,56 +234,6 @@ uint32_t minitar_checksum_header(const struct tar_header* hdr)
return sum;
}
void minitar_construct_header_from_metadata(struct tar_header* hdr, const struct minitar_entry_metadata* metadata)
{
if (strlen(metadata->path) > 100)
{
minitar_handle_panic("FIXME: pathnames over 100 (using the prefix field) are unsupported for now");
}
// We intentionally want strncpy to not write a null terminator here if the path field is 100 bytes long.
strncpy(hdr->name, metadata->path, 100);
snprintf(hdr->mode, 8, "%.7o", metadata->mode);
snprintf(hdr->uid, 8, "%.7o", metadata->uid);
snprintf(hdr->gid, 8, "%.7o", metadata->gid);
// snprintf will write the null terminator past the size field. We don't care, as we will overwrite that zero later.
snprintf(hdr->size, 13, "%.12zo", metadata->size);
// Same here.
snprintf(hdr->mtime, 13, "%.12llo", (long long)metadata->mtime);
switch (metadata->type)
{
case MTAR_REGULAR: hdr->typeflag = '0'; break;
case MTAR_HARDLINK: hdr->typeflag = '1'; break;
case MTAR_SYMLINK: hdr->typeflag = '2'; break;
case MTAR_CHRDEV: hdr->typeflag = '3'; break;
case MTAR_BLKDEV: hdr->typeflag = '4'; break;
case MTAR_DIRECTORY: hdr->typeflag = '5'; break;
case MTAR_FIFO: hdr->typeflag = '6'; break;
}
strncpy(hdr->linkname, metadata->link, 100);
memcpy(hdr->magic, "ustar", 6);
hdr->version[0] = '0';
hdr->version[1] = '0';
strncpy(hdr->uname, metadata->uname, 32);
strncpy(hdr->gname, metadata->gname, 32);
snprintf(hdr->devmajor, 8, "%.7o", metadata->devmajor);
snprintf(hdr->devminor, 8, "%.7o", metadata->devminor);
memset(hdr->prefix, 0, sizeof(hdr->prefix));
memset(hdr->padding, 0, sizeof(hdr->padding));
uint32_t checksum = minitar_checksum_header(hdr);
snprintf(hdr->chksum, 8, "%.7o", checksum);
}
int minitar_validate_header(const struct tar_header* hdr)
{
if (hdr->typeflag != '\0' && hdr->typeflag != '0' && hdr->typeflag != '1' && hdr->typeflag != '2' &&