//
// tardy - a tar post-processor
// Copyright (C) 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/string.h>

#include <libtardy/tar/input/ar/bsd.h>
#include <libtardy/tar/input/filter/ar_long_names.h>
#include <libtardy/tar/input/filter/ar_long_names2.h>
#include <libtardy/tar/output/ar/bsd.h>


tar_input_ar_bsd::~tar_input_ar_bsd()
{
}


tar_input_ar_bsd::tar_input_ar_bsd(const file_input::pointer &a_ifp) :
    tar_input_ar(a_ifp, 2)
{
}


tar_input::pointer
tar_input_ar_bsd::create(const file_input::pointer &a_ifp)
{
    pointer p(new tar_input_ar_bsd(a_ifp));
    p = tar_input_filter_ar_long_names2::create(p);
    return tar_input_filter_ar_long_names::create(p);
}


bool
tar_input_ar_bsd::candidate(const file_input::pointer &a_ifp)
{
    char buffer[8];
    return (a_ifp->peek(buffer, 8) == 8 && 0 == memcmp(buffer, "!<arch>\n", 8));
}


const char *
tar_input_ar_bsd::get_format_name(void)
    const
{
    return "ar-bsd";
}


void
tar_input_ar_bsd::read_archive_begin(void)
{
    char buffer[8];
    if
    (
        read_deeper(buffer, sizeof(buffer)) != sizeof(buffer)
    ||
        0 != memcmp(buffer, "!<arch>\n", 8)
    )
        fatal("bad magic number");
}


static rcstring
slurp(const char *data, size_t data_size)
{
    while (data_size > 0 && data[data_size - 1] == ' ')
        --data_size;
    if (data_size > 0 && data[data_size - 1] == '/')
        --data_size;
    return rcstring(data, data_size);
}


static unsigned long
dec(const char *data, size_t data_size)
{
    unsigned long result = 0;
    while (data_size > 0 && *data == ' ')
    {
        ++data;
        --data_size;
    }
    while (data_size > 0)
    {
        --data_size;
        unsigned char c = *data++;
        switch (c)
        {
        case ' ':
            return result;

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            result = result * 10 + c - '0';
            break;

        default:
            return -1;
        }
    }
    return result;
}


static unsigned long
oct(const char *data, size_t data_size)
{
    unsigned long result = 0;
    while (data_size > 0 && *data == ' ')
    {
        ++data;
        --data_size;
    }
    while (data_size > 0)
    {
        --data_size;
        unsigned char c = *data++;
        switch (c)
        {
        case ' ':
            return result;

        case '0': case '1': case '2': case '3':
        case '4': case '5': case '6': case '7':
            result = (result << 3) + (c & 7);
            break;

        default:
            return -1;
        }
    }
    return result;
}


bool
tar_input_ar_bsd::read_header(tar_header &hdr)
{
    //
    // Name Size Offset
    // ---- ---- ------
    // name   16      0
    // date   12     16
    // uid     6     28
    // gid     6     34
    // mode    8     40
    // size   10     48
    // fmag    2     58
    // ---- ---- ------
    // Total:        60
    char buffer[60];
    size_t nbytes = read_deeper(buffer, sizeof(buffer));
    if (nbytes == 0)
        return false;
    if (nbytes != sizeof(buffer))
    {
        fatal
        (
            "short header read (expected %u, got %u)",
            (int)sizeof(buffer),
            (int)nbytes
        );
    }
    if (buffer[58] != '`' || buffer[59] != '\n')
        fatal("header format error");
    hdr.name = slurp(buffer, 16);
    hdr.mtime = dec(buffer + 16, 12);
    hdr.user_id = dec(buffer + 28, 6);
    hdr.group_id = dec(buffer + 34, 6);
    unsigned mode = oct(buffer + 40, 8);
    hdr.mode = mode & 07777;
    hdr.type = type_from_mode(mode);
    hdr.size = dec(buffer + 48, 10);
    return true;
}


tar_output::pointer
tar_input_ar_bsd::tar_output_factory(const file_output::pointer &ofp)
    const
{
    return tar_output_ar_bsd::create(ofp);
}


size_t
tar_input_ar_bsd::get_maximum_name_length(void)
    const
{
    return 16;
}
