Initial commit

Signed-off-by: David Oberhollenzer <david.oberhollenzer@tele2.at>
This commit is contained in:
David Oberhollenzer 2019-01-19 17:26:41 +01:00
commit 6ec11b532e
36 changed files with 2931 additions and 0 deletions

19
.gitignore vendored Normal file
View File

@ -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

50
Makefile.am Normal file
View File

@ -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

3
autogen.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
autoreconf --force --install --symlink

56
configure.ac Normal file
View File

@ -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])

31
include/command.h Normal file
View File

@ -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 */

43
include/compressor.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef COMPRESSOR_H
#define COMPRESSOR_H
#include <stddef.h>
#include <sys/types.h>
#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 */

37
include/image_entry.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef IMAGE_ENTRY_H
#define IMAGE_ENTRY_H
#include <sys/types.h>
#include <stdint.h>
#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 */

56
include/pkgformat.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef PKG_FORMAT_H
#define PKG_FORMAT_H
#include <sys/types.h>
#include <stdint.h>
#include <stddef.h>
#include <endian.h>
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 */

23
include/pkgreader.h Normal file
View File

@ -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 */

20
include/pkgwriter.h Normal file
View File

@ -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 */

12
include/util.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef UTIL_H
#define UTIL_H
#include <sys/types.h>
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 */

40
m4/compiler.m4 Normal file
View File

@ -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"])])
])

103
main/cmd/dump/dump.c Normal file
View File

@ -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...] <pkgfile>",
.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 <format> 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 <path> 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)

24
main/cmd/dump/dump.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef DUMP_H
#define DUMP_H
#include <sys/stat.h>
#include <getopt.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#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 */

101
main/cmd/dump/dump_toc.c Normal file
View File

@ -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;
}

120
main/cmd/help.c Normal file
View File

@ -0,0 +1,120 @@
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>
#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 <command>", __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 = "<command>",
.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)

183
main/cmd/pack/filelist.c Normal file
View File

@ -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;
}

View File

@ -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;
}

163
main/cmd/pack/pack.c Normal file
View File

@ -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 <path> Specify a file containing a list of input files.\n"
" --output, -o <path> Specify the path of the resulting package file.\n"
"\n"
" --toc-compressor, -t <compressor>\n"
" --file-compressor, -f <compressor>\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)

43
main/cmd/pack/pack.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef PACK_H
#define PACK_H
#include <sys/types.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#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 */

View File

@ -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);
}

71
main/cmd/pack/write_toc.c Normal file
View File

@ -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);
}

View File

@ -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;
}

34
main/cmd/unpack/mkdir_p.c Normal file
View File

@ -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;
}

249
main/cmd/unpack/unpack.c Normal file
View File

@ -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...] <pkgfile>",
.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 <director> 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)

28
main/cmd/unpack/unpack.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef UNPACK_H
#define UNPACK_H
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#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 */

70
main/command.c Normal file
View File

@ -0,0 +1,70 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#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 <command> [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 <command>' 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;
}

31
main/compressor.c Normal file
View File

@ -0,0 +1,31 @@
#include <string.h>
#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;
}

90
main/compressors/none.c Normal file
View File

@ -0,0 +1,90 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <zlib.h>
#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)

168
main/compressors/zlib.c Normal file
View File

@ -0,0 +1,168 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <zlib.h>
#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)

204
main/image_entry.c Normal file
View File

@ -0,0 +1,204 @@
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#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;
}

69
main/image_entry_sort.c Normal file
View File

@ -0,0 +1,69 @@
#include <sys/stat.h>
#include <string.h>
#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;
}

21
main/pkg.c Normal file
View File

@ -0,0 +1,21 @@
#include <stdlib.h>
#include <stdio.h>
#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);
}

267
main/pkgreader.c Normal file
View File

@ -0,0 +1,267 @@
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#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;
}

181
main/pkgwriter.c Normal file
View File

@ -0,0 +1,181 @@
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#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;
}

101
main/util.c Normal file
View File

@ -0,0 +1,101 @@
#include <unistd.h>
#include <string.h>
#include <errno.h>
#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;
}