Blob Blame History Raw
/**************************************************************/
/*  tpax: a topological pax implementation                    */
/*  Copyright (C) 2020--2024  SysDeer Technologies, LLC       */
/*  Released under GPLv2 and GPLv3; see COPYING.TPAX.         */
/**************************************************************/

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <sys/mman.h>
#include <sys/stat.h>

#include <tpax/tpax.h>
#include <tpax/tpax_specs.h>
#include "tpax_driver_impl.h"
#include "tpax_getdents_impl.h"
#include "tpax_readlink_impl.h"
#include "tpax_tmpfile_impl.h"
#include "tpax_errinfo_impl.h"

static char * tpax_add_prefix_item(
	const struct tpax_driver_ctx *  dctx,
	const char *                    prefix)
{
	struct tpax_driver_ctx_impl *   ictx;
	char **                         prefv;
	char **                         psrc;
	char **                         pdst;
	off_t                           elements;
	char *                          pitem;

	ictx = tpax_get_driver_ictx(dctx);

	for (psrc=ictx->prefixv; *psrc; psrc++)
		if (!strcmp(*psrc,prefix))
			return *psrc;

	if (ictx->prefixp == ictx->prefcap) {
		elements  = ictx->prefcap - ictx->prefixv;
		elements += 256;

		if (!(prefv = calloc(elements,sizeof(char *))))
			return 0;

		for (psrc=ictx->prefixv,pdst=prefv; *psrc; psrc++,pdst++)
			*pdst = *psrc;

		if (ictx->prefixv != ictx->prefptr)
			free(ictx->prefixv);

		ictx->prefixv = prefv;
		ictx->prefixp = pdst;
		ictx->prefcap = &prefv[--elements];
	}

	if (!(pitem = strdup(prefix)))
		return 0;

	*ictx->prefixp++ = pitem;

	return pitem;
}

static char * tpax_add_prefix_item_from_path(
	const struct tpax_driver_ctx *  dctx,
	const char *                    path,
	const char *                    mark)
{
	off_t   nbytes;
	char    pathbuf[PATH_MAX];

	if ((nbytes = (mark - path)) >= (PATH_MAX - 1))
		return 0;

	memcpy(pathbuf,path,nbytes);
	pathbuf[nbytes++] = '/';
	pathbuf[nbytes]   = '\0';

	return tpax_add_prefix_item(dctx,pathbuf);
}

static int tpax_dirent_init_from_stat(
	const struct stat * st,
	const char *        basename,
	struct dirent *     dirent)
{
	/* st_mode to d_type translation */
	if (S_ISREG(st->st_mode))
		dirent->d_type = DT_REG;
	else if (S_ISLNK(st->st_mode))
		dirent->d_type = DT_LNK;
	else if (S_ISDIR(st->st_mode))
		dirent->d_type = DT_DIR;
	else if (S_ISCHR(st->st_mode))
		dirent->d_type = DT_CHR;
	else if (S_ISBLK(st->st_mode))
		dirent->d_type = DT_CHR;
	else if (S_ISFIFO(st->st_mode))
		dirent->d_type = DT_CHR;
	else
		return -1;

	/* d_off, d_ino */
	dirent->d_off    = 0;
	dirent->d_ino    = st->st_ino;

	/* d_reclen */
	dirent->d_reclen  = offsetof(struct dirent,d_name);
	dirent->d_reclen += strlen(basename) + 1;

	dirent->d_reclen += 0x1;
	dirent->d_reclen |= 0x1;
	dirent->d_reclen ^= 0x1;

	/* d_name */
	strcpy(dirent->d_name,basename);

	return 0;
}

static struct tpax_dirent_buffer * tpax_dirent_buf_first_alloc(
	const struct tpax_driver_ctx * dctx)
{
	void * addr;
	struct tpax_driver_ctx_impl * ictx;

	addr = (struct tpax_dirent_buffer *)mmap(
		0,TPAX_DIRENT_BUFLEN,
		PROT_READ|PROT_WRITE,
		MAP_PRIVATE|MAP_ANONYMOUS,
		-1,0);

	if (addr == MAP_FAILED)
		return 0;

	ictx                  = tpax_get_driver_ictx(dctx);
	ictx->dirents         = (struct tpax_dirent_buffer *)addr;
	ictx->dirents->cdent  = ictx->dirents->dbuf;

	ictx->dirents->size   = TPAX_DIRENT_BUFLEN;
	ictx->dirents->next   = 0;

	ictx->dirents->nfree  = TPAX_DIRENT_BUFLEN;
	ictx->dirents->nfree -= offsetof(struct tpax_dirent_buffer,dbuf);

	return ictx->dirents;
}

static struct tpax_dirent_buffer * tpax_dirent_buf_next_alloc(
	struct tpax_dirent_buffer * current)
{
	void * addr;

	addr = (struct tpax_dirent_buffer *)mmap(
		0,TPAX_DIRENT_BUFLEN,
		PROT_READ|PROT_WRITE,
		MAP_PRIVATE|MAP_ANONYMOUS,
		-1,0);

	if (addr == MAP_FAILED)
		return 0;

	current->next         = (struct tpax_dirent_buffer *)addr;
	current->next->cdent  = current->next->dbuf;

	current->next->size   = TPAX_DIRENT_BUFLEN;
	current->next->next   = 0;

	current->next->nfree  = TPAX_DIRENT_BUFLEN;
	current->next->nfree -= offsetof(struct tpax_dirent_buffer,dbuf);

	return current->next;
}

static int tpax_archive_enqueue_ret(
	int                     ret,
	struct tpax_unit_ctx *  unit)
{
	if (unit)
		tpax_lib_free_unit_ctx(unit);

	return ret;
}

static int tpax_archive_add_queue_item(
	const struct tpax_driver_ctx *  dctx,
	const struct dirent *           dirent,
	const struct tpax_dirent *      parent,
	const char *                    prefix,
	dev_t                           stdev,
	int                             depth,
	int                             flags,
	int                             fdat,
	bool *                          fkeep)
{
	struct tpax_dirent_buffer *     dentbuf;
	struct tpax_dirent *            cdent;
	const char *                    src;
	char *                          dst;
	char *                          cap;
	size_t                          needed;

	if (!(dentbuf = tpax_get_driver_dirents(dctx)))
		if (!(dentbuf = tpax_dirent_buf_first_alloc(dctx)))
			return tpax_archive_enqueue_ret(
				TPAX_SYSTEM_ERROR(dctx),
				0);

	needed  = dirent->d_reclen;
	needed += offsetof(struct tpax_dirent,dirent);
	needed += 0x7;
	needed |= 0x7;
	needed ^= 0x7;

	for (; dentbuf->next && (dentbuf->nfree < needed); )
		dentbuf = dentbuf->next;

	if (dentbuf->nfree < needed)
		if (!(dentbuf = tpax_dirent_buf_next_alloc(dentbuf)))
			return tpax_archive_enqueue_ret(
				TPAX_SYSTEM_ERROR(dctx),
				0);

	*fkeep        = true;
	cdent         = dentbuf->cdent;

	cdent->fdat   = fdat;
	cdent->depth  = depth;
	cdent->flags  = flags;
	cdent->stdev  = stdev;
	cdent->nsize  = needed;
	cdent->parent = parent;
	cdent->prefix = prefix;

	memset(&cdent->dirent,0,offsetof(struct dirent,d_name));

	cdent->dirent.d_ino    = dirent->d_ino;
	cdent->dirent.d_type   = dirent->d_type;
	cdent->dirent.d_reclen = dirent->d_reclen;

	src  = dirent->d_name;
	dst  = cdent->dirent.d_name;

	cap  = dst - offsetof(struct dirent,d_name);
	cap -= offsetof(struct tpax_dirent,dirent);
	cap += needed;

	for (; *src; )
		*dst++ = *src++;

	for (; dst<cap; )
		*dst++ = 0;

	dentbuf->cdent  = (struct tpax_dirent *)cap;
	dentbuf->nfree -= needed;

	tpax_set_driver_dirmark(dctx,cdent);

	return 0;
}

static int tpax_archive_enqueue_dir_entries(
	const struct tpax_driver_ctx *  dctx,
	struct tpax_dirent *            dent)
{
	int                             fd;
	int                             fdat;
	int                             fdlnk;
	int                             depth;
	bool                            fkeep;
	bool                            flinks;
	bool                            fstdev;
	long                            nbytes;
	struct dirent *                 lnkent;
	struct dirent *                 dirent;
	struct dirent *                 dirents;
	struct tpax_dirent *            cdent;
	struct tpax_unit_ctx *          uctx;
	struct stat                     st;
	struct stat                     lnkst;
	uintptr_t                       addr;
	char                            lnktgt[PATH_MAX];
	char                            lnkbuf[PATH_MAX + sizeof(struct dirent)];

	/* init */
	fdat  = dent->fdat;
	depth = dent->depth;

	/* uctx on the fly */
	if (tpax_lib_get_unit_ctx(
			dctx,fdat,
			dent->dirent.d_name,
			&uctx) < 0)
		return TPAX_NESTED_ERROR(dctx);

	/* verify that recursion item is still a directory */
	if (!S_ISDIR(uctx->st->st_mode))
		return tpax_archive_enqueue_ret(
			TPAX_CUSTOM_ERROR(dctx,TPAX_ERR_FLOW_ERROR),
			uctx);

	/* ensure physical device identity as needed */
	if (dctx->cctx->drvflags & TPAX_DRIVER_STRICT_DEVICE_ID)
		if (dent->parent && (uctx->st->st_dev != dent->parent->stdev))
			return 0;

	/* obtain buffer for file-system directory entries */
	dirents = tpax_get_driver_getdents_buffer(dctx);
	dirent  = dirents;
	fkeep   = false;
	nbytes  = 0;
	depth++;

	/* open directory and obtain first directory entries */
	if ((fd = openat(fdat,dent->dirent.d_name,O_RDONLY|O_DIRECTORY|O_CLOEXEC,0)) < 0)
		return tpax_archive_enqueue_ret(
			TPAX_SYSTEM_ERROR(dctx),
			uctx);

	lnkent = (struct dirent *)lnkbuf;
	flinks = (dctx->cctx->drvflags & TPAX_DRIVER_PAX_SYMLINK_ITEMS);
	fstdev = (dctx->cctx->drvflags & TPAX_DRIVER_STRICT_DEVICE_ID);
	nbytes = tpax_getdents(fd,dirents,TPAX_DIRENT_BUFLEN);

	/* debugging, struct stat initialization when fstdev is false */
	memset(&st,0,sizeof(st));

	while ((nbytes == -EINTR) || ((nbytes < 0) && (errno == EINTR)))
		nbytes = tpax_getdents(fd,dirents,TPAX_DIRENT_BUFLEN);

	if (nbytes < 0)
		return tpax_archive_enqueue_ret(
			TPAX_SYSTEM_ERROR(dctx),
			uctx);

	/* iterate */
	for (; nbytes; ) {
		if (!strcmp(dirent->d_name,".")) {
			(void)0;

		} else if (!strcmp(dirent->d_name,"..")) {
			(void)0;

		} else {
			if (dirent->d_type == DT_UNKNOWN) {
				if (fstatat(fd,dirent->d_name,&st,AT_SYMLINK_NOFOLLOW))
					return tpax_archive_enqueue_ret(
						TPAX_SYSTEM_ERROR(dctx),
						uctx);

				if (S_ISDIR(st.st_mode)) {
					dirent->d_type = DT_DIR;
				}
			} else if (fstdev) {
				if (fstatat(fd,dirent->d_name,&st,AT_SYMLINK_NOFOLLOW))
					return tpax_archive_enqueue_ret(
						TPAX_SYSTEM_ERROR(dctx),
						uctx);
			}

			if (tpax_archive_add_queue_item(
					dctx,dirent,dent,0,
					st.st_dev,depth,
					TPAX_ITEM_IMPLICIT,
					fd,&fkeep) < 0)
				return tpax_archive_enqueue_ret(
					TPAX_NESTED_ERROR(dctx),
					uctx);

			/* follow encountered symlink arguments as needed */
			fdlnk = (flinks && (dirent->d_type == DT_LNK))
				? openat(fd,dirent->d_name,O_RDONLY|O_CLOEXEC)
				: (-1);

			if (fdlnk >= 0) {
				if (fstat(fdlnk,&lnkst) <0) {
					close(fdlnk);
					return tpax_archive_enqueue_ret(
						TPAX_SYSTEM_ERROR(dctx),
						uctx);
				}

				if (tpax_readlinkat(fd,dirent->d_name,lnktgt,PATH_MAX) < 0)
					return tpax_archive_enqueue_ret(
						TPAX_SYSTEM_ERROR(dctx),
						uctx);

				close(fdlnk);

				if (tpax_dirent_init_from_stat(&lnkst,lnktgt,lnkent) < 0)
					return tpax_archive_enqueue_ret(
						TPAX_CUSTOM_ERROR(
							dctx,
							TPAX_ERR_FLOW_ERROR),
						0);

				cdent = tpax_get_driver_dirmark(dctx);
				cdent->flags |= TPAX_ITEM_NAMEREF;

				if (tpax_archive_add_queue_item(
						dctx,lnkent,cdent,0,
						lnkst.st_dev,depth+1,
						TPAX_ITEM_IMPLICIT|TPAX_ITEM_SYMLINK,
						fd,&fkeep) < 0)
					return tpax_archive_enqueue_ret(
						TPAX_NESTED_ERROR(dctx),
						0);
			}
		}

		addr    = (uintptr_t)dirent;
		addr   += dirent->d_reclen;
		nbytes -= dirent->d_reclen;
		dirent  = (struct dirent *)addr;

		if (nbytes == 0) {
			nbytes = tpax_getdents(fd,dirents,TPAX_DIRENT_BUFLEN);

			while ((nbytes == -EINTR) || ((nbytes < 0) && (errno == EINTR)))
				nbytes = tpax_getdents(fd,dirents,TPAX_DIRENT_BUFLEN);

			if (nbytes < 0)
				tpax_archive_enqueue_ret(
					TPAX_SYSTEM_ERROR(dctx),
					uctx);

			dirent = dirents;
		}
	}

	/* all done */
	return tpax_archive_enqueue_ret(
		fkeep ? 0 : close(fd),
		uctx);
}

static const char * tpax_path_prefix_mark(const char * path)
{
	const char *    src;
	char *          mark;
	char            pathbuf[PATH_MAX];

	src  = path;
	mark = pathbuf;

	for (; *src; )
		*mark++ = *src++;

	for (--mark; (*mark == '/'); mark--)
		(void)0;

	*++mark = '\0';

	for (; (mark > pathbuf) && (mark[-1] != '/'); )
		mark--;

	return (mark <= pathbuf) ? 0 : &path[--mark-pathbuf];
}

int tpax_archive_enqueue(
	const struct tpax_driver_ctx *  dctx,
	const struct tpax_unit_ctx *    uctx)
{
	int                             fdat;
	int                             fdlnk;
	uintptr_t                       addr;
	const char *                    name;
	const char *                    mark;
	const char *                    prefix;
	bool                            fkeep;
	struct tpax_dirent_buffer *     dentbuf;
	struct tpax_dirent *            cdent;
	struct tpax_dirent *            cnext;
	struct dirent *                 dirent;
	struct dirent *                 lnkent;
	struct stat                     lnkst;
	char                            entbuf[PATH_MAX + sizeof(struct dirent)];
	char                            lnkbuf[PATH_MAX + sizeof(struct dirent)];

	/* init */
	fdat   = tpax_driver_fdcwd(dctx);
	dirent = (struct dirent *)entbuf;
	lnkent = (struct dirent *)lnkbuf;
	prefix = 0;

	/* split path to prefix + basename */
	if ((mark = tpax_path_prefix_mark(*uctx->path)))
		if (!(prefix = tpax_add_prefix_item_from_path(
				dctx,*uctx->path,mark)))
			return tpax_archive_enqueue_ret(
				TPAX_BUFFER_ERROR(dctx),
				0);

	name = mark ? ++mark : *uctx->path;

	if (prefix)
		if ((fdat = openat(fdat,prefix,O_RDONLY|O_DIRECTORY|O_CLOEXEC,0)) < 0)
			return tpax_archive_enqueue_ret(
				TPAX_SYSTEM_ERROR(dctx),
				0);

	/* explicit item directory entry */
	if (tpax_dirent_init_from_stat(uctx->st,name,dirent) < 0)
			return tpax_archive_enqueue_ret(
				TPAX_CUSTOM_ERROR(
					dctx,
					TPAX_ERR_FLOW_ERROR),
				0);

	/* add to queue */
	if (tpax_archive_add_queue_item(
			dctx,dirent,0,prefix,
			uctx->st->st_dev,0,
			TPAX_ITEM_EXPLICIT,
			fdat,&fkeep) < 0)
		return tpax_archive_enqueue_ret(
			TPAX_NESTED_ERROR(dctx),
			0);

	/* follow command-line symlink arguments as needed */
	fdlnk = (uctx->link[0] && (dctx->cctx->drvflags & TPAX_DRIVER_PAX_SYMLINK_ARGS))
		? openat(fdat,uctx->link[0],O_RDONLY|O_CLOEXEC) : (-1);

	if (fdlnk >= 0) {
		if (fstat(fdlnk,&lnkst) <0) {
			close(fdlnk);
			return tpax_archive_enqueue_ret(
				TPAX_SYSTEM_ERROR(dctx),
				0);
		}

		close(fdlnk);

		if (tpax_dirent_init_from_stat(&lnkst,uctx->link[0],lnkent) < 0)
			return tpax_archive_enqueue_ret(
				TPAX_CUSTOM_ERROR(
					dctx,
					TPAX_ERR_FLOW_ERROR),
				0);

		cdent = tpax_get_driver_dirmark(dctx);
		cdent->flags |= TPAX_ITEM_NAMEREF;

		if (tpax_archive_add_queue_item(
				dctx,lnkent,cdent,0,
				lnkst.st_dev,1,
				TPAX_ITEM_EXPLICIT|TPAX_ITEM_SYMLINK,
				fdat,&fkeep) < 0)
			return tpax_archive_enqueue_ret(
				TPAX_NESTED_ERROR(dctx),
				0);
	}

	/* queue directory child items */
	dentbuf = tpax_get_driver_dirents(dctx);
	cdent   = tpax_get_driver_dirmark(dctx);

	for (; cdent; ) {
		if (cdent->dirent.d_type == DT_DIR)
			if (dctx->cctx->drvflags & TPAX_DRIVER_DIR_MEMBER_RECURSE)
				if (tpax_archive_enqueue_dir_entries(dctx,cdent) < 0)
					return TPAX_NESTED_ERROR(dctx);

		addr  = (uintptr_t)cdent;
		addr += cdent->nsize;
		cnext = (struct tpax_dirent *)addr;

		if (cnext == dentbuf->cdent) {
			dentbuf = dentbuf->next;
			cnext   = dentbuf ? dentbuf->dbuf : 0;
		}

		cdent = cnext;
	}

	return 0;
}