dav/tar.c

changeset 334
5f80c5d0e87f
child 336
6331271116d0
--- /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);
+}

mercurial