/**************************************************************/
/* tpax: a topological pax implementation */
/* Copyright (C) 2020--2024 SysDeer Technologies, LLC */
/* Released under GPLv2 and GPLv3; see COPYING.TPAX. */
/**************************************************************/
#define _DEFAULT_SOURCE 1
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mman.h>
#define ARGV_DRIVER
#include <tpax/tpax.h>
#include "tpax_version.h"
#include "tpax_driver_impl.h"
#include "argv/argv.h"
#define TPAX_DRIVER_EXEC_MODE_MASK \
(TPAX_DRIVER_EXEC_MODE_LIST \
| TPAX_DRIVER_EXEC_MODE_READ \
| TPAX_DRIVER_EXEC_MODE_WRITE \
| TPAX_DRIVER_EXEC_MODE_COPY)
#define TPAX_DRIVER_WRITE_FORMAT_MASK \
(TPAX_DRIVER_WRITE_FORMAT_PAX \
| TPAX_DRIVER_WRITE_FORMAT_CPIO \
| TPAX_DRIVER_WRITE_FORMAT_USTAR \
| TPAX_DRIVER_WRITE_FORMAT_RUSTAR)
/* package info */
static const struct tpax_source_version tpax_src_version = {
TPAX_TAG_VER_MAJOR,
TPAX_TAG_VER_MINOR,
TPAX_TAG_VER_PATCH,
TPAX_GIT_VERSION
};
/* default fd context */
static const struct tpax_fd_ctx tpax_default_fdctx = {
.fdin = STDIN_FILENO,
.fdout = STDOUT_FILENO,
.fderr = STDERR_FILENO,
.fdcwd = AT_FDCWD,
.fddst = AT_FDCWD,
.fdlog = (-1),
};
struct tpax_driver_ctx_alloc {
struct argv_meta * meta;
struct tpax_driver_ctx_impl ctx;
uint64_t guard;
const char * units[];
};
static uint32_t tpax_argv_flags(uint32_t flags)
{
uint32_t ret = ARGV_CLONE_VECTOR;
if (flags & TPAX_DRIVER_VERBOSITY_NONE)
ret |= ARGV_VERBOSITY_NONE;
if (flags & TPAX_DRIVER_VERBOSITY_ERRORS)
ret |= ARGV_VERBOSITY_ERRORS;
if (flags & TPAX_DRIVER_VERBOSITY_STATUS)
ret |= ARGV_VERBOSITY_STATUS;
return ret;
}
static int tpax_driver_usage(
int fdout,
const char * program,
const char * arg,
const struct argv_option ** optv,
struct argv_meta * meta)
{
char header[512];
snprintf(header,sizeof(header),
"%s — topological pax implementation\n\n"
"Synopsis:\n"
" %s [-d] [-f archive]\n"
" %s -r [-d] [-f archive]\n"
" %s -w [−x format] [-b blocksize] [-d] [-f archive]\n"
" %s -r -w [-d]\n\n"
"Options:\n",
program,program,program,program,program);
argv_usage(fdout,header,optv,arg);
argv_free(meta);
return TPAX_USAGE;
}
static int tpax_driver_usage_exec_mode(
int fdout,
const char * program,
const char * arg,
const struct argv_option ** optv,
struct argv_meta * meta)
{
tpax_driver_usage(
fdout,program,
arg,optv,meta);
tpax_dprintf(
fdout,
"\n%s: usage error: When using explicit (long) mode options, "
"only one of --list, --read, --write, --copy "
"may be used.\n",program);
return TPAX_USAGE;
}
static int tpax_driver_usage_copy_mode(
int fdout,
const char * program,
const char * arg,
const struct argv_option ** optv,
struct argv_meta * meta,
struct argv_entry * operand,
struct argv_entry * archive)
{
const char * errdesc;
if (archive || !operand) {
tpax_driver_usage(
fdout,program,
arg,optv,meta);
if (archive) {
tpax_dprintf(
fdout,
"\n%s: usage error: the __copy__ mode does not permit specifying "
"an archive path.\n\n",program);
} else {
tpax_dprintf(
fdout,
"\n%s: usage error: the __copy__ mode requires a destination "
"directory argument.\n\n",program);
}
return TPAX_USAGE;
}
switch (errno) {
case ENOENT:
tpax_dprintf(
fdout,
"%s: file-system error: "
"the destination directory '%s' "
"does not exist.\n\n",
program,operand->arg);
break;
case ENOTDIR:
tpax_dprintf(
fdout,
"%s: file-system error: "
"'%s' is not a directory.\n\n",
program,operand->arg);
break;
default:
if (!(errdesc = strerror(errno)))
errdesc = "<no error description>";
tpax_dprintf(
fdout,
"%s: general error: while opening the "
"destination directory '%s': %s.\n\n",
program,operand->arg,errdesc);
break;
}
argv_free(meta);
return TPAX_USAGE;
}
static int tpax_driver_usage_write_format(
int fdout,
const char * program,
const char * arg,
const struct argv_option ** optv,
struct argv_meta * meta)
{
tpax_driver_usage(
fdout,program,
arg,optv,meta);
tpax_dprintf(
fdout,
"\n%s: usage error: Archive format may only be specified "
"in write mode.\n",program);
return TPAX_USAGE;
}
static int tpax_driver_usage_block_size(
int fdout,
const char * program,
const char * arg,
struct argv_meta * meta)
{
tpax_dprintf(
fdout,
"%s: usage error: the requested block size `%s' is not a positive decimal integer.\n",
program,arg);
argv_free(meta);
return TPAX_USAGE;
}
static int tpax_driver_usage_block_size_range(
int fdout,
const char * program,
const char * arg,
struct argv_meta * meta)
{
tpax_dprintf(
fdout,
"%s: usage error: `%s' is outside the specified range of 512 to 32256.\n",
program,arg);
argv_free(meta);
return TPAX_USAGE;
}
static int tpax_driver_usage_block_constraints(
int fdout,
const char * program,
const char * arg,
struct argv_meta * meta)
{
tpax_dprintf(
fdout,
"%s: usage error: the specified block size `%s' is not a multiple of 512 bytes.\n",
program,arg);
argv_free(meta);
return TPAX_USAGE;
}
static int tpax_driver_error_archive_path(
int fdout,
const char * program,
const char * arg,
struct argv_meta * meta,
bool fwrite)
{
int lerrno;
const char * errstr;
lerrno = errno;
errno = 0;
errstr = strerror(lerrno);
errstr = errno ? "" : errstr;
errno = lerrno;
if (fwrite) {
tpax_dprintf(
fdout,
"%s: file-system error: archive file <%s> could not be created "
"or opened for writing (%s).\n",
program,arg,errstr);
} else {
tpax_dprintf(
fdout,
"%s: file-system error: archive file <%s> could not be opened "
"for reading (%s).\n",
program,arg,errstr);
}
argv_free(meta);
return TPAX_FATAL;
}
static int tpax_driver_error_not_implemented(
int fdout,
const char * program,
const char * feature,
struct argv_meta * meta)
{
tpax_dprintf(
fdout,"%s: error: %s is not yet implemented!\n\n",
program,feature);
argv_free(meta);
return TPAX_FATAL;
}
static void tpax_set_archive_block_size(struct tpax_common_ctx * cctx)
{
if (cctx->blksize)
(void)0;
else if (cctx->drvflags & TPAX_DRIVER_WRITE_FORMAT_PAX)
cctx->blksize = TPAX_PAX_BLOCK_SIZE;
else if (cctx->drvflags & TPAX_DRIVER_WRITE_FORMAT_CPIO)
cctx->blksize = TPAX_CPIO_BLOCK_SIZE;
else if (cctx->drvflags & TPAX_DRIVER_WRITE_FORMAT_USTAR)
cctx->blksize = TPAX_USTAR_BLOCK_SIZE;
else if (cctx->drvflags & TPAX_DRIVER_WRITE_FORMAT_RUSTAR)
cctx->blksize = TPAX_USTAR_BLOCK_SIZE;
}
static struct tpax_driver_ctx_impl * tpax_driver_ctx_alloc(
struct argv_meta * meta,
const struct tpax_fd_ctx * fdctx,
const struct tpax_common_ctx * cctx,
size_t nunits)
{
struct tpax_driver_ctx_alloc * ictx;
size_t size;
struct argv_entry * entry;
const char ** units;
int elements;
size = sizeof(struct tpax_driver_ctx_alloc);
size += (nunits+1)*sizeof(const char *);
if (!(ictx = calloc(1,size)))
return 0;
memcpy(&ictx->ctx.fdctx,fdctx,sizeof(*fdctx));
memcpy(&ictx->ctx.cctx,cctx,sizeof(*cctx));
elements = sizeof(ictx->ctx.erribuf) / sizeof(*ictx->ctx.erribuf);
ictx->ctx.errinfp = &ictx->ctx.erriptr[0];
ictx->ctx.erricap = &ictx->ctx.erriptr[--elements];
elements = sizeof(ictx->ctx.prefptr) / sizeof(*ictx->ctx.prefptr);
ictx->ctx.prefixv = &ictx->ctx.prefptr[0];
ictx->ctx.prefixp = &ictx->ctx.prefptr[0];
ictx->ctx.prefcap = &ictx->ctx.prefptr[--elements];
ictx->meta = meta;
for (entry=meta->entries,units=ictx->units; entry->fopt || entry->arg; entry++)
if (!entry->fopt)
*units++ = entry->arg;
if (cctx->drvflags & TPAX_DRIVER_EXEC_MODE_WRITE_COPY) {
ictx->ctx.bufsize = TPAX_FILEIO_BUFLEN;
ictx->ctx.bufaddr = mmap(
0,ictx->ctx.bufsize,
PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS,
-1,0);
if (ictx->ctx.bufaddr == MAP_FAILED) {
free(ictx);
return 0;
}
ictx->ctx.dirbuff = mmap(
0,TPAX_DIRENT_BUFLEN,
PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS,
-1,0);
if (ictx->ctx.dirbuff == MAP_FAILED) {
munmap(ictx->ctx.bufaddr,ictx->ctx.bufsize);
free(ictx);
return 0;
}
}
tpax_set_archive_block_size(&ictx->ctx.cctx);
ictx->ctx.ctx.units = ictx->units;
ictx->ctx.ctx.errv = ictx->ctx.errinfp;
return &ictx->ctx;
}
static int tpax_get_driver_ctx_fail(struct argv_meta * meta)
{
argv_free(meta);
return -1;
}
int tpax_lib_get_driver_ctx(
char ** argv,
char ** envp,
uint32_t flags,
const struct tpax_fd_ctx * fdctx,
struct tpax_driver_ctx ** pctx)
{
struct tpax_driver_ctx_impl * ctx;
struct tpax_common_ctx cctx;
const struct argv_option * optv[TPAX_OPTV_ELEMENTS];
struct argv_meta * meta;
struct argv_entry * entry;
struct argv_entry * archive;
struct argv_entry * operand;
struct tpax_fd_ctx lfdctx;
size_t nunits;
const char * program;
int fddst;
const char * ch;
(void)envp;
if (!fdctx)
fdctx = &tpax_default_fdctx;
argv_optv_init(tpax_default_options,optv);
if (!(meta = argv_get(
argv,optv,
tpax_argv_flags(flags),
fdctx->fderr)))
return -1;
nunits = 0;
archive = 0;
operand = 0;
program = argv_program_name(argv[0]);
memset(&cctx,0,sizeof(cctx));
cctx.drvflags = flags;
fddst = fdctx->fddst;
/* get options, count units */
for (entry=meta->entries; entry->fopt || entry->arg; entry++) {
if (entry->fopt) {
switch (entry->tag) {
case TAG_HELP:
if (flags & TPAX_DRIVER_VERBOSITY_USAGE)
return tpax_driver_usage(
fdctx->fdout,
program,entry->arg,
optv,meta);
break;
case TAG_VERSION:
cctx.drvflags |= TPAX_DRIVER_VERSION;
break;
case TAG_LIST:
cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_LIST;
break;
case TAG_READ:
cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_READ;
break;
case TAG_WRITE:
cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_WRITE;
break;
case TAG_COPY:
cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_COPY;
break;
case TAG_FILE:
archive = entry;
break;
case TAG_FORMAT:
cctx.drvflags &= ~(uint64_t)(TPAX_DRIVER_WRITE_FORMAT_MASK);
if (!strcmp(entry->arg,"pax"))
cctx.drvflags |= TPAX_DRIVER_WRITE_FORMAT_PAX;
else if (!strcmp(entry->arg,"cpio"))
cctx.drvflags |= TPAX_DRIVER_WRITE_FORMAT_CPIO;
else if (!strcmp(entry->arg,"ustar"))
cctx.drvflags |= TPAX_DRIVER_WRITE_FORMAT_USTAR;
else if (!strcmp(entry->arg,"rustar"))
cctx.drvflags |= TPAX_DRIVER_WRITE_FORMAT_RUSTAR;
break;
case TAG_BLKSIZE:
ch = (entry->arg[0] == '+')
? &entry->arg[1]
: entry->arg;
for (; *ch; ch++)
if ((*ch < '0') || (*ch > '9'))
return tpax_driver_usage_block_size(
fdctx->fderr,
program,entry->arg,
meta);
cctx.blksize = atoi(entry->arg);
if ((cctx.blksize < 512) || (cctx.blksize > 32256))
return tpax_driver_usage_block_size_range(
fdctx->fderr,
program,entry->arg,
meta);
if (cctx.blksize % 512)
return tpax_driver_usage_block_constraints(
fdctx->fderr,
program,entry->arg,
meta);
break;
case TAG_RECURSE:
cctx.drvflags |= TPAX_DRIVER_DIR_MEMBER_RECURSE;
break;
case TAG_NORECURSE:
cctx.drvflags &= ~(uintptr_t)TPAX_DRIVER_DIR_MEMBER_RECURSE;
break;
case TAG_PRESERVE_ATIME:
cctx.drvflags |= TPAX_DRIVER_PRESERVE_ATIME;
break;
case TAG_STRICT_PATH:
cctx.drvflags |= TPAX_DRIVER_STRICT_PATH_INPUT;
break;
case TAG_PURE_PATH:
cctx.drvflags |= TPAX_DRIVER_PURE_PATH_OUTPUT;
break;
}
} else {
operand = entry;
nunits++;
}
}
/* incompatible arguments? */
switch (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_MASK) {
case 0:
if (!(cctx.drvflags & TPAX_DRIVER_VERSION))
cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_LIST;
break;
case TPAX_DRIVER_EXEC_MODE_READ|TPAX_DRIVER_EXEC_MODE_WRITE:
cctx.drvflags &= ~(uint64_t)(TPAX_DRIVER_EXEC_MODE_READ);
cctx.drvflags &= ~(uint64_t)(TPAX_DRIVER_EXEC_MODE_WRITE);
cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_COPY;
break;
case TPAX_DRIVER_EXEC_MODE_LIST:
case TPAX_DRIVER_EXEC_MODE_READ:
case TPAX_DRIVER_EXEC_MODE_WRITE:
case TPAX_DRIVER_EXEC_MODE_COPY:
break;
default:
return tpax_driver_usage_exec_mode(
fdctx->fderr,
program,entry->arg,
optv,meta);
}
/* copy mode: destination directory */
if (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_COPY) {
if (archive || !operand)
return tpax_driver_usage_copy_mode(
fdctx->fderr,
program,entry->arg,
optv,meta,operand,archive);
fddst = openat(
fdctx->fdcwd,
operand->arg,
O_RDONLY|O_DIRECTORY|O_CLOEXEC,0);
if (fddst < 0)
return tpax_driver_usage_copy_mode(
fdctx->fderr,
program,entry->arg,
optv,meta,operand,archive);
}
/* archive path */
if (archive && (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_WRITE)) {
memcpy(&lfdctx,fdctx,sizeof(*fdctx));
lfdctx.fdout = openat(
fdctx->fdcwd,
archive->arg,
O_WRONLY|O_CREAT|O_TRUNC,
0644);
if (lfdctx.fdout < 0)
return tpax_driver_error_archive_path(
fdctx->fderr,
program,archive->arg,
meta,true);
fdctx = &lfdctx;
} else if (archive) {
memcpy(&lfdctx,fdctx,sizeof(*fdctx));
lfdctx.fdin = openat(
fdctx->fdcwd,
archive->arg,
O_RDONLY,0);
if (lfdctx.fdin < 0)
return tpax_driver_error_archive_path(
fdctx->fderr,
program,archive->arg,
meta,false);
fdctx = &lfdctx;
}
/* not implemented mode(s) */
switch (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_MASK) {
case TPAX_DRIVER_EXEC_MODE_LIST:
return tpax_driver_error_not_implemented(
fdctx->fderr,program,"list mode",meta);
case TPAX_DRIVER_EXEC_MODE_READ:
return tpax_driver_error_not_implemented(
fdctx->fderr,program,"read mode",meta);
case TPAX_DRIVER_EXEC_MODE_COPY:
close(fddst);
return tpax_driver_error_not_implemented(
fdctx->fderr,program,"copy mode",meta);
default:
break;
}
/* archive format vs. execution mode*/
switch (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_MASK) {
case TPAX_DRIVER_EXEC_MODE_WRITE:
if (!(cctx.drvflags & TPAX_DRIVER_WRITE_FORMAT_MASK))
cctx.drvflags |= TPAX_DRIVER_WRITE_FORMAT_USTAR;
break;
default:
if (cctx.drvflags & TPAX_DRIVER_WRITE_FORMAT_MASK)
return tpax_driver_usage_write_format(
fdctx->fderr,program,
entry->arg,optv,meta);
}
/* not implemented archive format(s) */
switch (cctx.drvflags & TPAX_DRIVER_WRITE_FORMAT_MASK) {
case TPAX_DRIVER_WRITE_FORMAT_PAX:
return tpax_driver_error_not_implemented(
fdctx->fderr,program,"the pax format",meta);
case TPAX_DRIVER_WRITE_FORMAT_CPIO:
return tpax_driver_error_not_implemented(
fdctx->fderr,program,"the cpio format",meta);
default:
break;
}
/* driver ctx */
if (!(ctx = tpax_driver_ctx_alloc(meta,fdctx,&cctx,nunits))) {
if (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_COPY)
close(fddst);
return tpax_get_driver_ctx_fail(meta);
}
if (archive) {
ctx->file = archive->arg;
ctx->ctx.file = &ctx->file;
}
ctx->ctx.program = program;
ctx->ctx.cctx = &ctx->cctx;
*pctx = &ctx->ctx;
return TPAX_OK;
}
static void tpax_free_driver_ctx_impl(struct tpax_driver_ctx_alloc * ictx)
{
void * next;
size_t size;
char ** ppref;
for (; ictx->ctx.dirents; ) {
next = ictx->ctx.dirents->next;
size = ictx->ctx.dirents->size;
munmap(ictx->ctx.dirents,size);
ictx->ctx.dirents = (struct tpax_dirent_buffer *)next;
}
if (ictx->ctx.bufaddr)
munmap(ictx->ctx.bufaddr,ictx->ctx.bufsize);
if (ictx->ctx.dirbuff)
munmap(ictx->ctx.dirbuff,TPAX_DIRENT_BUFLEN);
for (ppref=ictx->ctx.prefixv; *ppref; ppref++)
free(*ppref);
if (ictx->ctx.prefixv != ictx->ctx.prefptr)
free(ictx->ctx.prefixv);
if (ictx->ctx.direntv)
free(ictx->ctx.direntv);
argv_free(ictx->meta);
free(ictx);
}
void tpax_lib_free_driver_ctx(struct tpax_driver_ctx * ctx)
{
struct tpax_driver_ctx_alloc * ictx;
uintptr_t addr;
if (ctx) {
addr = (uintptr_t)ctx - offsetof(struct tpax_driver_ctx_impl,ctx);
addr = addr - offsetof(struct tpax_driver_ctx_alloc,ctx);
ictx = (struct tpax_driver_ctx_alloc *)addr;
tpax_free_driver_ctx_impl(ictx);
}
}
const struct tpax_source_version * tpax_api_source_version(void)
{
return &tpax_src_version;
}
int tpax_lib_get_driver_fdctx(
const struct tpax_driver_ctx * dctx,
struct tpax_fd_ctx * fdctx)
{
struct tpax_driver_ctx_impl * ictx;
ictx = tpax_get_driver_ictx(dctx);
fdctx->fdin = ictx->fdctx.fdin;
fdctx->fdout = ictx->fdctx.fdout;
fdctx->fderr = ictx->fdctx.fderr;
fdctx->fdlog = ictx->fdctx.fdlog;
fdctx->fdcwd = ictx->fdctx.fdcwd;
fdctx->fddst = ictx->fdctx.fddst;
return 0;
}
int tpax_lib_set_driver_fdctx(
struct tpax_driver_ctx * dctx,
const struct tpax_fd_ctx * fdctx)
{
struct tpax_driver_ctx_impl * ictx;
ictx = tpax_get_driver_ictx(dctx);
ictx->fdctx.fdin = fdctx->fdin;
ictx->fdctx.fdout = fdctx->fdout;
ictx->fdctx.fderr = fdctx->fderr;
ictx->fdctx.fdlog = fdctx->fdlog;
ictx->fdctx.fdcwd = fdctx->fdcwd;
ictx->fdctx.fddst = fdctx->fddst;
return 0;
}