/**************************************************************/ /* tpax: a topological pax implementation */ /* Copyright (C) 2020--2024 SysDeer Technologies, LLC */ /* Released under GPLv2 and GPLv3; see COPYING.TPAX. */ /**************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #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 (; dstcdent = (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; }