/*
 * 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
 */
/* Portions Copyright 2010-2014 by Rainer Gerhards and Adiscon
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */
#include "config.h"
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <pthread.h>
#include <fcntl.h>
#include <stropts.h>
#include <assert.h>

#include <sys/param.h>
#include <sys/strlog.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <door.h>
#include <sys/door.h>

#include "rsyslog.h"
#include "srUtils.h"
#include "debug.h"
#include "imsolaris.h"

#define DOORFILE "/var/run/syslog_door"
#define RELATIVE_DOORFILE "../var/run/syslog_door"
#define OLD_DOORFILE "/etc/.syslog_door"

/* Buffer to allocate for error messages: */
#define ERRMSG_LEN 1024

/* Max number of door server threads for syslogd. Since door is used
 * to check the health of syslogd, we don't need large number of
 * server threads.
 */
#define MAX_DOOR_SERVER_THR 3


struct pollfd sun_Pfd; /* Pollfd for local log device */

static int DoorFd = -1;
static int DoorCreated = 0;
static char *DoorFileName = DOORFILE;

/* for managing door server threads */
static pthread_mutex_t door_server_cnt_lock = PTHREAD_MUTEX_INITIALIZER;
static uint_t door_server_cnt = 0;
static pthread_attr_t door_thr_attr;

/* the 'server' function that we export via the door. It does
 * nothing but return.
 */
/*ARGSUSED*/
static void server(void __attribute__((unused)) * cookie,
                   char __attribute__((unused)) * argp,
                   size_t __attribute__((unused)) arg_size,
                   door_desc_t __attribute__((unused)) * dp,
                   __attribute__((unused)) uint_t n) {
    (void)door_return(NULL, 0, NULL, 0);
    /* NOTREACHED */
}

/*ARGSUSED*/
static void *create_door_thr(void __attribute__((unused)) * arg) {
    (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    (void)door_return(NULL, 0, NULL, 0);

    /* If there is an error in door_return(), it will return here and
     * the thread will exit. Hence we need to decrement door_server_cnt.
     */
    (void)pthread_mutex_lock(&door_server_cnt_lock);
    door_server_cnt--;
    (void)pthread_mutex_unlock(&door_server_cnt_lock);
    return (NULL);
}

/*
 * Manage door server thread pool.
 */
/*ARGSUSED*/
static void door_server_pool(door_info_t __attribute__((unused)) * dip) {
    (void)pthread_mutex_lock(&door_server_cnt_lock);
    if (door_server_cnt <= MAX_DOOR_SERVER_THR && pthread_create(NULL, &door_thr_attr, create_door_thr, NULL) == 0) {
        door_server_cnt++;
        (void)pthread_mutex_unlock(&door_server_cnt_lock);
        return;
    }

    (void)pthread_mutex_unlock(&door_server_cnt_lock);
}

void sun_delete_doorfiles(void) {
    struct stat sb;
    int err;
    char line[ERRMSG_LEN + 1];

    if (lstat(DoorFileName, &sb) == 0 && !S_ISDIR(sb.st_mode)) {
        if (unlink(DoorFileName) < 0) {
            err = errno;
            (void)snprintf(line, sizeof(line), "unlink() of %s failed - fatal", DoorFileName);
            imsolaris_logerror(err, line);
            DBGPRINTF(
                "delete_doorfiles: error: %s, "
                "errno=%d\n",
                line, err);
            exit(1);
        }

        DBGPRINTF("delete_doorfiles: deleted %s\n", DoorFileName);
    }

    if (strcmp(DoorFileName, DOORFILE) == 0) {
        if (lstat(OLD_DOORFILE, &sb) == 0 && !S_ISDIR(sb.st_mode)) {
            if (unlink(OLD_DOORFILE) < 0) {
                err = errno;
                (void)snprintf(line, sizeof(line), "unlink() of %s failed", OLD_DOORFILE);
                DBGPRINTF("delete_doorfiles: %s\n", line);

                if (err != EROFS) {
                    errno = err;
                    (void)strlcat(line, " - fatal", sizeof(line));
                    imsolaris_logerror(err, line);
                    DBGPRINTF(
                        "delete_doorfiles: "
                        "error: %s, errno=%d\n",
                        line, err);
                    exit(1);
                }

                DBGPRINTF(
                    "delete_doorfiles: unlink() "
                    "failure OK on RO file system\n");
            }

            DBGPRINTF("delete_doorfiles: deleted %s\n", OLD_DOORFILE);
        }
    }

    if (DoorFd != -1) {
        (void)door_revoke(DoorFd);
    }

    DBGPRINTF("delete_doorfiles: revoked door: DoorFd=%d\n", DoorFd);
}


/* Create the door file.  If the filesystem
 * containing /etc is writable, create symlinks /etc/.syslog_door
 * to them.  On systems that do not support /var/run, create
 * /etc/.syslog_door directly.
 */
void sun_open_door(void) {
    struct stat buf;
    door_info_t info;
    char line[ERRMSG_LEN + 1];
    int err;

    /* first see if another instance of imsolaris OR another
     * syslogd is running by trying a door call - if it succeeds,
     * there is already one active.
     */

    if (!DoorCreated) {
        int door;

        if ((door = open(DoorFileName, O_RDONLY)) >= 0) {
            DBGPRINTF(
                "open_door: %s opened "
                "successfully\n",
                DoorFileName);

            if (door_info(door, &info) >= 0) {
                DBGPRINTF(
                    "open_door: "
                    "door_info:info.di_target = %ld\n",
                    info.di_target);

                if (info.di_target > 0) {
                    (void)sprintf(line,
                                  "syslogd pid %ld"
                                  " already running. Cannot "
                                  "start another syslogd pid %ld",
                                  info.di_target, getpid());
                    DBGPRINTF(
                        "open_door: error: "
                        "%s\n",
                        line);
                    imsolaris_logerror(0, line);
                    exit(1);
                }
            }

            (void)close(door);
        } else {
            if (lstat(DoorFileName, &buf) < 0) {
                err = errno;

                DBGPRINTF(
                    "open_door: lstat() of %s "
                    "failed, errno=%d\n",
                    DoorFileName, err);

                if ((door = creat(DoorFileName, 0644)) < 0) {
                    err = errno;
                    (void)snprintf(line, sizeof(line), "creat() of %s failed - fatal", DoorFileName);
                    DBGPRINTF(
                        "open_door: error: %s, "
                        "errno=%d\n",
                        line, err);
                    imsolaris_logerror(err, line);
                    sun_delete_doorfiles();
                    exit(1);
                }

                (void)fchmod(door, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

                DBGPRINTF(
                    "open_door: creat() of %s "
                    "succeeded\n",
                    DoorFileName);

                (void)close(door);
            }
        }

        if (strcmp(DoorFileName, DOORFILE) == 0) {
            if (lstat(OLD_DOORFILE, &buf) == 0) {
                DBGPRINTF(
                    "open_door: lstat() of %s "
                    "succeeded\n",
                    OLD_DOORFILE);

                if (S_ISDIR(buf.st_mode)) {
                    (void)snprintf(line, sizeof(line), "%s is a directory - fatal", OLD_DOORFILE);
                    DBGPRINTF(
                        "open_door: error: "
                        "%s\n",
                        line);
                    imsolaris_logerror(0, line);
                    sun_delete_doorfiles();
                    exit(1);
                }

                DBGPRINTF(
                    "open_door: %s is not a "
                    "directory\n",
                    OLD_DOORFILE);
                if (unlink(OLD_DOORFILE) < 0) {
                    err = errno;
                    (void)snprintf(line, sizeof(line), "unlink() of %s failed", OLD_DOORFILE);
                    DBGPRINTF("open_door: %s\n", line);

                    if (err != EROFS) {
                        DBGPRINTF(
                            "open_door: "
                            "error: %s, "
                            "errno=%d\n",
                            line, err);
                        (void)strcat(line, " - fatal");
                        imsolaris_logerror(err, line);
                        sun_delete_doorfiles();
                        exit(1);
                    }

                    DBGPRINTF(
                        "open_door: unlink "
                        "failure OK on RO file "
                        "system\n");
                }
            } else {
                DBGPRINTF(
                    "open_door: file %s doesn't "
                    "exist\n",
                    OLD_DOORFILE);
            }

            if (symlink(RELATIVE_DOORFILE, OLD_DOORFILE) < 0) {
                err = errno;
                (void)snprintf(line, sizeof(line), "symlink %s -> %s failed", OLD_DOORFILE, RELATIVE_DOORFILE);
                DBGPRINTF("open_door: %s\n", line);

                if (err != EROFS) {
                    DBGPRINTF(
                        "open_door: error: %s, "
                        "errno=%d\n",
                        line, err);
                    (void)strcat(line, " - fatal");
                    imsolaris_logerror(err, line);
                    sun_delete_doorfiles();
                    exit(1);
                }

                DBGPRINTF(
                    "open_door: symlink failure OK "
                    "on RO file system\n");
            } else {
                DBGPRINTF(
                    "open_door: symlink %s -> %s "
                    "succeeded\n",
                    OLD_DOORFILE, RELATIVE_DOORFILE);
            }
        }

        if ((DoorFd = door_create(server, 0, DOOR_REFUSE_DESC)) < 0) {
            //???? DOOR_NO_CANEL requires newer libs??? DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) < 0) {
            err = errno;
            (void)sprintf(line, "door_create() failed - fatal");
            DBGPRINTF("open_door: error: %s, errno=%d\n", line, err);
            imsolaris_logerror(err, line);
            sun_delete_doorfiles();
            exit(1);
        }
        //???? (void) door_setparam(DoorFd, DOOR_PARAM_DATA_MAX, 0);
        DBGPRINTF(
            "open_door: door_create() succeeded, "
            "DoorFd=%d\n",
            DoorFd);

        DoorCreated = 1;
    }

    (void)fdetach(DoorFileName); /* just in case... */

    (void)door_server_create(door_server_pool);

    if (fattach(DoorFd, DoorFileName) < 0) {
        err = errno;
        (void)snprintf(line, sizeof(line),
                       "fattach() of fd"
                       " %d to %s failed - fatal",
                       DoorFd, DoorFileName);
        DBGPRINTF("open_door: error: %s, errno=%d\n", line, err);
        imsolaris_logerror(err, line);
        sun_delete_doorfiles();
        exit(1);
    }

    DBGPRINTF("open_door: attached server() to %s\n", DoorFileName);
}


/* Attempts to open the local log device
 * and return a file descriptor.
 */
rsRetVal sun_openklog(char *name) {
    DEFiRet;
    int fd;
    struct strioctl str;
    char errBuf[1024];

    if ((fd = open(name, O_RDONLY)) < 0) {
        rs_strerror_r(errno, errBuf, sizeof(errBuf));
        DBGPRINTF("imsolaris:openklog: cannot open %s: %s\n", name, errBuf);
        ABORT_FINALIZE(RS_RET_ERR_OPEN_KLOG);
    }
    str.ic_cmd = I_CONSLOG;
    str.ic_timout = 0;
    str.ic_len = 0;
    str.ic_dp = NULL;
    if (ioctl(fd, I_STR, &str) < 0) {
        rs_strerror_r(errno, errBuf, sizeof(errBuf));
        DBGPRINTF(
            "imsolaris:openklog: cannot register to log "
            "console messages: %s\n",
            errBuf);
        ABORT_FINALIZE(RS_RET_ERR_AQ_CONLOG);
    }
    sun_Pfd.fd = fd;
    sun_Pfd.events = POLLIN;
    DBGPRINTF("imsolaris/openklog: opened '%s' as fd %d.\n", name, fd);

finalize_it:
    RETiRet;
}
