--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dav/tar.c Sat Oct 28 15:25:17 2017 +0200 @@ -0,0 +1,319 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 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 <ucx/string.h> +#include <libidav/utils.h> + + +const char* tar_error2str(TarError error) { + switch(error) { + 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); + char *n = util_resource_name(path); + if(!p || !n) { + return -1; + } + + sstr_t prefix = sstr(p); + sstr_t name = sstr(n); + + if(prefix.ptr[prefix.length-1] == '/') { + prefix.length--; + } + + if(prefix.length > 154) { + tar->error = TAR_PATH_TOO_LONG; + return -1; + } + if(name.length > 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.ptr, name.length); + // mode + snprintf(h.mode, 8, "%0.7o", mode); + h.mode[7] = ' '; + // uid/gid + memset(h.uid, '0', 16); + h.uid[7] = ' '; + h.gid[7] = ' '; + // size + snprintf(h.size, 12, "%0.11lo", size); + h.size[11] = ' '; + // mtime + uint64_t t = (uint64_t)mtime; + snprintf(h.mtime, 12, "%0.11lo", 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 + snprintf(h.devmajor, 16, "%0.15o", 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, "%0.7o", 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) { + 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)) { + 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] != ' ') { + 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_inputstream_close(TarInputStream *tar) { + if(tar->cur_entry.path) { + free(tar->cur_entry.path); + } + free(tar); +}