/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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. */ #include "utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define CONSADM "/usr/sbin/consadm" #define CONSADMD "/usr/sbin/consadmd" #define CONSADMLOCK "/tmp/CoNsAdM.lck" #define CONSDAEMON "consadmd" #define MSGLOG "/dev/msglog" #define CONSOLE "/dev/console" #define WSCONS "/dev/wscons" #define CONSCONFIG "/etc/consadm.conf" #define SETCONSOLEPID "/etc/consadm.pid" #define CONFIG 0 #define UNCONFIG 1 #define COMMENT '#' #define NEWLINE '\n' #define SPACE ' ' #define TAB ' ' #define E_SUCCESS 0 /* Exit status for success */ #define E_ERROR 1 /* Exit status for error */ #define E_USAGE 2 /* Exit status for usage error */ #define E_NO_CARRIER 3 /* Exit status for no carrier */ /* useful data structures for lock function */ static struct flock fl; #define LOCK_EX F_WRLCK static char usage[] = "Usage: \n" "\tconsadm [ -p ] [ -a device ... ]\n" "\tconsadm [ -p ] [ -d device ... ]\n" "\tconsadm [ -p ]\n"; /* data structures ... */ static char conshdr[] = "#\n# consadm.conf\n#" "# Configuration parameters for console message redirection.\n" "# Do NOT edit this file by hand -- use consadm(8) instead.\n" "#\n"; const char *pname; /* program name */ static sigjmp_buf deadline; /* command line arguments */ static int display; static int persist; static int addflag; static int deleteflag; /* function headers */ static void setaux(char *); static void unsetaux(char *); static void getconsole(void); static boolean_t has_carrier(int fd); static boolean_t modem_support(int fd); static void setfallback(char *argv[]); static void removefallback(void); static void fallbackdaemon(void); static void persistlist(void); static int verifyarg(char *, int); static int safeopen(char *); static void catch_term(int); static void catch_alarm(int); static void catch_hup(int); static void cleanup_on_exit(int); static void addtolist(char *); static void removefromlist(char *); static int pathcmp(char *, char *); static int lckfunc(int, int); typedef void (*sig_handler_t)(); static int getlock(void); /* * In main, return codes carry the following meaning: * 0 - successful * 1 - error during the command execution */ int main(int argc, char *argv[]) { int index; struct sigaction sa; int c; char *p = strrchr(argv[0], '/'); if (p == NULL) p = argv[0]; else p++; pname = p; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); if (getuid() != 0) die(gettext("must be root to run this program\n")); /* * Handle normal termination signals that may be received. */ sa.sa_handler = SIG_IGN; sa.sa_flags = 0; (void) sigemptyset(&sa.sa_mask); (void) sigaction(SIGHUP, &sa, NULL); (void) sigaction(SIGINT, &sa, NULL); (void) sigaction(SIGQUIT, &sa, NULL); (void) sigaction(SIGTERM, &sa, NULL); /* * To make sure persistent state gets removed. */ sa.sa_handler = cleanup_on_exit; sa.sa_flags = 0; (void) sigemptyset(&sa.sa_mask); (void) sigaction(SIGSEGV, &sa, NULL); (void) sigaction(SIGILL, &sa, NULL); (void) sigaction(SIGABRT, &sa, NULL); (void) sigaction(SIGBUS, &sa, NULL); if (strcmp(pname, CONSDAEMON) == 0) { fallbackdaemon(); return (E_SUCCESS); } if (argc == 1) display++; else { while ((c = getopt(argc, argv, "adp")) != EOF) { switch (c) { case 'a': addflag++; break; case 'd': deleteflag++; break; case 'p': persist++; break; default: (void) fprintf(stderr, gettext(usage)); exit(E_USAGE); /*NOTREACHED*/ } } } if (display) { getconsole(); return (E_SUCCESS); } if (addflag && deleteflag) { (void) fprintf(stderr, gettext(usage)); return (E_ERROR); } if (addflag) { if (optind == argc) { (void) fprintf(stderr, gettext(usage)); return (E_ERROR); } /* separately check every device path specified */ for (index = optind; index < argc; index++) { if (verifyarg(argv[index], addflag)) return (E_ERROR); } for (index = optind; index < argc; index++) { setaux(argv[index]); if (persist) addtolist(argv[index]); } /* * start/restart daemon based on the auxilary * consoles at this time. */ setfallback(argv); return (E_SUCCESS); } else if (deleteflag) { if (optind == argc) { (void) fprintf(stderr, gettext(usage)); return (E_ERROR); } /* separately check every device path specified */ for (index = optind; index < argc; index++) { if (verifyarg(argv[index], 0)) return (E_ERROR); } for (index = optind; index < argc; index++) { unsetaux(argv[index]); if (persist && deleteflag) removefromlist(argv[index]); } /* * kill off daemon and restart with * new list of auxiliary consoles */ setfallback(argv); return (E_SUCCESS); } else if (persist) { if (optind < argc) { (void) fprintf(stderr, gettext(usage)); return (E_ERROR); } persistlist(); return (E_SUCCESS); } else { (void) fprintf(stderr, gettext(usage)); return (E_ERROR); } } /* main */ /* for daemon to handle termination from user command */ static void catch_term(int signal __unused) { exit(E_SUCCESS); } /* handle lack of carrier on open */ static void catch_alarm(int signal __unused) { siglongjmp(deadline, 1); } /* caught a sighup */ static void catch_hup(int signal __unused) { /* * ttymon sends sighup to consadmd because it has the serial * port open. We catch the signal here, but process it * within fallbackdaemon(). We ignore the signal if the * errno returned was EINTR. */ } /* Remove persistent state on receiving signal. */ static void cleanup_on_exit(int signal __unused) { (void) unlink(CONSADMLOCK); exit(E_ERROR); } /* * send ioctl to /dev/sysmsg to route msgs of the device specified. */ static void setaux(char *dev) { int fd; if ((fd = safeopen(SYSMSG)) < 0) die(gettext("%s is missing or not a valid device\n"), SYSMSG); if (ioctl(fd, CIOCSETCONSOLE, dev) != 0) { /* * Let setting duplicate device be warning, consadm * must proceed to set persistence if requested. */ if (errno == EBUSY) die(gettext("%s is already the default console\n"), dev); else if (errno != EEXIST) die(gettext("cannot get table entry")); } syslog(LOG_WARNING, "%s: Added auxiliary device %s", CONSADM, dev); (void) close(fd); } /* * Send ioctl to device specified and * Remove the entry from the list of auxiliary devices. */ static void unsetaux(char *dev) { int fd; if ((fd = safeopen(SYSMSG)) < 0) die(gettext("%s is missing or not a valid device\n"), SYSMSG); if (ioctl(fd, CIOCRMCONSOLE, dev) != 0) { if (errno == EBUSY) die(gettext("cannot unset the default console\n")); } else syslog(LOG_WARNING, "%s: Removed auxiliary device %s", CONSADM, dev); (void) close(fd); } static int getlock(void) { int lckfd; if ((lckfd = open(CONSADMLOCK, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR)) < 0) { if (errno == EEXIST) die(gettext("currently busy, try again later.\n")); else die(gettext("cannot open %s"), CONSADMLOCK); } if (lckfunc(lckfd, LOCK_EX) == -1) { (void) close(lckfd); (void) unlink(CONSADMLOCK); die(gettext("fcntl operation failed")); } return (lckfd); } static void addtolist(char *dev) { int lckfd, fd; FILE *fp, *nfp; char newfile[MAXPATHLEN]; char buf[MAXPATHLEN]; int len; boolean_t found = B_FALSE; /* update file of devices configured to get console msgs. */ lckfd = getlock(); /* Open new file */ (void) snprintf(newfile, sizeof (newfile), "%s%d", CONSCONFIG, (int)getpid()); if (((fd = creat(newfile, 0644)) < 0) || ((nfp = fdopen(fd, "w")) == NULL)) { (void) close(lckfd); (void) unlink(CONSADMLOCK); die(gettext("could not create new %s file"), CONSCONFIG); } /* Add header to new file */ (void) fprintf(nfp, "%s", conshdr); /* Check that the file doesn't already exist */ if ((fp = fopen(CONSCONFIG, "r")) != NULL) { while (fgets(buf, MAXPATHLEN, fp) != NULL) { if (buf[0] == COMMENT || buf[0] == NEWLINE || buf[0] == SPACE || buf[0] == TAB) continue; len = strlen(buf); buf[len - 1] = '\0'; /* Clear carriage return */ if (pathcmp(dev, buf) == 0) { /* they match so use name passed in. */ (void) fprintf(nfp, "%s\n", dev); found = B_TRUE; } else (void) fprintf(nfp, "%s\n", buf); } } /* User specified persistent settings */ if (found == B_FALSE) (void) fprintf(nfp, "%s\n", dev); (void) fclose(fp); (void) fclose(nfp); (void) rename(newfile, CONSCONFIG); (void) close(lckfd); (void) unlink(CONSADMLOCK); } /* The list in CONSCONFIG gives the persistence capability in the proto */ static void removefromlist(char *dev) { int lckfd; FILE *fp, *nfp; char newfile[MAXPATHLEN + 1]; char len; char value[MAXPATHLEN + 1]; boolean_t newcontents = B_FALSE; /* update file of devices configured to get console msgs. */ lckfd = getlock(); if ((fp = fopen(CONSCONFIG, "r")) == NULL) { (void) close(lckfd); (void) unlink(CONSADMLOCK); return; } /* Open new file */ (void) snprintf(newfile, sizeof (newfile), "%s%d", CONSCONFIG, (int)getpid()); if ((nfp = fopen(newfile, "w")) == NULL) { (void) close(lckfd); (void) unlink(CONSADMLOCK); die(gettext("cannot create new %s file"), CONSCONFIG); } /* Add header to new file */ (void) fprintf(nfp, "%s", conshdr); /* * Check whether the path duplicates what is already in the * file. */ while (fgets(value, MAXPATHLEN, fp) != NULL) { /* skip comments */ if (value[0] == COMMENT || value[0] == NEWLINE || value[0] == SPACE || value[0] == TAB) continue; len = strlen(value); value[len - 1] = '\0'; /* Clear carriage return */ if (pathcmp(dev, value) == 0) { /* they match so don't write it */ continue; } (void) fprintf(nfp, "%s\n", value); newcontents = B_TRUE; } (void) fclose(fp); (void) fclose(nfp); /* Remove the file if there aren't any auxiliary consoles */ if (newcontents) (void) rename(newfile, CONSCONFIG); else { (void) unlink(CONSCONFIG); (void) unlink(newfile); } (void) close(lckfd); (void) unlink(CONSADMLOCK); } static int pathcmp(char *adev, char *bdev) { struct stat st1; struct stat st2; if (strcmp(adev, bdev) == 0) return (0); if (stat(adev, &st1) != 0 || !S_ISCHR(st1.st_mode)) die(gettext("invalid device %s\n"), adev); if (stat(bdev, &st2) != 0 || !S_ISCHR(st2.st_mode)) die(gettext("invalid device %s\n"), bdev); if (st1.st_rdev == st2.st_rdev) return (0); return (1); } /* * Display configured consoles. */ static void getconsole(void) { int fd; int bufsize = 0; /* size of device cache */ char *infop, *ptr, *p; /* info structure for ioctl's */ if ((fd = safeopen(SYSMSG)) < 0) die(gettext("%s is missing or not a valid device\n"), SYSMSG); if ((bufsize = ioctl(fd, CIOCGETCONSOLE, NULL)) < 0) die(gettext("cannot get table entry\n")); if (bufsize == 0) return; if ((infop = calloc(bufsize, sizeof (char))) == NULL) die(gettext("cannot allocate buffer")); if (ioctl(fd, CIOCGETCONSOLE, infop) < 0) die(gettext("cannot get table entry\n")); ptr = infop; while (ptr != NULL) { p = strchr(ptr, ' '); if (p == NULL) { (void) printf("%s\n", ptr); break; } *p++ = '\0'; (void) printf("%s\n", ptr); ptr = p; } (void) close(fd); } /* * It is supposed that if the device supports TIOCMGET then it * might be a serial device. */ static boolean_t modem_support(int fd) { int modem_state; if (ioctl(fd, TIOCMGET, &modem_state) == 0) return (B_TRUE); else return (B_FALSE); } static boolean_t has_carrier(int fd) { int modem_state; if (ioctl(fd, TIOCMGET, &modem_state) == 0) return ((modem_state & TIOCM_CAR) != 0); else { return (B_FALSE); } } static void setfallback(char *argv[]) { pid_t pid; FILE *fp; char *cmd = CONSADMD; int lckfd, fd; lckfd = getlock(); /* * kill off any existing daemon * remove /etc/consadm.pid */ removefallback(); /* kick off a daemon */ if ((pid = fork()) == (pid_t)0) { /* always fallback to /dev/console */ argv[0] = cmd; argv[1] = NULL; (void) close(0); (void) close(1); (void) close(2); (void) close(lckfd); if ((fd = open(MSGLOG, O_RDWR)) < 0) die(gettext("cannot open %s"), MSGLOG); (void) dup2(fd, 1); (void) dup2(fd, 2); (void) execv(cmd, argv); exit(E_SUCCESS); } else if (pid == -1) die(gettext("%s not started"), CONSADMD); if ((fp = fopen(SETCONSOLEPID, "w")) == NULL) die(gettext("cannot open %s"), SETCONSOLEPID); /* write daemon pid to file */ (void) fprintf(fp, "%d\n", (int)pid); (void) fclose(fp); (void) close(lckfd); (void) unlink(CONSADMLOCK); } /* * Remove the daemon that would have implemented the automatic * fallback in event of carrier loss on the serial console. */ static void removefallback(void) { FILE *fp; int pid; if ((fp = fopen(SETCONSOLEPID, "r+")) == NULL) /* file doesn't exist, so no work to do */ return; if (fscanf(fp, "%d\n", &pid) <= 0) { (void) fclose(fp); (void) unlink(SETCONSOLEPID); return; } /* * Don't shoot ourselves in the foot by killing init, * sched, pageout, or fsflush. */ if (pid == 0 || pid == 1 || pid == 2 || pid == 3) { (void) unlink(SETCONSOLEPID); return; } /* * kill off the existing daemon listed in * /etc/consadm.pid */ (void) kill((pid_t)pid, SIGTERM); (void) fclose(fp); (void) unlink(SETCONSOLEPID); } /* * Assume we always fall back to /dev/console. * parameter passed in will always be the auxiliary device. * The daemon will not start after the last device has been removed. */ static void fallbackdaemon(void) { int fd, sysmfd, ret = 0; char **devpaths; pollfd_t *fds; nfds_t nfds = 0; int index; int pollagain; struct sigaction sa; int bufsize = 0; /* length of device cache paths */ int cachesize = 0; /* size of device cache */ char *infop, *ptr, *p; /* info structure for ioctl's */ /* * catch SIGTERM cause it might be coming from user via consadm */ sa.sa_handler = catch_term; sa.sa_flags = 0; (void) sigemptyset(&sa.sa_mask); (void) sigaction(SIGTERM, &sa, NULL); /* * catch SIGHUP cause it might be coming from a disconnect */ sa.sa_handler = catch_hup; sa.sa_flags = 0; (void) sigemptyset(&sa.sa_mask); (void) sigaction(SIGHUP, &sa, NULL); if ((sysmfd = safeopen(SYSMSG)) < 0) die(gettext("%s is missing or not a valid device\n"), SYSMSG); if ((bufsize = ioctl(sysmfd, CIOCGETCONSOLE, NULL)) < 0) die(gettext("cannot get table entry\n")); if (bufsize == 0) return; if ((infop = calloc(bufsize, sizeof (char))) == NULL) die(gettext("cannot allocate buffer")); if (ioctl(sysmfd, CIOCGETCONSOLE, infop) < 0) die(gettext("cannot get table entry\n")); ptr = infop; while (ptr != NULL) { p = strchr(ptr, ' '); if (p == NULL) { cachesize++; break; } p++; cachesize++; ptr = p; } if ((fds = calloc(cachesize, sizeof (struct pollfd))) == NULL) die(gettext("cannot allocate buffer")); if ((devpaths = calloc(cachesize, sizeof (char *))) == NULL) die(gettext("cannot allocate buffer")); ptr = infop; while (ptr != NULL) { p = strchr(ptr, ' '); if (p == NULL) { if ((fd = safeopen(ptr)) < 0) { warn(gettext("cannot open %s, continuing"), ptr); break; } if (!has_carrier(fd)) { (void) close(fd); warn(gettext( "no carrier on %s, device will not be monitored.\n"), ptr); break; } else { fds[nfds].fd = fd; fds[nfds].events = 0; if ((devpaths[nfds] = malloc(strlen(ptr) + 1)) == NULL) die(gettext("cannot allocate buffer")); (void) strcpy(devpaths[nfds], ptr); nfds++; if (nfds >= cachesize) break; } break; } *p++ = '\0'; if ((fd = safeopen(ptr)) < 0) { warn(gettext("cannot open %s, continuing"), ptr); ptr = p; continue; } if (!has_carrier(fd)) { (void) close(fd); warn(gettext( "no carrier on %s, device will not be monitored.\n"), ptr); ptr = p; continue; } else { fds[nfds].fd = fd; fds[nfds].events = 0; if ((devpaths[nfds] = malloc(strlen(ptr) + 1)) == NULL) die(gettext("cannot allocate buffer")); (void) strcpy(devpaths[nfds], ptr); nfds++; if (nfds >= cachesize) break; } ptr = p; } (void) close(sysmfd); /* no point polling if no devices with carrier */ if (nfds == 0) return; for (;;) { /* daemon sleeps waiting for a hangup on the console */ ret = poll(fds, nfds, INFTIM); if (ret == -1) { /* Check if ttymon is trying to get rid of us */ if (errno == EINTR) continue; warn(gettext("cannot poll device")); return; } else if (ret == 0) { warn(gettext("timeout (%d milleseconds) occured\n"), INFTIM); return; } else { /* Go through poll list looking for events. */ for (index = 0; index < nfds; index++) { /* expected result */ if ((fds[index].revents & POLLHUP) == POLLHUP) { /* * unsetaux console. Take out of list * of current auxiliary consoles. */ unsetaux((char *)devpaths[index]); warn(gettext( "lost carrier, unsetting console %s\n"), devpaths[index]); syslog(LOG_WARNING, "%s: lost carrier, unsetting auxiliary device %s", CONSADM, devpaths[index]); free(devpaths[index]); devpaths[index] = NULL; (void) close(fds[index].fd); fds[index].fd = -1; fds[index].revents = 0; continue; } if ((fds[index].revents & POLLERR) == POLLERR) { warn(gettext("poll error\n")); continue; } else if (fds[index].revents != 0) { warn(gettext( "unexpected poll result 0x%x\n"), fds[index].revents); continue; } } /* check whether any left to poll */ pollagain = B_FALSE; for (index = 0; index < nfds; index++) if (fds[index].fd != -1) pollagain = B_TRUE; if (pollagain == B_TRUE) continue; else return; } } } static void persistlist(void) { FILE *fp; char value[MAXPATHLEN + 1]; int lckfd; lckfd = getlock(); if ((fp = fopen(CONSCONFIG, "r")) != NULL) { while (fgets(value, MAXPATHLEN, fp) != NULL) { /* skip comments */ if (value[0] == COMMENT || value[0] == NEWLINE || value[0] == SPACE || value[0] == TAB) continue; (void) fprintf(stdout, "%s", value); } (void) fclose(fp); } (void) close(lckfd); (void) unlink(CONSADMLOCK); } static int verifyarg(char *dev, int flag) { struct stat st; int fd; int ret = 0; if (dev == NULL) { warn(gettext("specify device(s)\n")); ret = 1; goto err_exit; } if (dev[0] != '/') { warn(gettext("device name must begin with a '/'\n")); ret = 1; goto err_exit; } if ((pathcmp(dev, SYSMSG) == 0) || (pathcmp(dev, WSCONS) == 0) || (pathcmp(dev, CONSOLE) == 0)) { /* they match */ warn(gettext("invalid device %s\n"), dev); ret = 1; goto err_exit; } if (stat(dev, &st) || ! S_ISCHR(st.st_mode)) { warn(gettext("invalid device %s\n"), dev); ret = 1; goto err_exit; } /* Delete operation doesn't require this checking */ if ((fd = safeopen(dev)) < 0) { if (flag) { warn(gettext("invalid device %s\n"), dev); ret = 1; } goto err_exit; } if (!modem_support(fd)) { warn(gettext("invalid device %s\n"), dev); (void) close(fd); ret = 1; goto err_exit; } /* Only verify carrier if it's an add operation */ if (flag) { if (!has_carrier(fd)) { warn(gettext("failure, no carrier on %s\n"), dev); ret = 1; goto err_exit; } } err_exit: return (ret); } /* * Open the pseudo device, but be prepared to catch sigalarm if we block * cause there isn't any carrier present. */ static int safeopen(char *devp) { int fd; struct sigaction sigact; sigact.sa_flags = SA_RESETHAND | SA_NODEFER; sigact.sa_handler = catch_alarm; (void) sigemptyset(&sigact.sa_mask); (void) sigaction(SIGALRM, &sigact, NULL); if (sigsetjmp(deadline, 1) != 0) return (-1); (void) alarm(5); /* The sysmsg driver sets NONBLOCK and NDELAY, but what the hell */ if ((fd = open(devp, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY)) < 0) return (-1); (void) alarm(0); sigact.sa_flags = 0; sigact.sa_handler = SIG_DFL; (void) sigemptyset(&sigact.sa_mask); (void) sigaction(SIGALRM, &sigact, NULL); return (fd); } static int lckfunc(int fd, int flag) { fl.l_type = flag; return (fcntl(fd, F_SETLKW, &fl)); }