Blob Blame History Raw
/**************************************************************/
/*  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] [-dt] [-H|-L] [-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_PAX_SYMLINK_ARGS:
					cctx.drvflags |= TPAX_DRIVER_PAX_SYMLINK_ARGS;
					cctx.drvflags &= ~(uint64_t)TPAX_DRIVER_PAX_SYMLINK_ITEMS;
					break;

				case TAG_PAX_SYMLINK_ITEMS:
					cctx.drvflags |= TPAX_DRIVER_PAX_SYMLINK_ARGS;
					cctx.drvflags |= TPAX_DRIVER_PAX_SYMLINK_ITEMS;
					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;
}