//
// tardy - a tar post-processor
// Copyright (C) 1998, 1999, 2001, 2002, 2008, 2009, 2011 Peter Miller
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

#include <libtardy/ac/assert.h>
#include <libtardy/ac/errno.h>
#include <libtardy/ac/string.h>

#include <libtardy/tar/format.h>


static long
octal(const char *buf, long len)
{
    errno = EINVAL;
    long value = 0;
    while (len > 0 && *buf == ' ')
    {
        ++buf;
        --len;
    }
    if (len <= 0)
        return -1;
    while (len > 0)
    {
        if (!*buf || *buf == ' ')
            break;
        if (*buf < '0' || *buf > '7')
            return -1;
        //
        // Limit the range to 0..2^31-1.
        // Must test for overflow *before* the shift.
        //
        if (value & 0xF0000000)
        {
            errno = ERANGE;
            return -1;
        }

        value = (value << 3) + (*buf++ & 7);
        --len;
    }
    errno = 0;
    return value;
}


static void
padded_octal(char pad, char *buf, long len, long n)
{
    assert(len >= 2);
    --len;
    buf[len] = 0;
    memset(buf, pad, len);
    while (len > 0)
    {
        buf[--len] = '0' + (n & 7);
        n >>= 3;
        if (!n)
            break;
    }
}


static void
space_padded_octal(char *buf, long len, long n)
{
    padded_octal(' ', buf, len, n);
}


static void
zero_padded_octal(char *buf, long len, long n)
{
    padded_octal('0', buf, len, n);
}


static size_t
careful_strlen(const char *s, size_t n)
{
    const char *ss = s;
    while (n > 0 && *s)
    {
        ++s;
        --n;
    }
    return (s - ss);
}


static void
string_field_set(char *to_buf, int to_len, const rcstring &from)
{
    int from_len = from.size();
    const char *from_buf = from.c_str();
    if (from_len > to_len - 1)
        from_len = to_len - 1;
    if (from_len)
        memcpy(to_buf, from_buf, from_len);
    if (from_len < to_len)
        memset(to_buf + from_len, 0, to_len - from_len);
}


rcstring
header_ty::name_get(void)
    const
{
    return rcstring(name, careful_strlen(name, sizeof(name)));
}


void
header_ty::name_set(const rcstring &arg)
{
    string_field_set(name, sizeof(name), arg);
}


long
header_ty::mode_get(void)
    const
{
    return (07777 & octal(mode, sizeof(mode)));
}


void
header_ty::mode_set(long n)
{
    space_padded_octal(mode, sizeof(mode), n);
}


void
header_ty::mode_set_z(long n)
{
    zero_padded_octal(mode, sizeof(mode), n);
}


long
header_ty::uid_get(void)
    const
{
    return octal(uid, sizeof(uid));
}


void
header_ty::uid_set(long n)
{
    space_padded_octal(uid, sizeof(uid), n);
}


void
header_ty::uid_set_z(long n)
{
    zero_padded_octal(uid, sizeof(uid), n);
}


long
header_ty::gid_get(void)
    const
{
    return octal(gid, sizeof(gid));
}


void
header_ty::gid_set(long n)
{
    space_padded_octal(gid, sizeof(gid), n);
}


void
header_ty::gid_set_z(long n)
{
    zero_padded_octal(gid, sizeof(gid), n);
}


long
header_ty::size_get(void)
    const
{
    return octal(size, sizeof(size));
}


void
header_ty::size_set(long n)
{
    space_padded_octal(size, sizeof(size), n);
}


void
header_ty::size_set_z(long n)
{
    zero_padded_octal(size, sizeof(size), n);
}


long
header_ty::mtime_get(void)
    const
{
    return octal(mtime, sizeof(mtime));
}


void
header_ty::mtime_set(long n)
{
    space_padded_octal(mtime, sizeof(mtime), n);
}


void
header_ty::mtime_set_z(long n)
{
    zero_padded_octal(mtime, sizeof(mtime), n);
}


long
header_ty::chksum_get(void)
    const
{
    return octal(chksum, sizeof(chksum));
}


void
header_ty::chksum_set(long n)
{
    space_padded_octal(chksum, sizeof(chksum), n);
}


void
header_ty::chksum_set_z(long n)
{
    zero_padded_octal(chksum, sizeof(chksum), n);
}


int
header_ty::linkflag_get(void)
    const
{
    return (unsigned char)linkflag;
}


void
header_ty::linkflag_set(int n)
{
    linkflag = n;
}


rcstring
header_ty::linkname_get(void)
    const
{
    return rcstring(linkname, careful_strlen(linkname, sizeof(linkname)));
}


void
header_ty::linkname_set(const rcstring &arg)
{
    string_field_set(linkname, sizeof(linkname), arg);
}


rcstring
header_ty::uname_get(void)
    const
{
    return rcstring(uname, careful_strlen(uname, sizeof(uname)));
}


void
header_ty::uname_set(const rcstring &arg)
{
    string_field_set(uname, sizeof(uname), arg);
}


rcstring
header_ty::gname_get(void)
    const
{
    return rcstring(gname, careful_strlen(gname, sizeof(gname)));
}


void
header_ty::gname_set(const rcstring &arg)
{
    string_field_set(gname, sizeof(gname), arg);
}


long
header_ty::devmajor_get(void)
    const
{
    return octal(devmajor, sizeof(devmajor));
}


void
header_ty::devmajor_set(long n)
{
    space_padded_octal(devmajor, sizeof(devmajor), n);
}


void
header_ty::devmajor_set_z(long n)
{
    zero_padded_octal(devmajor, sizeof(devmajor), n);
}


long
header_ty::devminor_get(void)
    const
{
    return octal(devminor, sizeof(devminor));
}


void
header_ty::devminor_set(long n)
{
    space_padded_octal(devminor, sizeof(devminor), n);
}


void
header_ty::devminor_set_z(long n)
{
    zero_padded_octal(devminor, sizeof(devminor), n);
}


long
header_ty::calculate_checksum(void)
    const
{
    unsigned char *cp = (unsigned char *)this;
    unsigned char *ep = (unsigned char *)this->chksum;
    long sum = ((unsigned char)' ') * sizeof(this->chksum);
    while (cp < ep)
        sum += *cp++;
    cp = (unsigned char *)(this->chksum + sizeof(this->chksum));
    ep = (unsigned char *)this + TBLOCK;
    while (cp < ep)
        sum += *cp++;
    return sum;
}


#include <libtardy/ac/stdio.h>


void
header_ty::dump(void)
    const
{
    unsigned char *cp = (unsigned char *)this;
    for (int j = 0; j < TBLOCK; j += 16)
    {
        fprintf(stderr, "%03X:", j);
        for (int k = 0; k < 16; ++k)
            fprintf(stderr, " %02X", cp[j + k]);
        fprintf(stderr, "  ");
        for (int k = 0; k < 16; ++k)
        {
            unsigned char c = cp[j + k] & 0x7F;
            if (c < ' ' || c > '~')
                c = '.';
            fputc(c, stderr);
        }
        fputc('\n', stderr);
    }
}
