/* $Source: /u/mark/src/pax/RCS/fileio.c,v $
 *
 * $Revision: 1.2 $
 *
 * fileio.c - file I/O functions for all archive interfaces
 *
 * DESCRIPTION
 *
 *	These function all do I/O of some form or another.  They are
 *	grouped here mainly for convienence.
 *
 * AUTHOR
 *
 *	Mark H. Colburn, NAPS International (mark@jhereg.mn.org)
 *
 * Sponsored by The USENIX Association for public distribution.
 *
 * Copyright (c) 1989 Mark H. Colburn.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice is duplicated in all such
 * forms and that any documentation, advertising materials, and other
 * materials related to such distribution and use acknowledge that the
 * software was developed * by Mark H. Colburn and sponsored by The
 * USENIX Association.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * $Log:	fileio.c,v $
 * Revision 1.2  89/02/12  10:04:31  mark
 * 1.2 release fixes
 *
 * Revision 1.1  88/12/23  18:02:09  mark
 * Initial revision
 *
 */

#ifndef lint
static char *ident = "$Id: fileio.c,v 1.2 89/02/12 10:04:31 mark Exp $";
static char *copyright = "Copyright (c) 1989 Mark H. Colburn.\nAll rights reserved.\n";
#endif /* ! lint */


/* Headers */

#include "pax.h"

int mknod (const char *path, mode_t mode, int dev);

/* open_archive -  open an archive file.
 *
 * DESCRIPTION
 *
 *	Open_archive will open an archive file for reading or writing,
 *	setting the proper file mode, depending on the "mode" passed to
 *	it.  All buffer pointers are reset according to the mode
 *	specified.
 *
 * PARAMETERS
 *
 * 	int	mode 	- specifies whether we are reading or writing.
 *
 * RETURNS
 *
 *	Returns a zero if successfull, or -1 if an error occured during
 *	the open.
 */

int open_archive(int mode)
{
#ifdef DF_TRACE_DEBUG
    printf("DF_TRACE_DEBUG: int open_archive() in fileio.c\n");
#endif
    if (ar_file[0] == '-' && ar_file[1] == '\0') {
        if (mode == AR_READ) {
            archivefd = STDIN;
            bufend = bufidx = bufstart;
        } else {
            archivefd = STDOUT;
        }
    } else if (mode == AR_READ) {
        archivefd = open(ar_file, O_RDONLY | O_BINARY);
        bufend = bufidx = bufstart; /* set up for initial read */
    } else if (mode == AR_WRITE) {
        archivefd = open(ar_file, O_WRONLY|O_TRUNC|O_CREAT|O_BINARY,  /* Xn */
                         S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);  /* Xn */
    } else if (mode == AR_APPEND) {
        archivefd = open(ar_file, O_RDWR | O_BINARY,  /* Xn */
                         S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);  /* Xn */
        bufend = bufidx = bufstart; /* set up for initial read */
    }

    if (archivefd < 0) {
        warnarch(strerror(errno), (OFFSET) 0);  /* Xn */
        return (-1);
    }
    ++arvolume;
    return (0);
}


/* close_archive - close the archive file
 *
 * DESCRIPTION
 *
 *	Closes the current archive and resets the archive end of file
 *	marker.
 */

void close_archive(void)
{
#ifdef DF_TRACE_DEBUG
    printf("DF_TRACE_DEBUG: void close_archive() in fileio.c\n");
#endif
    if (archivefd != STDIN && archivefd != STDOUT) {
        close(archivefd);
    }
    areof = 0;
}


/* openout - open an output file
 *
 * DESCRIPTION
 *
 *	Openo opens the named file for output.  The file mode and type are
 *	set based on the values stored in the stat structure for the file.
 *	If the file is a special file, then no data will be written, the
 *	file/directory/Fifo, etc., will just be created.  Appropriate
 *	permission may be required to create special files.
 *
 * PARAMETERS
 *
 *	char 	*name		- The name of the file to create
 *	Stat	*asb		- Stat structure for the file
 *	Link	*linkp;		- pointer to link chain for this file
 *	int	 ispass		- true if we are operating in "pass" mode
 *
 * RETURNS
 *
 * 	Returns the output file descriptor, 0 if no data is required or -1
 *	if unsuccessful. Note that UNIX open() will never return 0 because
 *	the standard input is in use.
 */

int openout(char *name, Stat *asb, Link *linkp, int ispass)
{
    int             exists;
    int             fd;
    ushort          perm;
    ushort          operm = 0;
    Stat            osb;
#ifdef	S_IFLNK
    int             ssize;
    char            sname[PATH_MAX + 1];
#endif	/* S_IFLNK */

#ifdef DF_TRACE_DEBUG
    printf("DF_TRACE_DEBUG: int openout() in fileio.c\n");
#endif
    if (exists = (LSTAT(name, &osb) == 0)) {
        if (ispass && osb.sb_ino == asb->sb_ino && osb.sb_dev == asb->sb_dev) {
            warn(name, "Same file");
            return (-1);
        } else if ((osb.sb_mode & S_IFMT) == (asb->sb_mode & S_IFMT)) {
            operm = (ushort)(osb.sb_mode & S_IPERM);
        } else if (REMOVE(name, &osb) < 0) {
            warn(name, strerror(errno));  /* Xn */
            return (-1);
        } else {
            exists = 0;
        }
    }
    if (linkp) {
        if (exists) {
            if (asb->sb_ino == osb.sb_ino && asb->sb_dev == osb.sb_dev) {
                return (0);
            } else if (unlink(name) < 0) {
                warn(name, strerror(errno));  /* Xn */
                return (-1);
            } else {
                exists = 0;
            }
        }
        if (link(linkp->l_name, name) != 0) {
            if (errno == ENOENT) {
                if (f_dir_create) {
                    if (dirneed(name) != 0 ||
                        link(linkp->l_name, name) != 0) {
                        warn(name, strerror(errno));  /* Xn */
                        return (-1);
                    }
                } else {
                    warn(name,
                         "Directories are not being created (-d option)");
                }
                return (0);
            } else if (errno != EXDEV) {
                warn(name, strerror(errno));  /* Xn */
                return (-1);
            }
        } else {
            return (0);
        }
    }
    perm = (ushort)(asb->sb_mode & S_IPERM);
    switch (asb->sb_mode & S_IFMT) {
        case S_IFBLK:
        case S_IFCHR:
            fd = 0;
            if (exists) {
                if (REMOVE(name, &osb) < 0) {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                } else {
                    exists = 0;
                }
            }
            if (mknod(name, (int) asb->sb_mode, 0)) {
                if (errno == ENOENT) {
                    if (f_dir_create) {
                        if (dirneed(name) < 0 || mknod(name, (int) asb->sb_mode,
                                                       0)) {
                            warn(name, strerror(errno));  /* Xn */
                            return (-1);
                        }
                    } else {
                        warn(name, "Directories are not being created (-d option)");
                    }
                } else {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                }
            }
            return (0);
            break;
        case S_IFDIR:
            if (exists) {
                if (perm != operm && chmod(name, (int) perm) < 0) {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                }
            } else if (f_dir_create) {
                if (dirmake(name, asb) < 0 || dirneed(name) < 0) {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                }
            } else {
                warn(name, "Directories are not being created (-d option)");
            }
            return (0);
#ifdef	S_IFIFO
        case S_IFIFO:
            fd = 0;
            if (exists) {
                if (perm != operm && chmod(name, (int) perm) < 0) {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                }
            } else if (mkfifo(name, asb->sb_mode) < 0) {  /* Xn */
                if (errno == ENOENT) {
                    if (f_dir_create) {
                        if (dirneed(name) < 0
                            || mkfifo(name, asb->sb_mode) < 0) {  /* Xn */
                            warn(name, strerror(errno));  /* Xn */
                            return (-1);
                        }
                    } else {
                        warn(name, "Directories are not being created (-d option)");
                    }
                } else {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                }
            }
            return (0);
            break;
#endif				/* S_IFIFO */
#ifdef	S_IFLNK
        case S_IFLNK:
            if (exists) {
                if ((ssize = readlink(name, sname, sizeof(sname))) < 0) {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                } else if (strncmp(sname, asb->sb_link, ssize) == 0) {
                    return (0);
                } else if (REMOVE(name, &osb) < 0) {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                } else {
                    exists = 0;
                }
            }
            if (symlink(asb->sb_link, name) < 0) {
                if (errno == ENOENT) {
                    if (f_dir_create) {
                        if (dirneed(name) < 0 || symlink(asb->sb_link, name) < 0) {
                            warn(name, strerror(errno));  /* Xn */
                            return (-1);
                        }
                    } else {
                        warn(name, "Directories are not being created (-d option)");
                    }
                } else {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                }
            }
            return (0);     /* Can't chown()/chmod() a symbolic link */
#endif				/* S_IFLNK */
        case S_IFREG:
            if (exists) {
                if (!f_unconditional && osb.sb_mtime > asb->sb_mtime) {
                    warn(name, "Newer file exists");
                    return (-1);
                } else if (unlink(name) < 0) {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                } else {
                    exists = 0;
                }
            }
            if ((fd = creat(name, (int) perm)) < 0) {
                if (errno == ENOENT) {
                    if (f_dir_create) {
                        if (dirneed(name) < 0 ||
                            (fd = creat(name, (int) perm)) < 0) {
                            warn(name, strerror(errno));  /* Xn */
                            return (-1);
                        }
                    } else {
                        /*
                         * the file requires a directory which does not exist
                         * and which the user does not want created, so skip
                         * the file...
                         */
                        warn(name, "Directories are not being created (-d option)");
                        return (0);
                    }
                } else {
                    warn(name, strerror(errno));  /* Xn */
                    return (-1);
                }
            }
            break;
        default:
            warn(name, "Unknown filetype");
            return (-1);
    }
    if (f_owner) {
        if (!exists || asb->sb_uid != osb.sb_uid || asb->sb_gid != osb.sb_gid) {
            chown(name, (int) asb->sb_uid, (int) asb->sb_gid);
        }
    }
    return (fd);
}


/* openin - open the next input file
 *
 * DESCRIPTION
 *
 *	Openi will attempt to open the next file for input.  If the file is
 *	a special file, such as a directory, FIFO, link, character- or
 *	block-special file, then the file size field of the stat structure
 *	is zeroed to make sure that no data is written out for the file.
 *	If the file is a special file, then a file descriptor of 0 is
 *	returned to the caller, which is handled specially.  If the file
 *	is a regular file, then the file is opened and a file descriptor
 *	to the open file is returned to the caller.
 *
 * PARAMETERS
 *
 *	char   *name	- pointer to the name of the file to open
 *	Stat   *asb	- pointer to the stat block for the file to open
 *
 * RETURNS
 *
 * 	Returns a file descriptor, 0 if no data exists, or -1 at EOF. This
 *	kludge works because standard input is in use, preventing open() from
 *	returning zero.
 */

int openin(char *name, Stat *asb)
{
    int             fd;

#ifdef DF_TRACE_DEBUG
    printf("DF_TRACE_DEBUG: int openin() in fileio.c\n");
#endif
    switch (asb->sb_mode & S_IFMT) {
        case S_IFDIR:
            asb->sb_size = 0;
            return (0);
#ifdef	S_IFLNK
        case S_IFLNK:
            if ((asb->sb_size = readlink(name,
                                         asb->sb_link, sizeof(asb->sb_link) - 1)) < 0) {
                warn(name, strerror(errno));  /* Xn */
                return (0);
            }
            asb->sb_link[asb->sb_size] = '\0';
            return (0);
#endif				/* S_IFLNK */
        case S_IFREG:
            if (asb->sb_size == 0) {
                return (0);
            }
            if ((fd = open(name, O_RDONLY | O_BINARY)) < 0) {
                warn(name, strerror(errno));  /* Xn */}
            return (fd);
        default:
            asb->sb_size = 0;
            return (0);
    }
}