/**************************************************************/ /* tpax: a topological pax implementation */ /* Copyright (C) 2020--2024 SysDeer Technologies, LLC */ /* Released under GPLv2 and GPLv3; see COPYING.TPAX. */ /**************************************************************/ #define _DEFAULT_SOURCE 1 #include #include #include #include #include #include #include #define ARGV_DRIVER #include #include "tpax_version.h" #include "tpax_driver_impl.h" #include "argv/argv.h" #define TPAX_DRIVER_EXEC_MODE_MASK \ (TPAX_DRIVER_EXEC_MODE_LIST \ | TPAX_DRIVER_EXEC_MODE_READ \ | TPAX_DRIVER_EXEC_MODE_WRITE \ | TPAX_DRIVER_EXEC_MODE_COPY) #define TPAX_DRIVER_WRITE_FORMAT_MASK \ (TPAX_DRIVER_WRITE_FORMAT_PAX \ | TPAX_DRIVER_WRITE_FORMAT_CPIO \ | TPAX_DRIVER_WRITE_FORMAT_USTAR \ | TPAX_DRIVER_WRITE_FORMAT_RUSTAR) /* package info */ static const struct tpax_source_version tpax_src_version = { TPAX_TAG_VER_MAJOR, TPAX_TAG_VER_MINOR, TPAX_TAG_VER_PATCH, TPAX_GIT_VERSION }; /* default fd context */ static const struct tpax_fd_ctx tpax_default_fdctx = { .fdin = STDIN_FILENO, .fdout = STDOUT_FILENO, .fderr = STDERR_FILENO, .fdcwd = AT_FDCWD, .fddst = AT_FDCWD, .fdlog = (-1), }; struct tpax_driver_ctx_alloc { struct argv_meta * meta; struct tpax_driver_ctx_impl ctx; uint64_t guard; const char * units[]; }; static uint32_t tpax_argv_flags(uint32_t flags) { uint32_t ret = ARGV_CLONE_VECTOR; if (flags & TPAX_DRIVER_VERBOSITY_NONE) ret |= ARGV_VERBOSITY_NONE; if (flags & TPAX_DRIVER_VERBOSITY_ERRORS) ret |= ARGV_VERBOSITY_ERRORS; if (flags & TPAX_DRIVER_VERBOSITY_STATUS) ret |= ARGV_VERBOSITY_STATUS; return ret; } static int tpax_driver_usage( int fdout, const char * program, const char * arg, const struct argv_option ** optv, struct argv_meta * meta) { char header[512]; snprintf(header,sizeof(header), "%s — topological pax implementation\n\n" "Synopsis:\n" " %s [-d] [-f archive]\n" " %s -r [-d] [-f archive]\n" " %s -w [−x format] [-b blocksize] [-dtv] [-H|-L] [-f archive]\n" " %s -r -w [-d]\n\n" "Options:\n", program,program,program,program,program); argv_usage(fdout,header,optv,arg); argv_free(meta); return TPAX_USAGE; } static int tpax_driver_usage_exec_mode( int fdout, const char * program, const char * arg, const struct argv_option ** optv, struct argv_meta * meta) { tpax_driver_usage( fdout,program, arg,optv,meta); tpax_dprintf( fdout, "\n%s: usage error: When using explicit (long) mode options, " "only one of --list, --read, --write, --copy " "may be used.\n",program); return TPAX_USAGE; } static int tpax_driver_usage_copy_mode( int fdout, const char * program, const char * arg, const struct argv_option ** optv, struct argv_meta * meta, struct argv_entry * operand, struct argv_entry * archive) { const char * errdesc; if (archive || !operand) { tpax_driver_usage( fdout,program, arg,optv,meta); if (archive) { tpax_dprintf( fdout, "\n%s: usage error: the __copy__ mode does not permit specifying " "an archive path.\n\n",program); } else { tpax_dprintf( fdout, "\n%s: usage error: the __copy__ mode requires a destination " "directory argument.\n\n",program); } return TPAX_USAGE; } switch (errno) { case ENOENT: tpax_dprintf( fdout, "%s: file-system error: " "the destination directory '%s' " "does not exist.\n\n", program,operand->arg); break; case ENOTDIR: tpax_dprintf( fdout, "%s: file-system error: " "'%s' is not a directory.\n\n", program,operand->arg); break; default: if (!(errdesc = strerror(errno))) errdesc = ""; tpax_dprintf( fdout, "%s: general error: while opening the " "destination directory '%s': %s.\n\n", program,operand->arg,errdesc); break; } argv_free(meta); return TPAX_USAGE; } static int tpax_driver_usage_write_format( int fdout, const char * program, const char * arg, const struct argv_option ** optv, struct argv_meta * meta) { tpax_driver_usage( fdout,program, arg,optv,meta); tpax_dprintf( fdout, "\n%s: usage error: Archive format may only be specified " "in write mode.\n",program); return TPAX_USAGE; } static int tpax_driver_usage_block_size( int fdout, const char * program, const char * arg, struct argv_meta * meta) { tpax_dprintf( fdout, "%s: usage error: the requested block size `%s' is not a positive decimal integer.\n", program,arg); argv_free(meta); return TPAX_USAGE; } static int tpax_driver_usage_block_size_range( int fdout, const char * program, const char * arg, struct argv_meta * meta) { tpax_dprintf( fdout, "%s: usage error: `%s' is outside the specified range of 512 to 32256.\n", program,arg); argv_free(meta); return TPAX_USAGE; } static int tpax_driver_usage_block_constraints( int fdout, const char * program, const char * arg, struct argv_meta * meta) { tpax_dprintf( fdout, "%s: usage error: the specified block size `%s' is not a multiple of 512 bytes.\n", program,arg); argv_free(meta); return TPAX_USAGE; } static int tpax_driver_error_archive_path( int fdout, const char * program, const char * arg, struct argv_meta * meta, bool fwrite) { int lerrno; const char * errstr; lerrno = errno; errno = 0; errstr = strerror(lerrno); errstr = errno ? "" : errstr; errno = lerrno; if (fwrite) { tpax_dprintf( fdout, "%s: file-system error: archive file <%s> could not be created " "or opened for writing (%s).\n", program,arg,errstr); } else { tpax_dprintf( fdout, "%s: file-system error: archive file <%s> could not be opened " "for reading (%s).\n", program,arg,errstr); } argv_free(meta); return TPAX_FATAL; } static int tpax_driver_error_not_implemented( int fdout, const char * program, const char * feature, struct argv_meta * meta) { tpax_dprintf( fdout,"%s: error: %s is not yet implemented!\n\n", program,feature); argv_free(meta); return TPAX_FATAL; } static void tpax_set_archive_block_size(struct tpax_common_ctx * cctx) { if (cctx->blksize) (void)0; else if (cctx->drvflags & TPAX_DRIVER_WRITE_FORMAT_PAX) cctx->blksize = TPAX_PAX_BLOCK_SIZE; else if (cctx->drvflags & TPAX_DRIVER_WRITE_FORMAT_CPIO) cctx->blksize = TPAX_CPIO_BLOCK_SIZE; else if (cctx->drvflags & TPAX_DRIVER_WRITE_FORMAT_USTAR) cctx->blksize = TPAX_USTAR_BLOCK_SIZE; else if (cctx->drvflags & TPAX_DRIVER_WRITE_FORMAT_RUSTAR) cctx->blksize = TPAX_USTAR_BLOCK_SIZE; } static int tpax_driver_is_valid_keyval(struct argv_keyval * keyval) { (void)keyval; return 0; } static struct tpax_driver_ctx_impl * tpax_driver_ctx_alloc( struct argv_meta * meta, const struct tpax_fd_ctx * fdctx, const struct tpax_common_ctx * cctx, size_t nunits) { struct tpax_driver_ctx_alloc * ictx; size_t size; struct argv_entry * entry; const char ** units; int elements; int nkeyval; struct argv_keyval ** pkeyval; struct argv_keyval * keyval; size = sizeof(struct tpax_driver_ctx_alloc); size += (nunits+1)*sizeof(const char *); if (!(ictx = calloc(1,size))) return 0; memcpy(&ictx->ctx.fdctx,fdctx,sizeof(*fdctx)); memcpy(&ictx->ctx.cctx,cctx,sizeof(*cctx)); elements = sizeof(ictx->ctx.erribuf) / sizeof(*ictx->ctx.erribuf); ictx->ctx.errinfp = &ictx->ctx.erriptr[0]; ictx->ctx.erricap = &ictx->ctx.erriptr[--elements]; elements = sizeof(ictx->ctx.prefptr) / sizeof(*ictx->ctx.prefptr); ictx->ctx.prefixv = &ictx->ctx.prefptr[0]; ictx->ctx.prefixp = &ictx->ctx.prefptr[0]; ictx->ctx.prefcap = &ictx->ctx.prefptr[--elements]; ictx->meta = meta; for (entry=meta->entries,units=ictx->units; entry->fopt || entry->arg; entry++) if (!entry->fopt) *units++ = entry->arg; for (entry=meta->entries,nkeyval=0; entry->fopt || entry->arg; entry++) if (entry->keyv) for (keyval=entry->keyv; keyval->keyword; keyval++) nkeyval++; if (nkeyval && !(ictx->ctx.keyvalv = calloc(nkeyval+1,sizeof(*ictx->ctx.keyvalv)))) { free(ictx); return 0; } if ((pkeyval = ictx->ctx.keyvalv)) for (entry=meta->entries; entry->fopt || entry->arg; entry++) if (entry->keyv) for (keyval=entry->keyv; keyval->keyword; keyval++) *pkeyval++ = keyval; if (cctx->drvflags & TPAX_DRIVER_EXEC_MODE_WRITE_COPY) { ictx->ctx.bufsize = TPAX_FILEIO_BUFLEN; ictx->ctx.bufaddr = mmap( 0,ictx->ctx.bufsize, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1,0); if (ictx->ctx.bufaddr == MAP_FAILED) { free(ictx->ctx.keyvalv); free(ictx); return 0; } ictx->ctx.dirbuff = mmap( 0,TPAX_DIRENT_BUFLEN, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1,0); if (ictx->ctx.dirbuff == MAP_FAILED) { munmap(ictx->ctx.bufaddr,ictx->ctx.bufsize); free(ictx->ctx.keyvalv); free(ictx); return 0; } } tpax_set_archive_block_size(&ictx->ctx.cctx); ictx->ctx.ctx.units = ictx->units; ictx->ctx.ctx.errv = ictx->ctx.errinfp; return &ictx->ctx; } static int tpax_get_driver_ctx_fail(struct argv_meta * meta) { argv_free(meta); return -1; } static int tpax_driver_keyval_error( struct tpax_driver_ctx_impl * ctx, struct argv_keyval * keyval, const char * program) { const char * equal; switch (keyval->flags) { case ARGV_KEYVAL_ASSIGN: equal = "="; break; case ARGV_KEYVAL_OVERRIDE: equal = ":="; break; default: equal = ""; } tpax_dprintf( ctx->fdctx.fderr, "%s: unsupported keyval argument (%s%s%s)\n", program,keyval->keyword,equal, keyval->value ? keyval->value : ""); tpax_lib_free_driver_ctx(&ctx->ctx); return TPAX_ERROR; } int tpax_lib_get_driver_ctx( char ** argv, char ** envp, uint32_t flags, const struct tpax_fd_ctx * fdctx, struct tpax_driver_ctx ** pctx) { struct tpax_driver_ctx_impl * ctx; struct tpax_common_ctx cctx; const struct argv_option * optv[TPAX_OPTV_ELEMENTS]; struct argv_meta * meta; struct argv_entry * entry; struct argv_entry * archive; struct argv_entry * operand; struct argv_keyval ** pkeyval; struct tpax_fd_ctx lfdctx; size_t nunits; const char * program; int fddst; const char * ch; (void)envp; if (!fdctx) fdctx = &tpax_default_fdctx; argv_optv_init(tpax_default_options,optv); if (!(meta = argv_get( argv,optv, tpax_argv_flags(flags), fdctx->fderr))) return -1; nunits = 0; archive = 0; operand = 0; program = argv_program_name(argv[0]); memset(&cctx,0,sizeof(cctx)); cctx.drvflags = flags; fddst = fdctx->fddst; /* get options, count units */ for (entry=meta->entries; entry->fopt || entry->arg; entry++) { if (entry->fopt) { switch (entry->tag) { case TAG_HELP: if (flags & TPAX_DRIVER_VERBOSITY_USAGE) return tpax_driver_usage( fdctx->fdout, program,entry->arg, optv,meta); break; case TAG_VERSION: cctx.drvflags |= TPAX_DRIVER_VERSION; break; case TAG_VERBOSE: cctx.drvflags |= TPAX_DRIVER_VERBOSE; break; case TAG_LIST: cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_LIST; break; case TAG_READ: cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_READ; break; case TAG_WRITE: cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_WRITE; break; case TAG_COPY: cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_COPY; break; case TAG_FILE: archive = entry; break; case TAG_FORMAT: cctx.drvflags &= ~(uint64_t)(TPAX_DRIVER_WRITE_FORMAT_MASK); if (!strcmp(entry->arg,"pax")) cctx.drvflags |= TPAX_DRIVER_WRITE_FORMAT_PAX; else if (!strcmp(entry->arg,"cpio")) cctx.drvflags |= TPAX_DRIVER_WRITE_FORMAT_CPIO; else if (!strcmp(entry->arg,"ustar")) cctx.drvflags |= TPAX_DRIVER_WRITE_FORMAT_USTAR; else if (!strcmp(entry->arg,"rustar")) cctx.drvflags |= TPAX_DRIVER_WRITE_FORMAT_RUSTAR; break; case TAG_BLKSIZE: ch = (entry->arg[0] == '+') ? &entry->arg[1] : entry->arg; for (; *ch; ch++) if ((*ch < '0') || (*ch > '9')) return tpax_driver_usage_block_size( fdctx->fderr, program,entry->arg, meta); cctx.blksize = atoi(entry->arg); if ((cctx.blksize < 512) || (cctx.blksize > 32256)) return tpax_driver_usage_block_size_range( fdctx->fderr, program,entry->arg, meta); if (cctx.blksize % 512) return tpax_driver_usage_block_constraints( fdctx->fderr, program,entry->arg, meta); break; case TAG_RECURSE: cctx.drvflags |= TPAX_DRIVER_DIR_MEMBER_RECURSE; break; case TAG_NORECURSE: cctx.drvflags &= ~(uintptr_t)TPAX_DRIVER_DIR_MEMBER_RECURSE; break; case TAG_PRESERVE_ATIME: cctx.drvflags |= TPAX_DRIVER_PRESERVE_ATIME; break; case TAG_PAX_SYMLINK_ARGS: cctx.drvflags |= TPAX_DRIVER_PAX_SYMLINK_ARGS; cctx.drvflags &= ~(uint64_t)TPAX_DRIVER_PAX_SYMLINK_ITEMS; break; case TAG_PAX_SYMLINK_ITEMS: cctx.drvflags |= TPAX_DRIVER_PAX_SYMLINK_ARGS; cctx.drvflags |= TPAX_DRIVER_PAX_SYMLINK_ITEMS; break; case TAG_STRICT_DEVICE_ID: cctx.drvflags |= TPAX_DRIVER_STRICT_DEVICE_ID; break; case TAG_STRICT_PATH: cctx.drvflags |= TPAX_DRIVER_STRICT_PATH_INPUT; break; case TAG_PURE_PATH: cctx.drvflags |= TPAX_DRIVER_PURE_PATH_OUTPUT; break; } } else { operand = entry; nunits++; } } /* incompatible arguments? */ switch (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_MASK) { case 0: if (!(cctx.drvflags & TPAX_DRIVER_VERSION)) cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_LIST; break; case TPAX_DRIVER_EXEC_MODE_READ|TPAX_DRIVER_EXEC_MODE_WRITE: cctx.drvflags &= ~(uint64_t)(TPAX_DRIVER_EXEC_MODE_READ); cctx.drvflags &= ~(uint64_t)(TPAX_DRIVER_EXEC_MODE_WRITE); cctx.drvflags |= TPAX_DRIVER_EXEC_MODE_COPY; break; case TPAX_DRIVER_EXEC_MODE_LIST: case TPAX_DRIVER_EXEC_MODE_READ: case TPAX_DRIVER_EXEC_MODE_WRITE: case TPAX_DRIVER_EXEC_MODE_COPY: break; default: return tpax_driver_usage_exec_mode( fdctx->fderr, program,entry->arg, optv,meta); } /* copy mode: destination directory */ if (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_COPY) { if (archive || !operand) return tpax_driver_usage_copy_mode( fdctx->fderr, program,entry->arg, optv,meta,operand,archive); fddst = openat( fdctx->fdcwd, operand->arg, O_RDONLY|O_DIRECTORY|O_CLOEXEC,0); if (fddst < 0) return tpax_driver_usage_copy_mode( fdctx->fderr, program,entry->arg, optv,meta,operand,archive); } /* archive path */ if (archive && (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_WRITE)) { memcpy(&lfdctx,fdctx,sizeof(*fdctx)); lfdctx.fdout = openat( fdctx->fdcwd, archive->arg, O_WRONLY|O_CREAT|O_TRUNC, 0644); if (lfdctx.fdout < 0) return tpax_driver_error_archive_path( fdctx->fderr, program,archive->arg, meta,true); fdctx = &lfdctx; } else if (archive) { memcpy(&lfdctx,fdctx,sizeof(*fdctx)); lfdctx.fdin = openat( fdctx->fdcwd, archive->arg, O_RDONLY,0); if (lfdctx.fdin < 0) return tpax_driver_error_archive_path( fdctx->fderr, program,archive->arg, meta,false); fdctx = &lfdctx; } /* not implemented mode(s) */ switch (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_MASK) { case TPAX_DRIVER_EXEC_MODE_LIST: return tpax_driver_error_not_implemented( fdctx->fderr,program,"list mode",meta); case TPAX_DRIVER_EXEC_MODE_READ: return tpax_driver_error_not_implemented( fdctx->fderr,program,"read mode",meta); case TPAX_DRIVER_EXEC_MODE_COPY: close(fddst); return tpax_driver_error_not_implemented( fdctx->fderr,program,"copy mode",meta); default: break; } /* archive format vs. execution mode*/ switch (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_MASK) { case TPAX_DRIVER_EXEC_MODE_WRITE: if (!(cctx.drvflags & TPAX_DRIVER_WRITE_FORMAT_MASK)) cctx.drvflags |= TPAX_DRIVER_WRITE_FORMAT_USTAR; break; default: if (cctx.drvflags & TPAX_DRIVER_WRITE_FORMAT_MASK) return tpax_driver_usage_write_format( fdctx->fderr,program, entry->arg,optv,meta); } /* not implemented archive format(s) */ switch (cctx.drvflags & TPAX_DRIVER_WRITE_FORMAT_MASK) { case TPAX_DRIVER_WRITE_FORMAT_PAX: return tpax_driver_error_not_implemented( fdctx->fderr,program,"the pax format",meta); case TPAX_DRIVER_WRITE_FORMAT_CPIO: return tpax_driver_error_not_implemented( fdctx->fderr,program,"the cpio format",meta); default: break; } /* driver ctx */ if (!(ctx = tpax_driver_ctx_alloc(meta,fdctx,&cctx,nunits))) { if (cctx.drvflags & TPAX_DRIVER_EXEC_MODE_COPY) close(fddst); return tpax_get_driver_ctx_fail(meta); } /* keyval validation */ for (pkeyval=ctx->keyvalv; pkeyval && *pkeyval; pkeyval++) if (!tpax_driver_is_valid_keyval(*pkeyval)) return tpax_driver_keyval_error(ctx,*pkeyval,program); if (archive) { ctx->file = archive->arg; ctx->ctx.file = &ctx->file; } ctx->ctx.program = program; ctx->ctx.cctx = &ctx->cctx; *pctx = &ctx->ctx; return TPAX_OK; } static void tpax_free_driver_ctx_impl(struct tpax_driver_ctx_alloc * ictx) { void * next; size_t size; char ** ppref; for (; ictx->ctx.dirents; ) { next = ictx->ctx.dirents->next; size = ictx->ctx.dirents->size; munmap(ictx->ctx.dirents,size); ictx->ctx.dirents = (struct tpax_dirent_buffer *)next; } if (ictx->ctx.keyvalv) free(ictx->ctx.keyvalv); if (ictx->ctx.bufaddr) munmap(ictx->ctx.bufaddr,ictx->ctx.bufsize); if (ictx->ctx.dirbuff) munmap(ictx->ctx.dirbuff,TPAX_DIRENT_BUFLEN); for (ppref=ictx->ctx.prefixv; *ppref; ppref++) free(*ppref); if (ictx->ctx.prefixv != ictx->ctx.prefptr) free(ictx->ctx.prefixv); if (ictx->ctx.direntv) free(ictx->ctx.direntv); argv_free(ictx->meta); free(ictx); } void tpax_lib_free_driver_ctx(struct tpax_driver_ctx * ctx) { struct tpax_driver_ctx_alloc * ictx; uintptr_t addr; if (ctx) { addr = (uintptr_t)ctx - offsetof(struct tpax_driver_ctx_impl,ctx); addr = addr - offsetof(struct tpax_driver_ctx_alloc,ctx); ictx = (struct tpax_driver_ctx_alloc *)addr; tpax_free_driver_ctx_impl(ictx); } } const struct tpax_source_version * tpax_api_source_version(void) { return &tpax_src_version; } int tpax_lib_get_driver_fdctx( const struct tpax_driver_ctx * dctx, struct tpax_fd_ctx * fdctx) { struct tpax_driver_ctx_impl * ictx; ictx = tpax_get_driver_ictx(dctx); fdctx->fdin = ictx->fdctx.fdin; fdctx->fdout = ictx->fdctx.fdout; fdctx->fderr = ictx->fdctx.fderr; fdctx->fdlog = ictx->fdctx.fdlog; fdctx->fdcwd = ictx->fdctx.fdcwd; fdctx->fddst = ictx->fdctx.fddst; return 0; } int tpax_lib_set_driver_fdctx( struct tpax_driver_ctx * dctx, const struct tpax_fd_ctx * fdctx) { struct tpax_driver_ctx_impl * ictx; ictx = tpax_get_driver_ictx(dctx); ictx->fdctx.fdin = fdctx->fdin; ictx->fdctx.fdout = fdctx->fdout; ictx->fdctx.fderr = fdctx->fderr; ictx->fdctx.fdlog = fdctx->fdlog; ictx->fdctx.fdcwd = fdctx->fdcwd; ictx->fdctx.fddst = fdctx->fddst; return 0; }