/******************************************************/ /* tpax: a topological pax implementation */ /* Copyright (C) 2020--2021 Z. Gilboa */ /* 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_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 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_one( const struct tpax_driver_ctx * dctx, const struct tpax_unit_ctx * uctx, const struct dirent * dent, int fdat, const char * prefix, const struct tpax_dirent * parent, const char * pdir) { struct tpax_unit_ctx * unit; struct tpax_ustar_header uhdr; off_t hpos; off_t dpos; int fdout; int fdtmp; ssize_t nread; ssize_t nbytes; void * buf; size_t buflen; size_t cmplen; void * membuf; size_t nlen; const char * path; const char * src; char * dst; char pbuf[1024]; /* fake uctx for recursion items */ unit = 0; if (dent && tpax_get_unit_ctx( dctx,fdat,dent->d_name, &unit) < 0) return TPAX_NESTED_ERROR(dctx); uctx = dent ? unit : uctx; /* prefixed path */ if (!prefix && !parent && !pdir) { path = *uctx->path; } else { nlen = strlen(*uctx->path); nlen += prefix ? strlen(prefix) + 1 : 0; nlen += parent ? strlen(parent->dirent.d_name) + 1 : 0; if (nlen >= sizeof(pbuf)) return TPAX_BUFFER_ERROR(dctx); dst = pbuf; if (prefix) { src = prefix; for (; *src; ) *dst++ = *src++; if (dst[-1] != '/') *dst++ = '/'; } if (parent) { src = parent->dirent.d_name; for (; *src; ) *dst++ = *src++; *dst++ = '/'; } if (pdir) { src = pdir; for (; *src; ) *dst++ = *src++; *dst++ = '/'; } src = *uctx->path; for (; *src; ) *dst++ = *src++; *dst = 0; path = pbuf; } /* record errors */ tpax_driver_set_ectx( dctx,0,path); /* driver */ fdout = tpax_driver_fdout(dctx); /* header and data offsets: todo pax and cpio */ hpos = tpax_get_driver_cpos(dctx); dpos = hpos + sizeof(uhdr); /* header */ if (tpax_init_ustar_header( dctx,path,uctx->st, *uctx->link,&uhdr) < 0) return tpax_archive_append_ret( TPAX_NESTED_ERROR(dctx), unit); /* buffer */ membuf = 0; fdtmp = -1; /* associated data? */ if S_ISREG(uctx->st->st_mode) { buf = tpax_get_driver_anon_map_addr( dctx,&buflen); if (buflen >= (cmplen = uctx->st->st_size)) membuf = buf; /* snapshot */ if (membuf) { if (tpax_file_create_memory_snapshot( dctx,fdat,*uctx->path, uctx->st,membuf) < 0) return tpax_archive_append_ret( TPAX_NESTED_ERROR(dctx), unit); } else { if ((fdtmp = tpax_file_create_tmpfs_snapshot( dctx,fdat,*uctx->path, uctx->st)) < 0) return tpax_archive_append_ret( TPAX_NESTED_ERROR(dctx), unit); if (lseek(fdtmp,0,SEEK_SET) < 0) return tpax_archive_append_ret( TPAX_SYSTEM_ERROR(dctx), unit); } } /* append header */ if (tpax_archive_append_memory_data(fdout,&uhdr,ssizeof(uhdr)) < 0) { if (fdtmp >= 0) close(fdtmp); return tpax_archive_append_ret( TPAX_SYSTEM_ERROR(dctx), unit); } tpax_set_driver_cpos(dctx,dpos); /* all done? */ if (!(S_ISREG(uctx->st->st_mode))) { tpax_archive_append_ret(0,unit); return 0; } /* append data from snapshot */ if (fdtmp >= 0) { buf = tpax_get_driver_anon_map_addr( dctx,&buflen); for (nread=0; nreadst->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_append_ret( TPAX_SYSTEM_ERROR(dctx), unit); } else if (nbytes == 0) { close(fdtmp); return tpax_archive_append_ret( TPAX_CUSTOM_ERROR(dctx,TPAX_ERR_FLOW_ERROR), unit); } else { nread += nbytes; } if (tpax_archive_append_memory_data(fdout,buf,nbytes) < 0) { close(fdtmp); return tpax_archive_append_ret( TPAX_SYSTEM_ERROR(dctx), unit); } } close(fdtmp); } else { if (tpax_archive_append_memory_data( fdout,membuf, uctx->st->st_size) < 0) return tpax_archive_append_ret( TPAX_SYSTEM_ERROR(dctx), unit); } return tpax_archive_append_ret( tpax_archive_append_pad(dctx,fdout,uctx->st), unit); } static int tpax_archive_append_dir( const struct tpax_driver_ctx * dctx, const struct tpax_unit_ctx * uctx, struct tpax_dirent * dent, int fdat, int depth, const char * prefix, const struct tpax_dirent * parent) { int fd; bool fkeep; long nbytes; size_t needed; struct dirent * dirent; struct dirent * dirents; struct tpax_dirent_buffer * dentbuf; struct tpax_dirent * cdent; struct tpax_unit_ctx * unit; struct stat st; uintptr_t addr; char * src; char * dst; char * cap; /* fake uctx for recursion items */ unit = 0; if (dent && tpax_get_unit_ctx( dctx,dent->fdat,dent->dirent.d_name, &unit) < 0) return TPAX_NESTED_ERROR(dctx); uctx = dent ? unit : uctx; /* verify that recursion item is still a directory */ if (unit && !S_ISDIR(unit->st->st_mode)) return tpax_archive_append_ret( TPAX_CUSTOM_ERROR(dctx,TPAX_ERR_FLOW_ERROR), unit); /* append directory entry to archive */ if (tpax_archive_append_one( dctx,uctx,0, tpax_driver_fdcwd(dctx), prefix,0,0) < 0) return tpax_archive_append_ret( TPAX_NESTED_ERROR(dctx), unit); /* 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,*uctx->path,O_RDONLY|O_DIRECTORY|O_CLOEXEC,0)) < 0) return tpax_archive_append_ret( TPAX_SYSTEM_ERROR(dctx), unit); 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), unit); /* 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), unit); if (S_ISDIR(st.st_mode)) { dirent->d_type = DT_DIR; } } if (dirent->d_type == DT_DIR) { if (!(dentbuf = tpax_get_driver_dirents(dctx))) if (!(dentbuf = tpax_dirent_buf_first_alloc(dctx))) return tpax_archive_append_ret( TPAX_SYSTEM_ERROR(dctx), unit); 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), unit); fkeep = true; cdent = dentbuf->cdent; cdent->fdat = fd; cdent->depth = depth; cdent->nsize = needed; cdent->parent = parent; memset(&cdent->dirent,0,offsetof(struct dirent,d_name)); 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; } else { if (tpax_archive_append_one( dctx,0,dirent,fd,prefix, 0,*uctx->path) < 0) return tpax_archive_append_ret( TPAX_NESTED_ERROR(dctx), unit); } } 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), unit); dirent = dirents; } } /* all done */ return tpax_archive_append_ret( fkeep ? 0 : close(fd), unit); } static int tpax_archive_append_impl( const struct tpax_driver_ctx * dctx, const struct tpax_unit_ctx * uctx, int depth, const char * prefix, struct tpax_dirent * parent) { if (S_ISDIR(uctx->st->st_mode)) if (dctx->cctx->drvflags & TPAX_DRIVER_DIR_MEMBER_RECURSE) return tpax_archive_append_dir( dctx,uctx,0, tpax_driver_fdcwd(dctx), depth,prefix,parent); return tpax_archive_append_one( dctx,uctx,0, tpax_driver_fdcwd(dctx), prefix,0,0); } int tpax_archive_append( const struct tpax_driver_ctx * dctx, const struct tpax_unit_ctx * uctx, const char * prefix) { struct tpax_dirent_buffer * dentbuf; struct tpax_dirent * cdent; struct tpax_dirent * cnext; const struct tpax_dirent * parent; uintptr_t addr; const char * rdir; const char * src; char * dst; char * cap; char * mark; int idx; const char * dirv[256]; char pbuf[2048]; /* normalized prefix */ if (prefix && (prefix[0] == '.') && (!prefix[1])) prefix = 0; if (prefix && (prefix[0] == '.') && (prefix[1] == '/')) for (++prefix; *prefix=='/'; prefix++) (void)0; /* append explicit item */ tpax_archive_append_impl(dctx,uctx,0,prefix,0); /* iterate through queued items */ dentbuf = tpax_get_driver_dirents(dctx); cdent = dentbuf ? dentbuf->dbuf : 0; if (cdent) { dst = pbuf; cap = &pbuf[sizeof(pbuf)]; if (prefix && prefix[0]) { for (; *src && dstpath; for (; *src && dstdirent.d_type) { case DT_DIR: if (tpax_archive_append_dir( dctx,0,cdent,cdent->fdat, cdent->depth,rdir,cdent) < 0) return TPAX_NESTED_ERROR(dctx); break; default: if (tpax_archive_append_one( dctx,0,&cdent->dirent, cdent->fdat,rdir,cdent,0) < 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; } if (cnext && (cnext->parent != cdent->parent)) { if (cnext->depth > 256) return TPAX_BUFFER_ERROR(dctx); for (parent=cnext->parent; parent; parent=parent->parent) dirv[parent->depth - 1] = parent->dirent.d_name; for (idx=0,dst=mark; idxparent->depth; idx++) { src = dirv[idx]; for (; *src; ) *dst++ = *src++; *dst++ = '/'; } *--dst = 0; } cdent = cnext; } 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