/* tar.c - output in tar format
   This has nothing to do with cryptography.
   Copyright (C) 1998 Paul Sheer

   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 2 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, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.
 */

#include "mostincludes.h"
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifdef HAVE_GRP_H
#    include <grp.h>
#endif
#include "mirrordir.h"
#include "tar.h"
#include "vfs/vfs.h"
#include "diffie/compat.h"
#include "mad.h"

int tar_block_size = 20;

void tar_init (char *tar_file_name);
void tar_shut (void);
int tar_append (char *file, char *arch_name, struct stat *s);

struct passwd *cached_getpwuid (pid_t pid);
struct group *cached_getgrgid (gid_t gid);

static char *block;
static int record_count = 0;
static int tar_file_fd = 0;
static FILE *tar_pipe = 0;
static int block_size;

extern int strict_locking;
extern int rest_access;

extern void utime_check (time_t modified_time, time_t access_time, char *path, int force);
extern void wait_for_shared_lock (int f, off_t offset, int size);
extern int is_special_prefix (char *path);

void tar_init (char *tar_file_name)
{
    block_size = tar_block_size * 512;
    block = malloc (block_size);
    if (tar_file_name[0] == '|') {
	tar_file_name++;
	while (*tar_file_name == ' ' || *tar_file_name == '\t')
	    tar_file_name++;
	tar_pipe = (FILE *) popen (tar_file_name, "w");
	if (!tar_pipe) {
	    progmess_strerror ("unable to open pipe for writing", tar_file_name);
	    exit (1);
	}
    } else {
	tar_file_fd = mc_open (tar_file_name, O_CREAT | O_TRUNC | O_WRONLY, 0644);
	if (tar_file_fd < 0) {
	    progmess_strerror ("unable to open tar file for writing", tar_file_name);
	    exit (1);
	}
    }
    return;
}

static int record_write (char *buf, int count)
{
    if (count <= 0)
	return 0;
    if (count > block_size)
	count = block_size;
    if (record_count + count >= block_size) {
	memcpy (block + record_count, buf, block_size - record_count);
	if (tar_pipe)
	    fwrite (block, block_size, 1, tar_pipe);
	else
	    mc_write (tar_file_fd, block, block_size);
	memcpy (block, buf + block_size - record_count, count - (block_size - record_count));
	record_count = count - (block_size - record_count);
    } else {
	memcpy (block + record_count, buf, count);
	record_count += count;
    }
    return count;
}

void tar_shut (void)
{
    char buf[512];
    memset (buf, 0, 512);
    record_write (buf, 512);
    if (record_count) {
	char *p;
	p = malloc (block_size - record_count);
	memset (p, 0, block_size - record_count);
	record_write (p, block_size - record_count);
	free (p);
    }
    if (tar_pipe)
	pclose (tar_pipe);
    else
	mc_close (tar_file_fd);
    free (block);
}


static void stat_to_header (struct stat *s, struct header *h)
{
    struct group *g;
    struct passwd *p;
    sprintf (h->mode, "%6lo ", (long) s->st_mode);
    sprintf (h->uid, "%6lo ", (long) s->st_uid);
    sprintf (h->gid, "%6lo ", (long) s->st_gid);
    sprintf (h->size, "%11lo ", (long) 0);
    sprintf (h->mtime, "%11lo ", (long) s->st_mtime);
    memcpy (h->chksum, CHKBLANKS, strlen (CHKBLANKS));
    sprintf (h->magic, TMAGIC);
    p = cached_getpwuid (s->st_uid);
    if (p)
	sprintf (h->uname, "%s", p->pw_name);
    g = cached_getgrgid (s->st_gid);
    if (g)
	sprintf (h->gname, "%s", g->gr_name);
}

static void send_header (union record *r)
{
    int i;
    unsigned char *p;
    unsigned long chksum = 0;
    p = (unsigned char *) r;
    for (i = 0; i < 512; i++)
	chksum += (unsigned long) *p++;
    sprintf (r->header.chksum, "%6lo", (long) chksum);
    record_write ((char *) r, 512);
}

static void send_long_name (char *arch_name, char linkflag, struct stat *s)
{
    union record record;
    char buf[512], l[13], *b;
    off_t len, count, c;
    memset (&record, 0, sizeof (record));
    strcpy (record.header.arch_name, "././@LongLink");
    stat_to_header (s, &record.header);
    record.header.linkflag = linkflag;
    len = count = strlen (arch_name) + 1;
    sprintf (l, "%11lo ", (long) len);
    memcpy (record.header.size, l, 12);
    sprintf (record.header.mode, "%6lo ", (long) 0);
    sprintf (l, "%11lo ", (long) 0);
    memcpy (record.header.mtime, l, 12);
    send_header (&record);
    b = arch_name;
    while (count > 0) {
	c = record_write (b, count);
	if (c < 0) {
	    progmess_strerror ("error trying to write to tar archive at file", arch_name);
	    exit (1);
	}
	b += c;
	count -= c;
    }
/* file last record */
    count = (unsigned int) -len & 511;
    memset (buf, 0, count);
    record_write (buf, count);
}

static void send_file (char *file, struct stat *s)
{
    int f = 0;
    off_t count;
    char buf[COPY_BUF_SIZE];
    off_t togo, offset;
    check_interrupt ();
    f = mc_open (file, O_RDONLY);
    if (f < 0) {
	progmess_strerror ("unable to open control file for reading", file);
	exit (1);
    }
    if (strict_locking)
	if (!is_special_prefix (file))
	    wait_for_shared_lock (f, 0, s->st_size);	/* lock the whole file */
    togo = s->st_size;
    offset = 0;
    while (togo > 0) {
	off_t c;
	char *b;
	be_nice_to_cpu ();
	do {
	    count = mc_read (f, buf, COPY_BUF_SIZE);
	} while (count < 0 && errno == EINTR);
	if (count <= 0) {
	    progmess_strerror ("error trying to read from file", file);
	    mc_close (f);
	    exit (1);
	}
	b = buf;
	togo -= count;
	offset += count;
	while (count > 0) {
	    c = record_write (b, count);
	    if (c < 0) {
		progmess_strerror ("error trying to write to tar archive at file", file);
		mc_close (f);
		exit (1);
	    }
	    b += c;
	    count -= c;
	}
    }
/* file last record */
    count = (unsigned int) -s->st_size & 511;
    memset (buf, 0, count);
    record_write (buf, count);
    mc_close (f);		/* locks are removed when we close the file */
    mc_setctl (file, MCCTL_REMOVELOCALCOPY, 0);
    if (rest_access)
	utime_check (s->st_mtime, s->st_atime, file, 1);
    check_interrupt ();
}

int tar_append (char *file, char *arch_name, struct stat *s)
{
    struct header *h;
    union record record;
    char *arch_linkname = 0;
    char *arch_dirname = 0;
    int linkflag = -1;
    int r = 0;
    if (is_special_prefix (arch_name)) {
	char *p;
	p = strchr (arch_name, ':');
	if (p) {
	    p++;
	    arch_name = p;
	    p = strstr (p, "//");
	    if (p) {
		p += 2;
		arch_name = p;
		p = strchr (p, PATH_SEP);
		if (p)
		    arch_name = p;
	    }
	}
    }
    while (*arch_name == PATH_SEP)
	arch_name++;
    memset (&record, 0, sizeof (record));
    h = &record.header;
    if (S_ISREG (s->st_mode)) {
	if (is_hardlink (s)) {
	    if (find_hardlink (s)) {
		if (verbose)
		    verbmess ("adding hardlink", arch_name);
		arch_linkname = (char *) strdup ((char *) retrieve_link ());
		linkflag = LF_LINK;
		reduce_hardlink ();
	    } else {
		linkflag = LF_NORMAL;
		add_hardlink (arch_name, s, 0);
		if (verbose)
		    verbmess ("adding hardlinked file", arch_name);
	    }
	} else {
	    linkflag = LF_NORMAL;
	    if (verbose)
		verbmess ("adding regular file", arch_name);
	}
    } else if (S_ISLNK (s->st_mode)) {
	char linkbuf[MAX_PATH_LEN + 1];
	int linklen;
	linkflag = LF_SYMLINK;
	linklen = mc_readlink (file, linkbuf, MAX_PATH_LEN);
	if (linklen < 0) {
	    progmess_strerror ("error trying read symlink", file);
	    r = 1;
	    goto done_tar;
	}
	linkbuf[linklen] = '\0';
	arch_linkname = (char *) strdup (linkbuf);
	if (verbose)
	    verbmess ("adding symlink", arch_name);
    } else if (S_ISDIR (s->st_mode)) {
/* directories must have a trailing slash */
	arch_dirname = malloc (strlen (arch_name) + 2);
	strcpy (arch_dirname, arch_name);
	strcat (arch_dirname, PATH_SEP_STR);
	arch_name = arch_dirname;
	linkflag = LF_DIR;
	if (verbose)
	    verbmess ("adding directory", arch_name);
    } else if (S_ISCHR (s->st_mode)) {
	linkflag = LF_CHR;
	if (verbose)
	    verbmess ("adding character device", arch_name);
    } else if (S_ISBLK (s->st_mode)) {
	linkflag = LF_BLK;
	if (verbose)
	    verbmess ("adding block device", arch_name);
    } else if (S_ISFIFO (s->st_mode)) {
	linkflag = LF_FIFO;
	if (verbose)
	    verbmess ("adding fifo", arch_name);
    } else if (S_ISSOCK (s->st_mode)) {
	linkflag = LF_FIFO;
	progmess ("***warning*** storing socket as fifo", file);
    } else {
	progmess ("***warning*** unkown file type", file);
	goto done_tar;
    }

    h->linkflag = linkflag;

    if (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode)) {
	sprintf (h->devmajor, "%6lo ", (long) dev_to_long (s->st_rdev) >> 8);
	sprintf (h->devminor, "%6lo ", (long) dev_to_long (s->st_rdev) & 0xFF);
    }
    if (arch_linkname) {
	strncpy (h->arch_linkname, arch_linkname, NAMSIZ - 1);
	h->arch_linkname[NAMSIZ - 1] = '\0';
	if (strlen (h->arch_linkname) < strlen (arch_linkname))
	    send_long_name (arch_linkname, LF_LONGLINK, s);
    }
    strncpy (h->arch_name, arch_name, NAMSIZ - 1);
    h->arch_name[NAMSIZ - 1] = '\0';
    if (strlen (h->arch_name) < strlen (arch_name))
	send_long_name (arch_name, LF_LONGNAME, s);
    stat_to_header (s, h);
    if (linkflag == LF_NORMAL) {
	char l[13];
	sprintf (l, "%11lo ", (long) s->st_size);
	memcpy (h->size, l, 12);
	send_header (&record);
	send_file (file, s);
    } else {
	send_header (&record);
    }
  done_tar:
    if (arch_linkname)
	free (arch_linkname);
    if (arch_dirname)
	free (arch_dirname);
    return r;
}


