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


tar_input_cpio_binary::~tar_input_cpio_binary()
{
}


static endian_t
sniff(const file_input::pointer &ifp)
{
    unsigned char buffer[2];
    int nbytes = ifp->peek(buffer, sizeof(buffer));
    if (nbytes >= 2 && 070707 == endian_get2be(buffer))
        return endian_big;
    return endian_little;
}


tar_input_cpio_binary::tar_input_cpio_binary(const file_input::pointer &a_ifp) :
    tar_input_cpio(a_ifp, 2),
    endian(sniff(a_ifp))
{
}


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


bool
tar_input_cpio_binary::candidate(const file_input::pointer &a_ifp)
{
    unsigned char buffer[26];
    int nbytes = a_ifp->peek(buffer, sizeof(buffer));
    if (nbytes < 26)
        return false;
    return
        (
            070707 == endian_get2le(buffer)
        ||
            070707 == endian_get2be(buffer)
        );
}


bool
tar_input_cpio_binary::read_header(tar_header &hdr)
{
    char buffer[26];
    size_t nbytes = read_deeper(buffer, sizeof(buffer));
    if (nbytes != sizeof(buffer))
    {
        fatal
        (
            "short header read (expected %d, read %d)",
            (int)sizeof(buffer),
            (int)nbytes
        );
    }
    unsigned magic = get2(buffer);
    if (magic != 070707)
        fatal("bad magic number (expected 070707, read %#o)", magic);
    unsigned dev = get2(buffer + 2);
    hdr.device_major = dev >> 8;
    hdr.device_minor = dev & 255;
    hdr.inode_number = get2(buffer + 4);
    unsigned mode = get2(buffer + 6);
    hdr.mode = mode & 07777;
    hdr.type = type_from_mode(mode);
    hdr.user_id = get2(buffer + 8);
    hdr.group_id = get2(buffer + 10);
    hdr.link_count = get2(buffer + 12);
    unsigned rdev = get2(buffer + 14);
    hdr.rdevice_major = rdev >> 8;
    hdr.rdevice_minor = rdev & 255;
    hdr.mtime = get4(buffer + 16);
    unsigned name_size = get2(buffer + 20);
    if (name_size < 2)
        fatal("invalid name size (%u)", name_size);
    hdr.size = get4(buffer + 22);
    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);
}


unsigned
tar_input_cpio_binary::get2(const void *data)
    const
{
    return endian_get2(data, endian);
}


unsigned long
tar_input_cpio_binary::get4(const void *data)
    const
{
    //
    // This is deeply weird.
    //
    // cpio(5) says "The four-byte integer is stored with the
    // most-significant 16 bits first followed by the least-significant
    // 16 bits.  Each of the two 16 bit values are stored in
    // machine-native byte order."
    //
    const unsigned char *p = (const unsigned char *)data;
    unsigned n1 = get2(p);
    unsigned n2 = get2(p + 2);
    return (((unsigned long)n1 << 16) | n2);
}


const char *
tar_input_cpio_binary::get_format_name(void)
    const
{
    return (endian == endian_little ?  "cpio-bin-le" : "cpio-bin-be");
}


tar_output::pointer
tar_input_cpio_binary::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_binary::create(ofp, endian);
}


size_t
tar_input_cpio_binary::get_maximum_name_length(void)
    const
{
    return 0xFFFFuL;
}
