commit 6ec11b532ef276929b072cd115e6a4e15c42255e Author: David Oberhollenzer Date: Sat Jan 19 17:26:41 2019 +0100 Initial commit Signed-off-by: David Oberhollenzer diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c4ca06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +.deps +.dirstamp +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +compile +config.h.in +config.log +config.status +configure +depcomp +install-sh +missing +stamp-h1 +config.h +*.o +*~ +pkg diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..9575e02 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,50 @@ +ACLOCAL_AMFLAGS = -I m4 + +AM_CPPFLAGS = -I$(top_srcdir)/include -D_GNU_SOURCE +AM_CFLAGS = $(WARN_CFLAGS) + +# application core +GLOBALHDR = include/pkgformat.h include/util.h include/pkgreader.h +GLOBALHDR += include/compressor.h include/command.h include/pkgwriter.h +GLOBALHDR += include/image_entry.h + +MAIN = main/pkg.c main/util.c main/compressor.c main/command.c main/pkgreader.c +MAIN += main/pkgwriter.c main/image_entry.c main/image_entry_sort.c + +pkg_SOURCES = $(GLOBALHDR) $(MAIN) +pkg_CFLAGS = $(AM_CFLAGS) +pkg_LDADD = + +bin_PROGRAMS = pkg + +EXTRA_DIST = autogen.sh + +##### commands ##### + +# pack command +pkg_SOURCES += main/cmd/pack/filelist.c main/cmd/pack/filelist_read.c +pkg_SOURCES += main/cmd/pack/write_toc.c main/cmd/pack/write_files.c +pkg_SOURCES += main/cmd/pack/pack.h main/cmd/pack/pack.c + +# dump command +pkg_SOURCES += main/cmd/dump/dump.c main/cmd/dump/dump.h main/cmd/dump/dump_toc.c + +# unpack command +pkg_SOURCES += main/cmd/unpack/unpack.c main/cmd/unpack/unpack.h +pkg_SOURCES += main/cmd/unpack/mkdir_p.c main/cmd/unpack/create_hierarchy.c + +# help command +pkg_SOURCES += main/cmd/help.c + +##### compressors ##### + +# dummy compressor +pkg_SOURCES += main/compressors/none.c + +# zlib compressor +if WITH_ZLIB +pkg_SOURCES += main/compressors/zlib.c + +pkg_CFLAGS += $(ZLIB_CFLAGS) +pkg_LDADD += $(ZLIB_LIBS) +endif diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..c08fadf --- /dev/null +++ b/autogen.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +autoreconf --force --install --symlink diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..8ebfe14 --- /dev/null +++ b/configure.ac @@ -0,0 +1,56 @@ +AC_PREREQ([2.60]) +AC_INIT([pkgtool], [0.1], [david.oberhollenzer@tele2.at], pkgtool) +AC_CONFIG_MACRO_DIR([m4]) +AM_INIT_AUTOMAKE([foreign dist-xz subdir-objects]) +AM_SILENT_RULES([yes]) +AC_PROG_CC +AC_PROG_CC_C99 +AC_PROG_INSTALL + +m4_ifndef([PKG_PROG_PKG_CONFIG], + [m4_fatal([Could not locate the pkg-config autoconf + macros. These are usually located in /usr/share/aclocal/pkg.m4. + If your macros are in a different location, try setting the + environment variable AL_OPTS="-I/other/macro/dir" before running + ./autogen.sh or autoreconf again. Make sure pkg-config is installed.])]) +PKG_PROG_PKG_CONFIG + +UL_WARN_ADD([-Wall]) +UL_WARN_ADD([-Wextra]) +UL_WARN_ADD([-Wunused]) +UL_WARN_ADD([-Wmissing-prototypes]) +UL_WARN_ADD([-Wmissing-declarations]) +UL_WARN_ADD([-Wwrite-strings]) +UL_WARN_ADD([-Wjump-misses-init]) +UL_WARN_ADD([-Wuninitialized]) +UL_WARN_ADD([-Winit-self]) +UL_WARN_ADD([-Wlogical-op]) +UL_WARN_ADD([-Wunused-but-set-parameter]) +UL_WARN_ADD([-Wunused-but-set-variable]) +UL_WARN_ADD([-Wunused-parameter]) +UL_WARN_ADD([-Wunused-result]) +UL_WARN_ADD([-Wunused-variable]) +UL_WARN_ADD([-Wduplicated-cond]) +UL_WARN_ADD([-Wduplicated-branches]) +UL_WARN_ADD([-Wrestrict]) +UL_WARN_ADD([-Wnull-dereference]) +UL_WARN_ADD([-pedantic]) + +AC_SUBST([WARN_CFLAGS]) + +##### search for dependencies ##### + +have_zlib="no" +have_lzma="no" + +PKG_CHECK_MODULES(ZLIB, [zlib], [have_zlib="yes"], []) +PKG_CHECK_MODULES(XZ, [liblzma >= 5.0.0], [have_lzma="yes"], []) + +AM_CONDITIONAL([WITH_ZLIB], [test "x$have_zlib" == "xyes"]) +AM_CONDITIONAL([WITH_LZMA], [test "x$have_lzma" == "xyes"]) + +##### generate output ##### + +AC_CONFIG_HEADERS([config.h]) + +AC_OUTPUT([Makefile]) diff --git a/include/command.h b/include/command.h new file mode 100644 index 0000000..5806e24 --- /dev/null +++ b/include/command.h @@ -0,0 +1,31 @@ +#ifndef COMMAND_H +#define COMMAND_H + +typedef struct command_t { + struct command_t *next; + + const char *cmd; /* command name */ + const char *usage; /* list of possible arguments */ + const char *s_desc; /* short description used by help */ + const char *l_desc; /* long description used by help */ + + int (*run_cmd)(int argc, char **argv); +} command_t; + +void command_register(command_t *cmd); + +command_t *command_by_name(const char *name); + +void __attribute__((noreturn)) usage(int status); + +void tell_read_help(const char *cmd); + +int check_arguments(const char *cmd, int argc, int minc, int maxc); + +#define REGISTER_COMMAND(cmd) \ + static void __attribute__((constructor)) register_##cmd(void) \ + { \ + command_register((command_t *)&cmd); \ + } + +#endif /* COMMAND_H */ diff --git a/include/compressor.h b/include/compressor.h new file mode 100644 index 0000000..a502e73 --- /dev/null +++ b/include/compressor.h @@ -0,0 +1,43 @@ +#ifndef COMPRESSOR_H +#define COMPRESSOR_H + +#include +#include + +#include "pkgformat.h" + +typedef struct compressor_stream_t { + ssize_t (*write)(struct compressor_stream_t *stream, const uint8_t *in, + size_t size); + + ssize_t (*read)(struct compressor_stream_t *stream, uint8_t *out, + size_t size); + + void (*flush)(struct compressor_stream_t *stream); + + void (*destroy)(struct compressor_stream_t *stream); +} compressor_stream_t; + +typedef struct compressor_t { + struct compressor_t *next; + const char *name; + PKG_COMPRESSION id; + + compressor_stream_t *(*compression_stream)(struct compressor_t *cmp); + + compressor_stream_t *(*uncompression_stream)(struct compressor_t *cmp); +} compressor_t; + +void compressor_register(compressor_t *compressor); + +compressor_t *compressor_by_name(const char *name); + +compressor_t *compressor_by_id(PKG_COMPRESSION id); + +#define REGISTER_COMPRESSOR(compressor) \ + static void __attribute__((constructor)) register_##compressor(void) \ + { \ + compressor_register((compressor_t *)&compressor); \ + } + +#endif /* COMPRESSOR_H */ diff --git a/include/image_entry.h b/include/image_entry.h new file mode 100644 index 0000000..fda45b3 --- /dev/null +++ b/include/image_entry.h @@ -0,0 +1,37 @@ +#ifndef IMAGE_ENTRY_H +#define IMAGE_ENTRY_H + +#include +#include + +#include "pkgreader.h" + +typedef struct image_entry_t { + struct image_entry_t *next; + char *name; + mode_t mode; + uid_t uid; + gid_t gid; + + union { + struct { + char *location; + uint64_t size; + uint32_t id; + } file; + + struct { + char *target; + } symlink; + } data; +} image_entry_t; + +void image_entry_free(image_entry_t *ent); + +void image_entry_free_list(image_entry_t *list); + +image_entry_t *image_entry_list_from_package(pkg_reader_t *pkg); + +image_entry_t *image_entry_sort(image_entry_t *list); + +#endif /* IMAGE_ENTRY_H */ diff --git a/include/pkgformat.h b/include/pkgformat.h new file mode 100644 index 0000000..804877b --- /dev/null +++ b/include/pkgformat.h @@ -0,0 +1,56 @@ +#ifndef PKG_FORMAT_H +#define PKG_FORMAT_H + +#include +#include +#include +#include + +typedef enum { + PKG_MAGIC_HEADER = 0x21676B70, + PKG_MAGIC_TOC = 0x21636F74, + PKG_MAGIC_DATA = 0x21746164, +} PKG_MAGIC; + +typedef enum { + PKG_COMPRESSION_NONE = 0, + PKG_COMPRESSION_ZLIB = 1, +} PKG_COMPRESSION; + +typedef struct { + uint32_t magic; + uint8_t compression; + uint8_t pad0; + uint8_t pad1; + uint8_t pad2; + uint64_t compressed_size; + uint64_t raw_size; + + /* uint8_t data[]; */ +} record_t; + +typedef struct { + uint32_t mode; + uint32_t uid; + uint32_t gid; + + uint16_t path_length; + /* uint8_t path[]; */ +} toc_entry_t; + +typedef struct { + uint16_t target_length; + /* uint8_t target[]; */ +} toc_symlink_extra_t; + +typedef struct { + uint64_t size; + uint32_t id; +} toc_file_extra_t; + +typedef struct { + uint32_t id; + /* uint8_t data[]; */ +} file_data_t; + +#endif /* PKG_FORMAT_H */ diff --git a/include/pkgreader.h b/include/pkgreader.h new file mode 100644 index 0000000..8d6164a --- /dev/null +++ b/include/pkgreader.h @@ -0,0 +1,23 @@ +#ifndef PKGREADER_H +#define PKGREADER_H + +#include "pkgformat.h" + +typedef struct pkg_reader_t pkg_reader_t; + +pkg_reader_t *pkg_reader_open(const char *path); + +void pkg_reader_close(pkg_reader_t *reader); + +int pkg_reader_get_next_record(pkg_reader_t *reader); + +record_t *pkg_reader_current_record_header(pkg_reader_t *reader); + +ssize_t pkg_reader_read_payload(pkg_reader_t *reader, void *buffer, + size_t size); + +int pkg_reader_rewind(pkg_reader_t *reader); + +const char *pkg_reader_get_filename(pkg_reader_t *reader); + +#endif /* PKGREADER_H */ diff --git a/include/pkgwriter.h b/include/pkgwriter.h new file mode 100644 index 0000000..65f552e --- /dev/null +++ b/include/pkgwriter.h @@ -0,0 +1,20 @@ +#ifndef PKGWRITER_H +#define PKGWRITER_H + +#include "pkgformat.h" +#include "compressor.h" + +typedef struct pkg_writer_t pkg_writer_t; + +pkg_writer_t *pkg_writer_open(const char *path); + +void pkg_writer_close(pkg_writer_t *writer); + +int pkg_writer_start_record(pkg_writer_t *writer, uint32_t magic, + compressor_t *cmp); + +int pkg_writer_write_payload(pkg_writer_t *wr, void *data, size_t size); + +int pkg_writer_end_record(pkg_writer_t *wr); + +#endif /* PKGWRITER_H */ diff --git a/include/util.h b/include/util.h new file mode 100644 index 0000000..33a86ae --- /dev/null +++ b/include/util.h @@ -0,0 +1,12 @@ +#ifndef UTIL_H +#define UTIL_H + +#include + +int canonicalize_name(char *filename); + +ssize_t write_retry(int fd, void *data, size_t size); + +ssize_t read_retry(int fd, void *buffer, size_t size); + +#endif /* UTIL_H */ diff --git a/m4/compiler.m4 b/m4/compiler.m4 new file mode 100644 index 0000000..058c73f --- /dev/null +++ b/m4/compiler.m4 @@ -0,0 +1,40 @@ +dnl Copyright (C) 2008-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Simon Josefsson +dnl -- derivated from coreutils m4/warnings.m4 + +# UL_AS_VAR_APPEND(VAR, VALUE) +# ---------------------------- +# Provide the functionality of AS_VAR_APPEND if Autoconf does not have it. +m4_ifdef([AS_VAR_APPEND], +[m4_copy([AS_VAR_APPEND], [UL_AS_VAR_APPEND])], +[m4_define([UL_AS_VAR_APPEND], +[AS_VAR_SET([$1], [AS_VAR_GET([$1])$2])])]) + +# UL_ADD_WARN(COMPILER_OPTION [, VARNAME]) +# ------------------------ +# Adds parameter to WARN_CFLAGS (or to $VARNAME) if the compiler supports it. +AC_DEFUN([UL_WARN_ADD], [ + m4_define([warnvarname], m4_default([$2],WARN_CFLAGS)) + AS_VAR_PUSHDEF([ul_Warn], [ul_cv_warn_$1])dnl + AC_CACHE_CHECK([whether compiler handles $1], m4_defn([ul_Warn]), [ + # store AC_LANG_WERROR status, then turn it on + save_ac_[]_AC_LANG_ABBREV[]_werror_flag="${ac_[]_AC_LANG_ABBREV[]_werror_flag}" + AC_LANG_WERROR + + ul_save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="-Werror ${CPPFLAGS} $1" + AC_PREPROC_IFELSE([AC_LANG_PROGRAM([])], + [AS_VAR_SET(ul_Warn, [yes])], + [AS_VAR_SET(ul_Warn, [no])]) + # restore AC_LANG_WERROR + ac_[]_AC_LANG_ABBREV[]_werror_flag="${save_ac_[]_AC_LANG_ABBREV[]_werror_flag}" + + CPPFLAGS="$ul_save_CPPFLAGS" + ]) + AS_VAR_IF(ul_Warn, [yes], [UL_AS_VAR_APPEND(warnvarname, [" $1"])]) +]) + diff --git a/main/cmd/dump/dump.c b/main/cmd/dump/dump.c new file mode 100644 index 0000000..dcb2da0 --- /dev/null +++ b/main/cmd/dump/dump.c @@ -0,0 +1,103 @@ +#include "dump.h" + +static const struct option long_opts[] = { + { "format", required_argument, NULL, 'f' }, + { "root", required_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = "f:r:"; + +static int cmd_dump(int argc, char **argv) +{ + TOC_FORMAT format = TOC_FORMAT_PRETTY; + image_entry_t *list = NULL; + const char *root = NULL; + int ret = EXIT_FAILURE; + pkg_reader_t *rd; + int i; + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'f': + if (strcmp(optarg, "sqfs") == 0) { + format = TOC_FORMAT_SQFS; + } else if (strcmp(optarg, "initrd") == 0) { + format = TOC_FORMAT_INITRD; + } else { + fprintf(stderr, "unknown format '%s'\n", + optarg); + tell_read_help(argv[0]); + return EXIT_FAILURE; + } + break; + case 'r': + root = optarg; + break; + default: + tell_read_help(argv[0]); + return EXIT_FAILURE; + } + } + + if (optind >= argc) { + fputs("missing argument: package file\n", stderr); + tell_read_help(argv[0]); + return EXIT_FAILURE; + } + + rd = pkg_reader_open(argv[optind++]); + if (rd == NULL) + return EXIT_FAILURE; + + if (optind < argc) + fputs("warning: ignoring extra arguments\n", stderr); + + list = image_entry_list_from_package(rd); + if (list == NULL) + goto out; + + if (dump_toc(list, root, format)) + goto out; + + ret = EXIT_SUCCESS; +out: + if (list != NULL) + image_entry_free_list(list); + pkg_reader_close(rd); + return ret; +} + +static command_t dump = { + .cmd = "dump", + .usage = "[OPTIONS...] ", + .s_desc = "read dump all information from a package file", + .l_desc = +"Parse a package file and dump information it contains, such as the list of\n" +"files, et cetera.\n" +"\n" +"Possible options:\n" +" --format, -f Specify what format to use for printing the table\n" +" of contents. Default is a pretty printed, human\n" +" readable version.\n" +"\n" +" If \"sqfs\" is specified, a squashfs pseudo file\n" +" is genareated for setting permissions bits and\n" +" ownership appropriately.\n" +"\n" +" If \"initrd\" is specified, a format appropriate\n" +" for Linux gen_init_cpio is produced.\n" +"\n" +" --root, -r If a format is used that requires absoulute input\n" +" paths (e.g. initrd), prefix all file paths with\n" +" this. Can be used, for instance to unpack a\n" +" package to a staging directory and generate a\n" +" a listing for Linux CONFIG_INITRAMFS_SOURCE.\n", + .run_cmd = cmd_dump, +}; + +REGISTER_COMMAND(dump) diff --git a/main/cmd/dump/dump.h b/main/cmd/dump/dump.h new file mode 100644 index 0000000..e18bd1a --- /dev/null +++ b/main/cmd/dump/dump.h @@ -0,0 +1,24 @@ +#ifndef DUMP_H +#define DUMP_H + +#include +#include +#include +#include +#include + +#include "image_entry.h" +#include "pkgformat.h" +#include "pkgreader.h" +#include "command.h" +#include "util.h" + +typedef enum { + TOC_FORMAT_PRETTY = 0, + TOC_FORMAT_SQFS = 1, + TOC_FORMAT_INITRD = 2, +} TOC_FORMAT; + +int dump_toc(image_entry_t *list, const char *root, TOC_FORMAT format); + +#endif /* DUMP_H */ diff --git a/main/cmd/dump/dump_toc.c b/main/cmd/dump/dump_toc.c new file mode 100644 index 0000000..231db89 --- /dev/null +++ b/main/cmd/dump/dump_toc.c @@ -0,0 +1,101 @@ +#include "dump.h" + +typedef int (*print_fun_t)(image_entry_t *ent, const char *root); + +static int print_pretty(image_entry_t *ent, const char *root) +{ + (void)root; + + printf("%s\n", ent->name); + printf("\towner: %d/%d\n", ent->uid, ent->gid); + printf("\tpermissions: 0%04o\n", ent->mode & (~S_IFMT)); + + fputs("\ttype: ", stdout); + + switch (ent->mode & S_IFMT) { + case S_IFLNK: + fputs("symlink\n", stdout); + + printf("\ttarget: %s\n\n", ent->data.symlink.target); + break; + case S_IFREG: + fputs("file\n", stdout); + + printf("\tsize: %lu\n", (unsigned long)ent->data.file.size); + printf("\tid: %u\n\n", (unsigned int)ent->data.file.id); + break; + case S_IFDIR: + fputs("directory\n\n", stdout); + break; + default: + fputs("unknown\n\n", stdout); + goto fail_type; + } + return 0; +fail_type: + fputs("unknown file type in table of contents\n", stderr); + return -1; +} + +static int print_sqfs(image_entry_t *ent, const char *root) +{ + mode_t mode = ent->mode & (~S_IFMT); + (void)root; + + if ((ent->mode & S_IFMT) == S_IFLNK) + mode = 0777; + + printf("%s m %o %u %u\n", ent->name, mode, + (unsigned int)ent->uid, + (unsigned int)ent->gid); + return 0; +} + +static int print_initrd(image_entry_t *ent, const char *root) +{ + mode_t mode = ent->mode & (~S_IFMT); + + switch (ent->mode & S_IFMT) { + case S_IFLNK: + printf("slink /%s %s", ent->name, ent->data.symlink.target); + mode = 0777; + break; + case S_IFREG: + printf("file /%s ", ent->name); + + if (root == NULL) { + fputs(ent->name, stdout); + } else { + printf("%s/%s", root, ent->name); + } + break; + case S_IFDIR: + printf("dir /%s", ent->name); + break; + default: + fputs("unknown file type in table of contents\n", stderr); + return -1; + } + + printf(" 0%o %u %u\n", mode, + (unsigned int)ent->uid, (unsigned int)ent->gid); + return 0; +} + +static print_fun_t printers[] = { + print_pretty, + print_sqfs, + print_initrd, +}; + +int dump_toc(image_entry_t *list, const char *root, TOC_FORMAT format) +{ + while (list != NULL) { + if (printers[format](list, root)) + return -1; + + list = list->next; + } + + return 0; +} diff --git a/main/cmd/help.c b/main/cmd/help.c new file mode 100644 index 0000000..4d96dcf --- /dev/null +++ b/main/cmd/help.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include + +#include "command.h" + +extern char *__progname; + +static void pretty_print(const char *str, int padd, int len) +{ + int i, brk; +next: + while (isspace(*str) && *str != '\n') + ++str; + + if (!(*str)) + return; + + if (*str == '\n') { + fputc('\n', stdout); + ++str; + len = padd; + goto next; + } + + for (i = 0, brk = 0; str[i]; ++i) { + if (str[i] == '<' || str[i] == '[') + ++brk; + if (str[i] == '>' || str[i] == ']') + --brk; + if (!brk && isspace(str[i])) + break; + } + + if ((len + i) < 80) { + fwrite(str, 1, i, stdout); + str += i; + len += i; + + if ((len + 1) < 80) { + fputc(' ', stdout); + ++len; + goto next; + } + } + + printf("\n%*s", padd, ""); + len = padd; + goto next; +} + +static void print_cmd_usage(const char *cmd, const char *usage) +{ + int padd; + + padd = printf("Usage: %s %s ", __progname, cmd); + + if ((strlen(usage) + padd) < 80) { + fputs(usage, stdout); + return; + } + + pretty_print(usage, padd, padd); +} + +static int cmd_help(int argc, char **argv) +{ + const char *help; + command_t *cmd; + + if (argc < 2) + usage(EXIT_SUCCESS); + + if (argc > 2) { + fprintf(stderr, "Too many arguments\n\n" + "Usage: %s help ", __progname); + return EXIT_FAILURE; + } + + cmd = command_by_name(argv[1]); + + if (cmd == NULL) { + fprintf(stderr, "Unknown command '%s'\n\n" + "Try `%s help' for a list of available commands\n", + argv[1], __progname); + return EXIT_FAILURE; + } + + print_cmd_usage(cmd->cmd, cmd->usage); + fputs("\n\n", stdout); + + if (cmd->l_desc != NULL) { + fputs(cmd->l_desc, stdout); + } else { + help = cmd->s_desc; + + if (islower(*help)) { + fputc(toupper(*(help++)), stdout); + pretty_print(help, 0, 1); + } else { + pretty_print(help, 0, 0); + } + } + + fputc('\n', stdout); + return EXIT_SUCCESS; +} + +static command_t help = { + .cmd = "help", + .usage = "", + .s_desc = "print a help text for a command", + .l_desc = +"Print a help text for a specified command. If no command is specified,\n" +"a generic help text and a list of available commands is shown.\n", + .run_cmd = cmd_help, +}; + +REGISTER_COMMAND(help) diff --git a/main/cmd/pack/filelist.c b/main/cmd/pack/filelist.c new file mode 100644 index 0000000..5d63548 --- /dev/null +++ b/main/cmd/pack/filelist.c @@ -0,0 +1,183 @@ +#include "pack.h" + +static char *skipspace(char *str) +{ + while (isspace(*str)) + ++str; + return str; +} + +static void complain(const input_file_t *f, const char *msg) +{ + fprintf(stderr, "%s: %zu: %s\n", f->filename, f->linenum, msg); +} + +static void oom(input_file_t *f) +{ + complain(f, "out of memory"); +} + +static image_entry_t *filelist_mkentry(input_file_t *f, mode_t filetype) +{ + char *line = f->line; + image_entry_t *ent; + size_t i; + + ent = calloc(1, sizeof(*ent)); + if (ent == NULL) { + oom(f); + return NULL; + } + + /* name */ + for (i = 0; !isspace(line[i]) && line[i] != '\0'; ++i) + ; + + if (!isspace(line[i])) { + complain(f, "expected space after file name"); + goto fail; + } + + ent->name = calloc(1, i + 1); + if (ent->name == NULL) { + oom(f); + goto fail; + } + + memcpy(ent->name, line, i); + if (canonicalize_name(ent->name)) { + complain(f, "invalid file name"); + goto fail; + } + + if (ent->name[0] == '\0') { + complain(f, "refusing to add entry for '/'"); + goto fail; + } + + if (strlen(ent->name) > 0xFFFF) { + complain(f, "name too long"); + goto fail; + } + + line = skipspace(line + i); + + /* mode */ + if (!isdigit(*line)) { + complain(f, "expected numeric mode after file name"); + goto fail; + } + + while (isdigit(*line)) { + if (*line > '7') { + complain(f, "mode must be octal number"); + goto fail; + } + ent->mode = (ent->mode << 3) | (*(line++) - '0'); + } + + if (!isspace(*line)) { + complain(f, "expected space after file mode"); + goto fail; + } + + line = skipspace(line); + + ent->mode = (ent->mode & ~S_IFMT) | filetype; + + /* uid */ + if (!isdigit(*line)) { + complain(f, "expected numeric UID after mode"); + goto fail; + } + + while (isdigit(*line)) + ent->uid = (ent->uid * 10) + (*(line++) - '0'); + + if (!isspace(*line)) { + complain(f, "expected space after UID"); + goto fail; + } + + line = skipspace(line); + + /* gid */ + if (!isdigit(*line)) { + complain(f, "expected numeric GID after UID"); + goto fail; + } + + while (isdigit(*line)) + ent->gid = (ent->gid * 10) + (*(line++) - '0'); + + line = skipspace(line); + + /* remove processed data */ + memmove(f->line, line, strlen(line) + 1); + return ent; +fail: + free(ent->name); + free(ent); + return NULL; +} + +image_entry_t *filelist_mkdir(input_file_t *f) +{ + return filelist_mkentry(f, S_IFDIR); +} + +image_entry_t *filelist_mkslink(input_file_t *f) +{ + image_entry_t *ent = filelist_mkentry(f, S_IFLNK); + + if (ent == NULL) + return NULL; + + ent->data.symlink.target = strdup(f->line); + if (ent->data.symlink.target == NULL) { + oom(f); + goto fail; + } + + if (strlen(ent->data.symlink.target) > 0xFFFF) { + complain(f, "symlink target too long"); + goto fail; + } + + return ent; +fail: + image_entry_free(ent); + return NULL; +} + +image_entry_t *filelist_mkfile(input_file_t *f) +{ + image_entry_t *ent = filelist_mkentry(f, S_IFREG); + struct stat sb; + + if (ent == NULL) + return NULL; + + ent->data.file.location = strdup(f->line); + if (ent->data.file.location == NULL) { + oom(f); + goto fail; + } + + if (stat(ent->data.file.location, &sb) != 0) { + perror(ent->data.file.location); + goto fail; + } + + if (sizeof(off_t) > sizeof(uint64_t) && + sb.st_size > (off_t)(~((uint64_t)0))) { + complain(f, "input file is too big"); + goto fail; + } + + ent->data.file.size = sb.st_size; + return ent; +fail: + image_entry_free(ent); + return NULL; +} diff --git a/main/cmd/pack/filelist_read.c b/main/cmd/pack/filelist_read.c new file mode 100644 index 0000000..edd88af --- /dev/null +++ b/main/cmd/pack/filelist_read.c @@ -0,0 +1,130 @@ +#include "pack.h" + +static const struct { + const char *name; + image_entry_t *(*handle)(input_file_t *f); +} line_hooks[] = { + { "file", filelist_mkfile }, + { "dir", filelist_mkdir }, + { "slink", filelist_mkslink }, +}; + +#define NUM_LINE_HOOKS (sizeof(line_hooks) / sizeof(line_hooks[0])) + +static int prefetch_line(input_file_t *f) +{ + char *line = NULL; + size_t n = 0; + ssize_t ret; + + free(f->line); + f->line = NULL; + + errno = 0; + ret = getline(&line, &n, f->f); + + if (ret < 0) { + if (errno != 0) { + perror(f->filename); + free(line); + return -1; + } + free(line); + return 1; + } + + n = strlen(line); + while (n >0 && isspace(line[n - 1])) + --n; + line[n] = '\0'; + + f->line = line; + f->linenum += 1; + return 0; +} + +static void cleanup_file(input_file_t *f) +{ + fclose(f->f); + free(f->line); +} + +static int open_file(input_file_t *f, const char *filename) +{ + memset(f, 0, sizeof(*f)); + + f->filename = filename; + f->f = fopen(filename, "r"); + + if (f->f == NULL) { + perror(f->filename); + return -1; + } + + return 0; +} + +image_entry_t *filelist_read(const char *filename) +{ + image_entry_t *ent, *list = NULL; + input_file_t f; + size_t i, len; + char *ptr; + int ret; + + if (open_file(&f, filename)) + return NULL; + + for (;;) { + ret = prefetch_line(&f); + if (ret < 0) + goto fail; + if (ret > 0) + break; + + for (ptr = f.line; isspace(*ptr); ++ptr) + ; + + if (*ptr == '\0' || *ptr == '#') + continue; + + for (i = 0; i < NUM_LINE_HOOKS; ++i) { + len = strlen(line_hooks[i].name); + + if (strncmp(ptr, line_hooks[i].name, len) != 0) + continue; + if (!isspace(ptr[len]) && ptr[len] != '\0') + continue; + for (ptr += len; isspace(*ptr); ++ptr) + ; + memmove(f.line, ptr, strlen(ptr) + 1); + break; + } + + if (i == NUM_LINE_HOOKS) { + fprintf(stderr, "%s: %zu: unknown entry type\n", + f.filename, f.linenum); + goto fail; + } + + ent = line_hooks[i].handle(&f); + if (ent == NULL) + goto fail; + + ent->next = list; + list = ent; + } + + if (list == NULL) { + fprintf(stderr, "%s: does not contain any entries\n", + f.filename); + goto fail; + } + + cleanup_file(&f); + return list; +fail: + cleanup_file(&f); + image_entry_free_list(list); + return NULL; +} diff --git a/main/cmd/pack/pack.c b/main/cmd/pack/pack.c new file mode 100644 index 0000000..5bc5583 --- /dev/null +++ b/main/cmd/pack/pack.c @@ -0,0 +1,163 @@ +#include "pack.h" + +static const struct option long_opts[] = { + { "toc-compressor", required_argument, NULL, 't' }, + { "file-compressor", required_argument, NULL, 'f' }, + { "file-list", required_argument, NULL, 'l' }, + { "output", required_argument, NULL, 'o' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = "t:f:l:o:"; + +static compressor_t *get_default_compressor(void) +{ + compressor_t *cmp; + + cmp = compressor_by_id(PKG_COMPRESSION_ZLIB); + if (cmp != NULL) + return cmp; + + cmp = compressor_by_id(PKG_COMPRESSION_NONE); + if (cmp != NULL) + return cmp; + + return cmp; +} + +static compressor_t *try_get_compressor(const char *name) +{ + compressor_t *cmp = compressor_by_name(name); + + if (cmp == NULL) + fprintf(stderr, "unkown compressor: %s\n", name); + + return cmp; +} + +static int alloc_file_ids(image_entry_t *list) +{ + image_entry_t *ent; + uint64_t file_id = 0; + + for (ent = list; ent != NULL; ent = ent->next) { + if (!S_ISREG(ent->mode)) + continue; + + if (file_id > 0x00000000FFFFFFFFUL) { + fprintf(stderr, "too many input files\n"); + return -1; + } + + ent->data.file.id = file_id++; + } + + return 0; +} + +static int cmd_pack(int argc, char **argv) +{ + const char *filelist = NULL, *filename = NULL; + compressor_t *cmp_toc, *cmp_files; + image_entry_t *list; + pkg_writer_t *wr; + int i; + + cmp_toc = get_default_compressor(); + cmp_files = cmp_toc; + + if (cmp_toc == NULL) { + fputs("no compressor implementations available\n", stderr); + return EXIT_FAILURE; + } + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 't': + cmp_toc = try_get_compressor(optarg); + if (cmp_toc == NULL) + return EXIT_FAILURE; + break; + case 'f': + cmp_files = try_get_compressor(optarg); + if (cmp_files == NULL) + return EXIT_FAILURE; + break; + case 'l': + filelist = optarg; + break; + case 'o': + filename = optarg; + break; + default: + tell_read_help(argv[0]); + return EXIT_FAILURE; + } + } + + if (filename == NULL) { + fputs("missing argument: output package file\n", stderr); + tell_read_help(argv[0]); + return EXIT_FAILURE; + } + + if (filelist == NULL) { + fputs("missing argument: input file list\n", stderr); + tell_read_help(argv[0]); + return EXIT_FAILURE; + } + + if (optind < argc) + fputs("warning: ignoring extra arguments\n", stderr); + + list = filelist_read(filelist); + if (list == NULL) + return EXIT_FAILURE; + + list = image_entry_sort(list); + + if (alloc_file_ids(list)) + goto fail_fp; + + wr = pkg_writer_open(filename); + if (wr == NULL) + goto fail_fp; + + if (write_toc(wr, list, cmp_toc)) + goto fail; + + if (write_files(wr, list, cmp_files)) + goto fail; + + pkg_writer_close(wr); + image_entry_free_list(list); + return EXIT_SUCCESS; +fail: + pkg_writer_close(wr); +fail_fp: + image_entry_free_list(list); + return EXIT_FAILURE; +} + +static command_t pack = { + .cmd = "pack", + .usage = "OPTIONS...", + .s_desc = "generate a package file", + .l_desc = +"Read a list of files from the given lists file and generate a package.\n" +"Possible options:\n" +" --file-list, -l Specify a file containing a list of input files.\n" +" --output, -o Specify the path of the resulting package file.\n" +"\n" +" --toc-compressor, -t \n" +" --file-compressor, -f \n" +" Specify what compressor to use for compressing the table of contents,\n" +" or for compressing files respectively.\n", + .run_cmd = cmd_pack, +}; + +REGISTER_COMMAND(pack) diff --git a/main/cmd/pack/pack.h b/main/cmd/pack/pack.h new file mode 100644 index 0000000..6fc3023 --- /dev/null +++ b/main/cmd/pack/pack.h @@ -0,0 +1,43 @@ +#ifndef PACK_H +#define PACK_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "image_entry.h" +#include "compressor.h" +#include "pkgformat.h" +#include "pkgwriter.h" +#include "command.h" +#include "util.h" + +typedef struct { + FILE *f; + char *line; + const char *filename; + size_t linenum; +} input_file_t; + +image_entry_t *filelist_mkdir(input_file_t *f); + +image_entry_t *filelist_mkslink(input_file_t *f); + +image_entry_t *filelist_mkfile(input_file_t *f); + +image_entry_t *filelist_read(const char *filename); + +int write_toc(pkg_writer_t *wr, image_entry_t *list, compressor_t *cmp); + +int write_files(pkg_writer_t *wr, image_entry_t *list, compressor_t *cmp); + +#endif /* PACK_H */ diff --git a/main/cmd/pack/write_files.c b/main/cmd/pack/write_files.c new file mode 100644 index 0000000..fbe43bb --- /dev/null +++ b/main/cmd/pack/write_files.c @@ -0,0 +1,64 @@ +#include "pack.h" + +static int write_file(pkg_writer_t *wr, image_entry_t *ent) +{ + uint64_t offset = 0; + uint8_t buffer[1024]; + file_data_t fdata; + ssize_t ret; + int fd; + + memset(&fdata, 0, sizeof(fdata)); + + fdata.id = htole32(ent->data.file.id); + + if (pkg_writer_write_payload(wr, &fdata, sizeof(fdata))) + return -1; + + fd = open(ent->data.file.location, O_RDONLY); + + if (fd < 0) { + perror(ent->data.file.location); + return -1; + } + + while (offset < ent->data.file.size) { + ret = read_retry(fd, buffer, sizeof(buffer)); + + if (ret < 0) { + perror(ent->data.file.location); + goto fail_fd; + } + + if (ret == 0) + break; + + if (pkg_writer_write_payload(wr, buffer, ret)) + goto fail_fd; + + offset += (uint64_t)ret; + } + + close(fd); + return 0; +fail_fd: + close(fd); + return -1; +} + +int write_files(pkg_writer_t *wr, image_entry_t *list, compressor_t *cmp) +{ + if (pkg_writer_start_record(wr, PKG_MAGIC_DATA, cmp)) + return -1; + + while (list != NULL) { + if (S_ISREG(list->mode)) { + if (write_file(wr, list)) + return -1; + } + + list = list->next; + } + + return pkg_writer_end_record(wr); +} diff --git a/main/cmd/pack/write_toc.c b/main/cmd/pack/write_toc.c new file mode 100644 index 0000000..924e846 --- /dev/null +++ b/main/cmd/pack/write_toc.c @@ -0,0 +1,71 @@ +#include "pack.h" + +static int write_entry(pkg_writer_t *wr, image_entry_t *it) +{ + toc_entry_t ent; + size_t len; + + len = strlen(it->name); + + memset(&ent, 0, sizeof(ent)); + ent.mode = htole32(it->mode); + ent.uid = htole32(it->uid); + ent.gid = htole32(it->gid); + ent.path_length = htole16(len); + + if (pkg_writer_write_payload(wr, &ent, sizeof(ent))) + return -1; + + if (pkg_writer_write_payload(wr, it->name, len)) + return -1; + + switch (it->mode & S_IFMT) { + case S_IFREG: { + toc_file_extra_t file; + + memset(&file, 0, sizeof(file)); + file.size = htole64(it->data.file.size); + file.id = htole32(it->data.file.id); + + if (pkg_writer_write_payload(wr, &file, sizeof(file))) + return -1; + break; + } + case S_IFLNK: { + toc_symlink_extra_t sym; + + len = strlen(it->data.symlink.target); + + memset(&sym, 0, sizeof(sym)); + sym.target_length = htole16(len); + + if (pkg_writer_write_payload(wr, &sym, sizeof(sym))) + return -1; + + if (pkg_writer_write_payload(wr, it->data.symlink.target, len)) + return -1; + break; + } + case S_IFDIR: + break; + default: + assert(0); + } + + return 0; +} + +int write_toc(pkg_writer_t *wr, image_entry_t *list, compressor_t *cmp) +{ + if (pkg_writer_start_record(wr, PKG_MAGIC_TOC, cmp)) + return -1; + + while (list != NULL) { + if (write_entry(wr, list)) + return -1; + + list = list->next; + } + + return pkg_writer_end_record(wr); +} diff --git a/main/cmd/unpack/create_hierarchy.c b/main/cmd/unpack/create_hierarchy.c new file mode 100644 index 0000000..ac73162 --- /dev/null +++ b/main/cmd/unpack/create_hierarchy.c @@ -0,0 +1,26 @@ +#include "unpack.h" + +int create_hierarchy(image_entry_t *list) +{ + image_entry_t *ent; + + for (ent = list; ent != NULL; ent = ent->next) { + if (S_ISDIR(ent->mode)) { + if (mkdir(ent->name, 0755)) { + perror(ent->name); + return -1; + } + } + } + + for (ent = list; ent != NULL; ent = ent->next) { + if (S_ISLNK(ent->mode)) { + if (symlink(ent->data.symlink.target, ent->name)) { + perror(ent->name); + return -1; + } + } + } + + return 0; +} diff --git a/main/cmd/unpack/mkdir_p.c b/main/cmd/unpack/mkdir_p.c new file mode 100644 index 0000000..1c8e20e --- /dev/null +++ b/main/cmd/unpack/mkdir_p.c @@ -0,0 +1,34 @@ +#include "unpack.h" + +int mkdir_p(const char *path) +{ + size_t i, len; + char *buffer; + + while (*path == '/') + ++path; + + if (*path == '\0') + return 0; + + len = strlen(path) + 1; + buffer = alloca(len); + + for (i = 0; i < len; ++i) { + if (path[i] == '/' || path[i] == '\0') { + buffer[i] = '\0'; + + if (mkdir(buffer, 0755) != 0) { + if (errno != EEXIST) { + fprintf(stderr, "mkdir %s: %s\n", + buffer, strerror(errno)); + return -1; + } + } + } + + buffer[i] = path[i]; + } + + return 0; +} diff --git a/main/cmd/unpack/unpack.c b/main/cmd/unpack/unpack.c new file mode 100644 index 0000000..9a95081 --- /dev/null +++ b/main/cmd/unpack/unpack.c @@ -0,0 +1,249 @@ +#include "unpack.h" + + +static const struct option long_opts[] = { + { "root", required_argument, NULL, 'r' }, + { "no-chown", required_argument, NULL, 'o' }, + { "no-chmod", required_argument, NULL, 'm' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = "r:om"; + +static int set_root(const char *path) +{ + if (mkdir_p(path)) + return -1; + + if (chdir(path)) { + fprintf(stderr, "cd %s: %s\n", path, strerror(errno)); + return -1; + } + + return 0; +} + +static image_entry_t *get_file_entry(image_entry_t *list, uint32_t id) +{ + while (list != NULL) { + if (S_ISREG(list->mode) && list->data.file.id == id) + return list; + + list = list->next; + } + + return NULL; +} + +static int unpack_files(image_entry_t *list, pkg_reader_t *rd) +{ + uint8_t buffer[2048]; + image_entry_t *meta; + file_data_t frec; + ssize_t ret; + size_t diff; + uint64_t i; + int fd; + + for (;;) { + ret = pkg_reader_read_payload(rd, &frec, sizeof(frec)); + if (ret == 0) + break; + if (ret < 0) + return -1; + if ((size_t)ret < sizeof(frec)) + goto fail_trunc; + + frec.id = le32toh(frec.id); + + meta = get_file_entry(list, frec.id); + if (meta == NULL) { + fprintf(stderr, "%s: missing meta information for " + "file %u\n", pkg_reader_get_filename(rd), + (unsigned int)frec.id); + return -1; + } + + fd = open(meta->name, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd < 0) { + perror(meta->name); + return -1; + } + + for (i = 0; i < meta->data.file.size; i += ret) { + if ((meta->data.file.size - i) < + (uint64_t)sizeof(buffer)) { + diff = meta->data.file.size - i; + } else { + diff = sizeof(buffer); + } + + ret = pkg_reader_read_payload(rd, buffer, diff); + if (ret < 0) + goto fail_fd; + if ((size_t)ret < diff) + goto fail_trunc; + + ret = write_retry(fd, buffer, diff); + if (ret < 0) { + perror(meta->name); + goto fail_fd; + } + + if ((size_t)ret < diff) { + fprintf(stderr, "%s: truncated write\n", + pkg_reader_get_filename(rd)); + goto fail_fd; + } + } + + close(fd); + } + + return 0; +fail_fd: + close(fd); + return -1; +fail_trunc: + fprintf(stderr, "%s: truncated file data record\n", + pkg_reader_get_filename(rd)); + return -1; +} + +static int change_permissions(image_entry_t *list, int flags) +{ + while (list != NULL) { + if (S_ISLNK(list->mode)) { + list = list->next; + continue; + } + + if (!(flags & FLAG_NO_CHMOD)) { + if (chmod(list->name, list->mode)) { + fprintf(stderr, "%s: chmod: %s\n", list->name, + strerror(errno)); + return -1; + } + } + + if (!(flags & FLAG_NO_CHOWN)) { + if (chown(list->name, list->uid, list->gid)) { + fprintf(stderr, "%s: chown: %s\n", list->name, + strerror(errno)); + return -1; + } + } + + list = list->next; + } + + return 0; +} + +static int cmd_unpack(int argc, char **argv) +{ + const char *root = NULL, *filename; + image_entry_t *list = NULL; + int i, ret, flags = 0; + pkg_reader_t *rd; + record_t *hdr; + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'r': + root = optarg; + break; + case 'o': + flags |= FLAG_NO_CHOWN; + break; + case 'm': + flags |= FLAG_NO_CHMOD; + break; + default: + tell_read_help(argv[0]); + return EXIT_FAILURE; + } + } + + if (optind >= argc) { + fputs("missing argument: package file\n", stderr); + return EXIT_FAILURE; + } + + filename = argv[optind++]; + + if (optind < argc) + fputs("warning: ignoring extra arguments\n", stderr); + + rd = pkg_reader_open(filename); + if (rd == NULL) + return EXIT_FAILURE; + + list = image_entry_list_from_package(rd); + if (list == NULL) { + pkg_reader_close(rd); + return EXIT_FAILURE; + } + + list = image_entry_sort(list); + + if (pkg_reader_rewind(rd)) + goto fail; + + if (root != NULL && set_root(root) != 0) + goto fail; + + if (create_hierarchy(list)) + goto fail; + + for (;;) { + ret = pkg_reader_get_next_record(rd); + if (ret == 0) + break; + if (ret < 0) + goto fail; + + hdr = pkg_reader_current_record_header(rd); + + if (hdr->magic == PKG_MAGIC_DATA) { + if (unpack_files(list, rd)) + goto fail; + } + } + + if (change_permissions(list, flags)) + goto fail; + + image_entry_free_list(list); + pkg_reader_close(rd); + return EXIT_SUCCESS; +fail: + image_entry_free_list(list); + pkg_reader_close(rd); + return EXIT_FAILURE; +} + +static command_t unpack = { + .cmd = "unpack", + .usage = "[OPTIONS...] ", + .s_desc = "unpack the contents of a package file", + .l_desc = +"The unpack command extracts the file hierarchy stored in a package into\n" +"a destination directory (default: current working directory).\n" +"\n" +"Possible options:\n" +" --root, -r A root directory to unpack the package. Defaults\n" +" to the current working directory if not set.\n" +" --no-chown, -o Do not change ownership of the extracted data.\n" +" Keep the uid/gid of the user who runs the program.\n" +" --no-chmod, -m Do not change permission flags of the extarcted\n" +" data. Use 0644 for all files and 0755 for all\n" +" directories.\n", + .run_cmd = cmd_unpack, +}; + +REGISTER_COMMAND(unpack) diff --git a/main/cmd/unpack/unpack.h b/main/cmd/unpack/unpack.h new file mode 100644 index 0000000..52b1ae3 --- /dev/null +++ b/main/cmd/unpack/unpack.h @@ -0,0 +1,28 @@ +#ifndef UNPACK_H +#define UNPACK_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "image_entry.h" +#include "pkgreader.h" +#include "command.h" +#include "util.h" + +enum { + FLAG_NO_CHOWN = 0x01, + FLAG_NO_CHMOD = 0x02, +}; + +int create_hierarchy(image_entry_t *list); + +int mkdir_p(const char *path); + +#endif /* UNPACK_H */ diff --git a/main/command.c b/main/command.c new file mode 100644 index 0000000..1208c11 --- /dev/null +++ b/main/command.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +#include "command.h" + +extern char *__progname; + +static command_t *commands; + +void command_register(command_t *cmd) +{ + cmd->next = commands; + commands = cmd; +} + +command_t *command_by_name(const char *name) +{ + command_t *cmd; + + for (cmd = commands; cmd != NULL; cmd = cmd->next) { + if (strcmp(cmd->cmd, name) == 0) + break; + } + + return cmd; +} + +void usage(int status) +{ + FILE *stream = (status == EXIT_SUCCESS ? stdout : stderr); + int padd = 0, len; + command_t *cmd; + + fprintf(stream, "usage: %s [args...]\n\n" + "Available commands:\n\n", __progname); + + for (cmd = commands; cmd != NULL; cmd = cmd->next) { + len = strlen(cmd->cmd); + + padd = len > padd ? len : padd; + } + + for (cmd = commands; cmd != NULL; cmd = cmd->next) { + fprintf(stream, "%*s - %s\n", + (int)padd + 1, cmd->cmd, cmd->s_desc); + } + + fprintf(stream, "\nTry `%s help ' for more information " + "on a specific command\n", __progname); + exit(status); +} + +void tell_read_help(const char *cmd) +{ + fprintf(stderr, "Try `%s help %s' for more information.\n", + __progname, cmd); +} + +int check_arguments(const char *cmd, int argc, int minc, int maxc) +{ + if (argc >= minc && argc <= maxc) + return 0; + + fprintf(stderr, "Too %s arguments for `%s'\n", + argc > maxc ? "many" : "few", cmd); + tell_read_help(cmd); + return -1; +} diff --git a/main/compressor.c b/main/compressor.c new file mode 100644 index 0000000..a01de6d --- /dev/null +++ b/main/compressor.c @@ -0,0 +1,31 @@ +#include + +#include "compressor.h" + +static compressor_t *list = NULL; + +void compressor_register(compressor_t *compressor) +{ + compressor->next = list; + list = compressor; +} + +compressor_t *compressor_by_name(const char *name) +{ + compressor_t *it = list; + + while (it != NULL && strcmp(it->name, name) != 0) + it = it->next; + + return it; +} + +compressor_t *compressor_by_id(PKG_COMPRESSION id) +{ + compressor_t *it = list; + + while (it != NULL && it->id != id) + it = it->next; + + return it; +} diff --git a/main/compressors/none.c b/main/compressors/none.c new file mode 100644 index 0000000..9eb7009 --- /dev/null +++ b/main/compressors/none.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +#include "compressor.h" + +#define CHUNK_SIZE 16384 + +typedef struct { + compressor_stream_t base; + uint8_t chunk[CHUNK_SIZE]; + size_t used; +} dummy_stream_t; + +static ssize_t dummy_write(compressor_stream_t *base, + const uint8_t *in, size_t size) +{ + dummy_stream_t *dummy = (dummy_stream_t *)base; + + if (size > (CHUNK_SIZE - dummy->used)) + size = CHUNK_SIZE - dummy->used; + + if (size == 0) + return 0; + + memcpy(dummy->chunk + dummy->used, in, size); + dummy->used += size; + return size; +} + +static ssize_t dummy_read(compressor_stream_t *base, + uint8_t *out, size_t size) +{ + dummy_stream_t *dummy = (dummy_stream_t *)base; + + if (size > dummy->used) + size = dummy->used; + + if (size == 0) + return 0; + + memcpy(out, dummy->chunk, size); + + if (size < dummy->used) { + memmove(dummy->chunk, dummy->chunk + size, + dummy->used - size); + } + + dummy->used -= size; + return size; +} + +static void dummy_flush(compressor_stream_t *base) +{ + (void)base; +} + +static void dummy_destroy(compressor_stream_t *base) +{ + free(base); +} + +static compressor_stream_t *create_dummy_stream(compressor_t *cmp) +{ + dummy_stream_t *dummy = calloc(1, sizeof(*dummy)); + compressor_stream_t *base; + (void)cmp; + + if (dummy == NULL) { + perror("creating dummy compressor stream"); + return NULL; + } + + base = (compressor_stream_t *)dummy; + base->write = dummy_write; + base->read = dummy_read; + base->flush = dummy_flush; + base->destroy = dummy_destroy; + return base; +} + +static compressor_t none = { + .name = "none", + .id = PKG_COMPRESSION_NONE, + .compression_stream = create_dummy_stream, + .uncompression_stream = create_dummy_stream, +}; + +REGISTER_COMPRESSOR(none) diff --git a/main/compressors/zlib.c b/main/compressors/zlib.c new file mode 100644 index 0000000..e34a1a2 --- /dev/null +++ b/main/compressors/zlib.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include + +#include "compressor.h" + +#define CHUNK_SIZE 16384 + +typedef struct { + compressor_stream_t base; + z_stream strm; + uint8_t chunk[CHUNK_SIZE]; + int used; + int flush_mode; + bool compress; + bool eof; + bool error; +} zlib_stream_t; + +static ssize_t zlib_write(compressor_stream_t *base, + const uint8_t *in, size_t size) +{ + zlib_stream_t *zlib = (zlib_stream_t *)base; + + if (size > (size_t)(CHUNK_SIZE - zlib->used)) + size = CHUNK_SIZE - zlib->used; + + if (size == 0) + return 0; + + memcpy(zlib->chunk + zlib->used, in, size); + zlib->used += size; + return size; +} + +static ssize_t zlib_read(compressor_stream_t *base, + uint8_t *out, size_t size) +{ + zlib_stream_t *zlib = (zlib_stream_t *)base; + int ret, have, total = 0, processed; + + if (zlib->error) + return -1; + if (zlib->eof) + return 0; + + zlib->strm.avail_in = zlib->used; + zlib->strm.next_in = zlib->chunk; + + do { + zlib->strm.avail_out = size; + zlib->strm.next_out = out; + + if (zlib->compress) { + ret = deflate(&zlib->strm, zlib->flush_mode); + } else { + ret = inflate(&zlib->strm, zlib->flush_mode); + } + + switch (ret) { + case Z_STREAM_END: + zlib->eof = true; + break; + case Z_BUF_ERROR: + total += (size - zlib->strm.avail_out); + goto out_remove; + case Z_OK: + break; + default: + zlib->error = true; + return -1; + } + + have = size - zlib->strm.avail_out; + + out += have; + size -= have; + total += have; + } while (zlib->strm.avail_out == 0 && !zlib->eof); +out_remove: + processed = zlib->used - zlib->strm.avail_in; + + if (processed < zlib->used) { + memmove(zlib->chunk, zlib->chunk + processed, + zlib->used - processed); + } + + zlib->used -= processed; + return total; +} + +static void zlib_flush(compressor_stream_t *base) +{ + zlib_stream_t *zlib = (zlib_stream_t *)base; + + zlib->flush_mode = Z_FINISH; +} + +static void zlib_destroy(compressor_stream_t *base) +{ + zlib_stream_t *zlib = (zlib_stream_t *)base; + + if (zlib->compress) { + deflateEnd(&zlib->strm); + } else { + inflateEnd(&zlib->strm); + } + + free(zlib); +} + +static compressor_stream_t *create_stream(bool compress) +{ + zlib_stream_t *zlib = calloc(1, sizeof(*zlib)); + compressor_stream_t *base; + int ret; + + if (zlib == NULL) { + perror("creating zlib stream"); + return NULL; + } + + zlib->flush_mode = Z_NO_FLUSH; + zlib->compress = compress; + + base = (compressor_stream_t *)zlib; + base->write = zlib_write; + base->read = zlib_read; + base->flush = zlib_flush; + base->destroy = zlib_destroy; + + if (compress) { + ret = deflateInit(&zlib->strm, Z_BEST_COMPRESSION); + } else { + ret = inflateInit(&zlib->strm); + } + + if (ret != Z_OK) { + fputs("internal error creating zlib stream\n", stderr); + free(zlib); + return NULL; + } + + return base; +} + +static compressor_stream_t *zlib_compress(compressor_t *cmp) +{ + (void)cmp; + return create_stream(true); +} + +static compressor_stream_t *zlib_uncompress(compressor_t *cmp) +{ + (void)cmp; + return create_stream(false); +} + +static compressor_t zlib = { + .name = "zlib", + .id = PKG_COMPRESSION_ZLIB, + .compression_stream = zlib_compress, + .uncompression_stream = zlib_uncompress, +}; + +REGISTER_COMPRESSOR(zlib) diff --git a/main/image_entry.c b/main/image_entry.c new file mode 100644 index 0000000..9d082ee --- /dev/null +++ b/main/image_entry.c @@ -0,0 +1,204 @@ +#include +#include +#include + +#include "image_entry.h" +#include "util.h" + +void image_entry_free(image_entry_t *ent) +{ + if (ent != NULL) { + switch (ent->mode & S_IFMT) { + case S_IFREG: + free(ent->data.file.location); + break; + case S_IFLNK: + free(ent->data.symlink.target); + break; + default: + break; + } + + free(ent->name); + free(ent); + } +} + +void image_entry_free_list(image_entry_t *list) +{ + image_entry_t *ent; + + while (list != NULL) { + ent = list; + list = list->next; + + image_entry_free(ent); + } +} + +static int read_extra(pkg_reader_t *pkg, image_entry_t *ent) +{ + switch (ent->mode & S_IFMT) { + case S_IFLNK: { + toc_symlink_extra_t extra; + char *path; + int ret; + + ret = pkg_reader_read_payload(pkg, &extra, sizeof(extra)); + if (ret < 0) + return -1; + if ((size_t)ret < sizeof(extra)) + goto fail_trunc; + + extra.target_length = le16toh(extra.target_length); + + path = malloc(extra.target_length + 1); + if (path == NULL) + goto fail_oom; + + ret = pkg_reader_read_payload(pkg, path, extra.target_length); + if (ret < 0) + return -1; + if ((size_t)ret < extra.target_length) + goto fail_trunc; + + path[extra.target_length] = '\0'; + ent->data.symlink.target = path; + break; + } + case S_IFREG: { + toc_file_extra_t extra; + int ret; + + ret = pkg_reader_read_payload(pkg, &extra, sizeof(extra)); + if (ret < 0) + return -1; + if ((size_t)ret < sizeof(extra)) + goto fail_trunc; + + ent->data.file.size = le64toh(extra.size); + ent->data.file.id = le32toh(extra.id); + break; + } + case S_IFDIR: + break; + default: + goto fail_unknown; + } + return 0; +fail_unknown: + fprintf(stderr, "%s: unsupported file type in table of contents\n", + pkg_reader_get_filename(pkg)); + return -1; +fail_oom: + fputs("out of memory\n", stderr); + return -1; +fail_trunc: + fprintf(stderr, "%s: truncated extra data in table of contents\n", + pkg_reader_get_filename(pkg)); + return -1; +} + +image_entry_t *image_entry_list_from_package(pkg_reader_t *pkg) +{ + image_entry_t *list = NULL, *end = NULL, *imgent; + toc_entry_t ent; + record_t *hdr; + ssize_t ret; + char *path; + + if (pkg_reader_rewind(pkg)) + return NULL; + + do { + if (pkg_reader_get_next_record(pkg) <= 0) + return NULL; + + hdr = pkg_reader_current_record_header(pkg); + } while (hdr->magic != PKG_MAGIC_TOC); + + for (;;) { + ret = pkg_reader_read_payload(pkg, &ent, sizeof(ent)); + if (ret == 0) + break; + if (ret < 0) + goto fail; + if ((size_t)ret < sizeof(ent)) + goto fail_trunc; + + ent.mode = le32toh(ent.mode); + ent.uid = le32toh(ent.uid); + ent.gid = le32toh(ent.gid); + ent.path_length = le16toh(ent.path_length); + + path = malloc(ent.path_length + 1); + if (path == NULL) + goto fail_oom; + + ret = pkg_reader_read_payload(pkg, path, ent.path_length); + if (ret < 0) + goto fail; + if ((size_t)ret < ent.path_length) + goto fail_trunc; + path[ent.path_length] = '\0'; + + if (canonicalize_name(path)) { + fprintf(stderr, + "%s: invalid file path '%s' in package\n", + pkg_reader_get_filename(pkg), path); + free(path); + goto fail; + } + + imgent = calloc(1, sizeof(*imgent)); + if (imgent == NULL) { + free(path); + goto fail_oom; + } + + imgent->name = path; + imgent->mode = ent.mode; + imgent->uid = ent.uid; + imgent->gid = ent.gid; + + if (read_extra(pkg, imgent)) { + image_entry_free(imgent); + goto fail; + } + + if (list) { + end->next = imgent; + end = imgent; + } else { + list = end = imgent; + } + } + + for (;;) { + ret = pkg_reader_get_next_record(pkg); + if (ret == 0) + break; + if (ret < 0) + goto fail; + + hdr = pkg_reader_current_record_header(pkg); + if (hdr->magic == PKG_MAGIC_TOC) + goto fail_multi; + } + + return list; +fail_oom: + fputs("out of memory\n", stderr); + goto fail; +fail_trunc: + fprintf(stderr, "%s: truncated entry in table of contents\n", + pkg_reader_get_filename(pkg)); + goto fail; +fail_multi: + fprintf(stderr, "%s: multiple table of contents entries found\n", + pkg_reader_get_filename(pkg)); + goto fail; +fail: + image_entry_free_list(list); + return NULL; +} diff --git a/main/image_entry_sort.c b/main/image_entry_sort.c new file mode 100644 index 0000000..40bf901 --- /dev/null +++ b/main/image_entry_sort.c @@ -0,0 +1,69 @@ +#include +#include + +#include "image_entry.h" + +static int compare_ent(image_entry_t *a, image_entry_t *b) +{ + if (S_ISDIR(a->mode)) { + if (S_ISDIR(b->mode)) + goto out_len; + return -1; + } + if (S_ISDIR(b->mode)) + return 1; + + if (S_ISREG(a->mode)) { + if (S_ISREG(b->mode)) { + if (a->data.file.size > b->data.file.size) + return 1; + if (a->data.file.size < b->data.file.size) + return -1; + return 0; + } + return 1; + } + if (S_ISREG(b->mode)) + return 1; +out_len: + return (int)strlen(a->name) - (int)strlen(b->name); +} + +static image_entry_t *insert_sorted(image_entry_t *list, image_entry_t *ent) +{ + image_entry_t *it, *prev; + + if (list == NULL || compare_ent(list, ent) > 0) { + ent->next = list; + return ent; + } + + it = list->next; + prev = list; + + while (it != NULL) { + if (compare_ent(it, ent) > 0) + break; + + prev = it; + it = it->next; + } + + prev->next = ent; + ent->next = it; + return list; +} + +image_entry_t *image_entry_sort(image_entry_t *list) +{ + image_entry_t *sorted = NULL, *ent; + + while (list != NULL) { + ent = list; + list = list->next; + + sorted = insert_sorted(sorted, ent); + } + + return sorted; +} diff --git a/main/pkg.c b/main/pkg.c new file mode 100644 index 0000000..4119b66 --- /dev/null +++ b/main/pkg.c @@ -0,0 +1,21 @@ +#include +#include + +#include "command.h" + +int main(int argc, char **argv) +{ + command_t *cmd; + + if (argc < 2) + usage(EXIT_SUCCESS); + + cmd = command_by_name(argv[1]); + + if (cmd == NULL) { + fprintf(stderr, "Unknown command '%s'\n\n", argv[1]); + usage(EXIT_FAILURE); + } + + return cmd->run_cmd(argc - 1, argv + 1); +} diff --git a/main/pkgreader.c b/main/pkgreader.c new file mode 100644 index 0000000..2894347 --- /dev/null +++ b/main/pkgreader.c @@ -0,0 +1,267 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "pkgreader.h" +#include "compressor.h" + +struct pkg_reader_t { + int fd; + bool have_eof; + bool have_error; + uint64_t offset_compressed; + uint64_t offset_raw; + compressor_stream_t *stream; + const char *path; + + record_t current; +}; + +static int read_header(pkg_reader_t *rd) +{ + ssize_t diff = read_retry(rd->fd, &rd->current, sizeof(rd->current)); + + if (diff == 0) { + rd->have_eof = true; + return 0; + } + + if (diff < 0) + goto fail_io; + + if ((size_t)diff < sizeof(rd->current)) + goto fail_trunc; + + rd->offset_raw = 0; + rd->offset_compressed = 0; + + rd->current.magic = le32toh(rd->current.magic); + rd->current.compressed_size = le64toh(rd->current.compressed_size); + rd->current.raw_size = le64toh(rd->current.raw_size); + return 1; +fail_trunc: + rd->have_error = true; + fprintf(stderr, "%s: package file seems to be truncated\n", rd->path); + return -1; +fail_io: + rd->have_error = true; + fprintf(stderr, "%s: reading from package file: %s\n", + rd->path, strerror(errno)); + return -1; +} + +static int prefetch_compressed(pkg_reader_t *rd) +{ + uint8_t buffer[1024]; + compressor_t *cmp; + ssize_t ret; + size_t diff; + + if (rd->stream == NULL) { + cmp = compressor_by_id(rd->current.compression); + + if (cmp == NULL) + goto fail_comp; + + rd->stream = cmp->uncompression_stream(cmp); + if (rd->stream == NULL) { + rd->have_error = true; + return -1; + } + } + + while (rd->offset_compressed < rd->current.compressed_size) { + diff = rd->current.compressed_size - rd->offset_compressed; + + if (diff > sizeof(buffer)) + diff = sizeof(buffer); + + ret = read_retry(rd->fd, buffer, diff); + if (ret < 0) + goto fail_io; + if ((size_t)ret < diff) + goto fail_trunc; + + ret = rd->stream->write(rd->stream, buffer, diff); + if (ret < 0) + return -1; + + rd->offset_compressed += ret; + + if ((size_t)ret < diff) { + if (lseek(rd->fd, -((long)(diff - ret)), + SEEK_CUR) == -1) + goto fail_io; + break; + } + } + + return 0; +fail_trunc: + rd->have_error = true; + fprintf(stderr, "%s: truncated record in package file\n", rd->path); + return -1; +fail_io: + rd->have_error = true; + fprintf(stderr, "%s: reading from package file: %s\n", + rd->path, strerror(errno)); + return -1; +fail_comp: + fprintf(stderr, "%s: package uses unsupported compression\n", + rd->path); + rd->have_error = true; + return -1; +} + +pkg_reader_t *pkg_reader_open(const char *path) +{ + pkg_reader_t *rd = calloc(1, sizeof(*rd)); + int ret; + + if (rd == NULL) { + fputs("out of memory\n", stderr); + return NULL; + } + + rd->path = path; + rd->fd = open(path, O_RDONLY); + if (rd->fd < 0) { + perror(path); + free(rd); + return NULL; + } + + ret = read_header(rd); + if (ret < 0) + goto fail; + if (ret == 0 || rd->current.magic != PKG_MAGIC_HEADER) + goto fail_header; + + return rd; +fail_header: + fprintf(stderr, "%s: missing package header\n", path); +fail: + pkg_reader_close(rd); + return NULL; +} + +void pkg_reader_close(pkg_reader_t *rd) +{ + if (rd->stream != NULL) + rd->stream->destroy(rd->stream); + + close(rd->fd); + free(rd); +} + +int pkg_reader_get_next_record(pkg_reader_t *rd) +{ + uint64_t skip; + int ret; + + if (rd->have_eof) + return 0; + + if (rd->have_error) + return -1; + + skip = rd->current.compressed_size - rd->offset_compressed; + + if (lseek(rd->fd, skip, SEEK_CUR) == -1) + goto fail_io; + + if (rd->stream != NULL) { + rd->stream->destroy(rd->stream); + rd->stream = NULL; + } + + ret = read_header(rd); + if (ret <= 0) + return ret; + + if (rd->current.magic == PKG_MAGIC_HEADER) + goto fail_second_hdr; + + return 1; +fail_io: + rd->have_error = true; + fprintf(stderr, "%s: reading from packet file: %s\n", + rd->path, strerror(errno)); + return -1; +fail_second_hdr: + rd->have_error = true; + fprintf(stderr, "%s: found extra header record inside package\n", + rd->path); + return -1; +} + +record_t *pkg_reader_current_record_header(pkg_reader_t *rd) +{ + return (rd->have_error || rd->have_eof) ? NULL : &rd->current; +} + +ssize_t pkg_reader_read_payload(pkg_reader_t *rd, void *out, size_t size) +{ + ssize_t ret; + + if (rd->have_error) + return -1; + + if (rd->have_eof || rd->offset_raw == rd->current.raw_size) + return 0; + + if (prefetch_compressed(rd)) + return -1; + + if (size >= (rd->current.raw_size - rd->offset_raw)) + rd->stream->flush(rd->stream); + + ret = rd->stream->read(rd->stream, out, size); + if (ret < 0) + return -1; + + rd->offset_raw += ret; + return ret; +} + +int pkg_reader_rewind(pkg_reader_t *rd) +{ + int ret; + + if (lseek(rd->fd, 0, SEEK_SET) == -1) { + perror(rd->path); + return -1; + } + + if (rd->stream != NULL) { + rd->stream->destroy(rd->stream); + rd->stream = NULL; + } + + rd->have_eof = false; + rd->have_error = false; + + ret = read_header(rd); + if (ret < 0) + goto fail; + + if (ret == 0 || rd->current.magic != PKG_MAGIC_HEADER) + goto fail_header; + + return 0; +fail_header: + fprintf(stderr, "%s: missing package header\n", rd->path); +fail: + rd->have_error = true; + return -1; +} + +const char *pkg_reader_get_filename(pkg_reader_t *rd) +{ + return rd->path; +} diff --git a/main/pkgwriter.c b/main/pkgwriter.c new file mode 100644 index 0000000..3fda222 --- /dev/null +++ b/main/pkgwriter.c @@ -0,0 +1,181 @@ +#include +#include +#include +#include +#include +#include + +#include "pkgwriter.h" +#include "util.h" + +struct pkg_writer_t { + const char *path; + int fd; + + off_t start; + record_t current; + compressor_stream_t *stream; +}; + +static int write_header(pkg_writer_t *wr) +{ + ssize_t ret; + + wr->current.magic = htole32(wr->current.magic); + wr->current.compressed_size = htole64(wr->current.compressed_size); + wr->current.raw_size = htole64(wr->current.raw_size); + + ret = write_retry(wr->fd, &wr->current, sizeof(wr->current)); + if (ret < 0) + goto fail_fop; + + if ((size_t)ret < sizeof(wr->current)) { + fprintf(stderr, + "record header was truncated while writing to %s\n", + wr->path); + return -1; + } + + return 0; +fail_fop: + perror(wr->path); + return -1; +} + +static int flush_to_file(pkg_writer_t *wr) +{ + uint8_t buffer[2048]; + ssize_t count, ret; + + for (;;) { + count = wr->stream->read(wr->stream, buffer, sizeof(buffer)); + if (count == 0) + break; + if (count < 0) { + fprintf(stderr, "%s: error compressing data\n", + wr->path); + return -1; + } + + ret = write_retry(wr->fd, buffer, count); + + if (ret < 0) { + fprintf(stderr, "%s: writing to package file: %s\n", + wr->path, strerror(errno)); + return -1; + } + + if (ret < count) { + fprintf(stderr, + "%s: data written to file was truncated\n", + wr->path); + return -1; + } + + wr->current.compressed_size += count; + } + + return 0; +} + +pkg_writer_t *pkg_writer_open(const char *path) +{ + pkg_writer_t *wr = calloc(1, sizeof(*wr)); + + if (wr == NULL) { + fputs("out of memory\n", stderr); + return NULL; + } + + wr->fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (wr->fd == -1) { + perror(path); + free(wr); + return NULL; + } + + wr->path = path; + + wr->current.magic = PKG_MAGIC_HEADER; + wr->current.compression = PKG_COMPRESSION_NONE; + if (write_header(wr)) { + close(wr->fd); + free(wr); + return NULL; + } + + return wr; +} + +void pkg_writer_close(pkg_writer_t *wr) +{ + close(wr->fd); + free(wr); +} + +int pkg_writer_start_record(pkg_writer_t *wr, uint32_t magic, + compressor_t *cmp) +{ + wr->start = lseek(wr->fd, 0, SEEK_CUR); + if (wr->start == -1) { + perror(wr->path); + return -1; + } + + memset(&wr->current, 0, sizeof(wr->current)); + if (write_header(wr)) + return -1; + + wr->stream = cmp->compression_stream(cmp); + if (wr->stream == NULL) + return -1; + + wr->current.magic = magic; + wr->current.compression = cmp->id; + return 0; +} + +int pkg_writer_write_payload(pkg_writer_t *wr, void *data, size_t size) +{ + ssize_t ret; + + while (size > 0) { + ret = wr->stream->write(wr->stream, data, size); + if (ret < 0) + return -1; + + if ((size_t)ret < size) { + if (flush_to_file(wr)) + return -1; + } + + data = (char *)data + ret; + size -= ret; + wr->current.raw_size += ret; + } + + return 0; +} + +int pkg_writer_end_record(pkg_writer_t *wr) +{ + wr->stream->flush(wr->stream); + if (flush_to_file(wr)) + return -1; + + if (lseek(wr->fd, wr->start, SEEK_SET) == -1) + goto fail_seek; + + if (write_header(wr)) + return -1; + + if (lseek(wr->fd, 0, SEEK_END) == -1) + goto fail_seek; + + wr->stream->destroy(wr->stream); + wr->stream = NULL; + return 0; +fail_seek: + perror(wr->path); + return -1; +} diff --git a/main/util.c b/main/util.c new file mode 100644 index 0000000..42ecd20 --- /dev/null +++ b/main/util.c @@ -0,0 +1,101 @@ +#include +#include +#include + +#include "util.h" + +int canonicalize_name(char *filename) +{ + char *ptr = filename; + int i; + + while (*ptr == '/') + ++ptr; + + if (ptr != filename) { + memmove(filename, ptr, strlen(ptr) + 1); + ptr = filename; + } + + while (*ptr != '\0') { + if (*ptr == '/') { + for (i = 0; ptr[i] == '/'; ++i) + ; + + if (i > 1) + memmove(ptr + 1, ptr + i, strlen(ptr + i) + 1); + } + + if (ptr[0] == '/' && ptr[1] == '\0') { + *ptr = '\0'; + break; + } + + ++ptr; + } + + ptr = filename; + + while (*ptr != '\0') { + if (ptr[0] == '.') { + if (ptr[1] == '/' || ptr[1] == '\0') + return -1; + + if (ptr[1] == '.' && + (ptr[2] == '/' || ptr[2] == '\0')) { + return -1; + } + } + + while (*ptr != '\0' && *ptr != '/') + ++ptr; + if (*ptr == '/') + ++ptr; + } + + return 0; +} + +ssize_t write_retry(int fd, void *data, size_t size) +{ + ssize_t ret, total = 0; + + while (size > 0) { + ret = write(fd, data, size); + if (ret == 0) + break; + if (ret < 0) { + if (errno == EINTR) + continue; + return -1; + } + + data = (char *)data + ret; + size -= ret; + total += ret; + } + + return total; +} + +ssize_t read_retry(int fd, void *buffer, size_t size) +{ + ssize_t ret, total = 0; + + while (size > 0) { + ret = read(fd, buffer, size); + if (ret < 0) { + if (errno == EINTR) + continue; + return -1; + } + if (ret == 0) + break; + + total += ret; + size -= ret; + buffer = (char *)buffer + ret; + } + + return total; +}