Browse Source

Initial commit

Signed-off-by: David Oberhollenzer <david.oberhollenzer@tele2.at>
master
David Oberhollenzer 3 years ago
commit
6ec11b532e
  1. 19
      .gitignore
  2. 50
      Makefile.am
  3. 3
      autogen.sh
  4. 56
      configure.ac
  5. 31
      include/command.h
  6. 43
      include/compressor.h
  7. 37
      include/image_entry.h
  8. 56
      include/pkgformat.h
  9. 23
      include/pkgreader.h
  10. 20
      include/pkgwriter.h
  11. 12
      include/util.h
  12. 40
      m4/compiler.m4
  13. 103
      main/cmd/dump/dump.c
  14. 24
      main/cmd/dump/dump.h
  15. 101
      main/cmd/dump/dump_toc.c
  16. 120
      main/cmd/help.c
  17. 183
      main/cmd/pack/filelist.c
  18. 130
      main/cmd/pack/filelist_read.c
  19. 163
      main/cmd/pack/pack.c
  20. 43
      main/cmd/pack/pack.h
  21. 64
      main/cmd/pack/write_files.c
  22. 71
      main/cmd/pack/write_toc.c
  23. 26
      main/cmd/unpack/create_hierarchy.c
  24. 34
      main/cmd/unpack/mkdir_p.c
  25. 249
      main/cmd/unpack/unpack.c
  26. 28
      main/cmd/unpack/unpack.h
  27. 70
      main/command.c
  28. 31
      main/compressor.c
  29. 90
      main/compressors/none.c
  30. 168
      main/compressors/zlib.c
  31. 204
      main/image_entry.c
  32. 69
      main/image_entry_sort.c
  33. 21
      main/pkg.c
  34. 267
      main/pkgreader.c
  35. 181
      main/pkgwriter.c
  36. 101
      main/util.c

19
.gitignore vendored

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

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

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

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

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

43
include/compressor.h

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

34
main/cmd/unpack/mkdir_p.c