/* $Id$ */ /* Copyright (c) 2006-2014 Pierre Pronchery */ /* This file is part of DeforaOS Unix others */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* TODO: * - libutils in UNIX * - reuse code from mkdir -p and ls -l * - share code with ar etc (packing framework) * - implement ustar format */ #include #include #include #include #include #include #include #include "tar.h" #ifndef PROGNAME # define PROGNAME "tar" #endif #ifndef min # define min(a, b) ((a) < (b) ? (a) : (b)) #endif /* tar */ /* private */ /* types */ typedef unsigned int Prefs; #define PREFS_A 0x01 #define PREFS_c 0x02 #define PREFS_t 0x04 #define PREFS_v 0x08 #define PREFS_vv 0x18 #define PREFS_x 0x20 /* constants */ #define TAR_BLKSIZ 512 /* prototypes */ static int _tar(Prefs * prefs, char const * archive, int filec, char * filev[]); static int _tar_error(char const * message, int ret); static int _tar_usage(void); /* functions */ /* tar */ static int _tar_create(Prefs * prefs, char const * archive, int filec, char * filev[]); static int _tar_extract(Prefs * prefs, char const * archive, int filec, char * filev[]); static int _tar_list(Prefs * prefs, char const * archive, int filec, char * filev[]); static int _tar(Prefs * prefs, char const * archive, int filec, char * filev[]) { if(*prefs & PREFS_c) return _tar_create(prefs, archive, filec, filev); if(*prefs & PREFS_t) return _tar_list(prefs, archive, filec, filev); if(*prefs & PREFS_x) return _tar_extract(prefs, archive, filec, filev); return 1; } #define _from_buffer_cpy(a) tfhb->a[sizeof(tfhb->a)-1] = '\0'; \ tfh->a = strtol(tfhb->a, &p, 8); \ if(*tfhb->a == '\0' || *p != '\0') return 1; static int _tar_from_buffer(TarFileHeaderBuffer * tfhb, TarFileHeader * tfh) { char * p; size_t len; _from_buffer_cpy(mode); _from_buffer_cpy(uid); _from_buffer_cpy(gid); _from_buffer_cpy(size); /* FIXME data type too short? */ _from_buffer_cpy(mtime); for(p = tfhb->filename; *p == '/'; p++); /* FIXME ".." directory traversal */ memcpy(&tfh->filename, p, tfhb->filename + sizeof(tfhb->filename) - p); tfh->filename[sizeof(tfh->filename) - 1] = '\0'; len = strlen(tfh->filename); if(tfh->filename[len] == '/') tfh->type = FT_DIRECTORY; else tfh->type = tfhb->type; memcpy(&tfh->link, tfhb->link, sizeof(tfhb->link)); tfh->link[sizeof(tfh->link) - 1] = '\0'; return 0; } static int _tar_mkdir_parent(char * filename) { char * p; struct stat st; if(filename[0] == '\0') return 0; for(p = &filename[1]; *p != '\0'; p++) { if(*p != '/') continue; *p = '\0'; if(!(stat(filename, &st) == 0 && S_ISDIR(st.st_mode)) && mkdir(filename, 0777) == -1) { *p = '/'; return _tar_error(filename, 1); } for(*p++ = '/'; *p == '/'; p++); if(*p == '\0') return 0; } return 0; } static void _tar_print(Prefs * prefs, TarFileHeader * fh) { if((*prefs & PREFS_vv) == PREFS_vv) /* FIXME */ fprintf(stderr, "%s %u %u %s\n", "----------", (unsigned)fh->uid, (unsigned)fh->gid, fh->filename); else if(*prefs & PREFS_v) fprintf(stderr, "%s\n", fh->filename); } static int _tar_seek(FILE * fp, char const * archive, size_t count) { char buf[TAR_BLKSIZ]; size_t step; if(fp == stdin) for(; count != 0; count -= step) { if((step = count % TAR_BLKSIZ) == 0) step = TAR_BLKSIZ; if(fread(buf, sizeof(*buf), step, fp) != step) return _tar_error(archive, 1); } else if(fseek(fp, count, SEEK_CUR) != 0) return _tar_error(archive, 1); return 0; } static int _tar_skip(FILE * fp, char const * archive, TarFileHeader * fh) { size_t count = fh->size % TAR_BLKSIZ; if(count != 0) count = TAR_BLKSIZ - count; count+=fh->size; return _tar_seek(fp, archive, count); } static void _tar_stat_to_buffer(char const * filename, struct stat * st, TarFileHeaderBuffer * tfhb) { uint8_t * p; size_t i; int checksum = 0; memset(tfhb, 0, sizeof(*tfhb)); snprintf(tfhb->filename, sizeof(tfhb->filename), "%s", filename); snprintf(tfhb->mode, sizeof(tfhb->mode), "%07o", (unsigned)st->st_mode); snprintf(tfhb->uid, sizeof(tfhb->uid), "%07o", (unsigned)st->st_uid); snprintf(tfhb->gid, sizeof(tfhb->gid), "%07o", (unsigned)st->st_gid); snprintf(tfhb->size, sizeof(tfhb->size), "%011o", (unsigned)st->st_size); snprintf(tfhb->mtime, sizeof(tfhb->mtime), "%011o", (unsigned)st->st_mtime); memset(&tfhb->checksum, ' ', sizeof(tfhb->checksum)); if(S_ISDIR(st->st_mode)) tfhb->type = FT_DIRECTORY; else if(S_ISCHR(st->st_mode)) tfhb->type = FT_CHAR; else if(S_ISBLK(st->st_mode)) tfhb->type = FT_BLOCK; /* FIXME link */ p = (uint8_t *)tfhb; for(i = 0; i < sizeof(*tfhb); i++) checksum += p[i]; snprintf(tfhb->checksum, sizeof(tfhb->checksum), "%06o%c ", checksum, '\0'); } static int _create_do(Prefs * prefs, FILE * fp, char const * archive, char const * filename); static int _tar_create(Prefs * prefs, char const * archive, int filec, char * filev[]) { FILE * fp = stdout; int i; if(archive != NULL && (fp = fopen(archive, "w")) == NULL) return _tar_error(archive, 1); for(i = 0; i < filec; i++) if(_create_do(prefs, fp, archive, filev[i]) != 0) break; if(i != filec) { fclose(fp); return 1; } for(i = 0; i < TAR_BLKSIZ * 2 && fputc('\0', fp) == '\0'; i++); if(archive != NULL) fclose(fp); return (i == TAR_BLKSIZ * 2) ? 0 : 1; } static int _doc_header(Prefs * prefs, FILE * fp, char const * archive, FILE * fp2, char const * filename, TarFileHeaderBuffer * tfhb); static int _doc_normal(FILE * fp, char const * archive, FILE * fp2, char const * filename); static int _create_do(Prefs * prefs, FILE * fp, char const * archive, char const * filename) { FILE * fp2; TarFileHeaderBuffer tfhb; int ret; if((fp2 = fopen(filename, "r")) == NULL) return 1; if(_doc_header(prefs, fp, archive, fp2, filename, &tfhb) != 0) { fclose(fp2); return 1; } switch(tfhb.type) { case FT_NORMAL: case FT_CONTIGUOUS: ret = _doc_normal(fp, archive, fp2, filename); break; case FT_HARDLINK: case FT_SYMLINK: case FT_CHAR: case FT_BLOCK: case FT_DIRECTORY: /* FIXME recurse */ case FT_FIFO: ret = 0; break; default: ret = 1; } fclose(fp2); return ret; } static int _doc_header(Prefs * prefs, FILE * fp, char const * archive, FILE * fp2, char const * filename, TarFileHeaderBuffer * tfhb) { TarFileHeader tfh; struct stat st; int i; if(fstat(fileno(fp2), &st) != 0) return _tar_error(filename, 1); _tar_stat_to_buffer(filename, &st, tfhb); _tar_from_buffer(tfhb, &tfh); _tar_print(prefs, &tfh); if(fwrite(tfhb, sizeof(*tfhb), 1, fp) != 1) return _tar_error(archive, 1); for(i = sizeof(*tfhb); i < TAR_BLKSIZ && fputc('\0', fp) == '\0'; i++); if(i != TAR_BLKSIZ) return _tar_error(archive, 1); return 0; } static int _doc_normal(FILE * fp, char const * archive, FILE * fp2, char const * filename) { int ret = 0; size_t read; size_t cnt; char buf[BUFSIZ]; for(cnt = 0; (read = fread(buf, sizeof(*buf), sizeof(buf), fp2)) != 0; cnt += read) if(fwrite(buf, sizeof(*buf), read, fp) != read) { ret = _tar_error(archive, 1); break; } if(ret == 0 && read == 0 && !feof(fp2)) return _tar_error(filename, 1); for(cnt = TAR_BLKSIZ - (cnt % TAR_BLKSIZ); cnt > 0 && fputc('\0', fp) == '\0'; cnt--); return (cnt == 0) ? 0 : _tar_error(archive, 1); } static int _extract_do(Prefs * prefs, FILE * fp, char const * archive, TarFileHeader * fh, int filec, char * filev[]); static int _tar_extract(Prefs * prefs, char const * archive, int filec, char * filev[]) { FILE * fp = stdin; TarFileHeaderBuffer fhdrb; TarFileHeader fhdr; size_t size; int ret = 0; if(archive != NULL && (fp = fopen(archive, "r")) == NULL) return _tar_error(archive, 1); while((size = fread(&fhdrb, sizeof(fhdrb), 1, fp)) == 1) { if(_tar_seek(fp, archive, TAR_BLKSIZ - sizeof(fhdrb)) != 0) { ret = 1; break; } if(_tar_from_buffer(&fhdrb, &fhdr) != 0 || _extract_do(prefs, fp, archive, &fhdr, filec, filev) != 0) ret = 1; } if(ret == 0 && size == 0 && !feof(fp)) ret = _tar_error(archive, 1); if(archive != NULL) fclose(fp); return ret; } static int _dox_normal(FILE * fp, char const * archive, TarFileHeader * fh); static int _dox_hardlink(TarFileHeader * fh); static int _dox_symlink(TarFileHeader * fh); static int _dox_char(FILE * fp, char const * archive, TarFileHeader * fh); static int _dox_block(FILE * fp, char const * archive, TarFileHeader * fh); static int _dox_directory(TarFileHeader * fh); static int _dox_fifo(TarFileHeader * fh); static int _extract_do(Prefs * prefs, FILE * fp, char const * archive, TarFileHeader * fh, int filec, char * filev[]) { int i; for(i = 0; i < filec; i++) if(strcmp(fh->filename, filev[i]) == 0) break; if(filec != 0 && i == filec) return _tar_skip(fp, archive, fh); _tar_print(prefs, fh); if(_tar_mkdir_parent(fh->filename) != 0) return _tar_skip(fp, archive, fh); switch((int)fh->type) { case FT_NORMAL: case FT_CONTIGUOUS: case '0': /* FIXME GNU compatibility */ return _dox_normal(fp, archive, fh); case FT_HARDLINK: return _dox_hardlink(fh); case FT_SYMLINK: return _dox_symlink(fh); case FT_CHAR: return _dox_char(fp, archive, fh); case FT_BLOCK: return _dox_block(fp, archive, fh); case FT_DIRECTORY: return _dox_directory(fh); case FT_FIFO: return _dox_fifo(fh); default: return _tar_skip(fp, archive, fh); } } static int _dox_normal(FILE * fp, char const * archive, TarFileHeader * fh) { FILE * fp2; size_t cnt; size_t read; char buf[BUFSIZ]; if((fp2 = fopen(fh->filename, "w")) == NULL) return _tar_error(fh->filename, 1); for(cnt = 0; cnt < fh->size; cnt += BUFSIZ) { read = fread(buf, sizeof(*buf), min(BUFSIZ, fh->size - cnt), fp); if(read == 0) return _tar_error(archive, 1); if(fwrite(buf, sizeof(*buf), read, fp2) != read) { fclose(fp2); return _tar_error(fh->filename, 1); } } fclose(fp2); if((cnt = fh->size % TAR_BLKSIZ) != 0 && _tar_seek(fp, archive, TAR_BLKSIZ - cnt) != 0) return 1; return 0; } static int _dox_hardlink(TarFileHeader * fh) { if(link(fh->link, fh->filename) != 0) return _tar_error(fh->filename, 1); return 0; } static int _dox_symlink(TarFileHeader * fh) { if(symlink(fh->link, fh->filename) != 0) return _tar_error(fh->filename, 1); return 0; } static int _dox_char(FILE * fp, char const * archive, TarFileHeader * fh) { /* FIXME */ return 1; } static int _dox_block(FILE * fp, char const * archive, TarFileHeader * fh) { /* FIXME */ return 1; } static int _dox_directory(TarFileHeader * fh) { if(mkdir(fh->filename, fh->mode) != 0) return _tar_error(fh->filename, 1); return 0; } static int _dox_fifo(TarFileHeader * fh) { if(mkfifo(fh->filename, fh->mode) != 0) return _tar_error(fh->filename, 1); return 0; } static int _list_do(Prefs * prefs, FILE * fp, char const * archive, TarFileHeader * fh, int filec, char * filev[]); static int _tar_list(Prefs * prefs, char const * archive, int filec, char * filev[]) { FILE * fp = stdin; TarFileHeaderBuffer fhdrb; TarFileHeader fhdr; size_t size; int ret = 0; if(archive != NULL && (fp = fopen(archive, "r")) == NULL) return _tar_error(archive, 1); while((size = fread(&fhdrb, sizeof(fhdrb), 1, fp)) == 1) { if(_tar_seek(fp, archive, TAR_BLKSIZ - sizeof(fhdrb)) != 0) { ret = 1; break; } if(_tar_from_buffer(&fhdrb, &fhdr) != 0 || _list_do(prefs, fp, archive, &fhdr, filec, filev) != 0) ret = 1; } if(ret == 0 && size == 0 && !feof(fp)) ret = _tar_error(archive, 1); if(archive != NULL) fclose(fp); return ret; } static int _list_do(Prefs * prefs, FILE * fp, char const * archive, TarFileHeader * fh, int filec, char * filev[]) { int i; for(i = 0; i < filec; i++) if(strcmp(fh->filename, filev[i]) == 0) break; if(filec == 0 || i != filec) _tar_print(prefs, fh); return _tar_skip(fp, archive, fh); } /* tar_error */ static int _tar_error(char const * message, int ret) { fputs(PROGNAME ": ", stderr); perror(message); return ret; } /* tar_usage */ static int _tar_usage(void) { fputs("Usage: " PROGNAME " -ctvx [-f archive][file...]\n\ -c Create an archive\n\ -f Specify an archive to work with (default: stdin or stdout)\n\ -t List the contents of an archive\n\ -v Verbose mode\n\ -x Extract from archive\n", stderr); return 1; } /* public */ /* functions */ /* main */ int main(int argc, char * argv[]) { int o; Prefs prefs = 0; char const * archive = NULL; while((o = getopt(argc, argv, "cvtxf:")) != -1) switch(o) { case 'c': prefs -= prefs & PREFS_t; prefs -= prefs & PREFS_x; prefs |= PREFS_c; break; case 'f': archive = optarg; break; case 't': prefs -= prefs & PREFS_c; prefs -= prefs & PREFS_x; prefs |= PREFS_t; break; case 'v': prefs |= (prefs & PREFS_v) == PREFS_v ? PREFS_vv : PREFS_v; break; case 'x': prefs -= prefs & PREFS_c; prefs -= prefs & PREFS_t; prefs |= PREFS_x; break; default: return _tar_usage(); } if(prefs == 0) return _tar_usage(); return (_tar(&prefs, archive, argc - optind, &argv[optind]) == 0) ? 0 : 2; }