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) 2017 rxi
Copyright (c) 2021 Aidan MacDonald
Permission is hereby granted, free of charge, to any person obtaining a copy of 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 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 # 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, No effort has been put into handling every tar format variant. Basically
which I fixed in order to improve the overall robustness of the library. what is accepted is the "old-style" format, which appears to work well
enough to access basic archives created by GNU `tar`.
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).
## Modifications for Rockbox ## Basic usage
Added file `microtar-rockbox.c` implementing `mtar_open()` with native The library consists of two files, `microtar.c` and `microtar.h`, which only
Rockbox filesystem API. 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 ### Initialization
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 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 ```c
mtar_t tar; mtar_t tar;
mtar_header_t h; mtar_init(&tar, MTAR_READ, my_io_ops, my_stream);
char *p; ```
/* Open archive for reading */ Or if using `microtar-stdio.c`:
mtar_open(&tar, "test.tar", "r");
/* Print all file names and sizes */ ```c
while ( (mtar_read_header(&tar, &h)) != MTAR_ENULLRECORD ) { int error = mtar_open(&tar, "file.tar", "rb");
printf("%s (%d bytes)\n", h.name, h.size); if(error) {
mtar_next(&tar); /* 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" */ if(err != MTAR_ENULLRECORD) {
mtar_find(&tar, "test.txt", &h); /* ENULLRECORD means we hit end of file; any
p = calloc(1, h.size + 1); * other return value is an actual error. */
mtar_read_data(&tar, p, h.size); }
printf("%s", p);
free(p);
/* Close archive */
mtar_close(&tar);
``` ```
#### 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 ```c
mtar_t tar; /* Will be called for each archive member visited by mtar_foreach().
const char *str1 = "Hello world"; * The member's header is passed in as an argument so you don't need
const char *str2 = "Goodbye world"; * 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 */ void main()
mtar_open(&tar, "test.tar", "w"); {
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 */ int ret = mtar_foreach(&tar, foreach_cb, NULL);
mtar_finalize(&tar); if(ret < 0) {
/* Microtar error codes are negative and may be returned if
/* Close archive */ * there is a problem with the iteration. */
mtar_close(&tar); } 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 ## 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 Most functions that return `int` return an error code from `enum mtar_error`.
returned; this value can be passed to the function `mtar_strerror()` to get its Zero is success and all other error codes are negative. `mtar_strerror()` can
corresponding error string. 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 ## I/O hooks
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.
All callback functions are passed a pointer to the `mtar_t` struct as their You can provide your own I/O hooks in a `mtar_ops_t` struct. The same ops
first argument. They should return `MTAR_ESUCCESS` if the operation succeeds struct can be shared among multiple `mtar_t` objects but each object gets
without an error, or an integer below zero if an error occurs. its own `void* stream` pointer.
After the `stream` field has been set, all required callbacks have been set and Name | Arguments | Required
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` `read` | `void* stream, void* data, unsigned size` | If reading
struct was initialized manually. `write` | `void* stream, void* data, unsigned size` | If writing
`seek` | `void* stream, unsigned pos` | If reading
`close` | `void* stream` | Always
#### Reading `read` and `write` should transfer the number of bytes indicated
The following callbacks should be set for reading an archive from a stream: and return the number of bytes actually read or written, or a negative
`enum mtar_error` code on error.
Name | Arguments | Description `seek` must have semantics like `lseek(..., pos, SEEK_SET)`; that is,
--------|------------------------------------------|--------------------------- the position is an absolute byte offset in the stream. Seeking is not
`read` | `mtar_t *tar, void *data, unsigned size` | Read data from the stream optional for read support, but the library only performs backward
`seek` | `mtar_t *tar, unsigned pos` | Set the position indicator seeks under two circumstances:
`close` | `mtar_t *tar` | Close the stream
#### Writing - `mtar_rewind()` seeks to position 0.
The following callbacks should be set for writing an archive to a stream: - `mtar_seek_data()` may seek backward if the user requests it.
Name | Arguments | Description 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
`write` | `mtar_t *tar, const void *data, unsigned size` | Write data to the stream 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 `seek` and `close` should return an `enum mtar_error` code, either
This library is free software; you can redistribute it and/or modify it under `MTAR_SUCCESS`, or a negative value on error.
the terms of the MIT license. See [LICENSE](LICENSE) for details.

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 "file.h"
#include "microtar.h" #include "microtar-rockbox.h"
#include <stdint.h> #include <stdint.h>
static int fd_write(mtar_t *tar, const void *data, unsigned size) { static int fd_read(void* stream, void* data, unsigned size)
intptr_t fd = (intptr_t)tar->stream; {
ssize_t res = write(fd, data, size); ssize_t res = read((intptr_t)stream, data, size);
if(res < 0 || ((unsigned)res != size)) if(res < 0)
return MTAR_EWRITEFAIL; return MTAR_EREADFAIL;
else
return res;
}
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(void* stream)
{
close((intptr_t)stream);
return MTAR_ESUCCESS; return MTAR_ESUCCESS;
} }
static int fd_read(mtar_t *tar, void *data, unsigned size) { static const mtar_ops_t fd_ops = {
intptr_t fd = (intptr_t)tar->stream; .read = fd_read,
ssize_t res = read(fd, data, size); .write = fd_write,
if(res < 0 || ((unsigned)res != size)) .seek = fd_seek,
return MTAR_EREADFAIL; .close = fd_close,
else };
return MTAR_ESUCCESS;
}
static int fd_seek(mtar_t *tar, unsigned offset) { int mtar_open(mtar_t* tar, const char* filename, int flags)
intptr_t fd = (intptr_t)tar->stream; {
off_t res = lseek(fd, offset, SEEK_SET); /* Determine access mode */
if(res < 0 || ((unsigned)res != offset)) int access;
return MTAR_ESEEKFAIL; switch(flags & O_ACCMODE) {
else case O_RDONLY:
return MTAR_ESUCCESS; access = MTAR_READ;
} break;
static int fd_close(mtar_t *tar) { case O_WRONLY:
intptr_t fd = (intptr_t)tar->stream; access = MTAR_WRITE;
close(fd); break;
return MTAR_ESUCCESS;
}
default:
int mtar_open(mtar_t *tar, const char *filename, const char *mode) { /* Note - O_RDWR isn't very useful so we don't allow it */
int err; return MTAR_EAPI;
int openmode;
int fd;
/* 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;
/* 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;
/* Open file */
fd = open(filename, openmode);
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 */ int fd = open(filename, flags);
return MTAR_ESUCCESS; if(fd < 0)
return MTAR_EOPENFAIL;
/* 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) 2017 rxi
* Copyright (c) 2021 Aidan MacDonald
* *
* This library is free software; you can redistribute it and/or modify it * Permission is hereby granted, free of charge, to any person obtaining a copy
* under the terms of the MIT license. See `microtar.c` for details. * 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 <stdio.h>
#include <string.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) { return res;
unsigned res = fwrite(data, 1, size, tar->stream);
return (res == size) ? MTAR_ESUCCESS : MTAR_EWRITEFAIL;
} }
static int file_read(mtar_t *tar, void *data, unsigned size) { static int file_write(void* stream, const void* data, unsigned size)
unsigned res = fread(data, 1, size, tar->stream); {
return (res == size) ? MTAR_ESUCCESS : MTAR_EREADFAIL; 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) { static int file_seek(void* stream, unsigned offset)
int res = fseek(tar->stream, offset, SEEK_SET); {
return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL; int res = fseek((FILE*)stream, offset, SEEK_SET);
return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
} }
static int file_close(mtar_t *tar) { static int file_close(void* stream)
fclose(tar->stream); {
return MTAR_ESUCCESS; 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 mtar_open(mtar_t* tar, const char* filename, const char* mode)
int err; {
mtar_header_t h; /* Determine access mode */
int access;
/* Init tar struct and functions */ char* read = strchr(mode, 'r');
memset(tar, 0, sizeof(*tar)); char* write = strchr(mode, 'w');
tar->write = file_write; if(read) {
tar->read = file_read; if(write)
tar->seek = file_seek; return MTAR_EAPI;
tar->close = file_close; access = MTAR_READ;
} else if(write) {
/* Assure mode is always binary */ access = MTAR_WRITE;
if ( strchr(mode, 'r') ) mode = "rb"; } else {
if ( strchr(mode, 'w') ) mode = "wb"; return MTAR_EAPI;
if ( strchr(mode, 'a') ) mode = "ab";
/* Open file */
tar->stream = fopen(filename, mode);
if (!tar->stream) {
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 */ /* Open file */
return MTAR_ESUCCESS; FILE* file = fopen(filename, mode);
if(!file)
return MTAR_EOPENFAIL;
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

File diff suppressed because it is too large Load diff

View file

@ -1,105 +1,132 @@
/** /*
* Copyright (c) 2017 rxi * Copyright (c) 2017 rxi
* Copyright (c) 2021 Aidan MacDonald
* *
* This library is free software; you can redistribute it and/or modify it * Permission is hereby granted, free of charge, to any person obtaining a copy
* under the terms of the MIT license. See `microtar.c` for details. * 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 #ifndef MICROTAR_H
#define MICROTAR_H #define MICROTAR_H
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C" {
{
#endif #endif
#include <stdio.h> #include <stdio.h> /* SEEK_SET et al. */
#include <stdlib.h>
#define MTAR_VERSION "0.1.0" enum mtar_error {
MTAR_ESUCCESS = 0,
enum { MTAR_EFAILURE = -1,
MTAR_ESUCCESS = 0, MTAR_EOPENFAIL = -2,
MTAR_EFAILURE = -1, MTAR_EREADFAIL = -3,
MTAR_EOPENFAIL = -2, MTAR_EWRITEFAIL = -4,
MTAR_EREADFAIL = -3, MTAR_ESEEKFAIL = -5,
MTAR_EWRITEFAIL = -4, MTAR_ESEEKRANGE = -6,
MTAR_ESEEKFAIL = -5, MTAR_EBADCHKSUM = -7,
MTAR_EBADCHKSUM = -6, MTAR_ENULLRECORD = -8,
MTAR_ENULLRECORD = -7, MTAR_ENOTFOUND = -9,
MTAR_ENOTFOUND = -8, MTAR_EOVERFLOW = -10,
MTAR_EOVERFLOW = -9, MTAR_EAPI = -11,
MTAR_ENAMETOOLONG = -12,
MTAR_EWRONGSIZE = -13,
MTAR_EACCESS = -14,
}; };
enum { enum mtar_type {
MTAR_TREG = '0', MTAR_TREG = '0',
MTAR_TLNK = '1', MTAR_TLNK = '1',
MTAR_TSYM = '2', MTAR_TSYM = '2',
MTAR_TCHR = '3', MTAR_TCHR = '3',
MTAR_TBLK = '4', MTAR_TBLK = '4',
MTAR_TDIR = '5', MTAR_TDIR = '5',
MTAR_TFIFO = '6' MTAR_TFIFO = '6',
}; };
typedef struct { enum mtar_access {
unsigned mode; MTAR_READ,
unsigned owner; MTAR_WRITE,
unsigned group;
unsigned size;
unsigned mtime;
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;
}; };
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;
unsigned size;
unsigned mtime;
unsigned type;
char name[101]; /* +1 byte in order to ensure null termination */
char linkname[101];
};
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); const char* mtar_strerror(int err);
int mtar_open(mtar_t *tar, const char *filename, const char *mode); void mtar_init(mtar_t* tar, int access, const mtar_ops_t* ops, void* stream);
int mtar_close(mtar_t *tar); int mtar_close(mtar_t* tar);
int mtar_is_open(mtar_t* tar);
int mtar_seek(mtar_t *tar, unsigned pos); mtar_header_t* mtar_get_header(mtar_t* tar);
int mtar_rewind(mtar_t *tar); int mtar_access_mode(const 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);
int mtar_write_header(mtar_t *tar, const mtar_header_t *h); int mtar_rewind(mtar_t* tar);
int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size); int mtar_next(mtar_t* tar);
int mtar_write_dir_header(mtar_t *tar, const char *name); int mtar_foreach(mtar_t* tar, mtar_foreach_cb cb, void* arg);
int mtar_write_data(mtar_t *tar, const void *data, unsigned size); int mtar_find(mtar_t* tar, const char* name);
int mtar_finalize(mtar_t *tar);
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 #ifdef __cplusplus
} }