microtar: Update to latest upstream commit

This is a major overhaul of the library with some API
changes and ease of use improvements, particularly for
creating new archives.

The updated functionality is intended to support a new
X1000 installer framework.

Change-Id: Icf192bb546831231d49303fbf529ef1c1ce8905c
This commit is contained in:
Aidan MacDonald 2021-11-26 14:37:36 +00:00
parent 4052a9ddcf
commit c1709e3194
12 changed files with 1468 additions and 611 deletions

3
lib/microtar/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.o
*.a
/mtar

View file

@ -1,4 +1,5 @@
Copyright (c) 2017 rxi
Copyright (c) 2021 Aidan MacDonald
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

25
lib/microtar/Makefile Normal file
View file

@ -0,0 +1,25 @@
CPPFLAGS = -Isrc
CFLAGS = -std=c99 -Wall -Wextra -O2
MTAR_OBJ = mtar.o
MTAR_BIN = mtar
MICROTAR_OBJ = src/microtar.o src/microtar-stdio.o
MICROTAR_LIB = libmicrotar.a
$(MTAR_BIN): $(MTAR_OBJ) $(MICROTAR_LIB)
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^
$(MICROTAR_LIB): $(MICROTAR_OBJ)
$(AR) cr $@ $^
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
src/microtar.o: src/microtar.h
src/microtar-stdio.o: src/microtar.h src/microtar-stdio.h
mtar.o: src/microtar.h src/microtar-stdio.h
clean:
rm -f $(MICROTAR_LIB) $(MICROTAR_OBJ)
rm -f $(MTAR_BIN) $(MTAR_OBJ)

View file

@ -1,128 +1,268 @@
# microtar
A lightweight tar library written in ANSI C
A lightweight tar library written in ANSI C.
This version is a fork of [rxi's microtar](https://github.com/rxi/microtar)
with bugfixes and API changes aimed at improving usability, but still keeping
with the minimal design of the original library.
## License
This library is free software; you can redistribute it and/or modify it under
the terms of the MIT license. See [LICENSE](LICENSE) for details.
## Modifications from upstream
## Supported format variants
[Upstream](https://github.com/rxi/microtar) has numerous bugs and gotchas,
which I fixed in order to improve the overall robustness of the library.
A summary of my changes, in no particular order:
- Fix possible sscanf beyond the bounds of the input buffer
- Fix possible buffer overruns due to strcpy on untrusted input
- Fix incorrect octal formatting by sprintf and possible output overrruns
- Catch read/writes which are too big and handle them gracefully
- Handle over-long names in `mtar_write_file_header` / `mtar_write_dir_header`
- Ensure strings in `mtar_header_t` are always null-terminated
- Save and load group information so we don't lose information
- Move `mtar_open()` to `microtar-stdio.c` so `microtar.c` can be used in
a freestanding environment
- Allow control of stack usage by moving temporary variables into `mtar_t`,
so the caller can decide whether to use the stack or heap
An up-to-date copy of this modified version can be found
[here](https://github.com/amachronic/microtar).
No effort has been put into handling every tar format variant. Basically
what is accepted is the "old-style" format, which appears to work well
enough to access basic archives created by GNU `tar`.
## Modifications for Rockbox
## Basic usage
Added file `microtar-rockbox.c` implementing `mtar_open()` with native
Rockbox filesystem API.
The library consists of two files, `microtar.c` and `microtar.h`, which only
depend on a tiny part of the standard C library & can be easily incorporated
into a host project's build system.
The core library does not include any I/O hooks as these are supposed to be
provided by the host application. If the C library's `fopen` and friends is
good enough, you can use `microtar-stdio.c`.
## Basic Usage
The library consists of `microtar.c` and `microtar.h`. These two files can be
dropped into an existing project and compiled along with it.
### Initialization
Initialization is very simple. Everything the library needs is contained in
the `mtar_t` struct; there is no memory allocation and no global state. It is
enough to zero-initialize an `mtar_t` object to put it into a "closed" state.
You can use `mtar_is_open()` to query whether the archive is open or not.
An archive can be opened for reading _or_ writing, but not both. You have to
specify which access mode you're using when you create the archive.
#### Reading
```c
mtar_t tar;
mtar_header_t h;
char *p;
mtar_init(&tar, MTAR_READ, my_io_ops, my_stream);
```
/* Open archive for reading */
mtar_open(&tar, "test.tar", "r");
Or if using `microtar-stdio.c`:
/* Print all file names and sizes */
while ( (mtar_read_header(&tar, &h)) != MTAR_ENULLRECORD ) {
printf("%s (%d bytes)\n", h.name, h.size);
mtar_next(&tar);
```c
int error = mtar_open(&tar, "file.tar", "rb");
if(error) {
/* do something about it */
}
```
Note that `mtar_init()` is called for you in this case and the access mode is
deduced from the mode flags.
### Iterating and locating files
If you opened an archive for reading, you'll likely want to iterate over
all the files. Here's the long way of doing it:
```c
mtar_t tar;
int err;
/* Go to the start of the archive... Not necessary if you've
* just opened the archive and are already at the beginning.
* (And of course you normally want to check the return value.) */
mtar_rewind(&tar);
/* Iterate over the archive members */
while((err = mtar_next(&tar)) == MTAR_ESUCCESS) {
/* Get a pointer to the current file header. It will
* remain valid until you move to another record with
* mtar_next() or call mtar_rewind() */
const mtar_header_t* header = mtar_get_header(&tar);
printf("%s (%d bytes)\n", header->name, header->size);
}
/* Load and print contents of file "test.txt" */
mtar_find(&tar, "test.txt", &h);
p = calloc(1, h.size + 1);
mtar_read_data(&tar, p, h.size);
printf("%s", p);
free(p);
/* Close archive */
mtar_close(&tar);
if(err != MTAR_ENULLRECORD) {
/* ENULLRECORD means we hit end of file; any
* other return value is an actual error. */
}
```
#### Writing
There's a useful shortcut for this type of iteration which removes the
loop boilerplate, replacing it with another kind of boilerplate that may
be more palatable in some cases.
```c
mtar_t tar;
const char *str1 = "Hello world";
const char *str2 = "Goodbye world";
/* Will be called for each archive member visited by mtar_foreach().
* The member's header is passed in as an argument so you don't need
* to fetch it manually with mtar_get_header(). You can freely read
* data (if present) and seek around. There is no special cleanup
* required and it is not necessary to read to the end of the stream.
*
* The callback should return zero (= MTAR_SUCCESS) to continue the
* iteration or return nonzero to abort. On abort, the value returned
* by the callback will be returned from mtar_foreach(). Since it may
* also return normal microtar error codes, it is suggested to use a
* positive value or pass the result via 'arg'.
*/
int foreach_cb(mtar_t* tar, const mtar_header_t* header, void* arg)
{
// ...
return 0;
}
/* Open archive for writing */
mtar_open(&tar, "test.tar", "w");
void main()
{
mtar_t tar;
/* Write strings to files `test1.txt` and `test2.txt` */
mtar_write_file_header(&tar, "test1.txt", strlen(str1));
mtar_write_data(&tar, str1, strlen(str1));
mtar_write_file_header(&tar, "test2.txt", strlen(str2));
mtar_write_data(&tar, str2, strlen(str2));
// ...
/* Finalize -- this needs to be the last thing done before closing */
mtar_finalize(&tar);
/* Close archive */
mtar_close(&tar);
int ret = mtar_foreach(&tar, foreach_cb, NULL);
if(ret < 0) {
/* Microtar error codes are negative and may be returned if
* there is a problem with the iteration. */
} else if(ret == MTAR_ESUCCESS) {
/* If the iteration reaches the end of the archive without
* errors, the return code is MTAR_ESUCCESS. */
} else if(ret > 0) {
/* Positive values might be returned by the callback to
* signal some condition was met; they'll never be returned
* by microtar */
}
}
```
The other thing you're likely to do is look for a specific file:
```c
/* Seek to a specific member in the archive */
int err = mtar_find(&tar, "foo.txt");
if(err == MTAR_ESUCCESS) {
/* File was found -- read the header with mtar_get_header() */
} else if(err == MTAR_ENOTFOUND) {
/* File wasn't in the archive */
} else {
/* Some error occurred */
}
```
Note this isn't terribly efficient since it scans the entire archive
looking for the file.
### Reading file data
Once pointed at a file via `mtar_next()` or `mtar_find()` you can read the
data with a simple POSIX-like API.
- `mtar_read_data(tar, buf, count)` reads up to `count` bytes into `buf`,
returning the actual number of bytes read, or a negative error value.
If at EOF, this returns zero.
- `mtar_seek_data(tar, offset, whence)` works exactly like `fseek()` with
`whence` being one of `SEEK_SET`, `SEEK_CUR`, or `SEEK_END` and `offset`
indicating a point relative to the beginning, current position, or end
of the file. Returns zero on success, or a negative error code.
- `mtar_eof_data(tar)` returns nonzero if the end of the file has been
reached. It is possible to seek backward to clear this condition.
### Writing archives
Microtar has limited support for creating archives. When an archive is opened
for writing, you can add new members using `mtar_write_header()`.
- `mtar_write_header(tar, header)` writes out the header for a new member.
The amount of data that follows is dictated by `header->size`, though if
the underlying stream supports seeking and re-writing data, this size can
be updated later with `mtar_update_header()` or `mtar_update_file_size()`.
- `mtar_update_header(tar, header)` will re-write the previously written
header. This may be used to change any header field. The underlying stream
must support seeking. On a successful return the stream will be returned
to the position it was at before the call.
File data can be written with `mtar_write_data()`, and if the underlying stream
supports seeking, you can seek with `mtar_seek_data()` and read back previously
written data with `mtar_read_data()`. Note that it is not possible to truncate
the file stream by any means.
- `mtar_write_data(tar, buf, count)` will write up to `count` bytes from
`buf` to the current member's data. Returns the number of bytes actually
written or a negative error code.
- `mtar_update_file_size(tar)` will update the header size to reflect the
actual amount of written data. This is intended to be called right before
`mtar_end_data()` if you are not declaring file sizes in advance.
- `mtar_end_data(tar)` will end the current member. It will complain if you
did not write the correct amount data provided in the header. This must be
called before writing the next header.
- `mtar_finalize(tar)` is called after you have written all members to the
archive. It writes out some null records which mark the end of the archive,
so you cannot write any more archive members after this.
Note that `mtar_close()` can fail if there was a problem flushing buffered
data to disk, so its return value should always be checked.
## Error handling
All functions which return an `int` will return `MTAR_ESUCCESS` if the operation
is successful. If an error occurs an error value less-than-zero will be
returned; this value can be passed to the function `mtar_strerror()` to get its
corresponding error string.
Most functions that return `int` return an error code from `enum mtar_error`.
Zero is success and all other error codes are negative. `mtar_strerror()` can
return a string describing the error code.
A couple of functions use a different return value convention:
- `mtar_foreach()` may error codes or an arbitrary nonzero value provided
by the callback.
- `mtar_read_data()` and `mtar_write_data()` returns the number of bytes read
or written, or a negative error code. In particular zero means that no bytes
were read or written.
- `mtar_get_header()` may return `NULL` if there is no valid header.
It is only possible to see a null pointer if misusing the API or after
a previous error so checking for this is usually not necessary.
There is essentially no support for error recovery. After an error you can
only do two things reliably: close the archive with `mtar_close()` or try
rewinding to the beginning with `mtar_rewind()`.
## Wrapping a stream
If you want to read or write from something other than a file, the `mtar_t`
struct can be manually initialized with your own callback functions and a
`stream` pointer.
## I/O hooks
All callback functions are passed a pointer to the `mtar_t` struct as their
first argument. They should return `MTAR_ESUCCESS` if the operation succeeds
without an error, or an integer below zero if an error occurs.
You can provide your own I/O hooks in a `mtar_ops_t` struct. The same ops
struct can be shared among multiple `mtar_t` objects but each object gets
its own `void* stream` pointer.
After the `stream` field has been set, all required callbacks have been set and
all unused fields have been zeroset the `mtar_t` struct can be safely used with
the microtar functions. `mtar_open` *should not* be called if the `mtar_t`
struct was initialized manually.
Name | Arguments | Required
--------|-------------------------------------------|------------
`read` | `void* stream, void* data, unsigned size` | If reading
`write` | `void* stream, void* data, unsigned size` | If writing
`seek` | `void* stream, unsigned pos` | If reading
`close` | `void* stream` | Always
#### Reading
The following callbacks should be set for reading an archive from a stream:
`read` and `write` should transfer the number of bytes indicated
and return the number of bytes actually read or written, or a negative
`enum mtar_error` code on error.
Name | Arguments | Description
--------|------------------------------------------|---------------------------
`read` | `mtar_t *tar, void *data, unsigned size` | Read data from the stream
`seek` | `mtar_t *tar, unsigned pos` | Set the position indicator
`close` | `mtar_t *tar` | Close the stream
`seek` must have semantics like `lseek(..., pos, SEEK_SET)`; that is,
the position is an absolute byte offset in the stream. Seeking is not
optional for read support, but the library only performs backward
seeks under two circumstances:
#### Writing
The following callbacks should be set for writing an archive to a stream:
- `mtar_rewind()` seeks to position 0.
- `mtar_seek_data()` may seek backward if the user requests it.
Name | Arguments | Description
--------|------------------------------------------------|---------------------
`write` | `mtar_t *tar, const void *data, unsigned size` | Write data to the stream
Therefore, you will be able to get away with a limited forward-only
seek function if you're able to read everything in a single pass use
the API carefully. Note `mtar_find()` and `mtar_foreach()` will call
`mtar_rewind()`.
`close` is called by `mtar_close()` to clean up the stream. Note the
library assumes that the stream handle is cleaned up by `close` even
if an error occurs.
## License
This library is free software; you can redistribute it and/or modify it under
the terms of the MIT license. See [LICENSE](LICENSE) for details.
`seek` and `close` should return an `enum mtar_error` code, either
`MTAR_SUCCESS`, or a negative value on error.

View file

@ -0,0 +1,14 @@
Upstream commit/repo
1e921369b2c92bb219fcef84a37d4d2347794c0f
https://github.com/amachronic/microtar
Summary of Rockbox-specific changes:
- microtar.make, SOURCES
Target build system integration.
- microtar-rockbox.c, microtar-rockbox.h
Implements mtar ops with Rockbox filesystem API.
- microtar.c
Define strncpy since Rockbox's libc doesn't define it.

243
lib/microtar/mtar.c Normal file
View file

@ -0,0 +1,243 @@
/*
* Copyright (c) 2021 Aidan MacDonald
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "microtar-stdio.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
/* exit codes */
#define E_TAR 1
#define E_FS 2
#define E_OTHER 4
#define E_ARGS 8
enum {
OP_LIST,
OP_CREATE,
OP_EXTRACT,
};
void die(int err, const char* msg, ...)
{
fprintf(stderr, "mtar: ");
va_list ap;
va_start(ap, msg);
vfprintf(stderr, msg, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(err);
}
int list_foreach_cb(mtar_t* tar, const mtar_header_t* h, void* arg)
{
(void)tar;
(void)arg;
printf("%s\n", h->name);
return 0;
}
void list_files(mtar_t* tar)
{
int err = mtar_foreach(tar, list_foreach_cb, NULL);
if(err)
die(E_TAR, "listing failed: %s", mtar_strerror(err));
}
struct extract_args {
char** names;
int count;
};
int extract_foreach_cb(mtar_t* tar, const mtar_header_t* h, void* arg)
{
struct extract_args* args = arg;
(void)args; /* TODO */
if(h->type == MTAR_TDIR) {
if(mkdir(h->name, h->mode) != 0)
die(E_FS, "cannot create directory \"%s\"", h->name);
return 0;
}
if(h->type != MTAR_TREG) {
fprintf(stderr, "warning: not extracting unsupported type \"%s\"", h->name);
return 0;
}
int fd = open(h->name, O_CREAT|O_WRONLY|O_TRUNC, h->mode);
if(fd < 0)
die(E_FS, "extracting \"%s\" failed: %s", h->name, strerror(errno));
char iobuf[1024];
while(!mtar_eof_data(tar)) {
int rcount = mtar_read_data(tar, iobuf, sizeof(iobuf));
if(rcount < 0)
die(E_TAR, "extracting \"%s\" failed: %s", h->name, mtar_strerror(rcount));
int wcount = write(fd, iobuf, rcount);
if(wcount != rcount)
die(E_FS, "extracting \"%s\" failed: %s", h->name, strerror(errno));
}
close(fd);
return 0;
}
void extract_files(mtar_t* tar, char** files, int num_files)
{
struct extract_args args;
args.names = files;
args.count = num_files;
int err = mtar_foreach(tar, extract_foreach_cb, &args);
if(err)
die(E_TAR, "extraction failed: %s", mtar_strerror(err));
}
void add_files(mtar_t* tar, char** files, int num_files)
{
for(int i = 0; i < num_files; ++i) {
int fd = open(files[i], O_RDONLY);
if(fd < 0)
die(E_FS, "adding \"%s\" failed: %s", files[i], strerror(errno));
off_t off = lseek(fd, 0, SEEK_END);
if(off < 0)
die(E_FS, "adding \"%s\" failed: %s", files[i], strerror(errno));
unsigned filesize = off;
lseek(fd, 0, SEEK_SET);
int err = mtar_write_file_header(tar, files[i], filesize);
if(err)
die(E_TAR, "adding \"%s\" failed: %s", files[i], mtar_strerror(err));
char iobuf[1024];
while(1) {
int rcount = read(fd, iobuf, sizeof(iobuf));
if(rcount < 0)
die(E_FS, "adding \"%s\" failed: %s", files[i], strerror(errno));
if(rcount == 0)
break;
int wcount = mtar_write_data(tar, iobuf, rcount);
if(wcount < 0)
die(E_TAR, "adding \"%s\" failed: %s", files[i], mtar_strerror(wcount));
if(wcount != rcount)
die(E_TAR, "adding \"%s\" failed: write too short %d/%d", files[i], wcount, rcount);
}
close(fd);
err = mtar_end_data(tar);
if(err)
die(E_TAR, "adding \"%s\" failed: %s", files[i], mtar_strerror(err));
}
}
int main(int argc, char* argv[])
{
++argv, --argc;
if(argc == 0)
die(E_ARGS, "no input files");
if(!strcmp(*argv, "--help")) {
printf(
"usage:\n"
" mtar list tar-file\n"
" List the members of the given tar archive, one filename per line.\n"
"\n"
" mtar create tar-file members...\n"
" mtar add tar-file members...\n"
" Create a new tar archive from the files listed on the command line.\n"
" WARNING: Any existing file at tar-file will be overwritten!\n"
"\n"
" mtar extract tar-file [members...]\n"
" Extract the contents of the tar archive to the current directory.\n"
" If filenames are given, only the named members will be extracted.\n"
"\n");
exit(E_ARGS);
}
int op;
if(!strcmp(*argv, "list"))
op = OP_LIST;
else if(!strcmp(*argv, "create"))
op = OP_CREATE;
else if(!strcmp(*argv, "extract"))
op = OP_EXTRACT;
else
die(E_ARGS, "invalid operation \"%s\"", *argv);
++argv, --argc;
if(argc == 0)
die(E_ARGS, "missing archive name");
const char* archive_name = *argv;
++argv, --argc;
if(op == OP_LIST && argc != 0)
die(E_ARGS, "excess arguments on command line");
const char* mode = "rb";
if(op == OP_CREATE)
mode = "wb";
mtar_t tar;
int err = mtar_open(&tar, archive_name, mode);
if(err)
die(E_TAR, "can't open archive: %s", mtar_strerror(err));
switch(op) {
case OP_LIST:
list_files(&tar);
break;
case OP_EXTRACT:
extract_files(&tar, argv, argc);
break;
case OP_CREATE:
add_files(&tar, argv, argc);
err = mtar_finalize(&tar);
if(err)
die(E_TAR, "failed to finalize archive: %s", mtar_strerror(err));
break;
default:
die(E_OTHER, "not implemented");
break;
}
err = mtar_close(&tar);
if(err)
die(E_TAR, "failed to close archive: %s", mtar_strerror(err));
return 0;
}

View file

@ -20,81 +20,72 @@
****************************************************************************/
#include "file.h"
#include "microtar.h"
#include "microtar-rockbox.h"
#include <stdint.h>
static int fd_write(mtar_t *tar, const void *data, unsigned size) {
intptr_t fd = (intptr_t)tar->stream;
ssize_t res = write(fd, data, size);
if(res < 0 || ((unsigned)res != size))
return MTAR_EWRITEFAIL;
else
return MTAR_ESUCCESS;
}
static int fd_read(mtar_t *tar, void *data, unsigned size) {
intptr_t fd = (intptr_t)tar->stream;
ssize_t res = read(fd, data, size);
if(res < 0 || ((unsigned)res != size))
static int fd_read(void* stream, void* data, unsigned size)
{
ssize_t res = read((intptr_t)stream, data, size);
if(res < 0)
return MTAR_EREADFAIL;
else
return MTAR_ESUCCESS;
return res;
}
static int fd_seek(mtar_t *tar, unsigned offset) {
intptr_t fd = (intptr_t)tar->stream;
off_t res = lseek(fd, offset, SEEK_SET);
static int fd_write(void* stream, const void* data, unsigned size)
{
ssize_t res = write((intptr_t)stream, data, size);
if(res < 0)
return MTAR_EWRITEFAIL;
return res;
}
static int fd_seek(void* stream, unsigned offset)
{
off_t res = lseek((intptr_t)stream, offset, SEEK_SET);
if(res < 0 || ((unsigned)res != offset))
return MTAR_ESEEKFAIL;
else
return MTAR_ESUCCESS;
}
static int fd_close(mtar_t *tar) {
intptr_t fd = (intptr_t)tar->stream;
close(fd);
static int fd_close(void* stream)
{
close((intptr_t)stream);
return MTAR_ESUCCESS;
}
static const mtar_ops_t fd_ops = {
.read = fd_read,
.write = fd_write,
.seek = fd_seek,
.close = fd_close,
};
int mtar_open(mtar_t *tar, const char *filename, const char *mode) {
int err;
int openmode;
int fd;
int mtar_open(mtar_t* tar, const char* filename, int flags)
{
/* Determine access mode */
int access;
switch(flags & O_ACCMODE) {
case O_RDONLY:
access = MTAR_READ;
break;
/* Init tar struct and functions */
memset(tar, 0, sizeof(*tar));
tar->write = fd_write;
tar->read = fd_read;
tar->seek = fd_seek;
tar->close = fd_close;
case O_WRONLY:
access = MTAR_WRITE;
break;
/* Get correct mode flags */
if ( strchr(mode, 'r') )
openmode = O_RDONLY;
else if ( strchr(mode, 'w') )
openmode = O_CREAT|O_TRUNC|O_WRONLY;
else if ( strchr(mode, 'a') )
openmode = O_WRONLY|O_APPEND;
else
return MTAR_EFAILURE;
default:
/* Note - O_RDWR isn't very useful so we don't allow it */
return MTAR_EAPI;
}
/* Open file */
fd = open(filename, openmode);
int fd = open(filename, flags);
if(fd < 0)
return MTAR_EOPENFAIL;
tar->stream = (void*)(intptr_t)fd;
/* Read first header to check it is valid if mode is `r` */
if ( openmode & O_RDONLY ) {
err = mtar_read_header(tar, &tar->header);
if (err != MTAR_ESUCCESS) {
mtar_close(tar);
return err;
}
}
/* Return ok */
/* Init tar struct and functions */
mtar_init(tar, access, &fd_ops, (void*)(intptr_t)fd);
return MTAR_ESUCCESS;
}

View file

@ -0,0 +1,29 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2021 Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef MICROTAR_ROCKBOX_H
#define MICROTAR_ROCKBOX_H
#include "microtar.h"
int mtar_open(mtar_t* tar, const char* filename, int flags);
#endif

View file

@ -1,65 +1,88 @@
/**
/*
* Copyright (c) 2017 rxi
* Copyright (c) 2021 Aidan MacDonald
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `microtar.c` for details.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "microtar-stdio.h"
#include <stdio.h>
#include <string.h>
#include "microtar.h"
static int file_read(void* stream, void* data, unsigned size)
{
unsigned res = fread(data, 1, size, (FILE*)stream);
if(res != size && ferror((FILE*)stream))
return MTAR_EREADFAIL;
static int file_write(mtar_t *tar, const void *data, unsigned size) {
unsigned res = fwrite(data, 1, size, tar->stream);
return (res == size) ? MTAR_ESUCCESS : MTAR_EWRITEFAIL;
return res;
}
static int file_read(mtar_t *tar, void *data, unsigned size) {
unsigned res = fread(data, 1, size, tar->stream);
return (res == size) ? MTAR_ESUCCESS : MTAR_EREADFAIL;
static int file_write(void* stream, const void* data, unsigned size)
{
unsigned res = fwrite(data, 1, size, (FILE*)stream);
if(res != size && ferror((FILE*)stream))
return MTAR_EWRITEFAIL;
return res;
}
static int file_seek(mtar_t *tar, unsigned offset) {
int res = fseek(tar->stream, offset, SEEK_SET);
static int file_seek(void* stream, unsigned offset)
{
int res = fseek((FILE*)stream, offset, SEEK_SET);
return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
}
static int file_close(mtar_t *tar) {
fclose(tar->stream);
return MTAR_ESUCCESS;
static int file_close(void* stream)
{
int err = fclose((FILE*)stream);
return (err == 0 ? MTAR_ESUCCESS : MTAR_EFAILURE);
}
static const mtar_ops_t file_ops = {
.read = file_read,
.write = file_write,
.seek = file_seek,
.close = file_close,
};
int mtar_open(mtar_t *tar, const char *filename, const char *mode) {
int err;
mtar_header_t h;
int mtar_open(mtar_t* tar, const char* filename, const char* mode)
{
/* Determine access mode */
int access;
char* read = strchr(mode, 'r');
char* write = strchr(mode, 'w');
if(read) {
if(write)
return MTAR_EAPI;
access = MTAR_READ;
} else if(write) {
access = MTAR_WRITE;
} else {
return MTAR_EAPI;
}
/* Init tar struct and functions */
memset(tar, 0, sizeof(*tar));
tar->write = file_write;
tar->read = file_read;
tar->seek = file_seek;
tar->close = file_close;
/* Assure mode is always binary */
if ( strchr(mode, 'r') ) mode = "rb";
if ( strchr(mode, 'w') ) mode = "wb";
if ( strchr(mode, 'a') ) mode = "ab";
/* Open file */
tar->stream = fopen(filename, mode);
if (!tar->stream) {
FILE* file = fopen(filename, mode);
if(!file)
return MTAR_EOPENFAIL;
}
/* Read first header to check it is valid if mode is `r` */
if (*mode == 'r') {
err = mtar_read_header(tar, &h);
if (err != MTAR_ESUCCESS) {
mtar_close(tar);
return err;
}
}
/* Return ok */
mtar_init(tar, access, &file_ops, file);
return MTAR_ESUCCESS;
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2017 rxi
* Copyright (c) 2021 Aidan MacDonald
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef MICROTAR_STDIO_H
#define MICROTAR_STDIO_H
#include "microtar.h"
#ifdef __cplusplus
extern "C" {
#endif
int mtar_open(mtar_t* tar, const char* filename, const char* mode);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2017 rxi
* Copyright (c) 2021 Aidan MacDonald
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -20,16 +21,14 @@
* IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <stddef.h>
#include "microtar.h"
#include <limits.h>
#include <string.h>
#include "microtar.h"
#ifdef ROCKBOX
/* Rockbox lacks strncpy in its libc */
static char* strncpy(char* dest, const char* src, size_t n) {
#define strncpy my_strncpy
static char* my_strncpy(char* dest, const char* src, size_t n) {
size_t i;
for(i = 0; i < n && *src; ++i)
@ -42,9 +41,32 @@ static char* strncpy(char* dest, const char* src, size_t n) {
}
#endif
enum {
S_HEADER_VALID = 1 << 0,
S_WROTE_HEADER = 1 << 1,
S_WROTE_DATA = 1 << 2,
S_WROTE_DATA_EOF = 1 << 3,
S_WROTE_FINALIZE = 1 << 4,
};
static int parse_octal(const char* str, size_t len, unsigned* ret) {
enum {
NAME_OFF = 0, NAME_LEN = 100,
MODE_OFF = NAME_OFF+NAME_LEN, MODE_LEN = 8,
OWNER_OFF = MODE_OFF+MODE_LEN, OWNER_LEN = 8,
GROUP_OFF = OWNER_OFF+OWNER_LEN, GROUP_LEN = 8,
SIZE_OFF = GROUP_OFF+GROUP_LEN, SIZE_LEN = 12,
MTIME_OFF = SIZE_OFF+SIZE_LEN, MTIME_LEN = 12,
CHKSUM_OFF = MTIME_OFF+MTIME_LEN, CHKSUM_LEN = 8,
TYPE_OFF = CHKSUM_OFF+CHKSUM_LEN,
LINKNAME_OFF = TYPE_OFF+1, LINKNAME_LEN = 100,
HEADER_LEN = 512,
};
static int parse_octal(const char* str, size_t len, unsigned* ret)
{
unsigned n = 0;
while(len-- > 0 && *str != 0) {
if(*str < '0' || *str > '9')
return MTAR_EOVERFLOW;
@ -55,6 +77,7 @@ static int parse_octal(const char* str, size_t len, unsigned* ret) {
n *= 8;
char r = *str++ - '0';
if(n > UINT_MAX - r)
return MTAR_EOVERFLOW;
else
@ -65,8 +88,8 @@ static int parse_octal(const char* str, size_t len, unsigned* ret) {
return MTAR_ESUCCESS;
}
static int print_octal(char* str, size_t len, unsigned value) {
static int print_octal(char* str, size_t len, unsigned value)
{
/* move backwards over the output string */
char* ptr = str + len - 1;
*ptr = 0;
@ -90,314 +113,613 @@ static int print_octal(char* str, size_t len, unsigned value) {
return MTAR_ESUCCESS;
}
static unsigned round_up(unsigned n, unsigned incr) {
return n + (incr - n % incr) % incr;
static unsigned round_up_512(unsigned n)
{
return (n + 511u) & ~511u;
}
static int tread(mtar_t* tar, void* data, unsigned size)
{
int ret = tar->ops->read(tar->stream, data, size);
if(ret >= 0)
tar->pos += ret;
static unsigned checksum(const mtar_raw_header_t* rh) {
unsigned i;
unsigned char *p = (unsigned char*) rh;
unsigned res = 256;
for (i = 0; i < offsetof(mtar_raw_header_t, checksum); i++) {
res += p[i];
}
for (i = offsetof(mtar_raw_header_t, type); i < sizeof(*rh); i++) {
res += p[i];
}
return res;
return ret;
}
static int twrite(mtar_t* tar, const void* data, unsigned size)
{
int ret = tar->ops->write(tar->stream, data, size);
if(ret >= 0)
tar->pos += ret;
static int tread(mtar_t *tar, void *data, unsigned size) {
int err = tar->read(tar, data, size);
tar->pos += size;
return ret;
}
static int tseek(mtar_t* tar, unsigned pos)
{
int err = tar->ops->seek(tar->stream, pos);
tar->pos = pos;
return err;
}
static int write_null_bytes(mtar_t* tar, size_t count)
{
int ret;
size_t n;
static int twrite(mtar_t *tar, const void *data, unsigned size) {
int err = tar->write(tar, data, size);
tar->pos += size;
return err;
}
memset(tar->buffer, 0, sizeof(tar->buffer));
while(count > 0) {
n = count < sizeof(tar->buffer) ? count : sizeof(tar->buffer);
ret = twrite(tar, tar->buffer, n);
if(ret < 0)
return ret;
if(ret != (int)n)
return MTAR_EWRITEFAIL;
static int write_null_bytes(mtar_t *tar, int n) {
int i, err;
char nul = '\0';
for (i = 0; i < n; i++) {
err = twrite(tar, &nul, 1);
if (err) {
return err;
}
count -= n;
}
return MTAR_ESUCCESS;
}
static unsigned checksum(const char* raw)
{
unsigned i;
unsigned char* p = (unsigned char*)raw;
unsigned res = 256;
static int raw_to_header(mtar_header_t *h, const mtar_raw_header_t *rh) {
unsigned chksum1, chksum2;
for(i = 0; i < CHKSUM_OFF; i++)
res += p[i];
for(i = TYPE_OFF; i < HEADER_LEN; i++)
res += p[i];
return res;
}
static int raw_to_header(mtar_header_t* h, const char* raw)
{
unsigned chksum;
int rc;
/* If the checksum starts with a null byte we assume the record is NULL */
if (*rh->checksum == '\0') {
if(raw[CHKSUM_OFF] == '\0')
return MTAR_ENULLRECORD;
}
/* Build and compare checksum */
chksum1 = checksum(rh);
if((rc = parse_octal(rh->checksum, sizeof(rh->checksum), &chksum2)))
/* Compare the checksum */
if((rc = parse_octal(&raw[CHKSUM_OFF], CHKSUM_LEN, &chksum)))
return rc;
if (chksum1 != chksum2) {
if(chksum != checksum(raw))
return MTAR_EBADCHKSUM;
}
/* Load raw header into header */
if((rc = parse_octal(rh->mode, sizeof(rh->mode), &h->mode)))
if((rc = parse_octal(&raw[MODE_OFF], MODE_LEN, &h->mode)))
return rc;
if((rc = parse_octal(rh->owner, sizeof(rh->owner), &h->owner)))
if((rc = parse_octal(&raw[OWNER_OFF], OWNER_LEN, &h->owner)))
return rc;
if((rc = parse_octal(rh->group, sizeof(rh->group), &h->group)))
if((rc = parse_octal(&raw[GROUP_OFF], GROUP_LEN, &h->group)))
return rc;
if((rc = parse_octal(rh->size, sizeof(rh->size), &h->size)))
if((rc = parse_octal(&raw[SIZE_OFF], SIZE_LEN, &h->size)))
return rc;
if((rc = parse_octal(rh->mtime, sizeof(rh->mtime), &h->mtime)))
if((rc = parse_octal(&raw[MTIME_OFF], MTIME_LEN, &h->mtime)))
return rc;
h->type = rh->type;
h->type = raw[TYPE_OFF];
if(!h->type)
h->type = MTAR_TREG;
memcpy(h->name, rh->name, sizeof(rh->name));
memcpy(h->name, &raw[NAME_OFF], NAME_LEN);
h->name[sizeof(h->name) - 1] = 0;
memcpy(h->linkname, rh->linkname, sizeof(rh->linkname));
memcpy(h->linkname, &raw[LINKNAME_OFF], LINKNAME_LEN);
h->linkname[sizeof(h->linkname) - 1] = 0;
return MTAR_ESUCCESS;
}
static int header_to_raw(mtar_raw_header_t *rh, const mtar_header_t *h) {
static int header_to_raw(char* raw, const mtar_header_t* h)
{
unsigned chksum;
int rc;
memset(raw, 0, HEADER_LEN);
/* Load header into raw header */
memset(rh, 0, sizeof(*rh));
if((rc = print_octal(rh->mode, sizeof(rh->mode), h->mode)))
if((rc = print_octal(&raw[MODE_OFF], MODE_LEN, h->mode)))
return rc;
if((rc = print_octal(rh->owner, sizeof(rh->owner), h->owner)))
if((rc = print_octal(&raw[OWNER_OFF], OWNER_LEN, h->owner)))
return rc;
if((rc = print_octal(rh->group, sizeof(rh->group), h->group)))
if((rc = print_octal(&raw[GROUP_OFF], GROUP_LEN, h->group)))
return rc;
if((rc = print_octal(rh->size, sizeof(rh->size), h->size)))
if((rc = print_octal(&raw[SIZE_OFF], SIZE_LEN, h->size)))
return rc;
if((rc = print_octal(rh->mtime, sizeof(rh->mtime), h->mtime)))
if((rc = print_octal(&raw[MTIME_OFF], MTIME_LEN, h->mtime)))
return rc;
rh->type = h->type ? h->type : MTAR_TREG;
strncpy(rh->name, h->name, sizeof(rh->name));
strncpy(rh->linkname, h->linkname, sizeof(rh->linkname));
raw[TYPE_OFF] = h->type ? h->type : MTAR_TREG;
#if defined(__GNUC__) && (__GNUC__ >= 8)
/* Sigh. GCC wrongly assumes the output of strncpy() is supposed to be
* a null-terminated string -- which it is not, and we are relying on
* that fact here -- and tries to warn about 'string truncation' because
* the null terminator might not be copied. Just suppress the warning. */
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wstringop-truncation"
#endif
strncpy(&raw[NAME_OFF], h->name, NAME_LEN);
strncpy(&raw[LINKNAME_OFF], h->linkname, LINKNAME_LEN);
#if defined(__GNUC__) && (__GNUC__ >= 8)
# pragma GCC diagnostic pop
#endif
/* Calculate and write checksum */
chksum = checksum(rh);
if((rc = print_octal(rh->checksum, 7, chksum)))
chksum = checksum(raw);
if((rc = print_octal(&raw[CHKSUM_OFF], CHKSUM_LEN-1, chksum)))
return rc;
rh->checksum[7] = ' ';
raw[CHKSUM_OFF + CHKSUM_LEN - 1] = ' ';
return MTAR_ESUCCESS;
}
const char* mtar_strerror(int err) {
switch (err) {
case MTAR_ESUCCESS : return "success";
case MTAR_EFAILURE : return "failure";
case MTAR_EOPENFAIL : return "could not open";
case MTAR_EREADFAIL : return "could not read";
case MTAR_EWRITEFAIL : return "could not write";
case MTAR_ESEEKFAIL : return "could not seek";
case MTAR_EBADCHKSUM : return "bad checksum";
case MTAR_ENULLRECORD : return "null record";
case MTAR_ENOTFOUND : return "file not found";
case MTAR_EOVERFLOW : return "overflow";
}
return "unknown error";
static unsigned data_beg_pos(const mtar_t* tar)
{
return tar->header_pos + HEADER_LEN;
}
int mtar_close(mtar_t *tar) {
return tar->close(tar);
static unsigned data_end_pos(const mtar_t* tar)
{
return tar->end_pos;
}
static int ensure_header(mtar_t* tar)
{
int ret, err;
int mtar_seek(mtar_t *tar, unsigned pos) {
int err = tar->seek(tar, pos);
tar->pos = pos;
return err;
}
int mtar_rewind(mtar_t *tar) {
tar->remaining_data = 0;
tar->last_header = 0;
return mtar_seek(tar, 0);
}
int mtar_next(mtar_t *tar) {
int err, n;
/* Load header */
err = mtar_read_header(tar, &tar->header);
if (err) {
return err;
}
/* Seek to next record */
n = round_up(tar->header.size, 512) + sizeof(mtar_raw_header_t);
return mtar_seek(tar, tar->pos + n);
}
int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h) {
int err;
/* Start at beginning */
err = mtar_rewind(tar);
if (err) {
return err;
}
/* Iterate all files until we hit an error or find the file */
while ( (err = mtar_read_header(tar, &tar->header)) == MTAR_ESUCCESS ) {
if ( !strcmp(tar->header.name, name) ) {
if (h) {
*h = tar->header;
}
if(tar->state & S_HEADER_VALID)
return MTAR_ESUCCESS;
if(tar->pos > UINT_MAX - HEADER_LEN)
return MTAR_EOVERFLOW;
tar->header_pos = tar->pos;
tar->end_pos = data_beg_pos(tar);
ret = tread(tar, tar->buffer, HEADER_LEN);
if(ret < 0)
return ret;
if(ret != HEADER_LEN)
return MTAR_EREADFAIL;
err = raw_to_header(&tar->header, tar->buffer);
if(err)
return err;
if(tar->end_pos > UINT_MAX - tar->header.size)
return MTAR_EOVERFLOW;
tar->end_pos += tar->header.size;
tar->state |= S_HEADER_VALID;
return MTAR_ESUCCESS;
}
const char* mtar_strerror(int err)
{
switch(err) {
case MTAR_ESUCCESS: return "success";
case MTAR_EFAILURE: return "failure";
case MTAR_EOPENFAIL: return "could not open";
case MTAR_EREADFAIL: return "could not read";
case MTAR_EWRITEFAIL: return "could not write";
case MTAR_ESEEKFAIL: return "could not seek";
case MTAR_ESEEKRANGE: return "seek out of bounds";
case MTAR_EBADCHKSUM: return "bad checksum";
case MTAR_ENULLRECORD: return "null record";
case MTAR_ENOTFOUND: return "file not found";
case MTAR_EOVERFLOW: return "overflow";
case MTAR_EAPI: return "API usage error";
case MTAR_ENAMETOOLONG: return "name too long";
case MTAR_EWRONGSIZE: return "wrong amount of data written";
case MTAR_EACCESS: return "wrong access mode";
default: return "unknown error";
}
err = mtar_next(tar);
if (err) {
}
void mtar_init(mtar_t* tar, int access, const mtar_ops_t* ops, void* stream)
{
memset(tar, 0, sizeof(mtar_t));
tar->access = access;
tar->ops = ops;
tar->stream = stream;
}
int mtar_close(mtar_t* tar)
{
int err = tar->ops->close(tar->stream);
tar->ops = NULL;
tar->stream = NULL;
return err;
}
int mtar_is_open(mtar_t* tar)
{
return (tar->ops != NULL) ? 1 : 0;
}
mtar_header_t* mtar_get_header(mtar_t* tar)
{
if(tar->state & S_HEADER_VALID)
return &tar->header;
else
return NULL;
}
int mtar_access_mode(const mtar_t* tar)
{
return tar->access;
}
int mtar_rewind(mtar_t* tar)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_READ)
return MTAR_EAPI;
#endif
int err = tseek(tar, 0);
tar->state = 0;
return err;
}
int mtar_next(mtar_t* tar)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_READ)
return MTAR_EACCESS;
#endif
if(tar->state & S_HEADER_VALID) {
tar->state &= ~S_HEADER_VALID;
/* seek to the next header */
int err = tseek(tar, round_up_512(data_end_pos(tar)));
if(err)
return err;
}
}
/* Return error */
if (err == MTAR_ENULLRECORD) {
return ensure_header(tar);
}
int mtar_foreach(mtar_t* tar, mtar_foreach_cb cb, void* arg)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_READ)
return MTAR_EACCESS;
#endif
int err = mtar_rewind(tar);
if(err)
return err;
while((err = mtar_next(tar)) == MTAR_ESUCCESS)
if((err = cb(tar, &tar->header, arg)) != 0)
return err;
if(err == MTAR_ENULLRECORD)
err = MTAR_ESUCCESS;
return err;
}
static int find_foreach_cb(mtar_t* tar, const mtar_header_t* h, void* arg)
{
(void)tar;
const char* name = (const char*)arg;
return strcmp(name, h->name) ? 0 : 1;
}
int mtar_find(mtar_t* tar, const char* name)
{
int err = mtar_foreach(tar, find_foreach_cb, (void*)name);
if(err == 1)
err = MTAR_ESUCCESS;
else if(err == MTAR_ESUCCESS)
err = MTAR_ENOTFOUND;
}
return err;
}
int mtar_read_data(mtar_t* tar, void* ptr, unsigned size)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(!(tar->state & S_HEADER_VALID))
return MTAR_EAPI;
#endif
int mtar_read_header(mtar_t *tar, mtar_header_t *h) {
int err;
/* Save header position */
tar->last_header = tar->pos;
/* Read raw header */
err = tread(tar, &tar->raw_header, sizeof(tar->raw_header));
if (err) {
return err;
}
/* Seek back to start of header */
err = mtar_seek(tar, tar->last_header);
if (err) {
return err;
}
/* Load raw header into header struct and return */
return raw_to_header(h, &tar->raw_header);
/* have we reached end of file? */
unsigned data_end = data_end_pos(tar);
if(tar->pos >= data_end)
return 0;
/* truncate the read if it would go beyond EOF */
unsigned data_left = data_end - tar->pos;
if(data_left < size)
size = data_left;
return tread(tar, ptr, size);
}
int mtar_seek_data(mtar_t* tar, int offset, int whence)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(!(tar->state & S_HEADER_VALID))
return MTAR_EAPI;
#endif
int mtar_read_data(mtar_t *tar, void *ptr, unsigned size) {
int err;
/* If we have no remaining data then this is the first read, we get the size,
* set the remaining data and seek to the beginning of the data */
if (tar->remaining_data == 0) {
/* Read header */
err = mtar_read_header(tar, &tar->header);
if (err) {
return err;
unsigned data_beg = data_beg_pos(tar);
unsigned data_end = data_end_pos(tar);
unsigned newpos;
switch(whence) {
case SEEK_SET:
if(offset < 0)
return MTAR_ESEEKRANGE;
newpos = data_beg + offset;
break;
case SEEK_CUR:
if((offset > 0 && (unsigned) offset > data_end - tar->pos) ||
(offset < 0 && (unsigned)-offset > tar->pos - data_beg))
return MTAR_ESEEKRANGE;
newpos = tar->pos + offset;
break;
case SEEK_END:
if(offset > 0)
return MTAR_ESEEKRANGE;
newpos = data_end + offset;
break;
default:
return MTAR_EAPI;
}
/* Seek past header and init remaining data */
err = mtar_seek(tar, tar->pos + sizeof(mtar_raw_header_t));
if (err) {
return err;
}
tar->remaining_data = tar->header.size;
}
/* Ensure caller does not read too much */
if(size > tar->remaining_data)
return tseek(tar, newpos);
}
unsigned mtar_tell_data(mtar_t* tar)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(!(tar->state & S_HEADER_VALID))
return MTAR_EAPI;
#endif
return tar->pos - data_beg_pos(tar);
}
int mtar_eof_data(mtar_t* tar)
{
/* API usage error, but just claim EOF. */
if(!(tar->state & S_HEADER_VALID))
return 1;
return tar->pos >= data_end_pos(tar) ? 1 : 0;
}
int mtar_write_header(mtar_t* tar, const mtar_header_t* h)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_WRITE)
return MTAR_EACCESS;
if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
(tar->state & S_WROTE_FINALIZE))
return MTAR_EAPI;
#endif
tar->state &= ~(S_HEADER_VALID | S_WROTE_HEADER |
S_WROTE_DATA | S_WROTE_DATA_EOF);
/* ensure we have enough space to write the declared amount of data */
if(tar->pos > UINT_MAX - HEADER_LEN - round_up_512(h->size))
return MTAR_EOVERFLOW;
/* Read data */
err = tread(tar, ptr, size);
if (err) {
tar->header_pos = tar->pos;
tar->end_pos = data_beg_pos(tar);
if(h != &tar->header)
tar->header = *h;
int err = header_to_raw(tar->buffer, &tar->header);
if(err)
return err;
}
tar->remaining_data -= size;
/* If there is no remaining data we've finished reading and seek back to the
* header */
if (tar->remaining_data == 0) {
return mtar_seek(tar, tar->last_header);
}
int ret = twrite(tar, tar->buffer, HEADER_LEN);
if(ret < 0)
return ret;
if(ret != HEADER_LEN)
return MTAR_EWRITEFAIL;
tar->state |= (S_HEADER_VALID | S_WROTE_HEADER);
return MTAR_ESUCCESS;
}
int mtar_update_header(mtar_t* tar, const mtar_header_t* h)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_WRITE)
return MTAR_EACCESS;
if(!(tar->state & S_WROTE_HEADER) ||
(tar->state & S_WROTE_DATA_EOF) ||
(tar->state & S_WROTE_FINALIZE))
return MTAR_EAPI;
#endif
int mtar_write_header(mtar_t *tar, const mtar_header_t *h) {
/* Build raw header and write */
header_to_raw(&tar->raw_header, h);
tar->remaining_data = h->size;
return twrite(tar, &tar->raw_header, sizeof(tar->raw_header));
unsigned beg_pos = data_beg_pos(tar);
if(beg_pos > UINT_MAX - h->size)
return MTAR_EOVERFLOW;
unsigned old_pos = tar->pos;
int err = tseek(tar, tar->header_pos);
if(err)
return err;
if(h != &tar->header)
tar->header = *h;
err = header_to_raw(tar->buffer, &tar->header);
if(err)
return err;
int len = twrite(tar, tar->buffer, HEADER_LEN);
if(len < 0)
return len;
if(len != HEADER_LEN)
return MTAR_EWRITEFAIL;
return tseek(tar, old_pos);
}
int mtar_write_file_header(mtar_t* tar, const char* name, unsigned size)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_WRITE)
return MTAR_EACCESS;
if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
(tar->state & S_WROTE_FINALIZE))
return MTAR_EAPI;
#endif
int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size) {
/* Build header */
memset(&tar->header, 0, sizeof(tar->header));
/* Ensure name fits within header */
if(strlen(name) > sizeof(tar->header.name))
return MTAR_EOVERFLOW;
strncpy(tar->header.name, name, sizeof(tar->header.name));
size_t namelen = strlen(name);
if(namelen > NAME_LEN)
return MTAR_ENAMETOOLONG;
tar->header.mode = 0644;
tar->header.owner = 0;
tar->header.group = 0;
tar->header.size = size;
tar->header.mtime = 0;
tar->header.type = MTAR_TREG;
tar->header.mode = 0664;
/* Write header */
memcpy(tar->header.name, name, namelen + 1);
tar->header.linkname[0] = '\0';
return mtar_write_header(tar, &tar->header);
}
int mtar_write_dir_header(mtar_t* tar, const char* name)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_WRITE)
return MTAR_EACCESS;
if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
(tar->state & S_WROTE_FINALIZE))
return MTAR_EAPI;
#endif
int mtar_write_dir_header(mtar_t *tar, const char *name) {
/* Build header */
memset(&tar->header, 0, sizeof(tar->header));
/* Ensure name fits within header */
if(strlen(name) > sizeof(tar->header.name))
return MTAR_EOVERFLOW;
strncpy(tar->header.name, name, sizeof(tar->header.name));
size_t namelen = strlen(name);
if(namelen > NAME_LEN)
return MTAR_ENAMETOOLONG;
tar->header.mode = 0755;
tar->header.owner = 0;
tar->header.group = 0;
tar->header.size = 0;
tar->header.mtime = 0;
tar->header.type = MTAR_TDIR;
tar->header.mode = 0775;
/* Write header */
memcpy(tar->header.name, name, namelen + 1);
tar->header.linkname[0] = '\0';
return mtar_write_header(tar, &tar->header);
}
int mtar_write_data(mtar_t* tar, const void* ptr, unsigned size)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_WRITE)
return MTAR_EACCESS;
if(!(tar->state & S_WROTE_HEADER) ||
(tar->state & S_WROTE_DATA_EOF) ||
(tar->state & S_WROTE_FINALIZE))
return MTAR_EAPI;
#endif
tar->state |= S_WROTE_DATA;
int err = twrite(tar, ptr, size);
if(tar->pos > tar->end_pos)
tar->end_pos = tar->pos;
return err;
}
int mtar_update_file_size(mtar_t* tar)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_WRITE)
return MTAR_EACCESS;
if(!(tar->state & S_WROTE_HEADER) ||
(tar->state & S_WROTE_DATA_EOF) ||
(tar->state & S_WROTE_FINALIZE))
return MTAR_EAPI;
#endif
unsigned new_size = data_end_pos(tar) - data_beg_pos(tar);
if(new_size == tar->header.size)
return MTAR_ESUCCESS;
else {
tar->header.size = new_size;
return mtar_update_header(tar, &tar->header);
}
}
int mtar_end_data(mtar_t* tar)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_WRITE)
return MTAR_EACCESS;
if(!(tar->state & S_WROTE_HEADER) ||
(tar->state & S_WROTE_DATA_EOF) ||
(tar->state & S_WROTE_FINALIZE))
return MTAR_EAPI;
#endif
int mtar_write_data(mtar_t *tar, const void *data, unsigned size) {
int err;
/* Ensure we are writing the correct amount of data */
if(size > tar->remaining_data)
return MTAR_EOVERFLOW;
/* ensure the caller wrote the correct amount of data */
unsigned expected_end = data_beg_pos(tar) + tar->header.size;
if(tar->end_pos != expected_end)
return MTAR_EWRONGSIZE;
/* Write data */
err = twrite(tar, data, size);
if (err) {
/* ensure we're positioned at the end of the stream */
if(tar->pos != tar->end_pos) {
err = tseek(tar, tar->end_pos);
if(err)
return err;
}
tar->remaining_data -= size;
/* Write padding if we've written all the data for this file */
if (tar->remaining_data == 0) {
return write_null_bytes(tar, round_up(tar->pos, 512) - tar->pos);
}
/* write remainder of the 512-byte record */
err = write_null_bytes(tar, round_up_512(tar->pos) - tar->pos);
if(err)
return err;
tar->state |= S_WROTE_DATA_EOF;
return MTAR_ESUCCESS;
}
int mtar_finalize(mtar_t* tar)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_WRITE)
return MTAR_EACCESS;
if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
(tar->state & S_WROTE_FINALIZE))
return MTAR_EAPI;
#endif
int mtar_finalize(mtar_t *tar) {
/* Write two NULL records */
return write_null_bytes(tar, sizeof(mtar_raw_header_t) * 2);
tar->state |= S_WROTE_FINALIZE;
return write_null_bytes(tar, 1024);
}

View file

@ -1,47 +1,75 @@
/**
/*
* Copyright (c) 2017 rxi
* Copyright (c) 2021 Aidan MacDonald
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `microtar.c` for details.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef MICROTAR_H
#define MICROTAR_H
#ifdef __cplusplus
extern "C"
{
extern "C" {
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h> /* SEEK_SET et al. */
#define MTAR_VERSION "0.1.0"
enum {
enum mtar_error {
MTAR_ESUCCESS = 0,
MTAR_EFAILURE = -1,
MTAR_EOPENFAIL = -2,
MTAR_EREADFAIL = -3,
MTAR_EWRITEFAIL = -4,
MTAR_ESEEKFAIL = -5,
MTAR_EBADCHKSUM = -6,
MTAR_ENULLRECORD = -7,
MTAR_ENOTFOUND = -8,
MTAR_EOVERFLOW = -9,
MTAR_ESEEKRANGE = -6,
MTAR_EBADCHKSUM = -7,
MTAR_ENULLRECORD = -8,
MTAR_ENOTFOUND = -9,
MTAR_EOVERFLOW = -10,
MTAR_EAPI = -11,
MTAR_ENAMETOOLONG = -12,
MTAR_EWRONGSIZE = -13,
MTAR_EACCESS = -14,
};
enum {
enum mtar_type {
MTAR_TREG = '0',
MTAR_TLNK = '1',
MTAR_TSYM = '2',
MTAR_TCHR = '3',
MTAR_TBLK = '4',
MTAR_TDIR = '5',
MTAR_TFIFO = '6'
MTAR_TFIFO = '6',
};
typedef struct {
enum mtar_access {
MTAR_READ,
MTAR_WRITE,
};
typedef struct mtar_header mtar_header_t;
typedef struct mtar mtar_t;
typedef struct mtar_ops mtar_ops_t;
typedef int(*mtar_foreach_cb)(mtar_t*, const mtar_header_t*, void*);
struct mtar_header {
unsigned mode;
unsigned owner;
unsigned group;
@ -50,56 +78,55 @@ typedef struct {
unsigned type;
char name[101]; /* +1 byte in order to ensure null termination */
char linkname[101];
} mtar_header_t;
typedef struct {
char name[100];
char mode[8];
char owner[8];
char group[8];
char size[12];
char mtime[12];
char checksum[8];
char type;
char linkname[100];
char _padding[255];
} mtar_raw_header_t;
typedef struct mtar_t mtar_t;
struct mtar_t {
int (*read)(mtar_t *tar, void *data, unsigned size);
int (*write)(mtar_t *tar, const void *data, unsigned size);
int (*seek)(mtar_t *tar, unsigned pos);
int (*close)(mtar_t *tar);
void *stream;
unsigned pos;
unsigned remaining_data;
unsigned last_header;
mtar_header_t header;
mtar_raw_header_t raw_header;
};
struct mtar_ops {
int(*read)(void* stream, void* data, unsigned size);
int(*write)(void* stream, const void* data, unsigned size);
int(*seek)(void* stream, unsigned pos);
int(*close)(void* stream);
};
struct mtar {
char buffer[512]; /* IO buffer, put first to allow library users to
* control its alignment */
int state; /* Used to simplify the API and verify API usage */
int access; /* Access mode */
unsigned pos; /* Current position in file */
unsigned end_pos; /* End position of the current file */
unsigned header_pos; /* Position of the current header */
mtar_header_t header; /* Most recently parsed header */
const mtar_ops_t* ops; /* Stream operations */
void* stream; /* Stream handle */
};
const char* mtar_strerror(int err);
int mtar_open(mtar_t *tar, const char *filename, const char *mode);
int mtar_close(mtar_t *tar);
void mtar_init(mtar_t* tar, int access, const mtar_ops_t* ops, void* stream);
int mtar_close(mtar_t* tar);
int mtar_is_open(mtar_t* tar);
int mtar_seek(mtar_t *tar, unsigned pos);
int mtar_rewind(mtar_t *tar);
int mtar_next(mtar_t *tar);
int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h);
int mtar_read_header(mtar_t *tar, mtar_header_t *h);
int mtar_read_data(mtar_t *tar, void *ptr, unsigned size);
mtar_header_t* mtar_get_header(mtar_t* tar);
int mtar_access_mode(const mtar_t* tar);
int mtar_write_header(mtar_t *tar, const mtar_header_t *h);
int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size);
int mtar_write_dir_header(mtar_t *tar, const char *name);
int mtar_write_data(mtar_t *tar, const void *data, unsigned size);
int mtar_finalize(mtar_t *tar);
int mtar_rewind(mtar_t* tar);
int mtar_next(mtar_t* tar);
int mtar_foreach(mtar_t* tar, mtar_foreach_cb cb, void* arg);
int mtar_find(mtar_t* tar, const char* name);
int mtar_read_data(mtar_t* tar, void* ptr, unsigned size);
int mtar_seek_data(mtar_t* tar, int offset, int whence);
unsigned mtar_tell_data(mtar_t* tar);
int mtar_eof_data(mtar_t* tar);
int mtar_write_header(mtar_t* tar, const mtar_header_t* h);
int mtar_update_header(mtar_t* tar, const mtar_header_t* h);
int mtar_write_file_header(mtar_t* tar, const char* name, unsigned size);
int mtar_write_dir_header(mtar_t* tar, const char* name);
int mtar_write_data(mtar_t* tar, const void* ptr, unsigned size);
int mtar_update_file_size(mtar_t* tar);
int mtar_end_data(mtar_t* tar);
int mtar_finalize(mtar_t* tar);
#ifdef __cplusplus
}