UNIXworkcode

/* * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE * Version 2, December 2004 * * Copyright (C) 2017 Olaf Wintermann <olaf.wintermann@gmail.com> * * Everyone is permitted to copy and distribute verbatim or modified * copies of this license document, and changing it is allowed as long * as the name is changed. * * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION * * 0. You just DO WHAT THE FUCK YOU WANT TO. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <inttypes.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #define TAR_TYPE_FILE '0' #define TAR_TYPE_DIRECTORY '5' enum TarError { TAR_OK = 0, TAR_PATH_TOO_LONG, TAR_FILE_TOO_LARGE, TAR_CONTENT_TOO_LARGE, TAR_UNFINISHED_FILE, TAR_CONTENT_BROKEN, TAR_ERROR }; typedef enum TarError TarError; typedef struct TarHeader { char name[100]; char mode[8]; char uid[8]; char gid[8]; char size[12]; char mtime[12]; char chksum[8]; char typeflag; char linkname[100]; char magic[6]; char version[2]; char uname[32]; char gname[32]; char devmajor[8]; char devminor[8]; char prefix[155]; char padding[12]; } TarHeader; typedef struct TarOutputStream { FILE *file; uint64_t cur_filesize; uint64_t cur_written; TarError error; } TarOutputStream; typedef struct TarEntry { char *path; uint64_t size; int type; } TarEntry; typedef struct TarInputStream { FILE *file; TarEntry cur_entry; uint64_t cur_read; TarError error; } TarInputStream; TarOutputStream* tar_open(FILE *f) { TarOutputStream *tar = malloc(sizeof(TarOutputStream)); tar->file= f; tar->cur_filesize = 0; tar->cur_written = 0; tar->error = 0; return tar; } static int add_header(TarOutputStream *tar, char *path, uint32_t mode, uint64_t size, time_t mtime, int type) { // split path in prefix and name char *prefix = path; size_t prefixlen = 0; char *name = path; size_t namelen = 0; int pathsep = 0; char *p = path; char c; size_t i = 0; while((c = *p) != '\0') { if(c == '/') { pathsep = 1; } else { if(pathsep) { name = p; prefixlen = i; } pathsep = 0; } i++; p++; } namelen = i - prefixlen; // check prefix/name length if(prefixlen > 154) { tar->error = TAR_PATH_TOO_LONG; return -1; } if(namelen > 99) { tar->error = TAR_PATH_TOO_LONG; return -1; } // check file length if(size >= 077777777777 ) { tar->error = TAR_FILE_TOO_LARGE; return -1; } // set header fields TarHeader h; memset(&h, 0, sizeof(TarHeader)); // name memcpy(h.name, name, namelen); // mode snprintf(h.mode, 8, "%07o", mode); h.mode[7] = ' '; // uid/gid memset(h.uid, '0', 16); h.uid[7] = ' '; h.gid[7] = ' '; // size snprintf(h.size, 12, "%011lo", size); h.size[11] = ' '; // mtime uint64_t t = (uint64_t)mtime; snprintf(h.mtime, 12, "%011lo", mtime); h.mtime[11] = ' '; // chksum memset(h.chksum, ' ', 8); // typeflag h.typeflag = type; // linkname - zeros // magic snprintf(h.magic, 6, "ustar"); // version h.version[0] = '0'; h.version[1] = '0'; // uname/gname - zero // devmajor/devminor char *devbuf = (char*)h.devmajor; snprintf(devbuf, 16, "%015o", 0); h.devmajor[7] = ' '; h.devminor[7] = ' '; // prefix memcpy(h.prefix, prefix, prefixlen); // compute checksum uint8_t *header = (uint8_t*)&h; uint32_t chksum = 0; for(int i=0;i<512;i++) { chksum += header[i]; } snprintf(h.chksum, 8, "%07o", chksum); fwrite(&h, 1, 512, tar->file); return 0; } int tar_add_dir(TarOutputStream *tar, char *path, uint32_t mode, time_t mtime) { return add_header(tar, path, mode, 0, mtime, TAR_TYPE_DIRECTORY); } int tar_begin_file( TarOutputStream *tar, char *path, uint32_t mode, uint64_t size, time_t mtime) { if(add_header(tar, path, mode, size, mtime, TAR_TYPE_FILE)) { return -1; } tar->cur_filesize = size; tar->cur_written = 0; return 0; } size_t tar_fwrite(const void *ptr, size_t s, size_t n, TarOutputStream *stream) { size_t w = fwrite(ptr, s, n, stream->file); stream->cur_written += w; if(stream->cur_written > stream->cur_filesize) { stream->error = TAR_CONTENT_TOO_LARGE; return 0; } return w; } int tar_end_file(TarOutputStream *tar) { // the file content must have an alignment of 512 bytes // this functions writes some padding bytes to the stream size_t pad = 512 - tar->cur_written % 512; char buf[512]; memset(buf, 0, 512); fwrite(buf, 1, pad, tar->file); tar->cur_filesize = 0; tar->cur_written = 0; return 0; } int tar_close(TarOutputStream *tar) { char buf[1024]; memset(buf, 0, 1024); fwrite(buf, 1, 1024, tar->file); return 0; } #define IO_BUF_SIZE 0x4000 int main(int argc, char **argv) { if(argc <= 1) { fprintf(stderr, "Usage: %s <file> ...\n", argv[0]); } TarOutputStream *tar = tar_open(stdout); for(int i=1;i<argc;i++) { char *file = argv[i]; struct stat s; if(stat(file, &s)) { error("stat"); continue; } // we wan't just the permission part uint32_t tmode = s.st_mode & 07777; // add directories or regular files to the tape archive if(S_ISDIR(s.st_mode)) { fprintf(stderr, "add: %s\n", file); // this adds just a header if(tar_add_dir(tar, file, tmode, s.st_mtime)) { fprintf(stderr, "tar_add_dir failed: %d\n", tar->error); } } else if(S_ISREG(s.st_mode)) { fprintf(stderr, "add: %s\n", file); FILE *f = fopen(file, "r"); if(!f) { perror("open"); continue; } // add tar header for this file if(tar_begin_file(tar, file, tmode, s.st_size, s.st_mtime)) { fprintf(stderr, "tar_begin_file failed: %d\n", tar->error); } else { // header successfully written // now we can copy the file content char buf[IO_BUF_SIZE]; size_t r; while((r = fread(buf, 1, IO_BUF_SIZE, f)) > 0) { if(tar_fwrite(buf, 1, r, tar) == 0) { fprintf(stderr, "tar_fwrite failed\n"); break; } } // this writes padding some padding bytes tar_end_file(tar); } fclose(f); } } // write last 512 bytes block with just zeros tar_close(tar); return 0; }