dav/tar.c

Mon, 30 Oct 2017 16:29:07 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 30 Oct 2017 16:29:07 +0100
changeset 336
6331271116d0
parent 334
5f80c5d0e87f
child 344
72791e299d64
permissions
-rw-r--r--

some small fixes in tar.c

/*
 * 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) {
        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);
    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);
    return 0;
}

mercurial