//
// 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/cpio/old_ascii.h>
#include <libtardy/tar/output/cpio/oldascii.h>


tar_input_cpio_old_ascii::~tar_input_cpio_old_ascii()
{
}


tar_input_cpio_old_ascii::tar_input_cpio_old_ascii(
    const file_input::pointer &a_ifp
) :
    tar_input_cpio(a_ifp, 0)
{
}


tar_input_cpio_old_ascii::pointer
tar_input_cpio_old_ascii::create(const file_input::pointer &a_ifp)
{
    return pointer(new tar_input_cpio_old_ascii(a_ifp));
}


bool
tar_input_cpio_old_ascii::candidate(const file_input::pointer &a_ifp)
{
    char buffer[6];
    return (a_ifp->peek(buffer, 6) == 6 && 0 == memcmp(buffer, "070707", 6));
}


tar_output::pointer
tar_input_cpio_old_ascii::tar_output_factory(const file_output::pointer &ofp)
    const
{
    //
    // Unfortunately, at this point we don't know whether it's a real
    // cpio(5) archive, or a bodgy HP/UX cpio(5) archive.  And if it has
    // no devices in it, we will be unable to tell, in any case.
    //
    // This is also called, if it is called, before any archive members
    // have been parsed.
    //
    return tar_output_cpio_oldascii::create(ofp);
}


static unsigned long
octal(unsigned char *data, unsigned data_size)
{
    unsigned long result = 0;
    while (data_size > 0)
    {
        --data_size;
        unsigned char c = *data++;
        switch (c)
        {
        case '\0':
        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_cpio_old_ascii::read_header(tar_header &hdr)
{
    //
    // Name       Size Offset
    // ---------- ---- ------
    // magic         6      0
    // dev           6      6
    // ino           6     12
    // mode          6     18
    // uid           6     24
    // gid           6     30
    // nlink         6     36
    // rdev          6     42
    // mtime        11     48
    // namesize      6     59
    // filesize     11     65
    // ---------- ---- ------
    // Total:              76
    //
    unsigned char buffer[76];
    size_t nbytes = read_deeper(buffer, sizeof(buffer));
    if (nbytes == 0)
        return false;
    if (nbytes != sizeof(buffer))
    {
        fatal
        (
            "short read (expected %d, read %d)",
            (int)sizeof(buffer),
            (int)nbytes
        );
    }
    if (octal(buffer, 6) != 070707)
        fatal("bad magic number");
    unsigned dev = octal(buffer + 6, 6);
    hdr.device_major = dev >> 8;
    hdr.device_minor = dev & 255;
    hdr.inode_number = octal(buffer + 12, 6);
    unsigned mode = octal(buffer + 18, 6);
    hdr.mode = mode & 07777;
    hdr.type = type_from_mode(mode);
    hdr.user_id = octal(buffer + 24, 6);
    hdr.group_id = octal(buffer + 30, 6);
    hdr.link_count = octal(buffer + 36, 6);
    unsigned rdev = octal(buffer + 42, 6);
    hdr.rdevice_major = rdev >> 8;
    hdr.rdevice_minor = rdev & 255;
    hdr.mtime = octal(buffer + 48, 11);
    unsigned name_size = octal(buffer + 59, 6);
    hdr.size = octal(buffer + 65, 11);
    hdr.name = read_name(name_size);

    //
    // HP/UX cpio(1) creates archives that look just like ordinary
    // cpio(5) archives, but for devices it sets major = 0, minor = 1,
    // and puts the actual major/minor number in the filesize field.
    // See if this is an HP/UX cpio(5) archive, and if so fix it.
    //
    switch (hdr.type)
    {
    case tar_header::type_device_character:
    case tar_header::type_device_block:
    case tar_header::type_socket:
    case tar_header::type_fifo:
        if (hdr.size != 0 && hdr.rdevice_major == 0 && hdr.rdevice_minor == 1)
        {
            hdr.rdevice_major = hdr.size >> 8;
            hdr.rdevice_minor = hdr.size & 255;
            hdr.size = 0;
        }
        break;

    default:
        break;
    }

    return !is_end(hdr);
}


const char *
tar_input_cpio_old_ascii::get_format_name(void)
    const
{
    return "cpio-odc";
}


size_t
tar_input_cpio_old_ascii::get_maximum_name_length(void)
    const
{
    return ((size_t)1 << (3 * 6));
}
