123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- /* vim: ts=4 sw=4 noexpandtab
- * This file is part of ToaruOS and is released under the terms
- * of the NCSA / University of Illinois License - see LICENSE.md
- * Copyright (C) 2018 K. Lange
- *
- * tar - extract archives
- *
- * This is a very minimal and incomplete implementation of tar.
- * It supports on ustar-formatted archives, and its arguments
- * must by the - forms. As of writing, creating archives is not
- * supported. No compression formats are supported, either.
- */
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #include <getopt.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <toaru/hashmap.h>
- struct ustar {
- char filename[100];
- char mode[8];
- char ownerid[8];
- char groupid[8];
- char size[12];
- char mtime[12];
- char checksum[8];
- char type[1];
- char link[100];
- char ustar[6];
- char version[2];
- char owner[32];
- char group[32];
- char dev_major[8];
- char dev_minor[8];
- char prefix[155];
- };
- static struct ustar * file_from_offset(FILE * f, size_t offset) {
- static struct ustar _ustar;
- fseek(f, offset, SEEK_SET);
- if (fread(&_ustar, 1, sizeof(struct ustar), f) != sizeof(struct ustar)) {
- fprintf(stderr, "failed to read file\n");
- return NULL;
- }
- if (_ustar.ustar[0] != 'u' ||
- _ustar.ustar[1] != 's' ||
- _ustar.ustar[2] != 't' ||
- _ustar.ustar[3] != 'a' ||
- _ustar.ustar[4] != 'r') {
- return NULL;
- }
- return &_ustar;
- }
- static unsigned int round_to_512(unsigned int i) {
- unsigned int t = i % 512;
- if (!t) return i;
- return i + (512 - t);
- }
- static unsigned int interpret_mode(struct ustar * file) {
- return
- ((file->mode[0] - '0') << 18) |
- ((file->mode[1] - '0') << 15) |
- ((file->mode[2] - '0') << 12) |
- ((file->mode[3] - '0') << 9) |
- ((file->mode[4] - '0') << 6) |
- ((file->mode[5] - '0') << 3) |
- ((file->mode[6] - '0') << 0);
- }
- static unsigned int interpret_size(struct ustar * file) {
- if (file->size[0] != '0') {
- fprintf(stderr, "\033[3;32mWarning:\033[0;3m File is too big.\033[0m\n");
- }
- return
- ((file->size[ 0] - '0') << 30) |
- ((file->size[ 1] - '0') << 27) |
- ((file->size[ 2] - '0') << 24) |
- ((file->size[ 3] - '0') << 21) |
- ((file->size[ 4] - '0') << 18) |
- ((file->size[ 5] - '0') << 15) |
- ((file->size[ 6] - '0') << 12) |
- ((file->size[ 7] - '0') << 9) |
- ((file->size[ 8] - '0') << 6) |
- ((file->size[ 9] - '0') << 3) |
- ((file->size[10] - '0') << 0);
- }
- static const char * type_to_string(char type) {
- switch (type) {
- case '\0':
- case '0':
- return "Normal file";
- case '1':
- return "Hard link (unsupported)";
- case '2':
- return "Symolic link";
- case '3':
- return "Character special (unsupported)";
- case '4':
- return "Block special (unsupported)";
- case '5':
- return "Directory";
- case '6':
- return "FIFO (unsupported)";
- case 'g':
- return "Extended header";
- case 'x':
- return "Extended preheader";
- default:
- return "Unknown";
- }
- }
- #if 0
- static void dump_file(struct ustar * file) {
- fprintf(stdout, "\033[1m%.155s%.100s\033[0m\n", file->prefix, file->filename);
- fprintf(stdout, "%c - %s\n", file->type[0], type_to_string(file->type[0]));
- fprintf(stdout, "File size: %u\n", interpret_size(file));
- }
- #endif
- #define CHUNK_SIZE 4096
- static void write_file(struct ustar * file, FILE * f, FILE * mf, size_t off, char * name) {
- size_t length = interpret_size(file);
- fseek(f, off + 512, SEEK_SET);
- char buf[CHUNK_SIZE];
- while (length > CHUNK_SIZE) {
- fread( buf, 1, CHUNK_SIZE, f);
- fwrite(buf, 1, CHUNK_SIZE, mf);
- length -= CHUNK_SIZE;
- }
- if (length > 0) {
- fread( buf, 1, length, f);
- fwrite(buf, 1, length, mf);
- }
- fclose(mf);
- fseek(f, off, SEEK_SET);
- /* TODO: fchmod? */
- chmod(name, interpret_mode(file));
- }
- int main(int argc, char * argv[]) {
- int opt;
- char * fname = NULL;
- int verbose = 0;
- int action = 0;
- #define TAR_ACTION_EXTRACT 1
- #define TAR_ACTION_CREATE 2
- #define TAR_ACTION_LIST 3
- while ((opt = getopt(argc, argv, "ctxvaf:")) != -1) {
- switch (opt) {
- case 'c':
- if (action) {
- fprintf(stderr, "%s: %c: already specified action\n", argv[0], opt);
- return 1;
- }
- action = TAR_ACTION_CREATE;
- break;
- case 'f':
- fname = optarg;
- break;
- case 'x':
- if (action) {
- fprintf(stderr, "%s: %c: already specified action\n", argv[0], opt);
- return 1;
- }
- action = TAR_ACTION_EXTRACT;
- break;
- case 't':
- if (action) {
- fprintf(stderr, "%s: %c: already specified action\n", argv[0], opt);
- return 1;
- }
- action = TAR_ACTION_LIST;
- break;
- case 'v':
- verbose = 1;
- break;
- default:
- fprintf(stderr, "%s: unsupported option '%c'\n", argv[0], opt);
- return 1;
- }
- }
- if (!fname) {
- fprintf(stderr, "%s: todo: stdin/stdout\n", argv[0]);
- return 1;
- }
- if (action == TAR_ACTION_EXTRACT || action == TAR_ACTION_LIST) {
- hashmap_t * files = hashmap_create(10);
- FILE * f = fopen(fname,"r");
- if (!f) {
- fprintf(stderr, "%s: %s: %s\n", argv[0], fname, strerror(errno));
- return 1;
- }
- fseek(f, 0, SEEK_END);
- size_t length = ftell(f);
- fseek(f, 0, SEEK_SET);
- size_t off = 0;
- while (!feof(f)) {
- struct ustar * file = file_from_offset(f, off);
- if (!file) {
- break;
- }
- if (action == TAR_ACTION_LIST || verbose) {
- fprintf(stdout, "%.155s%.100s\n", file->prefix, file->filename);
- }
- if (action == TAR_ACTION_EXTRACT) {
- char name[256] = {0};
- strncat(name, file->prefix, 155);
- strncat(name, file->filename, 100);
- if (file->type[0] == '0' || file->type[0] == 0) {
- FILE * mf = fopen(name,"w");
- if (!mf) {
- fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno));
- } else {
- write_file(file,f,mf,off,name);
- }
- struct ustar * tmp = malloc(sizeof(struct ustar));
- memcpy(tmp, file, sizeof(struct ustar));
- hashmap_set(files, name, tmp);
- } else if (file->type[0] == '5') {
- if (name[strlen(name)-1] == '/') {
- name[strlen(name)-1] = '\0';
- }
- if (strlen(name)) {
- if (mkdir(name, 0777) < 0) {
- fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno));
- }
- }
- } else if (file->type[0] == '1') {
- char tmp[101] = {0};
- strncat(tmp, file->link, 100);
- if (!hashmap_has(files, tmp)) {
- fprintf(stderr, "%s: %s: %s: %s: missing target\n", argv[0], fname, name, tmp);
- } else {
- FILE * mf = fopen(name,"w");
- if (!mf) {
- fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno));
- } else {
- write_file(hashmap_get(files,tmp),f,mf,off,name);
- }
- }
- } else if (file->type[0] == '2') {
- char tmp[101] = {0};
- strncat(tmp, file->link, 100);
- if (symlink(tmp, name) < 0) {
- fprintf(stderr, "%s: %s: %s: %s: %s\n", argv[0], fname, name, tmp, strerror(errno));
- }
- } else {
- fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, type_to_string(file->type[0]));
- }
- }
- off += 512;
- off += round_to_512(interpret_size(file));
- if (off >= length) break;
- }
- } else {
- fprintf(stderr, "%s: unsupported action\n", argv[0]);
- return 1;
- }
- return 0;
- }
|