Blob Blame History Raw
/**************************************************************/
/*  tpax: a topological pax implementation                    */
/*  Copyright (C) 2020--2021  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_tmpfile_impl.h"
#include "tpax_errinfo_impl.h"

#ifndef ssizeof
#define ssizeof(x) (ssize_t)(sizeof(x))
#endif

static int tpax_archive_append_memory_data(
	int                             fdout,
	void *                          buf,
	ssize_t                         nbytes)
{
	ssize_t ret;
	char  * ch;

	for (ch=buf; nbytes; ch+=ret) {
		ret = write(fdout,ch,nbytes);

		while ((ret < 0) && (errno == EINTR))
			ret = write(fdout,ch,nbytes);

		if (ret < 0)
			return ret;

		nbytes -= ret;
	}

	return 0;
}

static char * tpax_append_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 int tpax_archive_append_pad(
	const struct tpax_driver_ctx *  dctx,
	int                             fdout,
	const struct stat *             st)
{
	int     ret;
	off_t   cpos;
	ssize_t nbytes;
	char    buf[512];

	nbytes  = st->st_size;
	nbytes += 0x1ff;
	nbytes |= 0x1ff;
	nbytes ^= 0x1ff;
	nbytes -= st->st_size;

	memset(buf,0,nbytes);

	cpos  = tpax_get_driver_cpos(dctx);
	cpos += st->st_size + nbytes;

	if (!(ret = tpax_archive_append_memory_data(fdout,buf,nbytes)))
		tpax_set_driver_cpos(dctx,cpos);

	return ret;
}

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_append_ret(
	int                     ret,
	struct tpax_unit_ctx *  unit)
{
	if (unit)
		tpax_free_unit_ctx(unit);

	return ret;
}

static int tpax_archive_append_queue_item(
	const struct tpax_driver_ctx *  dctx,
	const struct dirent *           dirent,
	const struct tpax_dirent *      parent,
	const char *                    prefix,
	int                             depth,
	int                             flags,
	int                             fd,
	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_append_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_append_ret(
				TPAX_SYSTEM_ERROR(dctx),
				0);

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

	cdent->fdat   = fd;
	cdent->depth  = depth;
	cdent->flags  = flags;
	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;

	return 0;
}

static int tpax_archive_append_dir_entries(
	const struct tpax_driver_ctx *  dctx,
	struct tpax_dirent *            dent)
{
	int                             fd;
	int                             fdat;
	int                             depth;
	bool                            fkeep;
	long                            nbytes;
	struct dirent *                 dirent;
	struct dirent *                 dirents;
	struct tpax_unit_ctx *          uctx;
	struct stat                     st;
	uintptr_t                       addr;

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

	/* uctx on the fly */
	if (tpax_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_append_ret(
			TPAX_CUSTOM_ERROR(dctx,TPAX_ERR_FLOW_ERROR),
			uctx);

	/* 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_append_ret(
			TPAX_SYSTEM_ERROR(dctx),
			uctx);

	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)
		return tpax_archive_append_ret(
			TPAX_SYSTEM_ERROR(dctx),
			uctx);

	/* iterate */
	for (; nbytes>0; ) {
		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_append_ret(
						TPAX_SYSTEM_ERROR(dctx),
						uctx);

				if (S_ISDIR(st.st_mode)) {
					dirent->d_type = DT_DIR;
				}
			}

			if (tpax_archive_append_queue_item(
					dctx,dirent,
					dent,0,depth,
					TPAX_ITEM_IMPLICIT,
					fd,&fkeep) < 0)
				return tpax_archive_append_ret(
					TPAX_NESTED_ERROR(dctx),
					uctx);
		}

		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_append_ret(
					TPAX_SYSTEM_ERROR(dctx),
					uctx);

			dirent = dirents;
		}
	}

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

int tpax_archive_append_item(
	const struct tpax_driver_ctx *  dctx,
	const struct tpax_unit_ctx *    uctx)
{
	(void)dctx;
	(void)uctx;

	(void)tpax_archive_append_dir_entries;
	(void)tpax_append_prefix_item;

	return 0;
}

int tpax_archive_seal(const struct tpax_driver_ctx * dctx)
{
	int     fdout;
	off_t   cpos;
	ssize_t nbytes;
	ssize_t nwritten;
	ssize_t blksize;
	char    buf[512];

	blksize = tpax_get_archive_block_size(dctx);
	cpos    = tpax_get_driver_cpos(dctx);

	if (cpos % 512)
		return TPAX_CUSTOM_ERROR(dctx,TPAX_ERR_FLOW_ERROR);

	fdout = tpax_driver_fdout(dctx);
	memset(buf,0,sizeof(buf));

	switch (cpos % blksize) {
		case 0:
			nbytes = cpos + blksize;
			break;

		default:
			nbytes  = cpos / blksize;
			nbytes *= blksize;
			nbytes += blksize;

			if (nbytes-cpos == 512)
				nbytes += blksize;
	}

	for (nwritten=cpos; nwritten<nbytes; nwritten+=512) {
		if (tpax_archive_append_memory_data(fdout,buf,512) < 0)
			return TPAX_SYSTEM_ERROR(dctx);

		tpax_set_driver_cpos(dctx,nwritten);
	}

	return 0;
}