Compare commits

..

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

14 changed files with 263 additions and 1101 deletions

View File

@ -1,8 +1,6 @@
cmake_minimum_required(VERSION 3.8..3.22)
project(minitar LANGUAGES C VERSION 1.7.6)
option(MINITAR_IGNORE_UNSUPPORTED_TYPES "Skip past entries that have unsupported types instead of panicking (deprecated)" OFF)
project(minitar LANGUAGES C VERSION 1.0.0)
set(SOURCES
src/tar.c
@ -11,17 +9,8 @@ set(SOURCES
add_library(minitar STATIC ${SOURCES})
if(MINITAR_IGNORE_UNSUPPORTED_TYPES)
message(WARNING "MINITAR_IGNORE_UNSUPPORTED_TYPES is deprecated, since there are no unsupported types anymore")
endif()
target_include_directories(minitar PUBLIC ${CMAKE_CURRENT_LIST_DIR}) # for minitar.h
set_target_properties(minitar PROPERTIES OUTPUT_NAME mtar)
set_target_properties(minitar PROPERTIES C_STANDARD 11)
set_target_properties(minitar PROPERTIES C_STANDARD_REQUIRED ON)
if (MSVC)
target_compile_options(minitar PRIVATE /W4 /WX)
else()
@ -29,6 +18,4 @@ else()
endif()
install(TARGETS minitar DESTINATION lib)
install(FILES minitar.h DESTINATION include)
add_subdirectory(examples)
install(FILES minitar.h DESTINATION include)

View File

@ -1,4 +1,4 @@
Copyright (c) 2022-2023, apio
Copyright (c) 2022, apio
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

146
README.md
View File

@ -1,16 +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.
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)).
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.
Tiny C library to interact with tar archives
## Example
```c
```
#include <stdio.h>
#include <minitar.h>
@ -21,35 +15,139 @@ int main(int argc, char** argv)
fprintf(stderr, "Usage: %s [file]\n", argv[0]);
return 1;
}
struct minitar mp;
if(minitar_open(argv[1], &mp) != 0)
struct minitar* mp = minitar_open(argv[1]);
if(!mp)
{
perror(argv[1]);
return 1;
}
struct minitar_entry entry;
struct minitar_entry* entry;
do {
if(minitar_read_entry(&mp, &entry) == 0) {
printf("%s\n", entry.metadata.path);
} else break;
} while(1);
minitar_close(&mp);
entry = minitar_read_entry(mp);
if(entry) {
printf("%s\n", entry->metadata.name);
minitar_free_entry(entry);
}
} while(entry);
minitar_close(mp);
}
```
The output of this example program when running it with an uncompressed tar archive is identical to that of `tar --list -f archive.tar` with the same uncompressed archive. And in most cases, it's faster as well!
The output of this example program when running it with an uncompressed tar archive is identical to that of `tar --list -f archive.tar` with the same uncompressed archive.
See [examples](examples/) for more examples using minitar.
## Functions
### minitar_open
`struct minitar* minitar_open(const char* pathname)`
## Project structure
Opens a tar archive for reading, and returns a heap-allocated `struct minitar` which must be freed with `minitar_close()` after using it. If opening the file or allocating the struct fails, returns NULL.
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`.
A `struct minitar` is opaque, and should only be passed to other minitar functions. You should not care about its contents.
## Documentation
### minitar_read_entry
`struct minitar_entry* minitar_read_entry(struct minitar* mp)`
See the [build instructions](docs/Build.md) to start using minitar.
Reads the next entry from a `struct minitar` which should be the return value of a previous call to `minitar_open()`. The return value is a heap-allocated `struct minitar_entry`, which should be freed with `minitar_free_entry()` when no longer needed.
See the [API documentation](docs/API.md) for a full description of all functions and types.
This structure consists of the file metadata (in the `metadata` field), and other internally-used values.
To read the contents of an entry, you should allocate a buffer large enough to hold `metadata.size` bytes and pass it to `minitar_read_contents()`.
This function returns NULL on end-of-file (when all entries have been read).
### minitar_free_entry
`void minitar_free_entry(struct minitar_entry* entry)`
Frees the heap-allocated `struct minitar_entry`. The pointer passed to `minitar_free_entry()` should be the return value of a previous call to `minitar_read_entry()`, `minitar_find_by_name()` or `minitar_find_any_of()`.
### minitar_rewind
`void minitar_rewind(struct minitar* mp)`
Rewinds the `struct minitar` back to the beginning of the archive file, which means that the next call to `minitar_read_entry()` will return the first entry instead of the entry after the last read entry.
### minitar_find_by_name
`struct minitar_entry* minitar_find_by_name(struct minitar* mp, const char* name)`
Returns the first entry with a matching name, or NULL if none are found. The return value is a `struct minitar_entry`, which is heap-allocated and should be freed after use with `minitar_free_entry()`. This structure is already documented in the entry documenting `minitar_read_entry()`.
This function starts searching from the current archive position, which means that to find a matching entry in the entire archive `minitar_rewind()` should be called on it first.
The state of `mp` after `minitar_find_by_name()` returns is unspecified, but a successive call to `minitar_find_by_name()` will return the next matching entry, if there is one. (Calling `minitar_find_by_name()` in a loop until it returns NULL will return all matching entries.)
In order to perform other minitar operations on the archive, `minitar_rewind()` should probably be called first, to get a known state.
### minitar_find_any_of
`struct minitar_entry* minitar_find_any_of(struct minitar* mp, enum minitar_file_type type)`
Does the same thing 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 NULL will return all matching entries.
### minitar_read_contents
`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`.
This function can be called as many times as desired, and at any given point in time, provided both `mp` and `entry` are valid. (`mp` should be the return value of a previous call to `minitar_open()`, and `entry` the return value of a previous call to `minitar_read_entry()`, `minitar_find_by_name()` or `minitar_find_any_of()`).
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()` 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.
### minitar_close
`int minitar_close(struct minitar* mp)`
Closes the tar archive file `mp` points to and frees the heap memory it was using. The pointer passed to `minitar_close()` should be the return value of a previous call to `minitar_open()`.
Returns 0 on success, everything else is failure and you should check `errno`.
## Types
### minitar_file_type
`enum minitar_file_type`
This enum lists all supported file types:
`MTAR_REGULAR`: Regular files
`MTAR_BLKDEV`: Block special devices
`MTAR_CHRDEV`: Character special devices
`MTAR_DIRECTORY`: Directories
Other file types supported in tar archives, such as FIFOs or symlinks, are not supported and minitar will throw an error when encountering one of them.
### minitar_entry_metadata
`struct minitar_entry_metadata`
This structure represents an entry's metadata, with the following fields:
`name`: A string representing the full path of the entry within the archive. (`char[]`)
`mode`: An integer representing the permissions of the entry. (`mode_t`)
`uid`: An integer representing the user ID of the entry's owner. (`uid_t`)
`gid`: An integer representing the group ID of the entry's owner. (`gid_t`)
`size`: An integer representing the size of the entry's contents in bytes. (`size_t`)
`mtime`: A UNIX timestamp representing the last time the entry was modified. (`time_t`)
`type`: An enum representing the type of the entry. (`enum minitar_file_type`)
`uname`: A string representing the username of the entry's owner. (`char[]`)
`gname`: A string representing the group name of the entry's owner. (`char[]`)
### minitar_entry
`struct minitar_entry`
An entry in a tar archive. Fields:
`metadata`: The entry's metadata. (`struct minitar_entry_metadata`)
`position`: Reserved for internal use. (`fpos_t`)
## Error handling
@ -75,4 +173,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,10 +0,0 @@
#!/usr/bin/sh
# Small shell script to automatically generate release changelogs.
echo "New features:"
git log --pretty=format:%s $1..HEAD | grep "feat:" | sed 's/feat\:/*/g'
echo ""
echo "Fixes:"
git log --pretty=format:%s $1..HEAD | grep "fix:" | sed 's/fix\:/*/g'

View File

@ -1,183 +0,0 @@
# 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.
### minitar_read_entry
`int minitar_read_entry(struct minitar* mp, struct minitar_entry* out)`
Reads the next entry from a `struct minitar` which should be initialized by a previous call to `minitar_open()` and stores the result in `out`.
The `minitar_entry` structure consists of the file metadata (in the `metadata` field), and other internally-used values.
To read the contents of an entry, you should allocate a buffer large enough to hold `metadata.size` bytes and pass it to `minitar_read_contents()`.
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)`
Rewinds the `struct minitar` back to the beginning of the archive file, which means that the next call to `minitar_read_entry()` will fetch the first entry instead of the entry after the last read entry.
### minitar_find_by_name
`int minitar_find_by_name(struct minitar* mp, const char* name, struct minitar_entry* out)`
Stores the first entry with a matching name in `out` and returns 0, or non-zero if none are found. If none are found, the state of `out` is unspecified and might have been changed by the function. (In this context, a "name" means the base name of a file, so `baz.txt` given the path `foo/bar/baz.txt`)
This function starts searching from the current archive position, which means that to find a matching entry in the entire archive `minitar_rewind()` should be called on it first.
The state of `mp` after `minitar_find_by_name()` returns is unspecified, but a successive call to `minitar_find_by_name()` will find the next matching entry, if there is one. (Calling `minitar_find_by_name()` in a loop until it returns non-zero will return all matching entries.)
In order to perform other minitar operations on the archive, `minitar_rewind()` should probably be called first, to get a known state.
### minitar_find_by_path
`int minitar_find_by_path(struct minitar* mp, const char* path, struct minitar_entry* out)`
Same as `minitar_find_by_name()`, but matches the full path inside the archive instead of the file name.
### minitar_find_any_of
`int minitar_find_any_of(struct minitar* mp, enum minitar_file_type type, struct minitar_entry* out)`
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)`
Reads up to `max` bytes of an entry's contents from the archive stream `mp` and stores them into `buf`.
This function can be called as many times as desired, and at any given point in time, provided both `mp` and `entry` are valid. (`mp` should be initialized by a previous call to `minitar_open()`, and `entry` initialized by a previous call to `minitar_read_entry()`, `minitar_find_by_name()`, `minitar_find_by_path()` or `minitar_find_any_of()`).
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).
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.
### minitar_close
`int minitar_close(struct minitar* mp)`
Closes the tar archive file `mp` points to. The pointer passed to `minitar_close()` should be initialized by a previous call to `minitar_open()`.
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
`enum minitar_file_type`
This enum lists all supported file types:
`MTAR_REGULAR`: Regular files
`MTAR_DIRECTORY`: Directories
`MTAR_SYMLINK`: Symbolic links
`MTAR_HARDLINK`: Hard links
`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
### minitar_entry_metadata
`struct minitar_entry_metadata`
This structure represents an entry's metadata, with the following fields:
`path`: A string representing the full path of the entry within the archive. (`char[]`)
`name`: A string representing the base name of the entry (the last component of its path). (`char[]`)
`link`: A string representing the file being linked to. (Only applies to symlinks/hard links) (`char[]`)
`mode`: An integer representing the permissions of the entry. (`mode_t`)
`uid`: An integer representing the user ID of the entry's owner. (`uid_t`)
`gid`: An integer representing the group ID of the entry's owner. (`gid_t`)
`size`: An integer representing the size of the entry's contents in bytes. (`size_t`)
`mtime`: A UNIX timestamp representing the last time the entry was modified. (`time_t`)
`type`: An enum representing the type of the entry. (`enum minitar_file_type`)
`uname`: A string representing the username of the entry's owner. (`char[]`)
`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`
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.

View File

@ -1,42 +0,0 @@
# minitar build instructions
minitar uses the cross-platform [CMake](https://cmake.org/) build generator.
## Configuring
Standard CMake out-of-source build:
```sh
$ mkdir -p build
$ cmake -S . -B build
```
## Building
Simply run `$ cmake --build build`.
## Installing
`# cmake --install build`
This will (on UNIX-like platforms) install `minitar.h` to /usr/local/include, and `libmtar.a` to /usr/local/lib.
## Using
After installation, you can compile regular programs that include `minitar.h` and use the minitar API by adding the `-lmtar` flag to your compiler.
Example:
`cc -o my-own-tar my-own-tar.c -O2 -Wall -lmtar`
## Using (with CMake)
Add the `minitar` directory as a subdirectory of your project (perhaps using git submodules?) and add the following lines to your `CMakeLists.txt`:
```
add_subdirectory(minitar)
target_link_libraries(<YOUR_PROJECT_NAME> PRIVATE minitar)
```
If you're using this method, this is the only step necessary, since minitar will be built and linked along with the rest of your project when you invoke your own build system.

View File

@ -1,12 +0,0 @@
add_executable(list EXCLUDE_FROM_ALL list.c)
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)

View File

@ -1,32 +0,0 @@
/*
* Copyright (c) 2023, apio.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* list.c: Example utility which lists files in a tar archive.
*/
#include <minitar.h>
#include <stdio.h>
int main(int argc, char** argv)
{
if (argc == 1)
{
fprintf(stderr, "Usage: %s [file]\n", argv[0]);
return 1;
}
struct minitar mp;
if (minitar_open(argv[1], &mp) != 0)
{
perror(argv[1]);
return 1;
}
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); }
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

@ -1,227 +0,0 @@
/*
* Copyright (c) 2023, apio.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* untar.c: Example utility which extracts files from a tar archive (POSIX only).
*/
#define _XOPEN_SOURCE 700
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <minitar.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#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;
if (write(fd, buf, entry->metadata.size) < 0) return 1;
if (fchmod(fd, entry->metadata.mode) < 0) return 1;
return close(fd);
}
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;
}
int main(int argc, char** argv)
{
if (argc == 1)
{
fprintf(stderr, "Usage: %s [file]\n", argv[0]);
return 1;
}
struct minitar mp;
if (minitar_open(argv[1], &mp) != 0)
{
perror(argv[1]);
return 1;
}
int exit_status = 0;
struct minitar_entry entry;
do {
if (minitar_read_entry(&mp, &entry) == 0)
{
if (entry.metadata.type == MTAR_DIRECTORY)
{
if (!strcmp(entry.metadata.name, ".") || !strcmp(entry.metadata.name, "..")) continue;
int status = untar_directory(&entry);
if (status != 0)
{
fprintf(stderr, "Failed to create directory %s: %s\n", entry.metadata.path, strerror(errno));
exit_status = 1;
break;
}
printf("mkdir %s\n", entry.metadata.path);
}
else if (entry.metadata.type == MTAR_REGULAR)
{
char* ptr = (char*)malloc(entry.metadata.size);
if (!ptr)
{
perror("malloc");
exit_status = 1;
break;
}
minitar_read_contents(&mp, &entry, ptr, entry.metadata.size);
int status = untar_file(&entry, ptr);
free(ptr);
if (status != 0)
{
fprintf(stderr, "Failed to extract file %s: %s\n", entry.metadata.path, strerror(errno));
exit_status = 1;
break;
}
printf("untar %s\n", entry.metadata.path);
}
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;
}
printf("symlink %s -> %s\n", entry.metadata.path, entry.metadata.link);
}
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;
}
printf("link %s -> %s\n", entry.metadata.path, entry.metadata.link);
}
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);
}
else
{
fprintf(stderr, "error: unknown entry type: %d", entry.metadata.type);
exit_status = 1;
break;
}
}
else
break;
} while (1);
minitar_close(&mp);
return exit_status;
}

View File

@ -1,62 +1,28 @@
/*
* Copyright (c) 2022-2023, apio.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* minitar.h: The minitar header.
*/
#ifndef MINITAR_H
#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
#ifdef _IN_MINITAR
struct minitar
{
FILE* stream;
};
struct minitar_w
{
FILE* stream;
};
enum minitar_write_mode
{
MTAR_APPEND,
MTAR_OVERWRITE
};
#else
struct minitar;
#endif
enum minitar_file_type
{
MTAR_REGULAR,
MTAR_DIRECTORY,
MTAR_SYMLINK,
MTAR_HARDLINK,
MTAR_FIFO,
MTAR_CHRDEV,
MTAR_BLKDEV
};
struct minitar_entry_internal
{
fpos_t _mt_position;
MTAR_BLKDEV,
MTAR_DIRECTORY
};
struct minitar_entry_metadata
{
char path[257];
char name[101];
char link[101];
char name[257];
mode_t mode;
uid_t uid;
gid_t gid;
@ -65,14 +31,12 @@ struct minitar_entry_metadata
enum minitar_file_type type;
char uname[32];
char gname[32];
unsigned devminor;
unsigned devmajor;
};
struct minitar_entry
{
struct minitar_entry_metadata metadata;
struct minitar_entry_internal _internal;
fpos_t position;
};
#ifdef __cplusplus
@ -80,21 +44,17 @@ 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);
struct minitar* minitar_open(const char* pathname);
struct minitar_entry* minitar_read_entry(struct minitar* mp);
void minitar_free_entry(struct minitar_entry* entry);
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);
struct minitar_entry* minitar_find_by_name(struct minitar* mp, const char* name);
struct minitar_entry* minitar_find_any_of(struct minitar* mp, enum minitar_file_type type);
int minitar_close(struct minitar* mp);
int minitar_close_w(struct minitar_w* mp);
size_t minitar_read_contents(struct minitar* mp, struct minitar_entry* entry, char* buf, size_t max);
#ifdef __cplusplus
}
#endif
#endif
#endif

213
src/tar.c
View File

@ -1,212 +1,125 @@
/*
* Copyright (c) 2022-2023, apio.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* tar.c: minitar API implementation.
*/
#define _IN_MINITAR
#include "tar.h"
#include "minitar.h"
#include <stdio.h>
#include <stdlib.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);
void minitar_parse_tar_header(const struct tar_header*, struct minitar_entry_metadata*);
struct minitar_entry* minitar_dup_entry(const struct minitar_entry*);
char* minitar_read_file_contents(struct minitar_entry_metadata*, struct minitar*);
size_t minitar_get_size_in_blocks(size_t);
int minitar_open(const char* pathname, struct minitar* out)
struct minitar* minitar_open(const char* pathname)
{
FILE* fp =
fopen(pathname,
"rb"); // On some systems, opening the file in binary mode might be necessary to read the file properly.
if (!fp) return -1;
out->stream = fp;
return 0;
}
int minitar_open_w(const char* pathname, struct minitar_w* out, enum minitar_write_mode mode)
{
const char* mode_string;
switch (mode)
FILE* fp = fopen(pathname, "rb"); // On some systems, this might be necessary to read the file properly.
if (!fp) return NULL;
struct minitar* mp = malloc(sizeof(struct minitar));
if (!mp)
{
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");
fclose(fp);
return NULL;
}
FILE* fp = fopen(pathname, mode_string);
if (!fp) return -1;
out->stream = fp;
return 0;
mp->stream = fp;
return mp;
}
int minitar_close(struct minitar* mp)
{
return fclose(mp->stream);
int rc = fclose(mp->stream);
free(mp);
if (rc) return rc;
return 0;
}
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.
static int minitar_try_to_read_valid_entry(struct minitar* mp, struct minitar_entry* out, int* valid)
static struct minitar_entry* minitar_attempt_read_entry(struct minitar* mp, int* valid)
{
struct minitar_entry entry;
struct tar_header hdr;
*valid = 1;
if (!minitar_read_header(mp, &hdr)) return -1;
if (!minitar_read_header(mp, &hdr))
{
*valid = 1; // we are at end-of-file
return NULL;
}
if (!minitar_validate_header(&hdr))
{
*valid = 0;
return -1;
return NULL;
}
// Fetch the current read position (which is currently pointing to the start of the entry's contents), so we can
// return back to it when reading the contents of this entry using minitar_read_contents().
if (fgetpos(mp->stream, &out->_internal._mt_position)) return -1;
minitar_parse_metadata_from_tar_header(&hdr, &out->metadata);
if (out->metadata.size)
*valid = 0;
if (fgetpos(mp->stream, &entry.position)) return NULL;
*valid = 1;
minitar_parse_tar_header(&hdr, &entry.metadata);
if (entry.metadata.size)
{
size_t size_in_archive = minitar_align_up_to_block_size(out->metadata.size);
if (fseek(mp->stream, size_in_archive,
size_t size_in_blocks = minitar_get_size_in_blocks(entry.metadata.size);
if (fseek(mp->stream, size_in_blocks,
SEEK_CUR)) // move over to the next block, skipping over the file contents
{
return -1;
return NULL;
}
}
return 0;
return minitar_dup_entry(&entry);
}
int minitar_read_entry(struct minitar* mp, struct minitar_entry* out)
struct minitar_entry* minitar_read_entry(struct minitar* mp)
{
int valid, result;
int valid;
struct minitar_entry* result;
do {
result = minitar_try_to_read_valid_entry(mp, out, &valid);
} while (!valid); // Skip over invalid entries
result = minitar_attempt_read_entry(mp, &valid);
} while (!valid);
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);
}
int minitar_find_by_name(struct minitar* mp, const char* name, struct minitar_entry* out)
void minitar_free_entry(struct minitar_entry* entry)
{
int rc;
do {
rc = minitar_read_entry(mp, out);
if (rc == 0)
{
if (!strcmp(out->metadata.name, name)) return 0;
}
} while (rc == 0);
return -1;
free(entry);
}
int minitar_find_by_path(struct minitar* mp, const char* path, struct minitar_entry* out)
struct minitar_entry* minitar_find_by_name(struct minitar* mp, const char* name)
{
int rc;
struct minitar_entry* entry;
do {
rc = minitar_read_entry(mp, out);
if (rc == 0)
entry = minitar_read_entry(mp);
if (entry)
{
if (!strcmp(out->metadata.path, path)) return 0;
if (!strcmp(entry->metadata.name, name)) return entry;
minitar_free_entry(entry);
}
} while (rc == 0);
return -1;
} while (entry);
return NULL;
}
int minitar_find_any_of(struct minitar* mp, enum minitar_file_type type, struct minitar_entry* out)
struct minitar_entry* minitar_find_any_of(struct minitar* mp, enum minitar_file_type type)
{
int rc;
struct minitar_entry* entry;
do {
rc = minitar_read_entry(mp, out);
if (rc == 0)
entry = minitar_read_entry(mp);
if (entry)
{
if (out->metadata.type == type) return 0;
if (entry->metadata.type == type) return entry;
minitar_free_entry(entry);
}
} while (rc == 0);
return -1;
} while (entry);
return NULL;
}
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)
{
if (!max) return 0;
if (!entry->metadata.size) return 0;
fpos_t current_position;
// Save the current position
if (fgetpos(mp->stream, &current_position)) return 0;
// Move to the position stored in the entry
if (fsetpos(mp->stream, &entry->_internal._mt_position)) return 0;
// We refuse to read more than the size indicated by the archive
if (max > entry->metadata.size) max = entry->metadata.size;
size_t nread = fread(buf, 1, max, mp->stream);
if (fsetpos(mp->stream, &entry->position)) return 0;
size_t nread = fread(buf, 1, max > entry->metadata.size ? entry->metadata.size : max, mp->stream);
if (ferror(mp->stream)) return 0;
// Restore the current position
if (fsetpos(mp->stream, &current_position)) return 0;
return nread;
}
}

View File

@ -1,18 +1,6 @@
/*
* Copyright (c) 2022-2023, apio.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* tar.h: tar header structure.
*/
#ifndef MINITAR_TAR_H
#define MINITAR_TAR_H
// Format of a raw standard tar header.
#pragma pack(push, 1)
struct tar_header
{
char name[100];
@ -31,10 +19,7 @@ struct tar_header
char devmajor[8];
char devminor[8];
char prefix[155];
char padding[12]; // to make the structure 512 bytes
} __attribute__((packed));
char padding[12]; // Not part of the header, only used to make the structure 512 bytes
};
#pragma pack(pop)
#endif
#endif

View File

@ -1,286 +1,105 @@
/*
* Copyright (c) 2022-2023, apio.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* util.c: Utility functions for minitar.
*/
// FIXME: What if strndup is not available? On non-POSIX, for example...
#define _POSIX_C_SOURCE 200809L // for strndup
#define _IN_MINITAR
#include "minitar.h"
#include "tar.h"
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef __TINYC__
#include <stdnoreturn.h>
#else
#define noreturn _Noreturn
#endif
#if !defined(_WIN32) && !defined(__TINYC__)
#define WEAK __attribute__((weak))
#else
#define WEAK
#endif
#include <string.h>
// Default implementation for minitar_handle_panic(). Since it's declared weak, any other definition will silently
// override this one :)
WEAK noreturn void minitar_handle_panic(const char* message)
__attribute__((weak)) noreturn void minitar_handle_panic(const char* message)
{
fprintf(stderr, "minitar: %s\n", message);
abort();
}
// Safer BSD-style replacement for strcpy/strncpy. Copies at most size-1 bytes from src into dest, always
// null-terminating the result. Returns the full length of src, to make it easy to check for overflows. Non-standard, so
// we provide our own implementation.
// https://linux.die.net/man/3/strlcpy
static size_t minitar_strlcpy(char* dest, const char* src, size_t size)
noreturn void minitar_panic(const char* message)
{
size_t len, full_len; // full_len is the total length of src, len is the length we're copying
minitar_handle_panic(message);
}
size_t minitar_strlcpy(char* dest, const char* src, size_t size)
{
size_t len, full_len;
len = full_len = strlen(src);
if (size == 0) return len;
if (len > (size - 1)) len = size - 1;
memcpy(dest, src, len);
dest[len] = 0; // null-terminate
for (size_t i = 0; i < len; ++i) { *((char*)dest + i) = *((const char*)src + i); }
dest[len] = 0;
return full_len;
}
static char dot[] = ".";
// POSIX function to extract the basename from a path. Not present on non-POSIX, but since paths inside a tar archive
// are always POSIX (I believe?), we can use a replacement that does exactly the same thing as the original basename().
// https://linux.die.net/man/3/basename
static char* minitar_basename(char* path)
{
// If path is NULL, or the string's length is 0, return .
if (!path) return dot;
size_t len = strlen(path);
if (!len) return dot;
// Strip trailing slashes.
char* it = path + len - 1;
while (*it == '/' && it != path) { it--; }
*(it + 1) = 0;
if (it == path) return path;
// Return path from the first character if there are no more slashes, or from the first character after the last
// slash.
char* beg = strrchr(path, '/');
if (!beg) return path;
return beg + 1;
}
static uint64_t parse_digit(char c)
{
return c - '0';
}
static int is_valid_octal_digit(char c)
{
if (!isdigit(c)) return 0;
if (parse_digit(c) >= 8ull) return 0;
return 1;
}
static uint64_t minitar_parse_octal(const char* str)
{
uint64_t result = 0;
while (isspace(*str)) str++;
while (is_valid_octal_digit(*str))
{
result = (result * 8ull) + parse_digit(*str);
str++;
}
return result;
}
// strcat, but for characters :)
static void minitar_append_char(char* str, char c)
void minitar_append_char(char* str, char c)
{
size_t len = strlen(str);
str[len] = c;
str[len + 1] = 0;
}
static size_t minitar_is_aligned_to_block_size(size_t size)
size_t minitar_is_block_aligned(size_t size)
{
return (size % 512 == 0);
}
static size_t minitar_align_down_to_block_size(size_t size)
size_t minitar_align_down_to_block(size_t size)
{
return size - (size % 512);
}
// Return a static string formed by 'size' bytes copied from str, and a null terminator. This function is useful for
// when you have a fixed-size field without a null-terminator, and you need a null-terminated string to pass to a
// library function. The pointer returned WILL be overwritten by subsequent calls to this function.
static char* minitar_static_dup(const char* str, size_t size)
size_t minitar_get_size_in_blocks(size_t size)
{
static char result[1024];
memcpy(result, str, size);
result[size] = 0;
return result;
return minitar_is_block_aligned(size) ? size : minitar_align_down_to_block(size) + 512;
}
size_t minitar_align_up_to_block_size(size_t size)
void minitar_parse_tar_header(const struct tar_header* hdr, struct minitar_entry_metadata* metadata)
{
return minitar_is_aligned_to_block_size(size) ? size : minitar_align_down_to_block_size(size) + 512;
}
static void minitar_parse_basename(const char* path, char* out, size_t max)
{
static char mutable_path_copy[512];
minitar_strlcpy(mutable_path_copy, path, sizeof(mutable_path_copy));
char* bname = minitar_basename(mutable_path_copy);
minitar_strlcpy(out, bname, max);
}
void minitar_parse_metadata_from_tar_header(const struct tar_header* hdr, struct minitar_entry_metadata* metadata)
{
if (!strlen(hdr->prefix)) // If prefix is null, the full path is only the "name" field of the tar header.
minitar_strlcpy(
metadata->path, hdr->name,
101); // We use 101 instead of 100 so that we copy the full "name" field even if it is not null-terminated.
else // Construct the path by first taking the "prefix" field, then adding a slash, then concatenating the "name"
// field.
if (!strlen(hdr->prefix))
{
minitar_strlcpy(metadata->path, hdr->prefix, 155);
minitar_append_char(metadata->path, '/');
strncat(metadata->path, hdr->name, 100);
metadata->path[256] = '\0';
minitar_strlcpy(metadata->name, hdr->name, 100);
metadata->name[100] = '\0';
}
else
{
minitar_strlcpy(metadata->name, hdr->prefix, 155);
minitar_append_char(metadata->name, '/');
strncat(metadata->name, hdr->name, 100);
metadata->name[256] = '\0';
}
minitar_strlcpy(metadata->link, hdr->linkname, 101);
metadata->mode = (mode_t)strtoul(hdr->mode, NULL, 8);
metadata->uid = (uid_t)strtoul(hdr->uid, NULL, 8);
metadata->gid = (gid_t)strtoul(hdr->gid, NULL, 8);
minitar_parse_basename(metadata->path, metadata->name, sizeof(metadata->name));
char* sizeptr = strndup(
hdr->size, 12); // The hdr->size field is not null-terminated, yet strndup returns a null-terminated string.
if (!sizeptr) minitar_panic("Failed to allocate memory to duplicate a tar header's size field");
metadata->size = (size_t)strtoull(sizeptr, NULL, 8);
free(sizeptr);
// Numeric fields in tar archives are stored as octal-encoded ASCII strings. Weird decision (supposedly for
// portability), which means we have to parse these strings (the size and mtime fields aren't even null-terminated!)
// to get the far more user-friendlier integer values stored in our metadata structure.
metadata->mode = (mode_t)minitar_parse_octal(hdr->mode);
metadata->uid = (uid_t)minitar_parse_octal(hdr->uid);
metadata->gid = (gid_t)minitar_parse_octal(hdr->gid);
// These two fields aren't null-terminated.
char* sizeptr = minitar_static_dup(hdr->size, 12);
metadata->size = (size_t)minitar_parse_octal(sizeptr);
char* timeptr = minitar_static_dup(hdr->mtime, 12);
metadata->mtime = (time_t)minitar_parse_octal(timeptr);
// The type is stored as a character instead of an integer.
char* timeptr = strndup(
hdr->mtime, 12); // The hdr->mtime field is not null-terminated, yet strndup returns a null-terminated string.
if (!timeptr) minitar_panic("Failed to allocate memory to duplicate a tar header's mtime field");
metadata->mtime = (time_t)strtoull(timeptr, NULL, 8);
free(timeptr);
switch (hdr->typeflag)
{
case '\0':
case '0': metadata->type = MTAR_REGULAR; break;
case '1': metadata->type = MTAR_HARDLINK; break;
case '2': metadata->type = MTAR_SYMLINK; break;
case '1': minitar_panic("Links to other files within a tar archive are unsupported");
case '2': minitar_panic("Symbolic links are unsupported");
case '3': metadata->type = MTAR_CHRDEV; break;
case '4': metadata->type = MTAR_BLKDEV; break;
case '5': metadata->type = MTAR_DIRECTORY; break;
case '6': metadata->type = MTAR_FIFO; break;
// This case should have been previously handled by minitar_validate_header().
default: minitar_handle_panic("Unknown entry type in tar header");
case '6': minitar_panic("FIFOs are unsupported");
default: minitar_panic("Unknown entry type in tar header");
}
minitar_strlcpy(metadata->uname, hdr->uname, 32);
minitar_strlcpy(metadata->gname, hdr->gname, 32);
if (metadata->type == MTAR_CHRDEV || metadata->type == MTAR_BLKDEV)
{
metadata->devminor = minitar_parse_octal(hdr->devminor);
metadata->devmajor = minitar_parse_octal(hdr->devmajor);
}
}
uint32_t minitar_checksum_header(const struct tar_header* hdr)
{
uint32_t sum = 0;
const uint8_t* ptr = (const uint8_t*)hdr;
// Sum up all bytes in the header, as unsigned bytes...
while (ptr < (const uint8_t*)hdr + (sizeof *hdr - sizeof hdr->padding))
{
sum += *ptr;
ptr++;
}
// except for the chksum field, which is treated as...
ptr = (const uint8_t*)hdr->chksum;
while (ptr < (const uint8_t*)hdr->chksum + sizeof hdr->chksum)
{
sum -= *ptr;
ptr++;
}
// all blanks.
for (size_t i = 0; i < sizeof hdr->chksum; i++) { sum += ' '; }
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)
@ -288,8 +107,6 @@ int minitar_validate_header(const struct tar_header* hdr)
if (hdr->typeflag != '\0' && hdr->typeflag != '0' && hdr->typeflag != '1' && hdr->typeflag != '2' &&
hdr->typeflag != '3' && hdr->typeflag != '4' && hdr->typeflag != '5' && hdr->typeflag != '6')
return 0;
// FIXME: Warn on checksum mismatch unless header is all blanks?
if (minitar_checksum_header(hdr) != minitar_parse_octal(hdr->chksum)) return 0;
return !strncmp(hdr->magic, "ustar", 5);
}
@ -297,7 +114,15 @@ int minitar_read_header(struct minitar* mp, struct tar_header* hdr)
{
size_t rc = fread(hdr, 1, sizeof *hdr, mp->stream);
if (rc == 0 && feof(mp->stream)) return 0;
if (rc == 0 && ferror(mp->stream)) minitar_handle_panic("Error while reading file header from tar archive");
if (rc < sizeof *hdr) minitar_handle_panic("Valid tar files should be split in 512-byte blocks");
if (rc == 0 && ferror(mp->stream)) minitar_panic("Error while reading file header from tar archive");
if (rc < sizeof *hdr) minitar_panic("Valid tar files should be split in 512-byte blocks");
return 1;
}
struct minitar_entry* minitar_dup_entry(const struct minitar_entry* original)
{
struct minitar_entry* new = malloc(sizeof *original);
if (!new) return NULL;
memcpy(new, original, sizeof *new);
return new;
}