Compare commits
58 Commits
Author | SHA1 | Date | |
---|---|---|---|
1ce643b0c8 | |||
ae7bf076dc | |||
73c4dce573 | |||
7ef1c80fb6 | |||
6b1b8fef55 | |||
cd0c5df5f5 | |||
1644ab59eb | |||
f12f58bacf | |||
702348a365 | |||
f6507e5461 | |||
7e42b10078 | |||
cb432fd306 | |||
a6d38efc7d | |||
85a6d79151 | |||
320231c70b | |||
7571ef42c6 | |||
88d6fbffb7 | |||
01d3c27d41 | |||
bf52d9e321 | |||
7339aeeae5 | |||
40302ddd41 | |||
8cb5175630 | |||
5bfc7e45ac | |||
873f056cc3 | |||
9b39f43595 | |||
9c43461e02 | |||
53aa377bbb | |||
f09e6f2e85 | |||
845b357bef | |||
482ac6d949 | |||
95700b6916 | |||
45fbd789d6 | |||
e96f7127bf | |||
e9ca265068 | |||
5b0d597c09 | |||
1f08cf4b31 | |||
3772e9e3a6 | |||
94324c502e | |||
ce0db53a90 | |||
599cac5811 | |||
4c90f9078b | |||
56250a226f | |||
d8faf424ad | |||
240a4f2f1a | |||
9a7629459e | |||
bacc971007 | |||
8e16524e1c | |||
e758012948 | |||
27dac123f4 | |||
912b98d2d7 | |||
5be3c7d684 | |||
dead1e3ee2 | |||
07c156c30b | |||
a790b6f960 | |||
d9b0dce41a | |||
e568e88617 | |||
7b83dce96a | |||
f2f4d4ddab |
@ -1,8 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.8..3.22)
|
||||
|
||||
project(minitar LANGUAGES C VERSION 1.3.1)
|
||||
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
|
||||
@ -30,3 +30,5 @@ endif()
|
||||
|
||||
install(TARGETS minitar DESTINATION lib)
|
||||
install(FILES minitar.h DESTINATION include)
|
||||
|
||||
add_subdirectory(examples)
|
||||
|
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2022, apio
|
||||
Copyright (c) 2022-2023, apio
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
|
123
README.md
123
README.md
@ -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,127 +32,24 @@ 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`.
|
||||
|
||||
## Functions
|
||||
### minitar_open
|
||||
`int minitar_open(const char* pathname, struct minitar* mp)`
|
||||
## Documentation
|
||||
|
||||
Initializes the caller-provided `mp` structure by opening the archive pointed to by `pathname` for reading. Returns 0 on success, anything else is failure.
|
||||
See the [build instructions](docs/Build.md) to start using minitar.
|
||||
|
||||
### 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_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. In this case, the state of `out` is unspecified and might have been changed by the function.
|
||||
|
||||
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, 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()` 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. 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`.
|
||||
|
||||
## Types
|
||||
|
||||
### minitar_file_type
|
||||
`enum minitar_file_type`
|
||||
|
||||
This enum lists all supported file types:
|
||||
|
||||
`MTAR_REGULAR`: Regular files
|
||||
|
||||
`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.
|
||||
|
||||
### 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[]`)
|
||||
|
||||
`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`)
|
||||
See the [API documentation](docs/API.md) for a full description of all functions and types.
|
||||
|
||||
## Error handling
|
||||
|
||||
|
10
changelog.sh
Executable file
10
changelog.sh
Executable 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'
|
183
docs/API.md
Normal file
183
docs/API.md
Normal file
@ -0,0 +1,183 @@
|
||||
# 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.
|
42
docs/Build.md
Normal file
42
docs/Build.md
Normal 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
12
examples/CMakeLists.txt
Normal 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
32
examples/list.c
Normal 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
100
examples/pack.c
Normal 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
227
examples/untar.c
Normal 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;
|
||||
}
|
53
minitar.h
53
minitar.h
@ -1,23 +1,62 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
fpos_t _mt_position;
|
||||
};
|
||||
|
||||
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;
|
||||
@ -26,12 +65,14 @@ 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;
|
||||
fpos_t position;
|
||||
struct minitar_entry_internal _internal;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
@ -40,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
|
||||
}
|
||||
|
86
src/tar.c
86
src/tar.c
@ -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;
|
||||
@ -42,7 +77,7 @@ static int minitar_try_to_read_valid_entry(struct minitar* mp, struct minitar_en
|
||||
|
||||
// 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->position)) return -1;
|
||||
if (fgetpos(mp->stream, &out->_internal._mt_position)) return -1;
|
||||
|
||||
minitar_parse_metadata_from_tar_header(&hdr, &out->metadata);
|
||||
if (out->metadata.size)
|
||||
@ -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;
|
||||
@ -121,7 +197,7 @@ size_t minitar_read_contents(struct minitar* mp, struct minitar_entry* entry, ch
|
||||
// Save the current position
|
||||
if (fgetpos(mp->stream, ¤t_position)) return 0;
|
||||
// Move to the position stored in the entry
|
||||
if (fsetpos(mp->stream, &entry->position)) return 0;
|
||||
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;
|
||||
|
14
src/tar.h
14
src/tar.h
@ -1,8 +1,18 @@
|
||||
/*
|
||||
* 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];
|
||||
@ -23,6 +33,8 @@ struct tar_header
|
||||
char prefix[155];
|
||||
|
||||
char padding[12]; // Not part of the header, only used to make the structure 512 bytes
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif
|
||||
|
129
src/util.c
129
src/util.c
@ -1,12 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2022-2023, apio.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* util.c: Utility functions for minitar.
|
||||
*/
|
||||
|
||||
#include "minitar.h"
|
||||
#include "tar.h"
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifndef __TINYC__
|
||||
#include <stdnoreturn.h>
|
||||
@ -14,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
|
||||
@ -28,11 +34,6 @@ WEAK noreturn void minitar_handle_panic(const char* message)
|
||||
abort();
|
||||
}
|
||||
|
||||
noreturn void minitar_panic(const char* message)
|
||||
{
|
||||
minitar_handle_panic(message);
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -43,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;
|
||||
}
|
||||
@ -124,7 +125,6 @@ static size_t minitar_align_down_to_block_size(size_t size)
|
||||
static char* minitar_static_dup(const char* str, size_t size)
|
||||
{
|
||||
static char result[1024];
|
||||
assert(size < 1024);
|
||||
memcpy(result, str, size);
|
||||
result[size] = 0;
|
||||
return result;
|
||||
@ -161,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
|
||||
@ -171,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);
|
||||
|
||||
@ -183,28 +187,109 @@ 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_panic("Links to other files within a tar archive are unsupported");
|
||||
case '2': minitar_panic("Symbolic links are unsupported");
|
||||
case '3': minitar_panic("Character devices are unsupported");
|
||||
case '4': minitar_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_panic("FIFOs are unsupported");
|
||||
default: minitar_panic("Unknown entry type in tar header");
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
#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);
|
||||
}
|
||||
|
||||
@ -212,7 +297,7 @@ 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_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");
|
||||
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");
|
||||
return 1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user