root/usr/src/lib/cfgadm_plugins/fp/common/cfga_rep.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */



#include <libgen.h>
#include "cfga_fp.h"

/* The following are used by update_fabric_wwn_list() */
#define COPY_EXT        ".cpy."         /* Extn used in naming backup file */
#define TMP_EXT         ".tmp."         /* Extn used in naming temp file */
static char *HDR =
"#\n"
"# fabric_WWN_map\n"
"#\n"
"# The physical ap_id list of configured fabric devices.\n"
"# Do NOT edit this file by hand -- refer to the cfgadm_fp(8)\n"
"# man page and use cfgadm(8) instead.\n"
"#\n";

/*
 * This function searches for "srch_str" (of length "slen") in "buf" (of length
 * "buflen"). If it is not found, "write_offset" has the offset in "buf" where
 * "srch_str" would have to be added in "buf". If "srch_str" is found in "buf",
 * "write_offset" has its offset in "buf"
 *
 * ARGUMENTS :
 * buf          - buffer to search in
 * buflen       - length of buffer
 * srch_str     - string to search
 * slen         - length of srch_str
 * write_offset - Set in function on exit
 *              - It is the offset in buf where srch_str is or should be
 * bytes_left   - Set in function on exit
 *              - It is the # of bytes left beyond write_offset in buf
 *
 * Notes :
 * -    This function assumes "buf" is sorted in ascending order
 * -    If 'buflen' is > 0, it assumes it has a header on top and skips it
 * -    "srch_str" has '\n' at the end, but when update_fabric_wwn_list() calls
 *      this function, 'slen' does not include the last `\n'
 *
 * RETURN VALUES :
 * Zero - "srch_str" found in "buf"... "write_offset" has offset in "buf"
 * > 0  - "srch_str" NOT found in "buf" ... "write_offset" has offset in "buf"
 *              where "srch_str" can fit in.
 *              "buf" had contents > "srch_str"
 * < 0  - "srch_str" NOT found in "buf" ... "write_offset" has offset in "buf"
 *              where "srch_str" can fit in.
 *              "buf" had contents < "srch_str"
 */
static int
search_line(char *buf, int buflen, char *srch_str, int slen,
                                int *write_offset, int *bytes_left)
{
        int     retval, sizeof_rep_hdr = strlen(HDR);
        char    *sol;           /* Pointer to Start-Of-Line */
        char    *cur_pos;       /* current position */

        *bytes_left = buflen;
        *write_offset = 0;

        if (buf == NULL || *buf == '\0' || buflen <= 0)
                return (-2);    /* Arbitrary -ve val. srch_str not found */

        if (srch_str == NULL || *srch_str == '\0' || slen <= 0)
                return (0);     /* This says srch_str was found */

        sol = cur_pos = buf;
        if (buflen >= sizeof_rep_hdr) {
                /* skip header */
                sol = cur_pos = buf + sizeof_rep_hdr;
                *bytes_left -= sizeof_rep_hdr;
        }

        while (*bytes_left >= slen) {
                if ((retval = strncmp(sol, srch_str, slen)) >= 0) {
                        /* strncmp will pass if srch_str is a substring */
                        if ((retval == 0) && (*bytes_left > slen) &&
                                                (*(sol+slen) != '\n'))
                                retval = 1;     /* Force it to be > 0 */
                        *write_offset = sol - buf;
                        return (retval);
                }

                /* retval < 0 */
                if ((cur_pos = strchr(sol, (int)'\n')) == NULL) {
                        *write_offset = buflen;
                        return (retval);
                }

                /* Get the length of this line */
                *cur_pos = '\0';        /* kludge to get string length */
                *bytes_left -= (strlen(sol) + 1);
                *cur_pos = '\n';        /* Put back the original char */

                sol = cur_pos = cur_pos + 1;
        }

        if (*bytes_left > 0) {
                /* In this case the bytes left will be less than slen */
                if ((retval = strncmp(sol, srch_str, *bytes_left)) >= 0) {
                        *write_offset = sol - buf;
                } else {
                        *write_offset = buflen;
                }
                return (retval);
        }
        *write_offset = sol - buf;
        /* Should return a value < 0 to show that search string goes to eof */
        return (-1);
}

/*
 * This function sets an advisory lock on the file pointed to by the argument
 * fd, which is a file descriptor. The lock is set using fcntl() which uses
 * flock structure.
 */
int
lock_register(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
        struct flock lock;

        lock.l_type = type;
        lock.l_start = offset;
        lock.l_whence = whence;
        lock.l_len = len;

        return (fcntl(fd, cmd, &lock));
}

/* Lot of places to cleanup - Less chance of missing out using this macro */
#define CLEANUP_N_RET(ret)      \
                        if (fd != -1) { \
                                close(fd); \
                        } \
                        if (copy_fd != -1) { \
                                close(copy_fd); \
                        } \
                        if (tmp_fd != -1) { \
                                close(tmp_fd); \
                        } \
                        if (copy_rep != NULL) { \
                                remove(copy_rep); \
                                free(copy_rep); \
                        } \
                        if (tmp_rep != NULL) { \
                                remove(tmp_rep); \
                                free(tmp_rep); \
                        } \
                        if (upd_str != NULL) { \
                                free(upd_str); \
                        } \
                        if (repbuf != NULL) { \
                                munmap(repbuf, filesize); \
                        } \
                        if (c_repbuf != NULL) { \
                                munmap(c_repbuf, filesize); \
                        } \
                        if (t_repbuf != NULL) { \
                                munmap(t_repbuf, size); \
                        } \
                        return (ret)

/*
 * INPUTS:
 * cmd          - ADD_ENTRY or REMOVE_ENTRY
 * update_str   - string for repository operation
 *              - Assumed NOT to have a '\n' and that it is null terminated
 * errstring    - Pointer that will be updated by this function
 *              - Any error msgs that has to be sent back to caller
 *
 * RETURNS :
 * FPCFGA_OK on success
 * FPCFGA_LIB_ERR on error
 *
 * SYNOPSIS:
 * This function adds or deletes 'update_str' from FAB_REPOSITORY based on
 * value of 'cmd'. The repository has a warning line on the top to disallow
 * manual editing of the file. If the repository is being created fresh or if
 * it is of zero length or if it has only warning lines in it, the operation
 * speicified by 'cmd' is performed and returned. If the repository exists
 * and has some data, it is expected to be of atleast the size of the lenght
 * of the warning header. This is the only check that is performed on the
 * validity of the file. No other checks are performed. On a valid
 * repository, to perform the update, this function basically makes use of
 * 3 buffers - the original buffer (repbuf), a copy buffer (c_repbuf) and a
 * temp buffer (t_repbuf).
 * The contents of the repository are mmap-ed into the repbuf and then
 * copied into the c_repbuf. All further operations are done using the copy.
 * t_repbuf is created to be the size of c_repbuf +/- 'slen' (based on
 * whether it is add or remove operation). After adding/removing the
 * 'update_str', the c_repbuf is copied to a OLD_FAB_REPOSITORY and t_repbuf
 * is made FAB_REPOSITORY.
 *
 */
int
update_fabric_wwn_list(int cmd, const char *update_str, char **errstring)
{
        int     fd, copy_fd, tmp_fd, new_file_flag = 0;
        int     len, write_offset, bytes_left;
        int     sizeof_rep_hdr = strlen(HDR);
        char    *repbuf, *c_repbuf, *t_repbuf;
        char    *copy_rep, *tmp_rep, *upd_str;
        off_t   filesize, size;
        struct stat     stbuf;

        /* Do some initializations */
        fd = copy_fd = tmp_fd = -1;
        repbuf = c_repbuf = t_repbuf = NULL;
        copy_rep = tmp_rep = upd_str = NULL;
        size = filesize = write_offset = bytes_left = 0;

        /*
         * Set the mode to read only.  Root user can still open as RDWR.
         * We ignore errors in general here. But, just notice ENOENTs
         */
        if ((chmod(FAB_REPOSITORY, S_IRUSR|S_IRGRP|S_IROTH) == -1) &&
                                                        (errno == ENOENT)) {
                new_file_flag = 1;
                mkdirp(FAB_REPOSITORY_DIR,
                                S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
        }

        /* Create the repository if its not there */
        if ((fd = open(FAB_REPOSITORY, O_RDWR | O_CREAT)) == -1) {
                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                return (FPCFGA_LIB_ERR);
        }

        /* Now try to chmod again. This time we dont ignore errors */
        if (fchmod(fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) {
                close(fd);
                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                return (FPCFGA_LIB_ERR);
        }

        if (lock_register(fd, F_SETLKW, F_WRLCK, 0, SEEK_SET, 0) < 0) {
                close(fd);
                cfga_err(errstring, 0, ERR_UPD_REP, 0);
                return (FPCFGA_LIB_ERR);
        }

        if (fstat(fd, &stbuf) == -1) {
                close(fd);
                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                return (FPCFGA_LIB_ERR);
        }

        filesize = size = stbuf.st_size;

        /* A very Minimal check on repository */
        if (filesize && filesize < sizeof_rep_hdr) {
                /*
                 * If there is some data, it should be atleast the size of
                 * the header
                 */
                close(fd);
                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                return (FPCFGA_LIB_ERR);
        }

        if ((len = strlen(update_str)) == 0) {
                /*
                 * We are trying to add/remove a NULL string.
                 * Just return success
                 */
                close(fd);
                return (FPCFGA_OK);
        }

        if ((upd_str = calloc(1, len + 2)) == NULL) {
                close(fd);
                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                return (FPCFGA_LIB_ERR);
        }

        strcpy(upd_str, update_str);
        strcat(upd_str, "\n");          /* Append a new line char */
        len = strlen(upd_str);

        if (filesize > 0) {
                if ((copy_rep = (char *)calloc(1, strlen(FAB_REPOSITORY) +
                                sizeof (COPY_EXT) + sizeof (pid_t))) == NULL) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                (void) sprintf(copy_rep, "%s%s%ld", FAB_REPOSITORY, COPY_EXT,
                                                                getpid());

                if ((copy_fd = open(copy_rep, O_RDWR | O_CREAT | O_TRUNC,
                                                S_IRUSR | S_IWUSR)) < 0) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if ((repbuf = (char *)mmap(0, filesize, PROT_READ,
                                        MAP_SHARED, fd, 0)) == MAP_FAILED) {
                        close(fd);
                        free(upd_str);
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        return (FPCFGA_LIB_ERR);
                }

                if (lseek(copy_fd, filesize - 1, SEEK_SET) == -1) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if (write(copy_fd, "", 1) != 1) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if ((c_repbuf = (char *)mmap(0, filesize,
                                PROT_READ | PROT_WRITE,
                                MAP_SHARED, copy_fd, 0)) == MAP_FAILED) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                memcpy(c_repbuf, repbuf, filesize);
                /*
                 * We cannot close the repository since we hold a lock
                 * But we'll free up the mmap-ed area.
                 */
                munmap(repbuf, filesize);
                repbuf = NULL;
        }

        /*
         * If we just created this file, or it was an empty repository file
         * add a header to the beginning of file.
         * If it had was a repository file with just the header,
         */
        if (new_file_flag != 0 || filesize == 0 || filesize == sizeof_rep_hdr) {
                if ((filesize != sizeof_rep_hdr) &&
                        (write(fd, HDR, sizeof_rep_hdr) != sizeof_rep_hdr)) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                /*
                 * We know its a new file, empty file or a file with only a
                 * header so lets get the update operation done with
                 */
                switch (cmd) {
                case ADD_ENTRY:
                        /* If there is a header, we have to skip it */
                        if (lseek(fd, 0, SEEK_END) == -1) {
                                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                                CLEANUP_N_RET(FPCFGA_LIB_ERR);
                        }

                        if (write(fd, upd_str, len) != len) {
                                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                                CLEANUP_N_RET(FPCFGA_LIB_ERR);
                        }

                        if (filesize > 0) {
                                /* Now create the '.old' file */
                                if (msync(c_repbuf, filesize, MS_SYNC) == -1) {
                                        cfga_err(errstring, errno,
                                                                ERR_UPD_REP, 0);
                                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                                }

                                if (fchmod(copy_fd,
                                        S_IRUSR | S_IRGRP | S_IROTH) < 0) {
                                        cfga_err(errstring, errno,
                                                                ERR_UPD_REP, 0);
                                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                                }
                                rename(copy_rep, OLD_FAB_REPOSITORY);
                        }

                        CLEANUP_N_RET(FPCFGA_OK);

                case REMOVE_ENTRY:
                        /*
                         * So, the side effect of a remove on an empty or
                         * non-existing repository is that the repository got
                         * created
                         */
                        CLEANUP_N_RET(FPCFGA_OK);

                default:
                        cfga_err(errstring, 0, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }
        }

        /* Now, size and filesize are > sizeof_rep_hdr */

        switch (cmd) {
        case ADD_ENTRY:
                size += len;
                /*
                 * We'll search the full repository, header included, since
                 * we dont expect upd_str to match anything in the header.
                 */
                if (search_line(c_repbuf, filesize, upd_str,
                                len - 1, &write_offset, &bytes_left) == 0) {
                        /* line already exists in repository or len == 0 */
                        CLEANUP_N_RET(FPCFGA_OK); /* SUCCESS */
                }

                /* construct temp file name using pid. */
                if ((tmp_rep = (char *)calloc(1, strlen(FAB_REPOSITORY) +
                                sizeof (TMP_EXT) + sizeof (pid_t))) == NULL) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                (void) sprintf(tmp_rep, "%s%s%ld", FAB_REPOSITORY,
                                                        TMP_EXT, getpid());

                /* Open tmp repository file in absolute mode */
                if ((tmp_fd = open(tmp_rep, O_RDWR|O_CREAT|O_TRUNC,
                                                S_IRUSR | S_IWUSR)) < 0) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if (lseek(tmp_fd, size - 1, SEEK_SET) == -1) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if (write(tmp_fd, "", 1) != 1) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if ((t_repbuf = (char *)mmap(0, size, PROT_READ|PROT_WRITE,
                                        MAP_SHARED, tmp_fd, 0)) == MAP_FAILED) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                memcpy(t_repbuf, c_repbuf, write_offset);
                strncpy(t_repbuf + write_offset, upd_str, len);
                if (write_offset != filesize) {
                        memcpy(t_repbuf + write_offset + len,
                                        c_repbuf + write_offset, bytes_left);
                }

                /*
                 * we are using the copy of FAB_REPOSITORY and will
                 * do msync first since it will be renamed to '.old' file.
                 */
                if (msync(c_repbuf, filesize, MS_SYNC) == -1) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if (fchmod(copy_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if (msync(t_repbuf, size, MS_SYNC) == -1) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if (fchmod(tmp_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                close(copy_fd); copy_fd = -1;
                close(tmp_fd); tmp_fd = -1;

                /* here we do rename and rename before close fd */
                rename(copy_rep, OLD_FAB_REPOSITORY);
                rename(tmp_rep, FAB_REPOSITORY);

                if (lock_register(fd, F_SETLK, F_UNLCK, 0, SEEK_SET, 0) < 0) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                CLEANUP_N_RET(FPCFGA_OK);

        case REMOVE_ENTRY:
                if (size >= sizeof_rep_hdr + len - 1) {
                        size -= len;
                        /*
                         * No need to init the 'else' part (size < len) because
                         * in that case, there will be nothing to delete from
                         * the file and so 'size' will not be used in the code
                         * below since search_line() will not find upd_str.
                         */
                }

                if (search_line(c_repbuf, filesize, upd_str, len - 1,
                                        &write_offset, &bytes_left) != 0) {
                        /* this line does not exists - nothing to remove */
                        CLEANUP_N_RET(FPCFGA_OK); /* SUCCESS */
                }

                /* construct temp file name using pid. */
                if ((tmp_rep = (char *)calloc(1, strlen(FAB_REPOSITORY) +
                                sizeof (TMP_EXT) + sizeof (pid_t))) == NULL) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                (void) sprintf(tmp_rep, "%s%s%ld", FAB_REPOSITORY,
                                                        TMP_EXT, getpid());

                /* Open tmp repository file in absolute mode */
                if ((tmp_fd = open(tmp_rep, O_RDWR|O_CREAT|O_TRUNC,
                                                S_IRUSR | S_IWUSR)) < 0) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if (size > 0) {
                        if (lseek(tmp_fd, size - 1, SEEK_SET) == -1) {
                                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                                CLEANUP_N_RET(FPCFGA_LIB_ERR);
                        }

                        if (write(tmp_fd, "", 1) != 1) {
                                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                                CLEANUP_N_RET(FPCFGA_LIB_ERR);
                        }

                        if ((t_repbuf = (char *)mmap(0, size,
                                        PROT_READ|PROT_WRITE,
                                        MAP_SHARED, tmp_fd, 0)) == MAP_FAILED) {
                                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                                CLEANUP_N_RET(FPCFGA_LIB_ERR);
                        }

                        memcpy(t_repbuf, c_repbuf, write_offset);
                        if ((bytes_left - len) > 0) {
                                memcpy(t_repbuf + write_offset,
                                        c_repbuf + write_offset + len,
                                                        bytes_left - len);
                        }

                        if (msync(t_repbuf, size, MS_SYNC) == -1) {
                                cfga_err(errstring, errno, ERR_UPD_REP, 0);
                                CLEANUP_N_RET(FPCFGA_LIB_ERR);
                        }
                }

                if (fchmod(tmp_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                /*
                 * we are using the copy of FAB_REPOSITORY and will
                 * do msync first since it will be renamed to bak file.
                 */
                if (msync(c_repbuf, filesize, MS_SYNC) == -1) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                if (fchmod(copy_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                /* Close and invalidate the fd's */
                close(copy_fd); copy_fd = -1;
                close(tmp_fd); tmp_fd = -1;

                /* here we do rename and rename before close fd */
                rename(copy_rep, OLD_FAB_REPOSITORY);
                rename(tmp_rep, FAB_REPOSITORY);

                if (lock_register(fd, F_SETLK, F_UNLCK, 0, SEEK_SET, 0) < 0) {
                        cfga_err(errstring, errno, ERR_UPD_REP, 0);
                        CLEANUP_N_RET(FPCFGA_LIB_ERR);
                }

                CLEANUP_N_RET(FPCFGA_OK);

        default:
                /* Unexpected - just getout */
                break;
        }

        CLEANUP_N_RET(FPCFGA_OK);                       /* SUCCESS */
}