Add microtar library (for use by M3K bootloader)

This is an MIT-licensed library for reading and writing v7 format
tar files. The version here is my fork, which fixes security issues
in the original code (it hasn't been updated in 4 years, probably
abandoned by the author).

Change-Id: I86d41423dacc46e9fa0514b4fc7386a96c216e86
This commit is contained in:
Aidan MacDonald 2021-05-11 13:05:13 +01:00
parent adff45ca21
commit 9a19360398
10 changed files with 945 additions and 3 deletions

View file

@ -703,6 +703,9 @@ Georg Gadinger
Wolfram Sang
Aidan MacDonald
Caleb Connolly
Spencer Brennessel
Dana Conrad
Albert Song
The libmad team
The wavpack team
@ -730,7 +733,6 @@ The libpng team
The Pure Data team (Miller Puckette and others)
The MikMod team
Michael McTernan (The ARM unwinder author)
Albert Song
The New RAW team (Piotr Padkowski and others)
The Fabother World team (Fabien Sanglard and others)
The sgt-puzzles team (Simon Tatham and others)
@ -743,5 +745,4 @@ The Pocket Quake team (Dan East and others)
The bzip2 team
The bsdiff team
The libtomcrypt team
Spencer Brennessel
Dana Conrad
The microtar team (rxi and others)

19
lib/microtar/LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2017 rxi
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.

128
lib/microtar/README.md Normal file
View file

@ -0,0 +1,128 @@
# microtar
A lightweight tar library written in ANSI C
## Modifications from upstream
[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).
## Modifications for Rockbox
Added file `microtar-rockbox.c` implementing `mtar_open()` with native
Rockbox filesystem API.
## 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.
#### Reading
```c
mtar_t tar;
mtar_header_t h;
char *p;
/* Open archive for reading */
mtar_open(&tar, "test.tar", "r");
/* 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);
}
/* 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);
```
#### Writing
```c
mtar_t tar;
const char *str1 = "Hello world";
const char *str2 = "Goodbye world";
/* Open archive for writing */
mtar_open(&tar, "test.tar", "w");
/* 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);
```
## 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.
## 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.
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.
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.
#### Reading
The following callbacks should be set for reading an archive from a stream:
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
#### Writing
The following callbacks should be set for writing an archive to a stream:
Name | Arguments | Description
--------|------------------------------------------------|---------------------
`write` | `mtar_t *tar, const void *data, unsigned size` | Write data to the stream
## 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.

6
lib/microtar/SOURCES Normal file
View file

@ -0,0 +1,6 @@
src/microtar.c
#ifdef ROCKBOX
src/microtar-rockbox.c
#else
src/microtar-stdio.c
#endif

View file

@ -0,0 +1,21 @@
# __________ __ ___.
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
# \/ \/ \/ \/ \/
#
MICROTARLIB_DIR = $(ROOTDIR)/lib/microtar
MICROTARLIB_SRC = $(call preprocess, $(MICROTARLIB_DIR)/SOURCES)
MICROTARLIB_OBJ := $(call c2obj, $(MICROTARLIB_SRC))
MICROTARLIB = $(BUILDDIR)/lib/libmicrotar.a
INCLUDES += -I$(MICROTARLIB_DIR)/src
OTHER_SRC += $(MICROTARLIB_SRC)
CORE_LIBS += $(MICROTARLIB)
$(MICROTARLIB): $(MICROTARLIB_OBJ)
$(SILENT)$(shell rm -f $@)
$(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null

91
lib/microtar/src/Makefile Normal file
View file

@ -0,0 +1,91 @@
# __________ __ ___.
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
# \/ \/ \/ \/ \/
# $Id$
#
# Shameless copy+paste from tools/ucl/src/Makefile
# This is only used for rbutil builds, Rockbox uses microtar.make
ifndef V
SILENT = @
endif
ifeq ($(OS),Windows_NT)
SHELL = cmd.exe
mkdir = if not exist $(subst /,\,$(1)) mkdir $(subst /,\,$(1))
else
mkdir = mkdir -p $(1)
endif
ifdef RBARCH
CFLAGS += -arch $(RBARCH)
endif
CPPDEFINES := $(shell echo foo | $(CROSS)$(CC) -dM -E -)
#build standalone win32 executables on cygwin
ifeq ($(findstring CYGWIN,$(CPPDEFINES)),CYGWIN)
COMPILETARGET = cygwin
else
ifeq ($(findstring MINGW,$(CPPDEFINES)),MINGW)
COMPILETARGET = mingw
else
# OS X specifics. Needs to consider cross compiling for Windows.
ifeq ($(findstring APPLE,$(CPPDEFINES)),APPLE)
# When building for 10.4+ we need to use gcc. Otherwise clang is used, so use
# that to determine if we need to set arch and isysroot.
ifeq ($(findstring __clang__,$(CPPDEFINES)),__clang__)
CFLAGS += -mmacosx-version-min=10.5
ifneq ($(ISYSROOT),)
CFLAGS += -isysroot $(ISYSROOT)
endif
else
# when building libs for OS X 10.4+ build for both i386 and ppc at the same time.
# This creates fat objects, and ar can only create the archive but not operate
# on it. As a result the ar call must NOT use the u (update) flag.
ARCHFLAGS += -arch ppc -arch i386
# building against SDK 10.4 is not compatible with gcc-4.2 (default on newer Xcode)
# might need adjustment for older Xcode.
CC = gcc-4.0
CFLAGS += -isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4
NATIVECC ?= gcc-4.0
endif
COMPILETARGET = darwin
else
COMPILETARGET = posix
endif
endif
endif
$(info Compiler creates $(COMPILETARGET) binaries)
TARGET_DIR ?= $(shell pwd)/
OBJDIR = $(TARGET_DIR)build$(RBARCH)
SOURCES = microtar.c microtar-stdio.c
OBJS = $(addprefix $(OBJDIR)/,$(SOURCES:%.c=%.o))
libmicrotar$(RBARCH).a: $(TARGET_DIR)libmicrotar$(RBARCH).a
dll: microtar.dll
microtar.dll: $(TARGET_DIR)microtar.dll
$(TARGET_DIR)microtar.dll: $(OBJS)
@echo DLL $(notdir $@)
$(SILENT)$(CROSS)$(CC) $(CFLAGS) -shared -o $@ $^ \
-Wl,--output-def,$(TARGET_DIR)microtar.def
$(TARGET_DIR)libmicrotar$(RBARCH).a: $(OBJS)
@echo AR $(notdir $@)
$(SILENT)$(CROSS)$(AR) rcs $@ $(OBJS)
$(OBJDIR)/%.o: %.c
@echo CC $<
$(SILENT)$(call mkdir, $(dir $@))
$(SILENT)$(CROSS)$(CC) $(CFLAGS) $(ARCHFLAGS) -c $< -o $@
clean:
rm -f $(TARGET_DIR)libmicrotar*.a
rm -rf build*

View file

@ -0,0 +1,100 @@
/***************************************************************************
* __________ __ ___.
* 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.
*
****************************************************************************/
#include "file.h"
#include "microtar.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))
return MTAR_EREADFAIL;
else
return MTAR_ESUCCESS;
}
static int fd_seek(mtar_t *tar, unsigned offset) {
intptr_t fd = (intptr_t)tar->stream;
off_t res = lseek(fd, 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);
return MTAR_ESUCCESS;
}
int mtar_open(mtar_t *tar, const char *filename, const char *mode) {
int err;
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 */
return MTAR_ESUCCESS;
}

View file

@ -0,0 +1,65 @@
/**
* Copyright (c) 2017 rxi
*
* 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.
*/
#include <stdio.h>
#include <string.h>
#include "microtar.h"
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;
}
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_seek(mtar_t *tar, unsigned offset) {
int res = fseek(tar->stream, offset, SEEK_SET);
return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
}
static int file_close(mtar_t *tar) {
fclose(tar->stream);
return MTAR_ESUCCESS;
}
int mtar_open(mtar_t *tar, const char *filename, const char *mode) {
int err;
mtar_header_t h;
/* 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) {
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 */
return MTAR_ESUCCESS;
}

403
lib/microtar/src/microtar.c Normal file
View file

@ -0,0 +1,403 @@
/*
* Copyright (c) 2017 rxi
*
* 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 <stdlib.h>
#include <stddef.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) {
size_t i;
for(i = 0; i < n && *src; ++i)
dest[i] = src[i];
for(; i < n; ++i)
dest[i] = 0;
return dest;
}
#endif
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;
if(n > UINT_MAX/8)
return MTAR_EOVERFLOW;
else
n *= 8;
char r = *str++ - '0';
if(n > UINT_MAX - r)
return MTAR_EOVERFLOW;
else
n += r;
}
*ret = n;
return MTAR_ESUCCESS;
}
static int print_octal(char* str, size_t len, unsigned value) {
/* move backwards over the output string */
char* ptr = str + len - 1;
*ptr = 0;
/* output the significant digits */
while(value > 0) {
if(ptr == str)
return MTAR_EOVERFLOW;
--ptr;
*ptr = '0' + (value % 8);
value /= 8;
}
/* pad the remainder of the field with zeros */
while(ptr != str) {
--ptr;
*ptr = '0';
}
return MTAR_ESUCCESS;
}
static unsigned round_up(unsigned n, unsigned incr) {
return n + (incr - n % incr) % incr;
}
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;
}
static int tread(mtar_t *tar, void *data, unsigned size) {
int err = tar->read(tar, data, size);
tar->pos += size;
return err;
}
static int twrite(mtar_t *tar, const void *data, unsigned size) {
int err = tar->write(tar, data, size);
tar->pos += size;
return err;
}
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;
}
}
return MTAR_ESUCCESS;
}
static int raw_to_header(mtar_header_t *h, const mtar_raw_header_t *rh) {
unsigned chksum1, chksum2;
int rc;
/* If the checksum starts with a null byte we assume the record is NULL */
if (*rh->checksum == '\0') {
return MTAR_ENULLRECORD;
}
/* Build and compare checksum */
chksum1 = checksum(rh);
if((rc = parse_octal(rh->checksum, sizeof(rh->checksum), &chksum2)))
return rc;
if (chksum1 != chksum2) {
return MTAR_EBADCHKSUM;
}
/* Load raw header into header */
if((rc = parse_octal(rh->mode, sizeof(rh->mode), &h->mode)))
return rc;
if((rc = parse_octal(rh->owner, sizeof(rh->owner), &h->owner)))
return rc;
if((rc = parse_octal(rh->group, sizeof(rh->group), &h->group)))
return rc;
if((rc = parse_octal(rh->size, sizeof(rh->size), &h->size)))
return rc;
if((rc = parse_octal(rh->mtime, sizeof(rh->mtime), &h->mtime)))
return rc;
h->type = rh->type;
memcpy(h->name, rh->name, sizeof(rh->name));
h->name[sizeof(h->name) - 1] = 0;
memcpy(h->linkname, rh->linkname, sizeof(rh->linkname));
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) {
unsigned chksum;
int rc;
/* Load header into raw header */
memset(rh, 0, sizeof(*rh));
if((rc = print_octal(rh->mode, sizeof(rh->mode), h->mode)))
return rc;
if((rc = print_octal(rh->owner, sizeof(rh->owner), h->owner)))
return rc;
if((rc = print_octal(rh->group, sizeof(rh->group), h->group)))
return rc;
if((rc = print_octal(rh->size, sizeof(rh->size), h->size)))
return rc;
if((rc = print_octal(rh->mtime, sizeof(rh->mtime), 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));
/* Calculate and write checksum */
chksum = checksum(rh);
if((rc = print_octal(rh->checksum, 7, chksum)))
return rc;
rh->checksum[7] = ' ';
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";
}
int mtar_close(mtar_t *tar) {
return tar->close(tar);
}
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;
}
return MTAR_ESUCCESS;
}
err = mtar_next(tar);
if (err) {
return err;
}
}
/* Return error */
if (err == MTAR_ENULLRECORD) {
err = MTAR_ENOTFOUND;
}
return err;
}
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);
}
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;
}
/* 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 MTAR_EOVERFLOW;
/* Read data */
err = tread(tar, ptr, size);
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);
}
return MTAR_ESUCCESS;
}
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));
}
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));
tar->header.size = size;
tar->header.type = MTAR_TREG;
tar->header.mode = 0664;
/* Write header */
return mtar_write_header(tar, &tar->header);
}
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));
tar->header.type = MTAR_TDIR;
tar->header.mode = 0775;
/* Write header */
return mtar_write_header(tar, &tar->header);
}
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;
/* Write data */
err = twrite(tar, data, size);
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);
}
return MTAR_ESUCCESS;
}
int mtar_finalize(mtar_t *tar) {
/* Write two NULL records */
return write_null_bytes(tar, sizeof(mtar_raw_header_t) * 2);
}

108
lib/microtar/src/microtar.h Normal file
View file

@ -0,0 +1,108 @@
/**
* Copyright (c) 2017 rxi
*
* 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.
*/
#ifndef MICROTAR_H
#define MICROTAR_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdio.h>
#include <stdlib.h>
#define MTAR_VERSION "0.1.0"
enum {
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,
};
enum {
MTAR_TREG = '0',
MTAR_TLNK = '1',
MTAR_TSYM = '2',
MTAR_TCHR = '3',
MTAR_TBLK = '4',
MTAR_TDIR = '5',
MTAR_TFIFO = '6'
};
typedef struct {
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];
} 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;
};
const char* mtar_strerror(int err);
int mtar_open(mtar_t *tar, const char *filename, const char *mode);
int mtar_close(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);
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);
#ifdef __cplusplus
}
#endif
#endif