//
// tardy - a tar post-processor
// Copyright (C) 2002, 2003, 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/errno.h>
#include <libtardy/ac/string.h>
#include <libtardy/ac/unistd.h>
#include <libtardy/ac/sys/sysmacros.h>
#include <libtardy/ac/sys/param.h>
#include <libtardy/ac/sys/types.h>
#include <libtardy/ac/sys/stat.h>
#include <libtardy/ac/pwd.h>
#include <libtardy/ac/grp.h>
#include <libexplain/lstat.h>

#include <libtardy/file/input/normal.h>
#include <libtardy/tar/input/filename.h>


tar_input_filename::~tar_input_filename()
{
}


tar_input_filename::tar_input_filename(const rcstring &a_name) :
    name(a_name),
    length(0)
{
}


tar_input::pointer
tar_input_filename::create(const rcstring &a_name)
{
    return pointer(new tar_input_filename(a_name));
}


int
tar_input_filename::read_data(void *buffer, int buffer_length)
{
    //
    // If we have had enough, stop now, even if the file has grown
    // in the mean time.
    //
    if (length <= 0)
        return 0;

    //
    // If there is no input (because we have already complained
    // about it shrinking) fake a bunch of zeros.
    //
    if (!source)
    {
        // This should never happen
        int nbytes = length;
        if (nbytes > buffer_length)
            nbytes = buffer_length;
        memset(buffer, 0, nbytes);
        length -= nbytes;
        return nbytes;
    }

    //
    // Read data from the input.  As much as possible, up to the
    // maximum buffer length.
    //
    int maxbytes = length;
    if (maxbytes > buffer_length)
        maxbytes = buffer_length;
    int nbytes = source->read(buffer, maxbytes);
    if (nbytes == 0)
    {
        warning("file shortened unexpectedly");
        source.reset();
        memset(buffer, 0, maxbytes);
        length -= maxbytes;
        return maxbytes;
    }
    length -= nbytes;
    return nbytes;
}


bool
tar_input_filename::read_header(tar_header &result)
{
    result = tar_header();
    struct stat statbuf;
    explain_lstat_or_die(name.c_str(), &statbuf);
    result.name = name;
    result.user_id = statbuf.st_uid;
    struct passwd *pw = getpwuid(statbuf.st_uid);
    if (pw)
        result.user_name = rcstring(pw->pw_name);
    result.group_id = statbuf.st_gid;
    struct group *gr = getgrgid(statbuf.st_gid);
    if (gr)
        result.group_name = rcstring(gr->gr_name);
    result.inode_number = statbuf.st_ino;
    switch (statbuf.st_mode & S_IFMT)
    {
    case S_IFSOCK:
        result.type = tar_header::type_socket;
        break;

    case S_IFLNK:
        {
            result.type = tar_header::type_link_symbolic;
            char linkname[2000];
            int nbytes =
                readlink
                (
                    name.c_str(),
                    linkname,
                    sizeof(linkname)
                );
            result.linkname = rcstring(linkname, nbytes);
        }
        break;

    case S_IFREG:
        result.type = tar_header::type_normal;
        length = statbuf.st_size;
        result.size = statbuf.st_size;
        source = file_input_normal::create(name);
        break;

    case S_IFBLK:
        result.type = tar_header::type_device_block;
        result.device_major = major(statbuf.st_rdev);
        result.device_minor = minor(statbuf.st_rdev);
        break;

    case S_IFDIR:
        result.type = tar_header::type_directory;
        break;

    case S_IFCHR:
        result.type = tar_header::type_device_character;
        result.device_major = major(statbuf.st_rdev);
        result.device_minor = minor(statbuf.st_rdev);
        break;

    case S_IFIFO:
        result.type = tar_header::type_fifo;
        break;

    default:
        fatal("file type unknown");
        result.type = tar_header::type_normal;
        break;
    }
    result.atime = statbuf.st_atime;
    result.ctime = statbuf.st_ctime;
    result.mtime = statbuf.st_mtime;
    result.mode = statbuf.st_mode & 07777;
    return true;
}


rcstring
tar_input_filename::filename(void)
    const
{
    return name;
}


const char *
tar_input_filename::get_format_name(void)
    const
{
    return "list";
}


format_family_t
tar_input_filename::get_format_family(void)
    const
{
    return format_family_other;
}


size_t
tar_input_filename::get_maximum_name_length(void)
    const
{
    return PATH_MAX;
}
