/**************************************************************/ /* tpax: a topological pax implementation */ /* Copyright (C) 2020--2021 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]\n" " %s -r [-d]\n" " %s -w [−x format] [-b blocksize] [-d]\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, "\nWhen using explicit (long) mode options, " "only one of --list, --read, --write, --copy " "may be used.\n"); 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) { const char * errdesc; if (!operand) { tpax_driver_usage( fdout,program, arg,optv,meta); tpax_dprintf( fdout, "\nThe copy mode requires a destination " "directory argument.\n\n"); return TPAX_USAGE; } switch (errno) { case ENOENT: tpax_dprintf( fdout, "%s: error: " "the destination directory '%s' " "does not exist.\n\n", program,operand->arg); break; case ENOTDIR: tpax_dprintf( fdout, "%s: error: " "'%s' is not a directory.\n\n", program,operand->arg); break; default: if (!(errdesc = strerror(errno))) errdesc = ""; tpax_dprintf( fdout, "%s: 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, "\nArchive format may only be specified " "in write mode.\n"); return TPAX_USAGE; } static int tpax_driver_usage_block_size( 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, "`%s' is not a valid positive decimal integer.\n", arg); return TPAX_USAGE; } static int tpax_driver_usage_block_size_range( 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, "`%s' is outside the specified range of 512 to 32256.\n", arg); return TPAX_USAGE; } 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 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; 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]; ictx->meta = meta; for (entry=meta->entries,units=ictx->units; entry->fopt || entry->arg; entry++) if (!entry->fopt) *units++ = entry->arg; 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); return 0; } if (cctx->drvflags & TPAX_DRIVER_DIR_MEMBER_RECURSE) 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); return 0; } } 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; } int tpax_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 * operand; 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; 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_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_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->fdout, program,entry->arg, optv,meta); cctx.blksize = atoi(entry->arg); if ((cctx.blksize < 512) || (cctx.blksize > 32256)) return tpax_driver_usage_block_size_range( fdctx->fdout, program,entry->arg, optv,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_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 (!operand) return tpax_driver_usage_copy_mode( fdctx->fderr, program,entry->arg, optv,meta,operand); 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); } /* 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); } 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; 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.bufaddr) munmap(ictx->ctx.bufaddr,ictx->ctx.bufsize); if (ictx->ctx.dirbuff) munmap(ictx->ctx.dirbuff,TPAX_DIRENT_BUFLEN); argv_free(ictx->meta); free(ictx); } void tpax_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_source_version(void) { return &tpax_src_version; } int tpax_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_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; }