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 <tpax/tpax.h>
#include "tpax_driver_impl.h"
#include "tpax_errinfo_impl.h"
#include "tpax_visibility_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 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 int tpax_archive_write_ret(
	int                     ret,
	struct tpax_unit_ctx *  uctx)
{
	tpax_lib_free_unit_ctx(uctx);
	return ret;
}

static int tpax_archive_write_impl(
	const struct tpax_driver_ctx *  dctx,
	const struct tpax_dirent *      cdent,
	int                             fdcwd,
	int                             fdout)
{
	struct tpax_unit_ctx *          uctx;
	struct tpax_ustar_header        uhdr;
	const struct stat *             st;
	struct stat                     stbuf;
	const  char *                   path;
	const  char *                   slnk;
	const  char *                   mlnk;
	off_t                           hpos;
	off_t                           dpos;
	int                             fdtmp;
	ssize_t                         nread;
	ssize_t                         nbytes;
	void *                          buf;
	size_t                          buflen;
	size_t                          cmplen;
	void *				membuf;
	char *                          ch;
	char                           	pathbuf[PATH_MAX];

	/* followed symlink? */
	if (cdent->flags & TPAX_ITEM_NAMEREF)
		return 0;

	/* full path */
	if (!(path = tpax_queue_item_full_path(dctx,cdent)))
		return TPAX_CUSTOM_ERROR(
			dctx,
			TPAX_ERR_FLOW_ERROR);

	/* uctx */
	if (tpax_lib_get_unit_ctx(dctx,fdcwd,path,&uctx) < 0)
		return TPAX_NESTED_ERROR(dctx);

	st   = uctx->st;
	slnk = uctx->link[0];
	mlnk = 0;

	if (cdent->flags & TPAX_ITEM_SYMLINK) {
		st   = &stbuf;
		mlnk = slnk;
		slnk = 0;
		ch   = 0;

		if (mlnk[0] != '/') {
			if (strlen(path) >= PATH_MAX)
				return tpax_archive_write_ret(
					TPAX_CUSTOM_ERROR(
						dctx,
						TPAX_ERR_FLOW_ERROR),
					uctx);

			strcpy(pathbuf,path);

			ch = strrchr(pathbuf,'/');
		}

		if (ch && (++ch - pathbuf >= PATH_MAX))
			return tpax_archive_write_ret(
				TPAX_CUSTOM_ERROR(
					dctx,
					TPAX_ERR_FLOW_ERROR),
				uctx);

		if (ch) {
			strcpy(ch,mlnk);
			mlnk = pathbuf;
		}

		if (fstatat(fdcwd,mlnk,&stbuf,0) < 0)
			return tpax_archive_write_ret(
				TPAX_SYSTEM_ERROR(dctx),
				uctx);
	}

	/* record errors */
	tpax_driver_set_ectx(
		dctx,0,path);

	/* header and data offsets: todo pax and cpio */
	hpos = tpax_get_driver_cpos(dctx);
	dpos = hpos + sizeof(uhdr);

	/* header */
	if (tpax_meta_init_ustar_header(
			dctx,path,st,
			slnk,&uhdr) < 0)
		return tpax_archive_write_ret(
			TPAX_NESTED_ERROR(dctx),
			uctx);

	/* buffer  */
	membuf = 0;
	fdtmp  = -1;

	/* associated data? */
	if S_ISREG(st->st_mode) {
		buf = tpax_get_driver_anon_map_addr(
			dctx,&buflen);

		if (buflen >= (cmplen = st->st_size))
			membuf = buf;

		/* snapshot */
		if (membuf) {
			if (tpax_io_create_memory_snapshot(
					dctx,fdcwd,
					mlnk ? mlnk : path,
					st,membuf) < 0)
				return tpax_archive_write_ret(
					TPAX_NESTED_ERROR(dctx),
					uctx);
		} else {
			if ((fdtmp = tpax_io_create_tmpfs_snapshot(
					dctx,fdcwd,
					mlnk ? mlnk : path,
					st)) < 0)
				return tpax_archive_write_ret(
					TPAX_NESTED_ERROR(dctx),
					uctx);

			if (lseek(fdtmp,0,SEEK_SET) < 0)
				return tpax_archive_write_ret(
					TPAX_SYSTEM_ERROR(dctx),
					uctx);
		}
	}

	/* append header */
	if (tpax_archive_append_memory_data(fdout,&uhdr,ssizeof(uhdr)) < 0) {
		if (fdtmp >= 0)
			close(fdtmp);

		return tpax_archive_write_ret(
			TPAX_SYSTEM_ERROR(dctx),
			uctx);
	}

	tpax_set_driver_cpos(dctx,dpos);

	/* all done? */
	if (!(S_ISREG(st->st_mode))) {
		tpax_lib_free_unit_ctx(uctx);
		return 0;
	}

	/* append data from snapshot */
	if (fdtmp >= 0) {
		buf = tpax_get_driver_anon_map_addr(
			dctx,&buflen);

		for (nread=0; nread<st->st_size; ) {
			nbytes = read(fdtmp,buf,buflen);

			while ((nbytes < 0) && (errno == EINTR))
				nbytes = read(fdtmp,buf,buflen);

			if (nbytes < 0) {
				close(fdtmp);
				return tpax_archive_write_ret(
					TPAX_SYSTEM_ERROR(dctx),
					uctx);

			} else if (nbytes == 0) {
				close(fdtmp);
				return tpax_archive_write_ret(
					TPAX_CUSTOM_ERROR(dctx,TPAX_ERR_FLOW_ERROR),
					uctx);

			} else {
				nread += nbytes;
			}

			if (tpax_archive_append_memory_data(fdout,buf,nbytes) < 0) {
				close(fdtmp);
				return tpax_archive_write_ret(
					TPAX_SYSTEM_ERROR(dctx),
					uctx);
			}
		}

		close(fdtmp);
	} else {
		if (tpax_archive_append_memory_data(
				fdout,membuf,
				st->st_size) < 0)
			return tpax_archive_write_ret(
				TPAX_SYSTEM_ERROR(dctx),
				uctx);
	}

	return tpax_archive_write_ret(
		tpax_archive_append_pad(dctx,fdout,st),
		uctx);
}

int tpax_archive_write(const struct tpax_driver_ctx * dctx)
{
	struct tpax_driver_ctx_impl *   ictx;
	struct tpax_dirent **           direntv;
	int                             fdcwd;
	int                             fdout;

	/* driver */
	ictx  = tpax_get_driver_ictx(dctx);
	fdcwd = tpax_driver_fdcwd(dctx);
	fdout = tpax_driver_fdout(dctx);

	/* quote to archive */
	if (tpax_update_queue_vector(dctx) < 0)
		return TPAX_NESTED_ERROR(dctx);

	for (direntv=ictx->direntv; *direntv; direntv++)
		if (tpax_archive_write_impl(dctx,*direntv,fdcwd,fdout) < 0)
			return TPAX_NESTED_ERROR(dctx);

	return 0;
}

static int tpax_archive_seal_cpio(const struct tpax_driver_ctx * dctx)
{
	(void)dctx;
	return -1;
}

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[1024];

	/* archive block size, current position */
	blksize = dctx->cctx->blksize;
	cpos    = tpax_get_driver_cpos(dctx);

	/* internal error? */
	if (cpos % 512)
		return TPAX_CUSTOM_ERROR(dctx,TPAX_ERR_FLOW_ERROR);

	/* cpio trailer? */
	if (dctx->cctx->drvflags & TPAX_DRIVER_WRITE_FORMAT_CPIO)
		return tpax_archive_seal_cpio(dctx);

	/* ustar, pax */
	fdout = tpax_driver_fdout(dctx);
	memset(buf,0,sizeof(buf));

	if (tpax_archive_append_memory_data(fdout,buf,2*512) < 0)
		return TPAX_SYSTEM_ERROR(dctx);

	cpos += 2*512;

	/* pax? */
	if (dctx->cctx->drvflags & TPAX_DRIVER_WRITE_FORMAT_PAX) {
		tpax_set_driver_cpos(dctx,cpos);
		return 0;
	}

	/* already at block boundary? */
	if ((cpos % blksize) == 0) {
		tpax_set_driver_cpos(dctx,cpos);
		return 0;
	}

	/* zero-fill the remainder of the block */
	nbytes  = cpos / blksize;
	nbytes *= blksize;
	nbytes += blksize;

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

	/* all done */
	tpax_set_driver_cpos(dctx,nwritten);

	return 0;
}