/**************************************************************/
/* 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 char * tpax_append_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_append_prefix_item(dctx,pathbuf);
}
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;
tpax_set_driver_dirmark(dctx,cdent);
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);
}
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];
}
static int tpax_dirent_init_from_uctx(
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);
dirent->d_reclen += 0x1;
dirent->d_reclen |= 0x1;
dirent->d_reclen ^= 0x1;
/* d_name */
strcpy(dirent->d_name,basename);
return 0;
}
int tpax_archive_append_item(
const struct tpax_driver_ctx * dctx,
const struct tpax_unit_ctx * uctx)
{
int fdat;
const char * name;
const char * mark;
const char * prefix;
bool fkeep;
struct dirent * dirent;
char entbuf[PATH_MAX + sizeof(struct dirent)];
/* init */
fdat = tpax_driver_fdcwd(dctx);
dirent = (struct dirent *)entbuf;
prefix = 0;
/* split path to prefix + basename */
if ((mark = tpax_path_prefix_mark(*uctx->path)))
if (!(prefix = tpax_append_prefix_item_from_path(
dctx,*uctx->path,mark)))
return tpax_archive_append_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_append_ret(
TPAX_SYSTEM_ERROR(dctx),
0);
/* explicit item directory entry */
if (tpax_dirent_init_from_uctx(uctx->st,name,dirent) < 0)
return tpax_archive_append_ret(
TPAX_CUSTOM_ERROR(
dctx,
TPAX_ERR_FLOW_ERROR),
0);
/* add to queue */
if (tpax_archive_append_queue_item(
dctx,dirent,0,prefix,0,
TPAX_ITEM_EXPLICIT,
fdat,&fkeep) < 0)
return tpax_archive_append_ret(
TPAX_NESTED_ERROR(dctx),
0);
(void)tpax_archive_append_pad;
(void)tpax_archive_append_dir_entries;
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;
}