root/usr/src/cmd/svr4pkg/pkginstall/cppath.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */


/*
 * System includes
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <utime.h>
#include <locale.h>
#include <libintl.h>
#include <pkglocs.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

/*
 * consolidation pkg command library includes
 */

#include <pkglib.h>

/*
 * local pkg command library includes
 */

#include "libadm.h"
#include "libinst.h"
#include "install.h"
#include "messages.h"
#include "pkginstall.h"

/*
 * forward declarations
 */

static int      write_file(char **r_linknam, int a_ctrl, mode_t a_mode,
                        char *a_file);
static int      create_path(int a_ctrl, char *a_file);

/*
 * Name:        cppath
 * Description: copy a path object (install new file on system)
 * Arguments:
 *    - a_cntrl - determine how the destination file mode is set:
 *      |= MODE_0666 - force mode to 0666
 *      |= MODE_SET - mode is a_mode (no mask SET?ID bits)
 *      |= MODE_SRC - mode from source file (mask SET?ID bits)
 *      |= DIR_DISPLAY - display "%s <implied directory>" if directory created
 *    - a_srcPath - path to source to copy
 *    - a_dstPath - path to copy source to
 *    - a_mode - mode to set a_dstpath to (mode controlled by a_ctrl)
 * Returns:     int
 *      == 0 - success
 *      != 0 - failure
 */

int
cppath(int a_ctrl, char *a_srcPath, char *a_dstPath, mode_t a_mode)
{
        char            *linknam = (char *)NULL;
        int             dstFd;
        int             len;
        int             srcFd;
        long            status;
        struct stat     srcStatbuf;
        struct utimbuf  times;

        /* entry debugging info */

        echoDebug(DBG_CPPATH_ENTRY, a_ctrl, a_mode, a_srcPath, a_dstPath);

        /* open source file for reading */

        srcFd = open(a_srcPath, O_RDONLY);
        if (srcFd < 0) {
                progerr(ERR_OPEN_READ, a_srcPath,
                                errno, strerror(errno));
                return (1);
        }

        /* obtain file status of source file */

        if (fstat(srcFd, &srcStatbuf) != 0) {
                progerr(ERR_FSTAT, srcFd, a_srcPath, errno, strerror(errno));
                (void) close(srcFd);
                return (1);
        }

        /*
         * Determine the permissions mode for the destination:
         * - if MODE_SET is specified:
         * --> use a_mode (do not mask off any portion)
         * --> If a_mode is unknown (? in the pkgmap), then the file gets
         * --> installed with the default 0644 mode
         * - if MODE_SRC is specified:
         * --> use the mode of the source (srcStatbuf.st_mode) but mask off all
         * --> non-access mode bits (remove SET?UID bits)
         * - otherwise:
         * --> use 0666
         */

        if (a_ctrl & MODE_SET) {
                mode_t  usemode;

                usemode = (a_mode ^ BADMODE) ? a_mode : 0644;
                if (a_mode != usemode && usemode == 0644) {
                        logerr(WRN_DEF_MODE, a_dstPath);
                        a_mode = usemode;
                }
        } else if (a_ctrl & MODE_SRC) {
                a_mode = (srcStatbuf.st_mode & S_IAMB);
        } else {
                a_mode = 0666;
        }

        /*
         * Get fd of newly created destination file or, if this
         * is an overwrite,  a temporary file (linknam).
         */

        dstFd = write_file(&linknam, a_ctrl, a_mode, a_dstPath);
        if (dstFd < 0) {
                (void) close(srcFd);
                return (1);
        }

        /*
         * source and target files are open: copy data
         */

        status = copyFile(srcFd, dstFd, a_srcPath, a_dstPath, &srcStatbuf, 0);

        (void) close(srcFd);
        (void) close(dstFd);

        if (status != 0) {
                progerr(ERR_INPUT, a_srcPath, errno, strerror(errno));
                if (linknam) {
                        (void) remove(linknam);
                }
                return (1);
        }

        /*
         * If this is an overwrite, rename temp over original
         */

        if ((linknam != (char *)NULL) && (rename(linknam, a_dstPath) != 0)) {
                FILE    *logfp = (FILE *)NULL;
                char    busylog[PATH_MAX];

                /* output log message if busy else program error */

                if (errno == ETXTBSY) {
                        logerr(MSG_PROCMV, linknam);
                } else {
                        progerr(ERR_OUTPUT_WRITING, a_dstPath, errno,
                                strerror(errno));
                }

                (void) remove(linknam);

                /* open the log file and append log entry */

                len = snprintf(busylog, sizeof (busylog),
                                "%s/textbusy", get_PKGADM());
                if (len > sizeof (busylog)) {
                        progerr(ERR_CREATE_PATH_2, get_PKGADM(),
                                "textbusy");
                } else {
                        logfp = fopen(busylog, "a");
                        if (logfp == NULL) {
                                progerr(ERR_LOG, busylog, errno,
                                        strerror(errno));
                        } else {
                                (void) fprintf(logfp, "%s\n", linknam);
                                (void) fclose(logfp);
                        }
                }
        }

        /* set access/modification times for target */

        times.actime = srcStatbuf.st_atime;
        times.modtime = srcStatbuf.st_mtime;

        if (utime(a_dstPath, &times) != 0) {
                progerr(ERR_MODTIM, a_dstPath, errno, strerror(errno));
                return (1);
        }

        /* success! */

        return (0);
}

/*
 * This function creates all of the directory components of the specified path.
 */
static int
create_path(int a_ctrl, char *a_file)
{
        char    *pt;
        int     found = 0;

        for (pt = a_file; *pt; pt++) {
                /* continue if not at path separator or at start of path */

                if ((*pt != '/') || (pt == a_file)) {
                        continue;
                }

                /* at '/' - terminate path at current entry */

                *pt = '\0';

                /* continue if path element exists */

                if (access(a_file, F_OK) == 0) {
                        *pt = '/';
                        continue;
                }

                /* create directory in path */

                if (mkdir(a_file, 0755)) {
                        progerr(ERR_MAKE_DIR, a_file, errno, strerror(errno));
                        *pt = '/';
                        return (1);
                }

                /* display 'implied directory created' message */

                if (a_ctrl & DIR_DISPLAY) {
                        echo(MSG_IMPDIR, a_file);
                }

                found++;

                *pt = '/';
        }

        return (!found);
}

/*
 * Name:        write_file
 * Description: creates a new destination file if the file does not already
 *              exist; otherwise, creates a temporary file and places a
 *              pointer to the temporary file name in 'r_linknam'.
 * Arguments:   r_linknam - pointer to (char*) where name of temporary file
 *                      created is returned
 *              a_ctrl - determine if the destination file name is displayed:
 *                   |= DIR_DISPLAY - display "%s <implied directory>"
 *                      if directory created
 *              a_mode - permissions mode to set a_file to
 *              a_file - name of destination file to open
 * Returns:     int
 *                      success - file descriptor of the file it opened.
 *                      failure - returns -1
 */

static int
write_file(char **r_linknam, int a_ctrl, mode_t a_mode, char *a_file)
{
        int             len;
        int             fd = -1;
        static char     loc_link[PATH_MAX];

        /* entry debugging */

        echoDebug(DBG_WRITEFILE_ENTRY, a_ctrl, a_mode, a_file);

        /* reset pointer to returned 'temporary file name' */

        *r_linknam = (char *)NULL;

        /*
         * If we are overwriting an existing file, arrange to replace
         * it transparently.
         */

        if (access(a_file, F_OK) == 0) {
                /*
                 * link the file to be copied to a temporary name in case
                 * it is executing or it is being written/used (e.g., a shell
                 * script currently being executed
                 */

                if (!RELATIVE(a_file)) {
                        len = snprintf(loc_link, sizeof (loc_link),
                                        "%sXXXXXX", a_file);
                        if (len > sizeof (loc_link)) {
                                progerr(ERR_CREATE_PATH_2, a_file, "XXXXXX");
                        }
                } else {
                        logerr(WRN_RELATIVE, a_file);
                        len = snprintf(loc_link, sizeof (loc_link),
                                        "./%sXXXXXX", a_file);
                        if (len > sizeof (loc_link)) {
                                progerr(ERR_CREATE_PATH_3, "./", a_file,
                                        "XXXXXX");
                        }
                }

                /* create and open temporary file */

                fd = mkstemp(loc_link);
                if (fd == -1) {
                        progerr(ERR_MKTEMP, loc_link, errno, strerror(errno));
                        return (-1);
                }

                /* remember name of temporary file */

                *r_linknam = loc_link;

                /* make sure temporary file has correct mode */

                if (fchmod(fd, a_mode) < 0) {
                        progerr(ERR_FCHMOD, loc_link, a_mode, errno,
                                strerror(errno));
                }

                return (fd);
        }

        /*
         * We are not overwriting an existing file, create a new one directly.
         */

        fd = open(a_file, O_WRONLY | O_CREAT | O_TRUNC, a_mode);
        if (fd == -1) {
                if (create_path(a_ctrl, a_file) == 0) {
                        fd = open(a_file, O_WRONLY | O_CREAT | O_TRUNC, a_mode);
                }
        }

        if (fd == -1) {
                progerr(ERR_OPEN_WRITE, a_file, errno, strerror(errno));
        }

        return (fd);
}