Compare commits

...

51 Commits
1.4.0 ... main

Author SHA1 Message Date
1ce643b0c8
chore: bump patch version 2023-12-12 23:29:23 +01:00
ae7bf076dc
fix: Avoid leaking all kinds of stuff in examples/pack
Apart from potentionally leaking malloced memory on error,
which isn't too bad since we immediately exit afterwards,
we were leaving all opened files dangling, as fclose() was never called.
2023-12-12 23:27:05 +01:00
73c4dce573
fix: Functions implemented in tar.c are no longer implemented in the README 2023-06-18 21:09:35 +02:00
7ef1c80fb6
chore: bump patch version 2023-06-18 20:53:56 +02:00
6b1b8fef55
fix: Don't create . or .. in the untar example 2023-06-17 12:05:13 +02:00
cd0c5df5f5
chore: bump patch version 2023-05-27 20:02:46 +02:00
1644ab59eb
fix: Add support for my own OS to examples
Luna doesn't support FIFOs, and special device files are
automatically created by the kernel, so there is no mknod().
2023-05-27 19:58:23 +02:00
f12f58bacf
fix: Make the untar example create parent directories for all entry types 2023-03-08 17:05:10 +01:00
702348a365
chore: bump patch version 2023-02-25 20:31:45 +01:00
f6507e5461
chore: add note to self
untar creates parent dirs only for regular files now, not symlinks/hard links/FIFOs/etc
2023-02-25 20:30:31 +01:00
7e42b10078
fix: Make the untar example create parent directories if they don't exist (like mkdir -p)
This lets it deal with tar archives that are packaged like this:
usr/include/hello.h
usr/include/bye.h
usr/lib/libexample.a
Instead of requiring directory entries:
usr/
usr/include/
usr/include/hello.h (etc)

This helps since some tar archives (our own example tool, pack, for example, does this) are packaged without directory entries.
2023-02-25 20:28:23 +01:00
cb432fd306
docs: Fix potentially confusing statement
First we're saying "reads up to max" and then "always reads up to metadata.size, regardless of max".
We actually mean that we never read more than metadata.size, so that's what this patched text says.
2023-02-18 22:34:17 +01:00
a6d38efc7d
chore: bump patch version 2023-02-15 19:24:54 +01:00
85a6d79151
fix: Shorten the 'name' field in minitar_entry_metadata
Sure, 256 characters might fit in 'path', but because of the way
paths are stored in a tar archive, basenames cannot exceed 100
characters. So, adding space for a null-terminator, this reduces
our 'name' field from 128 bytes to 101.

This change is backwards-compatible since any reasonable application
should not depend on the name field being 128 bytes (this was never
mentioned in any documentation, API.md describes it as 'char[]' without
a fixed length). sizeof(metadata.name) should always be used instead.
2023-02-09 16:41:26 +01:00
320231c70b
fix: Make the 'list' example show more metadata 2023-02-09 16:40:57 +01:00
7571ef42c6
fix: Cast time_t to a known type for snprintf() 2023-02-06 23:18:05 +01:00
88d6fbffb7
fix: Check for _WIN32 instead of _MSC_VER 2023-02-06 23:17:28 +01:00
01d3c27d41
chore: bump patch version 2023-02-06 22:42:31 +01:00
bf52d9e321
fix: Handle malloc errors properly in pack.c 2023-02-05 14:38:31 +01:00
7339aeeae5
chore: Add platform-specific example targets
Someone might want to build only the examples that work on non-POSIX, or the ones that work on POSIX.
2023-02-05 14:33:59 +01:00
40302ddd41
fix: Do not define metadata twice in examples/pack.c 2023-02-05 14:30:29 +01:00
8cb5175630
fix: Do not #include <sys/types.h> on Windows
Instead, we provide our own typedef for mode_t, uid_t and gid_t, and we get time_t from <time.h>.
2023-02-05 14:22:31 +01:00
5bfc7e45ac
chore: Adjust comment in minitar_write_file_entry() 2023-02-05 14:14:37 +01:00
873f056cc3
chore: bump minor version 2023-02-04 19:42:12 +01:00
9b39f43595
docs: Make it clearer that "minitar" and "minitar_w" are handle structs 2023-02-04 19:39:12 +01:00
9c43461e02
docs: Remove section about unsupported entry types in API.md
There are no unsupported types anymore.
2023-02-04 19:37:15 +01:00
53aa377bbb
docs: Update docs/API.md with a proper link to the error handling section 2023-02-04 19:35:17 +01:00
f09e6f2e85
docs: Add documentation for the new "writing" API 2023-02-04 19:33:23 +01:00
845b357bef
Update README.md 2023-02-04 19:04:29 +01:00
482ac6d949
feat: Add write support
This uses a new struct (struct minitar_w).
This struct is initialized using the minitar_open_w function, and deleted using minitar_close_w.

The functions to write to an archive are minitar_write_file_entry() and minitar_write_special_entry().
The difference is that regular files have content, while the rest (special entries) do not.

This commit also adds a new example program (pack), which can create tar archives from files (no directory support though).

Archives created using pack and minitar can be read and extracted using GNU tar!!

Documentation is coming in another commit.
2023-02-04 19:02:08 +01:00
95700b6916
docs: Update function signature of minitar_read_contents() 2023-01-29 22:20:15 +01:00
45fbd789d6
docs: Add APi documentation for MTAR_CHRDEV and MTAR_BLKDEV
Missed that in the previous commits.
2023-01-29 22:14:05 +01:00
e96f7127bf
chore: bump minor version 2023-01-29 22:10:04 +01:00
e9ca265068
fix: Deprecate MINITAR_IGNORE_UNSUPPORTED_TYPES 2023-01-29 22:08:13 +01:00
5b0d597c09
feat: Add support for block and character devices 2023-01-29 22:07:54 +01:00
1f08cf4b31
feat: Add support for FIFOs 2023-01-27 23:00:53 +01:00
3772e9e3a6
feat: Add support for hard links 2023-01-27 22:46:18 +01:00
94324c502e
chore: Add a small shell script to generate release changelogs 2023-01-26 22:23:38 +01:00
ce0db53a90
chore: bump minor version 2023-01-26 22:14:22 +01:00
599cac5811
feat: Add support for symbolic links
All the pieces were in place, we just needed to put them together.

Sorry, but sub-500 LoC isn't gonna work anymore...
2023-01-26 22:12:42 +01:00
4c90f9078b
fix: Use memcpy() in minitar_strlcpy()
This allows the platform to provide a more optimized and FAST version of memcpy than
our manual implementation inside strlcpy.
2023-01-26 21:40:57 +01:00
56250a226f
chore: Add a CMake target to build all examples 2023-01-19 22:44:01 +01:00
d8faf424ad
fix: Skip the padding field when calculating header checksums 2023-01-19 22:32:01 +01:00
240a4f2f1a
chore: Add examples to CMake as opt-in targets 2023-01-19 22:30:23 +01:00
9a7629459e
chore: Add copyright headers to all source files 2023-01-12 21:04:52 +01:00
bacc971007
docs: Add build instructions 2023-01-12 20:45:46 +01:00
8e16524e1c
docs: Add a note about examples to README.md 2023-01-12 20:06:22 +01:00
e758012948
feat: Add two examples 2023-01-12 20:04:28 +01:00
27dac123f4
fix: Make minitar_read_contents() take a const pointer to entry 2023-01-12 20:03:42 +01:00
912b98d2d7
fix: Include stddef.h in minitar.h to make sure we have size_t 2023-01-12 20:02:13 +01:00
5be3c7d684
Enable syntax highlighting for the sample program 2023-01-12 19:34:33 +01:00
13 changed files with 722 additions and 34 deletions

View File

@ -1,8 +1,8 @@
cmake_minimum_required(VERSION 3.8..3.22)
project(minitar LANGUAGES C VERSION 1.4.0)
project(minitar LANGUAGES C VERSION 1.7.6)
option(MINITAR_IGNORE_UNSUPPORTED_TYPES "Skip past entries that have unsupported types instead of panicking" OFF)
option(MINITAR_IGNORE_UNSUPPORTED_TYPES "Skip past entries that have unsupported types instead of panicking (deprecated)" OFF)
set(SOURCES
src/tar.c
@ -12,7 +12,7 @@ set(SOURCES
add_library(minitar STATIC ${SOURCES})
if(MINITAR_IGNORE_UNSUPPORTED_TYPES)
target_compile_definitions(minitar PRIVATE 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
@ -29,4 +29,6 @@ else()
endif()
install(TARGETS minitar DESTINATION lib)
install(FILES minitar.h DESTINATION include)
install(FILES minitar.h DESTINATION include)
add_subdirectory(examples)

View File

@ -1,16 +1,16 @@
# minitar
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.
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 less than 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 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.
## Example
```
```c
#include <stdio.h>
#include <minitar.h>
@ -32,19 +32,23 @@ int main(int argc, char** argv)
if(minitar_read_entry(&mp, &entry) == 0) {
printf("%s\n", entry.metadata.path);
} else break;
} while(true);
} while(1);
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!
See [examples](examples/) for more examples using minitar.
## Project structure
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`.
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`.
## Documentation
See the [build instructions](docs/Build.md) to start using minitar.
See the [API documentation](docs/API.md) for a full description of all functions and types.
## Error handling
@ -71,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.

10
changelog.sh Executable file
View File

@ -0,0 +1,10 @@
#!/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,10 +1,17 @@
# 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` structure by opening the archive pointed to by `pathname` for reading. Returns 0 on success, anything else is failure.
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)`
@ -17,6 +24,30 @@ 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)`
@ -45,7 +76,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, struct minitar_entry* entry, char* buf, size_t max)`
`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`.
@ -53,7 +84,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()` only reads up to `metadata.size`, regardless of the value in `max`.
`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.
@ -64,6 +95,13 @@ 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
@ -75,7 +113,24 @@ This enum lists all supported file types:
`MTAR_DIRECTORY`: Directories
Other file types supported in tar archives, such as block/character devices, FIFOs, or symlinks, 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.
`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`
@ -86,6 +141,8 @@ This structure represents an entry's metadata, with the following fields:
`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`)
@ -102,6 +159,10 @@ 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`
@ -109,4 +170,14 @@ 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`)
`_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.

42
docs/Build.md Normal file
View File

@ -0,0 +1,42 @@
# 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.

12
examples/CMakeLists.txt Normal file
View File

@ -0,0 +1,12 @@
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)

32
examples/list.c Normal file
View File

@ -0,0 +1,32 @@
/*
* 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);
}

100
examples/pack.c Normal file
View File

@ -0,0 +1,100 @@
/*
* 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;
}

227
examples/untar.c Normal file
View File

@ -0,0 +1,227 @@
/*
* 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,17 +1,50 @@
/*
* 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
struct minitar
{
FILE* stream;
};
struct minitar_w
{
FILE* stream;
};
enum minitar_write_mode
{
MTAR_APPEND,
MTAR_OVERWRITE
};
enum minitar_file_type
{
MTAR_REGULAR,
MTAR_DIRECTORY
MTAR_DIRECTORY,
MTAR_SYMLINK,
MTAR_HARDLINK,
MTAR_FIFO,
MTAR_CHRDEV,
MTAR_BLKDEV
};
struct minitar_entry_internal
@ -22,7 +55,8 @@ struct minitar_entry_internal
struct minitar_entry_metadata
{
char path[257];
char name[128];
char name[101];
char link[101];
mode_t mode;
uid_t uid;
gid_t gid;
@ -31,6 +65,8 @@ struct minitar_entry_metadata
enum minitar_file_type type;
char uname[32];
char gname[32];
unsigned devminor;
unsigned devmajor;
};
struct minitar_entry
@ -45,13 +81,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);
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, struct minitar_entry* entry, char* buf, size_t max);
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

@ -1,12 +1,28 @@
/*
* Copyright (c) 2022-2023, apio.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* tar.c: minitar API implementation.
*/
#include "tar.h"
#include "minitar.h"
#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)
@ -19,15 +35,34 @@ 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, null pointers that should be returned to the user (for example, EOF), and
// invalid headers where we should just try again until we find a valid one.
// 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)
{
struct tar_header hdr;
@ -67,6 +102,47 @@ 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);
@ -111,7 +187,7 @@ int minitar_find_any_of(struct minitar* mp, enum minitar_file_type type, struct
return -1;
}
size_t minitar_read_contents(struct minitar* mp, struct minitar_entry* entry, char* buf, size_t max)
size_t minitar_read_contents(struct minitar* mp, const struct minitar_entry* entry, char* buf, size_t max)
{
if (!max) return 0;
if (!entry->metadata.size) return 0;

View File

@ -1,3 +1,11 @@
/*
* Copyright (c) 2022-2023, apio.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* tar.h: tar header structure.
*/
#ifndef MINITAR_TAR_H
#define MINITAR_TAR_H

View File

@ -1,3 +1,11 @@
/*
* Copyright (c) 2022-2023, apio.
*
* SPDX-License-Identifier: BSD-2-Clause
*
* util.c: Utility functions for minitar.
*/
#include "minitar.h"
#include "tar.h"
#include <ctype.h>
@ -5,7 +13,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#ifndef __TINYC__
#include <stdnoreturn.h>
@ -13,7 +20,7 @@
#define noreturn _Noreturn
#endif
#if !defined(_MSC_VER) && !defined(__TINYC__)
#if !defined(_WIN32) && !defined(__TINYC__)
#define WEAK __attribute__((weak))
#else
#define WEAK
@ -37,7 +44,7 @@ static size_t minitar_strlcpy(char* dest, const char* src, size_t size)
len = full_len = strlen(src);
if (size == 0) return len;
if (len > (size - 1)) len = size - 1;
for (size_t i = 0; i < len; ++i) { *(dest + i) = *(src + i); }
memcpy(dest, src, len);
dest[len] = 0; // null-terminate
return full_len;
}
@ -154,6 +161,8 @@ void minitar_parse_metadata_from_tar_header(const struct tar_header* hdr, struct
metadata->path[256] = '\0';
}
minitar_strlcpy(metadata->link, hdr->linkname, 101);
minitar_parse_basename(metadata->path, metadata->name, sizeof(metadata->name));
// Numeric fields in tar archives are stored as octal-encoded ASCII strings. Weird decision (supposedly for
@ -164,6 +173,8 @@ void minitar_parse_metadata_from_tar_header(const struct tar_header* hdr, struct
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);
@ -176,17 +187,24 @@ void minitar_parse_metadata_from_tar_header(const struct tar_header* hdr, struct
{
case '\0':
case '0': metadata->type = MTAR_REGULAR; break;
case '1': minitar_handle_panic("Links to other files within a tar archive are unsupported");
case '2': minitar_handle_panic("Symbolic links are unsupported");
case '3': minitar_handle_panic("Character devices are unsupported");
case '4': minitar_handle_panic("Block devices are unsupported");
case '1': metadata->type = MTAR_HARDLINK; break;
case '2': metadata->type = MTAR_SYMLINK; break;
case '3': metadata->type = MTAR_CHRDEV; break;
case '4': metadata->type = MTAR_BLKDEV; break;
case '5': metadata->type = MTAR_DIRECTORY; break;
case '6': minitar_handle_panic("FIFOs are unsupported");
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");
}
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)
@ -195,7 +213,7 @@ uint32_t minitar_checksum_header(const struct tar_header* hdr)
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)
while (ptr < (const uint8_t*)hdr + (sizeof *hdr - sizeof hdr->padding))
{
sum += *ptr;
ptr++;
@ -215,15 +233,61 @@ 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)
{
#ifdef MINITAR_IGNORE_UNSUPPORTED_TYPES
if (hdr->typeflag != '\0' && hdr->typeflag != '0' && hdr->typeflag != '5') return 0;
#else
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;
#endif
// 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);