UNIXworkcode

/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2018 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "tar.h" #include <string.h> #include <cx/string.h> #include <libidav/utils.h> const char* tar_error2str(TarError error) { switch(error) { default: break; case TAR_OK: return "ok"; case TAR_PATH_TOO_LONG: return "path too long"; case TAR_FILE_TOO_LARGE: return "file too large"; case TAR_CONTENT_TOO_LARGE: return "tar content too large"; case TAR_UNFINISHED_FILE: return "can''t read a tar header at this position"; case TAR_CONTENT_BROKEN: return "tar content broken"; } return "error"; } 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 and check length char *p = util_parent_path(path); const char *n = util_resource_name(path); if(!p || !n) { return -1; } cxstring prefix = cx_str(p); cxstring name = cx_str(n); if(prefix.ptr[prefix.length-1] == '/') { prefix.length--; } if(prefix.length > 154) { tar->error = TAR_PATH_TOO_LONG; free(p); return -1; } if(name.length > 99) { tar->error = TAR_PATH_TOO_LONG; free(p); return -1; } // check file length if(size >= 077777777777 ) { tar->error = TAR_FILE_TOO_LARGE; free(p); return -1; } // set header fields TarHeader h; memset(&h, 0, sizeof(TarHeader)); // name memcpy(h.name, name.ptr, name.length); // mode snprintf(h.mode, 8, "%07o", mode); h.mode[7] = ' '; // uid/gid memset(h.uid, '0', 8); h.uid[7] = ' '; memset(h.gid, '0', 8); h.gid[7] = ' '; // size snprintf(h.size, 12, "%011lo", (unsigned long)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.ptr, prefix.length); // 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); free(p); 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) { 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[512]; memset(buf, 0, 512); fwrite(buf, 1, 512, tar->file); return 0; } TarInputStream* tar_inputstream_open(FILE *f) { TarInputStream *tar = malloc(sizeof(TarInputStream)); memset(tar, 0, sizeof(TarInputStream)); tar->file = f; return tar; } TarEntry* tar_read_entry(TarInputStream *tar) { if(tar->cur_read < tar->cur_entry.size) { tar->error = TAR_UNFINISHED_FILE; return NULL; } TarHeader h; memset(&h, 0, sizeof(TarHeader)); if(fread(&h, 1, 512, tar->file) != 512) { tar->error = TAR_ERROR; return NULL; } // some checks tar->error = TAR_CONTENT_BROKEN; // set error for all following returns char *buf = (char*)&h; uint64_t chksum = 0; int chksumfield = 8 * 32; for(int i=0;i<148;i++) { chksum += buf[i]; } chksum += chksumfield; for(int i=156;i<512;i++) { chksum += buf[i]; } if(chksum != chksumfield) { long long int cks = strtoll(h.chksum, NULL, 8); if(cks != chksum) { return NULL; } } if(memcmp(h.magic, "ustar\0", 6)) { if(!memcmp(h.magic, "\0\0\0\0", 4)) { tar->error = TAR_OK; } return NULL; } if(memcmp(h.version, "00", 2)) { return NULL; } // check if name and prefix are null terminated int nameterm = 0; int prefixterm = 0; for(int i=0;i<100;i++) { if(h.name[i] == 0) { nameterm = 1; break; } } for(int i=0;i<155;i++) { if(h.prefix[i] == 0) { prefixterm = 1; break; } } if(!nameterm || !prefixterm) { return NULL; } // get size if(!(h.size[11] == ' ' || h.size[11] == 0)) { return NULL; } long long int size = strtoll(h.size, NULL, 8); if(size < 0) { return NULL; } if(h.name[0] == 0) { return NULL; } // get path char *path = h.prefix[0] != 0 ? util_concat_path(h.prefix, h.name) : strdup(h.name); if(tar->cur_entry.path) { free(tar->cur_entry.path); } tar->cur_entry.path = path; tar->cur_entry.size = (uint64_t)size; tar->cur_entry.type = h.typeflag; tar->cur_read = 0; tar->error = TAR_OK; return &tar->cur_entry; } size_t tar_fread(void *ptr, size_t s, size_t n, TarInputStream *stream) { size_t bufsize = s*n; size_t available = stream->cur_entry.size - stream->cur_read; size_t nb = available > bufsize ? bufsize : available; if(nb == 0) { return 0; } size_t r = fread(ptr, 1, nb, stream->file); if(r != nb) { stream->error = TAR_ERROR; return 0; } stream->cur_read += r; if(stream->cur_read >= stream->cur_entry.size) { // read padding size_t pad = 512 - stream->cur_read % 512; char buf[512]; if(fread(buf, 1, pad, stream->file) != pad) { stream->error = TAR_ERROR; return 0; } } return r; } int tar_seek(TarInputStream *stream, long offset, int whence) { return fseek(stream->file, offset, whence); } int tar_inputstream_close(TarInputStream *tar) { if(tar->cur_entry.path) { free(tar->cur_entry.path); } free(tar); return 0; }