summaryrefslogtreecommitdiffhomepage
path: root/src/logic/tpax_archive_enqueue.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/logic/tpax_archive_enqueue.c')
-rw-r--r--src/logic/tpax_archive_enqueue.c582
1 files changed, 582 insertions, 0 deletions
diff --git a/src/logic/tpax_archive_enqueue.c b/src/logic/tpax_archive_enqueue.c
new file mode 100644
index 0000000..8685cad
--- /dev/null
+++ b/src/logic/tpax_archive_enqueue.c
@@ -0,0 +1,582 @@
+/**************************************************************/
+/* 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;
+}