//
// 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/output/ar/port5.h>
#include <libtardy/tar/output/filter/ar_long_names.h>
#include <libtardy/tar/output/filter/basename.h>

//
// http://ftp.funet.fi/pub/unix/ctix/ar.h
//
// ARCHIVE File Organization:
//
// +---------------------------------------------+
// |          ARCHIVE_HEADER_DATA                |
// +  -----------------------------------------  +
// |       Archive Header  "ar_hdr"              |
// !  .........................................  !
// |                                             |
// |       Symbol Directory "ar_sym"             |
// |                                             |
// +---------------------------------------------+
// |        ARCHIVE FILE MEMBER 1                |
// +  -----------------------------------------  +
// |       Archive File Header "arf_hdr"         |
// !  .........................................  !
// |       Member Contents (either a.out.h       |
// |                        format or text file) |
// +---------------------------------------------+
// |       .               .               .     |
// |       .               .               .     |
// |       .               .               .     |
// +---------------------------------------------+
// |        ARCHIVE FILE MEMBER n                |
// +  -----------------------------------------  +
// |       Archive File Header "arf_hdr"         |
// !  .........................................  !
// |       Member Contents (either a.out.h       |
// |                        format or text file) |
// +---------------------------------------------+
//
//
// #define ARMAG   "<ar>"
// #define SARMAG  4
//
// struct ar_hdr                    // archive header
// {
//     char ar_magic[SARMAG];       // magic number
//     char ar_name[16];            // archive name
//     char ar_date[4];             // date of last archive modification
//     char ar_syms[4];             // number of ar_sym entries
// };
//
// struct ar_sym                    // archive symbol table entry
// {
//     char sym_name[8];            // symbol name, recognized by ld
//     char sym_ptr[4];             // archive position of symbol
// };
//
// struct arf_hdr                   // archive file member header
// {
//     char arf_name[16];           // file member name
//     char arf_date[4];            // file member date
//     char arf_uid[4];             // file member user identification
//     char arf_gid[4];             // file member group identification
//     char arf_mode[4];            // file member mode
//     char arf_size[4];            // file member size
// };
//


tar_output_ar_port5::~tar_output_ar_port5()
{
}


tar_output_ar_port5::tar_output_ar_port5(
    const file_output::pointer &a_ofp,
    endian_t a_endian
) :
    tar_output_ar(a_ofp, 4),
    endian(a_endian)
{
}


tar_output_ar_port5::pointer
tar_output_ar_port5::create(const file_output::pointer &a_ofp,
    endian_t a_endian)
{
    pointer p(new tar_output_ar_port5(a_ofp, a_endian));
    p = tar_output_filter_ar_long_names::create(p);
    return tar_output_filter_basename::create(p);
}


tar_output_ar_port5::pointer
tar_output_ar_port5::create_be(const file_output::pointer &a_ofp)
{
    return create(a_ofp, endian_big);
}


tar_output_ar_port5::pointer
tar_output_ar_port5::create_le(const file_output::pointer &a_ofp)
{
    return create(a_ofp, endian_little);
}


const char *
tar_output_ar_port5::get_format_name(void)
    const
{
    return (endian == endian_little ? "ar-port5-le" : "ar-port5-be");
}


void
tar_output_ar_port5::write_archive_begin(void)
{
    //
    //  +---------------------------------------------+
    //  |          ARCHIVE_HEADER_DATA                |
    //  +  -----------------------------------------  +
    //  |       Archive Header  "ar_hdr"              |
    //  !  .........................................  !
    //  |                                             |
    //  |       Symbol Directory "ar_sym"             |
    //  |                                             |
    //  +---------------------------------------------+
    //
    //  struct ar_hdr                    // archive header
    //  {
    //      char ar_magic[SARMAG];       // magic number
    //      char ar_name[16];            // archive name
    //      char ar_date[4];             // date of last archive modification
    //      char ar_syms[4];             // number of ar_sym entries
    //  };
    //
    //  struct ar_sym                    // archive symbol table entry
    //  {
    //      char sym_name[8];            // symbol name, recognized by ld
    //      char sym_ptr[4];             // archive position of symbol
    //  };
    //
    unsigned char buffer[28];
    memcpy(buffer, "<ar>", 4);
    rcstring name = filename().basename();
    size_t name_size = name.size();
    if (name_size > 16)
        name_size = 16;
    memcpy(buffer + 4, name.c_str(), name_size);
    memset(buffer + 4 + name_size, 0, 16 - name_size);
    put4(buffer + 20, 0);
    put4(buffer + 24, 0);
    write_deeper(buffer, sizeof(buffer));
}


void
tar_output_ar_port5::write_header(const tar_header &hdr)
{
    switch (hdr.type)
    {
    case tar_header::type_device_block:
    case tar_header::type_device_character:
    case tar_header::type_fifo:
    case tar_header::type_link_hard:
    case tar_header::type_link_symbolic:
    case tar_header::type_normal_gzipped:
    case tar_header::type_socket:
        fatal("%s: file type not supported", hdr.name.c_str());
        break;

    case tar_header::type_directory:
        // Silently ignore.
        break;

    case tar_header::type_normal:
    case tar_header::type_normal_contiguous:
        //
        //  +---------------------------------------------+
        //  |        ARCHIVE FILE MEMBER 1                |
        //  +  -----------------------------------------  +
        //  |       Archive File Header "arf_hdr"         |
        //  !  .........................................  !
        //  |       Member Contents (either a.out.h       |
        //  |                        format or text file) |
        //  +---------------------------------------------+
        //
        //  struct arf_hdr                   // archive file member header
        //  {
        //      char arf_name[16];           // file member name
        //      char arf_date[4];            // file member date
        //      char arf_uid[4];             // file member user identification
        //      char arf_gid[4];             // file member group identification
        //      char arf_mode[4];            // file member mode
        //      char arf_size[4];            // file member size
        //  };
        //
        unsigned char buffer[36];
        size_t name_size = hdr.name.size();
        if (name_size > 16)
            name_size = 16;
        memcpy(buffer, hdr.name.c_str(), name_size);
        memset(buffer + name_size, 0, 16 - name_size);
        put4(buffer + 16, hdr.mtime);
        put4(buffer + 20, hdr.user_id);
        put4(buffer + 24, hdr.group_id);
        put4(buffer + 28, calculate_mode(hdr));
        put4(buffer + 32, hdr.size);
        write_deeper(buffer, sizeof(buffer));
        break;
    }
}


void
tar_output_ar_port5::put4(unsigned char *data, unsigned long value)
    const
{
    endian_set4(data, value, endian);
}


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