root/usr/src/cmd/ttymon/ttymon.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.
 */

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

#include <ctype.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/stropts.h>
#include <sys/resource.h>
#include <sys/termios.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <ulimit.h>
#include <libdevinfo.h>

#include "sac.h"
#include "ttymon.h"
#include "tmstruct.h"
#include "tmextern.h"

static  int     Initialized;

static  void    initialize(void);
static  void    open_all(void);
static  int     set_poll(struct pollfd *);
static  int     check_spawnlimit(struct pmtab *);
static  int     mod_ttydefs(void);

void    open_device(struct pmtab *);
void    set_softcar(struct pmtab *);

/*
 *      ttymon  - a port monitor under SAC
 *              - monitor ports, set terminal modes, baud rate
 *                and line discipline for the port
 *              - invoke service on port if connection request received
 *              - Usage: ttymon
 *                       ttymon -g [options]
 *                       Valid options are
 *                       -h
 *                       -d device
 *                       -l ttylabel
 *                       -t timeout
 *                       -m modules
 *                       -p prompt
 *
 *              - ttymon without args is invoked by SAC
 *              - ttymon -g is invoked by process that needs to
 *                have login service on the fly
 */

int
main(int argc, char *argv[])
{
        int     nfds;

        /*
         * Only the superuser should execute this command.
         */
        if (getuid() != 0)
                return (1);

        /* remember original signal mask and dispositions */
        (void) sigprocmask(SIG_SETMASK, NULL, &Origmask);
        (void) sigaction(SIGINT, NULL, &Sigint);
        (void) sigaction(SIGALRM, NULL, &Sigalrm);
        (void) sigaction(SIGPOLL, NULL, &Sigpoll);
        (void) sigaction(SIGQUIT, NULL, &Sigquit);
        (void) sigaction(SIGCLD, NULL, &Sigcld);
        (void) sigaction(SIGTERM, NULL, &Sigterm);
#ifdef  DEBUG
        (void) sigaction(SIGUSR1, NULL, &Sigusr1);
        (void) sigaction(SIGUSR2, NULL, &Sigusr2);
#endif

        /*
         * SIGQUIT needs to be ignored. Otherwise, hitting ^\ from
         * console kills ttymon.
         */
        (void) signal(SIGQUIT, SIG_IGN);

        if ((argc > 1) || (strcmp(lastname(argv[0]), "getty") == 0)) {
                ttymon_express(argc, argv);
                return (1);     /*NOTREACHED*/
        }

        initialize();

        for (;;) {
                nfds = set_poll(Pollp);
                if (!Reread_flag) {
                        if (nfds > 0)
                                do_poll(Pollp, nfds);
                        else
                                (void) pause();
                }
                /*
                 * READDB messages may arrive during poll or pause.
                 * So the flag needs to be checked again.
                 */
                if (Reread_flag) {
                        Reread_flag = FALSE;
                        re_read();
                }
                while (Retry) {
                        Retry = FALSE;
                        open_all();
                }
        }
}

static  void
initialize(void)
{
        struct  pmtab   *tp;
        struct passwd *pwdp;
        struct  group   *gp;
        struct  rlimit rlimit;

        Initialized = FALSE;
        /*
         * get_environ() must be called first,
         * otherwise we don't know where the log file is
         */
        get_environ();
        openttymonlog();
        openpid();
        openpipes();
        setup_PCpipe();

        log("PMTAG: %s", Tag);
        log("Starting state: %s",
            (State == PM_ENABLED) ? "enabled" : "disabled");

#ifdef  DEBUG
        opendebug(FALSE);
        debug("***** ttymon in initialize *****");
        log("debug mode is \t on");
#endif

        catch_signals();

        /* register to receive SIGPOLL when data comes to pmpipe */
        if (ioctl(Pfd, I_SETSIG, S_INPUT) < 0)
                fatal("I_SETSIG on pmpipe failed: %s", strerror(errno));

        sacpoll(); /* this is needed because there may be data already */

        Maxfiles = (int)ulimit(4, 0L);  /* get max number of open files */
        if (Maxfiles < 0)
                fatal("ulimit(4,0L) failed: %s", strerror(errno));

        if (getrlimit(RLIMIT_NOFILE, &Rlimit) == -1)
                fatal("getrlimit failed: %s", strerror(errno));

        rlimit.rlim_cur = rlimit.rlim_max = Rlimit.rlim_max;
        if (setrlimit(RLIMIT_NOFILE, &rlimit) == -1)
                fatal("setrlimit failed: %s", strerror(errno));

        (void) enable_extended_FILE_stdio(-1, -1);

        Maxfiles = rlimit.rlim_cur;
        Maxfds = Maxfiles - FILE_RESERVED;

        log("max open files = %d", Maxfiles);
        log("max ports ttymon can monitor = %d", Maxfds);

        read_pmtab();

        /*
         * setup poll array
         *      - we allocate 10 extra pollfd so that
         *        we do not have to re-malloc when there is
         *        minor fluctuation in Nentries
         */
        Npollfd = Nentries + 10;
        if (Npollfd > Maxfds)
                Npollfd = Maxfds;
        if ((Pollp = (struct pollfd *)
            malloc((unsigned)(Npollfd * sizeof (struct pollfd))))
            == (struct pollfd *)NULL)
                fatal("malloc for Pollp failed");

        (void) mod_ttydefs();   /* just to initialize Mtime */
        if (check_version(TTYDEFS_VERS, TTYDEFS) != 0)
                fatal("check /etc/ttydefs version failed");

        read_ttydefs(NULL, FALSE);

        /* initialize global variables, Uucp_uid & Tty_gid */
        if ((pwdp = getpwnam(UUCP)) != NULL)
                Uucp_uid = pwdp->pw_uid;
        if ((gp = getgrnam(TTY)) == NULL)
                log("no group entry for <tty>, default is used");
        else
                Tty_gid = gp->gr_gid;
        endgrent();
        endpwent();
#ifdef  DEBUG
        debug("Uucp_uid = %u, Tty_gid = %u", Uucp_uid, Tty_gid);
#endif

        log("Initialization Completed");

        /* open the devices ttymon monitors */
        Retry = TRUE;
        while (Retry) {
                Retry = FALSE;
                for (tp = PMtab; tp; tp = tp->p_next) {
                        if ((tp->p_status > 0) && (tp->p_fd == 0) &&
                            (tp->p_childpid == 0) &&
                            !(tp->p_ttyflags & I_FLAG) &&
                            (!((State == PM_DISABLED) &&
                            ((tp->p_dmsg == NULL)||(*(tp->p_dmsg) == '\0'))))) {
                                open_device(tp);
                                if (tp->p_fd > 0)
                                        got_carrier(tp);
                        }
                }
        }
        Initialized = TRUE;
}

static  void    free_defs(void);

/*
 *      open_all - open devices in pmtab if the entry is
 *               - valid, fd = 0, and pid = 0
 */
static void
open_all(void)
{
        struct  pmtab   *tp;
        int     check_modtime;
        sigset_t cset;
        sigset_t tset;

#ifdef  DEBUG
        debug("in open_all");
#endif
        check_modtime = TRUE;

        for (tp = PMtab; tp; tp = tp->p_next) {
                if ((tp->p_status > 0) && (tp->p_fd == 0) &&
                    (tp->p_childpid == 0) &&
                    !(tp->p_ttyflags & I_FLAG) && (!((State == PM_DISABLED) &&
                    ((tp->p_dmsg == NULL)||(*(tp->p_dmsg) == '\0'))))) {
                        /*
                         * if we have not check modification time and
                         * /etc/ttydefs was modified, need to re-read it
                         */
                        if (check_modtime && mod_ttydefs()) {
                                check_modtime = FALSE;
                                (void) sigprocmask(SIG_SETMASK, NULL, &cset);
                                tset = cset;
                                (void) sigaddset(&tset, SIGCLD);
                                (void) sigprocmask(SIG_SETMASK, &tset, NULL);
                                free_defs();
#ifdef  DEBUG
                                debug("/etc/ttydefs is modified, re-read it");
#endif
                                read_ttydefs(NULL, FALSE);
                                (void) sigprocmask(SIG_SETMASK, &cset, NULL);
                        }
                        open_device(tp);
                        if (tp->p_fd > 0)
                                got_carrier(tp);
                } else if (((tp->p_status == LOCKED) ||
                    (tp->p_status == SESSION) ||
                    (tp->p_status == UNACCESS)) &&
                    (tp->p_fd > 0) &&
                    (!((State == PM_DISABLED) &&
                    ((tp->p_dmsg == NULL)||(*(tp->p_dmsg) == '\0'))))) {
                        if (check_modtime && mod_ttydefs()) {
                                check_modtime = FALSE;
                                (void) sigprocmask(SIG_SETMASK, NULL, &cset);
                                tset = cset;
                                (void) sigaddset(&tset, SIGCLD);
                                (void) sigprocmask(SIG_SETMASK, &tset, NULL);
                                free_defs();
#ifdef  DEBUG
                                debug("/etc/ttydefs is modified, re-read it");
#endif
                                read_ttydefs(NULL, FALSE);
                                (void) sigprocmask(SIG_SETMASK, &cset, NULL);
                        }
                        tp->p_status = VALID;
                        open_device(tp);
                        if (tp->p_fd > 0)
                                got_carrier(tp);
                }
        }
}

void
set_softcar(struct pmtab *pmptr)
{

        int fd, val = 0;

#ifdef  DEBUG
        debug("in set_softcar");
#endif
        /*
         * If soft carrier is not set one way or
         * the other, leave it alone.
         */
        if (*pmptr->p_softcar == '\0')
                return;

        if (*pmptr->p_softcar == 'y')
                val = 1;

        if ((fd = open(pmptr->p_device, O_RDONLY|O_NONBLOCK|O_NOCTTY)) < 0) {
                log("open (%s) failed: %s", pmptr->p_device, strerror(errno));
                return;
        }

        if (ioctl(fd, TIOCSSOFTCAR, &val) < 0)
                log("set soft-carrier (%s) failed: %s", pmptr->p_device,
                    strerror(errno));

        (void) close(fd);
}


/*
 *      open_device(pmptr)      - open the device
 *                              - check device lock
 *                              - change owner of device
 *                              - push line disciplines
 *                              - set termio
 */

void
open_device(struct pmtab *pmptr)
{
        int     fd, tmpfd;
        struct  sigaction       sigact;

#ifdef  DEBUG
        debug("in open_device");
#endif

        if (pmptr->p_status == GETTY) {
                revokedevaccess(pmptr->p_device, 0, 0, 0);

                if ((fd = open(pmptr->p_device, O_RDWR)) == -1)
                        fatal("open (%s) failed: %s", pmptr->p_device,
                            strerror(errno));

        } else {
                if (check_spawnlimit(pmptr) == -1) {
                        pmptr->p_status = NOTVALID;
                        log("service <%s> is respawning too rapidly",
                            pmptr->p_tag);
                        return;
                }
                if (pmptr->p_fd > 0) { /* file already open */
                        fd = pmptr->p_fd;
                        pmptr->p_fd = 0;
                } else if ((fd = open(pmptr->p_device, O_RDWR|O_NONBLOCK))
                    == -1) {
                        log("open (%s) failed: %s", pmptr->p_device,
                            strerror(errno));
                        if ((errno ==  ENODEV) || (errno == EBUSY)) {
                                pmptr->p_status = UNACCESS;
                                Nlocked++;
                                if (Nlocked == 1) {
                                        sigact.sa_flags = 0;
                                        sigact.sa_handler = sigalarm;
                                        (void) sigemptyset(&sigact.sa_mask);
                                        (void) sigaction(SIGALRM, &sigact,
                                            NULL);
                                        (void) alarm(ALARMTIME);
                                }
                        } else
                                Retry = TRUE;
                        return;
                }
                /* set close-on-exec flag */
                if (fcntl(fd, F_SETFD, 1) == -1)
                        fatal("F_SETFD fcntl failed: %s", strerror(errno));

                if (tm_checklock(fd) != 0) {
                        pmptr->p_status = LOCKED;
                        (void) close(fd);
                        Nlocked++;
                        if (Nlocked == 1) {
                                sigact.sa_flags = 0;
                                sigact.sa_handler = sigalarm;
                                (void) sigemptyset(&sigact.sa_mask);
                                (void) sigaction(SIGALRM, &sigact, NULL);
                                (void) alarm(ALARMTIME);
                        }
                        return;
                }
                if (check_session(fd) != 0) {
                        if ((Initialized) && (pmptr->p_inservice != SESSION)) {
                                log("Warning -- active session exists on <%s>",
                                    pmptr->p_device);
                        } else {
                                /*
                                 * this may happen if a service is running
                                 * and ttymon dies and is restarted,
                                 * or another process is running on the
                                 * port.
                                 */
                                pmptr->p_status = SESSION;
                                pmptr->p_inservice = 0;
                                (void) close(fd);
                                Nlocked++;
                                if (Nlocked == 1) {
                                        sigact.sa_flags = 0;
                                        sigact.sa_handler = sigalarm;
                                        (void) sigemptyset(&sigact.sa_mask);
                                        (void) sigaction(SIGALRM, &sigact,
                                            NULL);
                                        (void) alarm(ALARMTIME);
                                }
                                return;
                        }
                }
                pmptr->p_inservice = 0;
        }

        if (pmptr->p_ttyflags & H_FLAG) {
                /* drop DTR */
                (void) hang_up_line(fd);
                /*
                 * After hang_up_line, the stream is in STRHUP state.
                 * We need to do another open to reinitialize streams
                 * then we can close one fd
                 */
                if ((tmpfd = open(pmptr->p_device, O_RDWR|O_NONBLOCK)) == -1) {
                        log("open (%s) failed: %s", pmptr->p_device,
                            strerror(errno));
                        Retry = TRUE;
                        (void) close(fd);
                        return;
                }
                (void) close(tmpfd);
        }

#ifdef DEBUG
        debug("open_device (%s), fd = %d", pmptr->p_device, fd);
#endif

        /* Change ownership of the tty line to root/uucp and */
        /* set protections to only allow root/uucp to read the line. */

        if (pmptr->p_ttyflags & (B_FLAG|C_FLAG))
                (void) fchown(fd, Uucp_uid, Tty_gid);
        else
                (void) fchown(fd, ROOTUID, Tty_gid);
        (void) fchmod(fd, 0620);

        if ((pmptr->p_modules != NULL)&&(*(pmptr->p_modules) != '\0')) {
                if (push_linedisc(fd, pmptr->p_modules, pmptr->p_device)
                    == -1) {
                        Retry = TRUE;
                        (void) close(fd);
                        return;
                }
        }

        if (initial_termio(fd, pmptr) == -1)  {
                Retry = TRUE;
                (void) close(fd);
                return;
        }

        (void) di_devperm_logout((const char *)pmptr->p_device);
        pmptr->p_fd = fd;
}

/*
 *      set_poll(fdp)   - put all fd's in a pollfd array
 *                      - set poll event to POLLIN and POLLMSG
 *                      - return number of fd to be polled
 */

static  int
set_poll(struct pollfd *fdp)
{
        struct  pmtab   *tp;
        int     nfd = 0;

        for (tp = PMtab; tp; tp = tp->p_next) {
                if (tp->p_fd > 0)  {
                        fdp->fd = tp->p_fd;
                        fdp->events = POLLIN;
                        fdp++;
                        nfd++;
                }
        }
        return (nfd);
}

/*
 *      check_spawnlimit        - return 0 if spawnlimit is not reached
 *                              - otherwise return -1
 */
static  int
check_spawnlimit(struct pmtab *pmptr)
{
        time_t  now;

        (void) time(&now);
        if (pmptr->p_time == 0L)
                pmptr->p_time = now;
        if (pmptr->p_respawn >= SPAWN_LIMIT) {
                if ((now - pmptr->p_time) < SPAWN_INTERVAL) {
                        pmptr->p_time = now;
                        pmptr->p_respawn = 0;
                        return (-1);
                }
                pmptr->p_time = now;
                pmptr->p_respawn = 0;
        }
        pmptr->p_respawn++;
        return (0);
}

/*
 * mod_ttydefs  - to check if /etc/ttydefs has been modified
 *              - return TRUE if file modified
 *              - otherwise, return FALSE
 */
static  int
mod_ttydefs(void)
{
        struct  stat    statbuf;

        if (stat(TTYDEFS, &statbuf) == -1) {
                /* if stat failed, don't bother reread ttydefs */
                return (FALSE);
        }
        if ((long)statbuf.st_mtime != Mtime) {
                Mtime = (long)statbuf.st_mtime;
                return (TRUE);
        }
        return (FALSE);
}

/*
 *      free_defs - free the Gdef table
 */
static  void
free_defs(void)
{
        int     i;
        struct  Gdef    *tp;
        tp = &Gdef[0];
        for (i = 0; i < Ndefs; i++, tp++) {
                free(tp->g_id);
                free(tp->g_iflags);
                free(tp->g_fflags);
                free(tp->g_nextid);
                tp->g_id = NULL;
                tp->g_iflags = NULL;
                tp->g_fflags = NULL;
                tp->g_nextid = NULL;
        }
        Ndefs = 0;
}

/*
 * rebuild flags entry using speed from ttymode.
 */
static char *
merge_flags(char *src, char *ttymode)
{
        char *data, *ptr, *flags;

        /* copy speed entry */
        data = strsave(src);
        flags = strsave(ttymode);
        ptr = strchr(flags, ',');
        if (ptr == NULL) {      /* ttymode is corrupted */
                free(flags);
                return (data);
        }
        *ptr = '\0';
        ptr = flags;
        flags = strsave(flags);
        free(ptr);

        /*
         * The flags line is supposed to have stty keywords separated by space.
         * We need to split up the keywords, replace the speed and
         * reconstruct the flags line.
         */

        ptr = strtok(data, " \t");
        if (ptr == NULL) {
                free(data);
                return (flags);
        }

        do {
                char *tmp;

                /* skip speed */
                if (isdigit(*ptr))
                        continue;

                if (asprintf(&tmp, "%s %s", flags, ptr) <= 0) {
                        /* should we complain? */
                        break;
                }
                free(flags);
                flags = tmp;
        } while ((ptr = strtok(NULL, " \t")) != NULL);

        free(data);
        return (flags);
}

/*
 * struct Gdef *get_speed(ttylabel)
 *      - search "/etc/ttydefs" for speed and term. specification
 *        using "ttylabel". If "ttylabel" is NULL, default
 *        to DEFAULT
 *      - for /dev/console, if we are in fact using serial console,
 *        use ttyX-mode value to get speed. This allows us to use
 *        the value set for serial console either from firmware (or BMC sol),
 *        or boot loader default.
 * arg:   ttylabel - label/id of speed settings.
 */

struct Gdef *
get_speed(struct pmtab *pmptr)
{
        static struct Gdef serial = { 0 };
        struct Gdef *sp;
        char *ttylabel = pmptr->p_ttylabel;

        if ((ttylabel != NULL) && (*ttylabel != '\0')) {
                if ((sp = find_def(ttylabel)) == NULL) {
                        log("unable to find <%s> in \"%s\"", ttylabel, TTYDEFS);
                        sp = &DEFAULT; /* use default */
                }
        } else {
                sp = &DEFAULT; /* use default */
        }

        /*
         * if this is not /dev/console or /dev/console is not using serial,
         * we are done.
         */
        if (pmptr->p_ttymode == NULL ||
            strcmp(pmptr->p_device, "/dev/console") != 0)
                return (sp);

        /* is entry for serial set up? */
        if (serial.g_id == NULL) {
                /*
                 * Copy data from sp, except we need to update inital and
                 * final flags.
                 */
                serial.g_id = strsave(sp->g_id);
                serial.g_iflags = merge_flags(sp->g_iflags, pmptr->p_ttymode);
                serial.g_fflags = merge_flags(sp->g_fflags, pmptr->p_ttymode);
                serial.g_autobaud = sp->g_autobaud;
                serial.g_nextid = strsave(sp->g_nextid);
        }
        return (&serial);
}

/*
 * setup_PCpipe()       - setup the pipe between Parent and Children
 *                      - the pipe is used for a tmchild to send its
 *                        pid to inform ttymon that it is about to
 *                        invoke service
 *                      - the pipe also serves as a mean for tmchild
 *                        to detect failure of ttymon
 */
void
setup_PCpipe(void)
{
        int     flag = 0;

        if (pipe(PCpipe) == -1)
                fatal("pipe() failed: %s", strerror(errno));

        /* set close-on-exec flag */
        if (fcntl(PCpipe[0], F_SETFD, 1) == -1)
                fatal("F_SETFD fcntl failed: %s", strerror(errno));

        if (fcntl(PCpipe[1], F_SETFD, 1) == -1)
                fatal("F_SETFD fcntl failed: %s", strerror(errno));

        /* set O_NONBLOCK flag */
        if (fcntl(PCpipe[0], F_GETFL, flag) == -1)
                fatal("F_GETFL failed: %s", strerror(errno));

        flag |= O_NONBLOCK;
        if (fcntl(PCpipe[0], F_SETFL, flag) == -1)
                fatal("F_SETFL failed: %s", strerror(errno));

        /* set message discard mode */
        if (ioctl(PCpipe[0], I_SRDOPT, RMSGD) == -1)
                fatal("I_SRDOPT RMSGD failed: %s", strerror(errno));

        /* register to receive SIGPOLL when data come */
        if (ioctl(PCpipe[0], I_SETSIG, S_INPUT) == -1)
                fatal("I_SETSIG S_INPUT failed: %s", strerror(errno));

#ifdef  DEBUG
        log("PCpipe[0]\t = %d", PCpipe[0]);
        log("PCpipe[1]\t = %d", PCpipe[1]);
#endif
}