diff --git a/docs/CREDITS b/docs/CREDITS index 720c3a99db..7dee2ce527 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -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) diff --git a/lib/microtar/LICENSE b/lib/microtar/LICENSE new file mode 100644 index 0000000000..7e3bf17ccb --- /dev/null +++ b/lib/microtar/LICENSE @@ -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. diff --git a/lib/microtar/README.md b/lib/microtar/README.md new file mode 100644 index 0000000000..18153caa21 --- /dev/null +++ b/lib/microtar/README.md @@ -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. diff --git a/lib/microtar/SOURCES b/lib/microtar/SOURCES new file mode 100644 index 0000000000..d9e9b112d8 --- /dev/null +++ b/lib/microtar/SOURCES @@ -0,0 +1,6 @@ +src/microtar.c +#ifdef ROCKBOX +src/microtar-rockbox.c +#else +src/microtar-stdio.c +#endif diff --git a/lib/microtar/microtar.make b/lib/microtar/microtar.make new file mode 100644 index 0000000000..616561eea3 --- /dev/null +++ b/lib/microtar/microtar.make @@ -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 diff --git a/lib/microtar/src/Makefile b/lib/microtar/src/Makefile new file mode 100644 index 0000000000..27277f7c73 --- /dev/null +++ b/lib/microtar/src/Makefile @@ -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* diff --git a/lib/microtar/src/microtar-rockbox.c b/lib/microtar/src/microtar-rockbox.c new file mode 100644 index 0000000000..04ba93cc42 --- /dev/null +++ b/lib/microtar/src/microtar-rockbox.c @@ -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 + +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; +} diff --git a/lib/microtar/src/microtar-stdio.c b/lib/microtar/src/microtar-stdio.c new file mode 100644 index 0000000000..215000aa98 --- /dev/null +++ b/lib/microtar/src/microtar-stdio.c @@ -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 +#include + +#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; +} diff --git a/lib/microtar/src/microtar.c b/lib/microtar/src/microtar.c new file mode 100644 index 0000000000..04cd4ea132 --- /dev/null +++ b/lib/microtar/src/microtar.c @@ -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 +#include +#include +#include + +#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); +} diff --git a/lib/microtar/src/microtar.h b/lib/microtar/src/microtar.h new file mode 100644 index 0000000000..d30c829521 --- /dev/null +++ b/lib/microtar/src/microtar.h @@ -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 +#include + +#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