root/usr/src/cmd/vntsd/common.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * supporting modules.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/poll.h>
#include <wait.h>
#include <time.h>
#include <netinet/in.h>
#include <thread.h>
#include <signal.h>
#include <ctype.h>
#include <langinfo.h>
#include <libintl.h>
#include <syslog.h>
#include "vntsd.h"
#include "chars.h"

/*  vntsd_write_line() - write a line to TCP client */
int
vntsd_write_line(vntsd_client_t *clientp, char *line)
{
        int rv;

        rv = vntsd_write_client(clientp, line, strlen(line));
        if (rv == VNTSD_SUCCESS) {
                rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN);
        }

        return (rv);
}

/*  vntsd_write_lines() write one or more lines to client.  */
int
vntsd_write_lines(vntsd_client_t *clientp, char *lines)
{
        char    *buf;
        char    *line;
        char    *endofline;

        buf = strdup(lines);
        if (buf == NULL) {
                return (VNTSD_ERR_NO_MEM);
        }

        line = buf;

        while ((line != NULL) && (*line != '\0')) {

                endofline = strchr(line, '\n');
                if (endofline != NULL) {
                        *endofline = '\0';
                }

                (void) vntsd_write_line(clientp, line);

                if (endofline != NULL)
                        line = endofline + 1;
                else
                        line = NULL;
        }

        free(buf);
        return (VNTSD_SUCCESS);
}

/* vntsd_get_yes_no() -  read in a "y" or "n" */
int
vntsd_get_yes_no(vntsd_client_t *clientp, char *msg, int *yes_no)
{
        char    c;
        char    yesno[8];
        int     rv;

        /* create [y/n] prompt */
        (void) snprintf(yesno, sizeof (yesno), "[%c/%c] ",
            *nl_langinfo(YESSTR), *nl_langinfo(NOSTR));

        for (; ; ) {
                if ((rv = vntsd_write_client(clientp, msg, strlen(msg)))
                    != VNTSD_SUCCESS) {
                        return (rv);
                }

                if ((rv = vntsd_write_client(clientp, yesno, strlen(yesno))) !=
                    VNTSD_SUCCESS) {
                        return (rv);
                }

                if ((rv = vntsd_read_data(clientp, &c))
                    != VNTSD_SUCCESS) {
                        return (rv);
                }

                /* echo */
                if ((rv = vntsd_write_client(clientp, &c, 1)) !=
                    VNTSD_SUCCESS) {
                        return (rv);
                }

                if ((rv = vntsd_write_client(clientp, vntsd_eol,
                    VNTSD_EOL_LEN)) != VNTSD_SUCCESS) {
                        return (rv);
                }

                c = tolower(c);

                if (c == *nl_langinfo(YESSTR)) {
                        *yes_no = B_TRUE;
                        return (VNTSD_SUCCESS);
                }

                if (c == *nl_langinfo(NOSTR)) {
                        *yes_no = B_FALSE;
                        return (VNTSD_SUCCESS);
                }

                if ((rv = vntsd_write_line(clientp,
                    gettext("Invalid response. Try again.")))
                    != VNTSD_SUCCESS) {
                        return (rv);
                }
        }

        /*NOTREACHED*/
        return (0);
}

/* vntsd_open_vcc()  -  open a vcc port */
int
vntsd_open_vcc(char *dev_name, uint_t cons_no)
{
        int     drvfd;
        int     sz;
        char    *path;
        sz = strlen(VCC_DEVICE_PATH) + strlen(dev_name)+1;

        path = calloc(sz, 1);

        if (path == NULL) {
                return (-1);
        }

        (void) snprintf(path, sz-1, VCC_DEVICE_PATH, dev_name);

        for (; ; ) {
                drvfd = open(path, O_RDWR);

                if ((drvfd < 0) && (errno == EAGAIN)) {
                        if (vntsd_vcc_ioctl(VCC_FORCE_CLOSE, cons_no, &cons_no)
                            != VNTSD_SUCCESS) {
                                break;
                        }
                } else {
                        break;
                }
        }


        if (drvfd < 0) {
                D1(stderr, "t@%d open_vcc@%s exit\n", thr_self(), dev_name);
                free(path);
                return (-1);
        }

        free(path);
        return (drvfd);
}

/* vntsd_cons_by_consno() - match a console structure to cons no */
boolean_t
vntsd_cons_by_consno(vntsd_cons_t *consp, int *cons_id)
{
        if (consp->status & VNTSD_CONS_DELETED) {
                return (B_FALSE);
        }
        return (consp->cons_no == *cons_id);
}

/* vntsd_write_client() write to telnet client */
int
vntsd_write_client(vntsd_client_t *client, char *buffer, size_t sz)
{
        int rv;


        /* write to client */
        rv = vntsd_write_fd(client->sockfd, buffer, sz);

        /* client has output, reset timer */
        vntsd_reset_timer(client->cons_tid);

        return (rv);
}

/* vntsd_write_fd() write to tcp socket file descriptor  */
int
vntsd_write_fd(int fd, void *buf, size_t sz)
{
        int n;

        while (sz > 0) {
                n = write(fd, buf, sz);
                if (n < 0) {
                        if (errno == EINTR) {
                                return (VNTSD_STATUS_INTR);
                        }

                        return (VNTSD_STATUS_CLIENT_QUIT);
                }

                if (n == 0) {
                        return (VNTSD_STATUS_CLIENT_QUIT);
                }

                buf =  (caddr_t)buf + n;
                sz -= n;
        }
        return (VNTSD_SUCCESS);

}

/*
 * vntsd_read_char() - read a char from TCP Clienti. Returns:
 * VNTSD_SUCCESS, VNTSD_STATUS_CLIENT_QUIT or VNTSD_STATUS_INTR
 */
int
vntsd_read_char(vntsd_client_t *clientp, char *c)
{
        int             n;
        vntsd_timeout_t tmo;
        int             rv;

        tmo.tid = thr_self();
        tmo.minutes = 0;
        tmo.clientp = clientp;

        /* attach to timer */
        if ((rv = vntsd_attach_timer(&tmo)) != VNTSD_SUCCESS) {
                return (rv);
        }

        n = read(clientp->sockfd, c, 1);

        /* detach from timer */
        if ((rv = vntsd_detach_timer(&tmo)) != VNTSD_SUCCESS) {
                return (rv);
        }

        if (n == 1) {
                return (VNTSD_SUCCESS);
        }

        if (n == 0) {
                return (VNTSD_STATUS_CLIENT_QUIT);
        }

        /*
         * read error or wake up by signal, either console is being removed or
         * timeout occurs.
         */
        if (errno == EINTR) {
                return (VNTSD_STATUS_INTR);
        }

        /* any other error, we close client */
        return (VNTSD_STATUS_CLIENT_QUIT);
}

/*
 * vntsd_read_data() -  handle special commands
 * such as telnet, daemon and ctrl cmds. Returns:
 * from vntsd_read_char:
 *          VNTSD_STATUS_CLIENT_QUIT
 *          VNTSD_STATUS_INTR
 * from vnts_process_daemon_cmd:
 *          VNTSD_STATUS_RESELECT_CONS
 *          VNTSD_STATUS_MOV_CONS_FORWARD
 *          VNTSD_STATUS_MOV_CONS_BACKWARD
 *          VNTSD_STATUS_ACQURE_WRITER
 *          VNTSD_STATUS_CONTINUE
 * from vntsd_telnet_cmd
 *          VNTSD_STATUS_CONTINUE
 */
int
vntsd_read_data(vntsd_client_t *clientp, char *c)
{
        int rv;

        for (; ; ) {
                if ((rv = vntsd_read_char(clientp, c)) != VNTSD_SUCCESS) {
                        return (rv);
                }

                /* daemon cmd? */
                rv = vntsd_process_daemon_cmd(clientp, *c);

                if (rv == VNTSD_SUCCESS) {
                        /* telnet cmd? */
                        rv = vntsd_telnet_cmd(clientp, *c);
                }

                if (rv == VNTSD_STATUS_CONTINUE) {
                        /*
                         * either a daemon cmd or a telnet cmd
                         * was processed.
                         */
                        clientp->prev_char = 0;
                        continue;
                }

                return (rv);
        }

        /*NOTREACHED*/
        return (0);
}
/* vntsd_read_line() -  read a line from TCP client */
int
vntsd_read_line(vntsd_client_t *clientp, char *buf, int *in_sz)
{
        char    c;
        int     rv;
        int     out_sz = 0;


        for (; ; ) {

                if ((rv =  vntsd_read_data(clientp, &c)) !=  VNTSD_SUCCESS) {
                        return (rv);
                }

                if (c == BS) {
                        /* back */
                        if ((rv = vntsd_write_client(clientp, &c, 1)) !=
                            VNTSD_SUCCESS) {
                                return (rv);
                        }

                        c = ' ';
                        if ((rv = vntsd_write_client(clientp, &c, 1)) !=
                            VNTSD_SUCCESS) {
                                return (rv);
                        }

                        buf--;
                        out_sz--;
                        continue;
                }
                /* echo */
                if ((rv = vntsd_write_client(clientp, &c, 1)) !=
                    VNTSD_SUCCESS) {
                        return (rv);
                }

                *buf++ = c;
                out_sz++;

                if (c == CR) {
                        /* end of line */
                        *in_sz = out_sz;
                        return (VNTSD_SUCCESS);
                }

                if (out_sz == *in_sz) {
                        return (VNTSD_SUCCESS);
                }
        }

        /*NOTREACHED*/
        return (0);
}

/* free a client */
void
vntsd_free_client(vntsd_client_t *clientp)
{

        if (clientp->sockfd != -1) {
                (void) close(clientp->sockfd);
        }

        (void) mutex_destroy(&clientp->lock);

        free(clientp);
}


/* check if a vcc console port still ok */
boolean_t
vntsd_vcc_cons_alive(vntsd_cons_t *consp)
{
        vcc_console_t   vcc_cons;
        int             rv;

        assert(consp);
        assert(consp->group);

        /* construct current configuration */
        (void) strncpy(vcc_cons.domain_name, consp->domain_name, MAXPATHLEN);
        (void) strncpy(vcc_cons.group_name, consp->group->group_name,
            MAXPATHLEN);
        vcc_cons.tcp_port = consp->group->tcp_port;
        vcc_cons.cons_no   = consp->cons_no;

        /* call vcc to verify */
        rv = vntsd_vcc_ioctl(VCC_CONS_STATUS, consp->cons_no, &vcc_cons);
        if (rv != VNTSD_SUCCESS) {
                return (B_FALSE);
        }

        if (vcc_cons.cons_no == -1) {
                /* port is gone */
                return (B_FALSE);
        }

        /* port is ok */
        return (B_TRUE);

}

/* add to total if a console is alive  */
static boolean_t
total_cons(vntsd_cons_t *consp, int *num_cons)
{
        int rv;

        assert(consp->group);
        rv = vntsd_vcc_err(consp);
        if (rv == VNTSD_STATUS_CONTINUE) {
                (*num_cons)++;
        }
        return (B_FALSE);
}


/* total alive consoles in a group  */
int
vntsd_chk_group_total_cons(vntsd_group_t *groupp)
{
        uint_t num_cons = 0;

        (void) vntsd_que_find(groupp->conspq, (compare_func_t)total_cons,
            &num_cons);
        return (num_cons);
}

/* vntsd_log() log function for errors */
void
vntsd_log(vntsd_status_t status, char *msg)
{
        char    *status_msg = NULL;
        int     critical = 0;

        switch (status) {

        case VNTSD_SUCCESS:
                status_msg = "STATUS_OK";
                break;

        case VNTSD_STATUS_CONTINUE:
                status_msg = "CONTINUE";
                break;

        case VNTSD_STATUS_EXIT_SIG:
                critical = 1;
                status_msg = "KILL SIGNAL RECV";
                break;

        case VNTSD_STATUS_SIG:
                status_msg = "SIG RECV";
                break;

        case VNTSD_STATUS_NO_HOST_NAME:
                status_msg = "Warining NO HOST NAME";
                break;

        case VNTSD_STATUS_CLIENT_QUIT:
                status_msg = "CLIENT CLOSED  GROUP CONNECTION";
                break;

        case VNTSD_STATUS_RESELECT_CONS:
                status_msg = "CLIENT RESELECTS CONSOLE";
                break;

        case VNTSD_STATUS_VCC_IO_ERR:
                status_msg = "CONSOLE WAS DELETED";
                break;

        case VNTSD_STATUS_MOV_CONS_FORWARD:
                status_msg = "MOVE CONSOLE FORWARD";
                break;

        case VNTSD_STATUS_MOV_CONS_BACKWARD:
                status_msg = "MOVE CONSOLE BACKWARD";
                break;

        case VNTSD_STATUS_ACQUIRE_WRITER:
                status_msg = "FORCE CONSOLE WRITE";
                break;

        case VNTSD_STATUS_INTR:
                status_msg = "RECV SIGNAL";
                break;

        case VNTSD_STATUS_DISCONN_CONS:
                status_msg = "DELETING CONSOLE";
                break;

        case VNTSD_STATUS_NO_CONS:
                status_msg = "All console(s) in the group have been deleted.";
                break;

        case VNTSD_STATUS_AUTH_ENABLED:
                critical = 1;
                status_msg = "VNTSD_STATUS_AUTH_ENABLED";
                break;

        case VNTSD_ERR_NO_MEM:
                critical = 1;
                status_msg = "NO MEMORY";
                break;

        case VNTSD_ERR_NO_DRV:
                critical = 1;
                status_msg = "NO VCC DRIVER";
                break;

        case VNTSD_ERR_WRITE_CLIENT:
                status_msg  =  "WRITE CLIENT ERR";
                break;

        case VNTSD_ERR_EL_NOT_FOUND:
                critical = 1;
                status_msg = "ELEMENT_NOT_FOUND";
                break;

        case VNTSD_ERR_VCC_CTRL_DATA:
                critical = 1;
                status_msg = "VCC CTRL DATA  ERROR";
                break;

        case VNTSD_ERR_VCC_POLL:
                critical = 1;
                status_msg = "VCC POLL ERROR";
                break;

        case VNTSD_ERR_VCC_IOCTL:
                critical = 1;
                status_msg = "VCC IOCTL ERROR";
                break;

        case VNTSD_ERR_VCC_GRP_NAME:
                critical = 1;
                status_msg = "VCC GROUP NAME ERROR";
                break;

        case VNTSD_ERR_CREATE_LISTEN_THR:
                critical = 1;
                status_msg = "FAIL TO CREATE LISTEN THREAD";
                break;

        case VNTSD_ERR_CREATE_WR_THR:
                critical = 1;
                status_msg = "FAIL TO CREATE WRITE THREAD";
                break;

        case VNTSD_ERR_ADD_CONS_FAILED:
                critical = 1;
                status_msg = "FAIL TO ADD A CONSOLE";
                break;

        case VNTSD_ERR_LISTEN_SOCKET:
                critical = 1;
                status_msg = "LISTEN SOCKET ERROR";
                break;

        case VNTSD_ERR_LISTEN_OPTS:
                critical = 1;
                status_msg = "SET SOCKET OPTIONS ERROR";
                break;

        case VNTSD_ERR_LISTEN_BIND:
                critical = 1;
                status_msg = "BIND SOCKET ERROR";
                break;

        case VNTSD_STATUS_ACCEPT_ERR:
                critical = 1;
                status_msg = "LISTEN ACCEPT ERROR";
                break;

        case VNTSD_ERR_CREATE_CONS_THR:
                critical = 1;
                status_msg = "CREATE CONSOLE THREAD ERROR ";
                break;

        case VNTSD_ERR_SIG:
                critical = 1;
                status_msg = "RECV UNKNOWN SIG";
                break;

        case VNTSD_ERR_UNKNOWN_CMD:
                critical = 1;
                status_msg = "RECV UNKNOWN COMMAND";
                break;

        case VNTSD_ERR_CLIENT_TIMEOUT:
                status_msg  =  "CLOSE CLIENT BECAUSE TIMEOUT";
                break;
        default:
                status_msg = "Unknown status recv";
                break;
        }


        if (critical) {
                syslog(LOG_ERR, "%s: thread[%d] %s\n", status_msg,
                    thr_self(), msg);
        }
#ifdef DEBUG
        DERR(stderr, "%s: thread[%d] %s\n", status_msg, thr_self(), msg);
        syslog(LOG_ERR, "%s: thread[%d] %s\n", status_msg, thr_self(), msg);
#endif
}