int open_unixsock_obj(obj_t *unixsock) { /* (Re)opens the specified 'unixsock' obj. * Returns 0 if the console is successfully opened; o/w, returns -1. */ int rc = 0; assert(unixsock != NULL); assert(is_unixsock_obj(unixsock)); if (unixsock->aux.unixsock.state == CONMAN_UNIXSOCK_UP) { rc = disconnect_unixsock_obj(unixsock); } else { rc = connect_unixsock_obj(unixsock); } return(rc); }
static int disconnect_unixsock_obj(obj_t *unixsock) { /* Closes the existing connection with the specified (unixsock) obj * and sets a timer for establishing a new connection. * Always returns -1. */ unixsock_obj_t *auxp; assert(unixsock != NULL); assert(is_unixsock_obj(unixsock)); auxp = &(unixsock->aux.unixsock); if (auxp->timer >= 0) { (void) tpoll_timeout_cancel(tp_global, auxp->timer); auxp->timer = -1; } if (unixsock->fd >= 0) { if (close(unixsock->fd) < 0) { log_msg(LOG_ERR, "Unable to close console [%s] socket \"%s\": %s", unixsock->name, auxp->dev, strerror(errno)); } unixsock->fd = -1; } /* Notify linked objs when transitioning from an UP state. */ if (auxp->state == CONMAN_UNIXSOCK_UP) { auxp->state = CONMAN_UNIXSOCK_DOWN; write_notify_msg(unixsock, LOG_NOTICE, "Console [%s] disconnected from \"%s\"", unixsock->name, auxp->dev); } /* Set timer for establishing new connection. */ auxp->timer = tpoll_timeout_relative(tp_global, (callback_f) connect_unixsock_obj, unixsock, auxp->delay * 1000); if (auxp->delay < UNIXSOCK_MAX_TIMEOUT) { auxp->delay = MIN(auxp->delay * 2, UNIXSOCK_MAX_TIMEOUT); } return(-1); }
static void reset_unixsock_delay(obj_t *unixsock) { /* Resets the unixsock obj's reconnect-delay after the connection has been up * for the minimum length of time. This protects against spinning on * reconnects when the connection immediately terminates. */ unixsock_obj_t *auxp; assert(unixsock != NULL); assert(is_unixsock_obj(unixsock)); auxp = &(unixsock->aux.unixsock); /* Reset the timer ID since this routine is only invoked by a timer. */ auxp->timer = -1; DPRINTF((15, "Reset [%s] reconnect delay\n", unixsock->name)); auxp->delay = UNIXSOCK_MIN_TIMEOUT; return; }
obj_t * get_console_logfile_obj(obj_t *console) { /* Returns a ptr to the logfile obj associated with 'console' * if one exists and is currently active; o/w, returns NULL. */ obj_t *logfile = NULL; assert(console != NULL); assert(is_console_obj(console)); if (is_process_obj(console)) { logfile = console->aux.process.logfile; } else if (is_serial_obj(console)) { logfile = console->aux.serial.logfile; } else if (is_telnet_obj(console)) { logfile = console->aux.telnet.logfile; } else if (is_unixsock_obj(console)) { logfile = console->aux.unixsock.logfile; } #if WITH_FREEIPMI else if (is_ipmi_obj(console)) { logfile = console->aux.ipmi.logfile; } #endif /* WITH_FREEIPMI */ else { log_err(0, "INTERNAL: Unrecognized console [%s] type=%d", console->name, console->type); } if (!logfile || (logfile->fd < 0)) { return(NULL); } assert(is_logfile_obj(logfile)); return(logfile); }
obj_t * create_logfile_obj(server_conf_t *conf, char *name, obj_t *console, logopt_t *opts, char *errbuf, int errlen) { /* Creates a new logfile object and adds it to the master objs list. * Note: the logfile is open and set for non-blocking I/O. * Note: the logfile will later be opened and set for non-blocking I/O * by main:open_objs:reopen_obj:open_logfile_obj(). * Returns the new object, or NULL on error. */ ListIterator i; obj_t *logfile; char buf[MAX_LINE]; char *pname; obj_t *obj; assert(conf != NULL); assert((name != NULL) && (name[0] != '\0')); assert(console != NULL); assert(opts != NULL); /* Check for duplicate logfile names. * While the write-lock will protect against two separate daemons * using the same logfile, it will not protect against two logfile * objects within the same daemon process using the same filename. * So that check is performed here. */ if (strchr(name, '%') && (format_obj_string(buf, sizeof(buf), console, name) >= 0)) { pname = buf; } else { pname = name; } i = list_iterator_create(conf->objs); while ((logfile = list_next(i))) { if (!is_logfile_obj(logfile)) { continue; } if (!strcmp(logfile->name, pname)) { break; } } list_iterator_destroy(i); if (logfile) { snprintf(errbuf, errlen, "console [%s] already logging to \"%s\"", logfile->aux.logfile.console->name, pname); return(NULL); } logfile = create_obj(conf, name, -1, CONMAN_OBJ_LOGFILE); logfile->aux.logfile.console = console; logfile->aux.logfile.lineState = CONMAN_LOG_LINE_INIT; logfile->aux.logfile.opts = *opts; logfile->aux.logfile.gotTruncate = !!conf->enableZeroLogs; if (logfile->aux.logfile.opts.enableSanitize || logfile->aux.logfile.opts.enableTimestamp) { logfile->aux.logfile.gotProcessing = 1; } else { logfile->aux.logfile.gotProcessing = 0; } if (strchr(name, '%')) { logfile->aux.logfile.fmtName = create_string(name); } else { logfile->aux.logfile.fmtName = NULL; } if (is_process_obj(console)) { console->aux.process.logfile = logfile; } else if (is_serial_obj(console)) { console->aux.serial.logfile = logfile; } else if (is_telnet_obj(console)) { console->aux.telnet.logfile = logfile; } else if (is_unixsock_obj(console)) { console->aux.unixsock.logfile = logfile; } #if WITH_FREEIPMI else if (is_ipmi_obj(console)) { console->aux.ipmi.logfile = logfile; } #endif /* WITH_FREEIPMI */ else { log_err(0, "INTERNAL: Unrecognized console [%s] type=%d", console->name, console->type); } /* Add obj to the master conf->objs list * before its corresponding console obj. */ i = list_iterator_create(conf->objs); while ((obj = list_next(i))) { if (obj == console) { list_insert(i, logfile); break; } } list_iterator_destroy(i); if (!obj) { log_err(0, "INTERNAL: Console [%s] object not found in master list", console->name); } return(logfile); }
static void mux_io(server_conf_t *conf) { /* Multiplexes I/O between all of the objs in the configuration. * This routine is the heart of ConMan. */ ListIterator i; int n; obj_t *obj; int inevent_fd; assert(conf->tp != NULL); assert(!list_is_empty(conf->objs)); i = list_iterator_create(conf->objs); while (!done) { if (reconfig) { /* * FIXME: A reconfig should pro'ly resurrect "downed" serial objs * and reset reconnect timers of "downed" telnet objs. */ log_msg(LOG_NOTICE, "Performing reconfig on signal=%d", reconfig); reopen_logfiles(conf); reconfig = 0; } /* FIXME: Switch from recomputing the tpoll set on each loop iteration * to modifying it based on events. This will eliminate the 1sec * tpoll() sleep timeout and greatly reduce cpu utilization. * It will also eliminate the maze of twisty conditions below. */ DPRINTF((25, "Recomputing tpoll fd set\n")); (void) tpoll_zero(conf->tp, TPOLL_ZERO_FDS); tpoll_set(conf->tp, conf->ld, POLLIN); inevent_fd = inevent_get_fd(); if (inevent_fd >= 0) { tpoll_set(conf->tp, inevent_get_fd(), POLLIN); } list_iterator_reset(i); while ((obj = list_next(i))) { if (obj->gotReset) { reset_console(obj, conf->resetCmd); } if (obj->fd < 0) { continue; } if ( ( ( is_telnet_obj(obj) && obj->aux.telnet.state == CONMAN_TELNET_UP ) || ( is_process_obj(obj) && obj->aux.process.state == CONMAN_PROCESS_UP ) || #if WITH_FREEIPMI ( is_ipmi_obj(obj) && obj->aux.ipmi.state == CONMAN_IPMI_UP ) || #endif /* WITH_FREEIPMI */ ( is_unixsock_obj(obj) && obj->aux.unixsock.state == CONMAN_UNIXSOCK_UP ) || is_serial_obj(obj) || is_client_obj(obj) ) && ( ! obj->gotEOF ) ) { tpoll_set(conf->tp, obj->fd, POLLIN); } if ( ( (obj->bufInPtr != obj->bufOutPtr) || (obj->gotEOF) ) && ( ! (is_telnet_obj(obj) && obj->aux.telnet.state != CONMAN_TELNET_UP) ) && ( ! (is_process_obj(obj) && obj->aux.process.state != CONMAN_PROCESS_UP) ) && #if WITH_FREEIPMI ( ! (is_ipmi_obj(obj) && obj->aux.ipmi.state != CONMAN_IPMI_UP) ) && #endif /* WITH_FREEIPMI */ ( ! (is_unixsock_obj(obj) && obj->aux.unixsock.state != CONMAN_UNIXSOCK_UP) ) && ( ! (is_client_obj(obj) && obj->aux.client.gotSuspend) ) ) { tpoll_set(conf->tp, obj->fd, POLLOUT); } if (is_telnet_obj(obj) && obj->aux.telnet.state == CONMAN_TELNET_PENDING) { tpoll_set(conf->tp, obj->fd, POLLIN | POLLOUT); } } DPRINTF((25, "Calling tpoll\n")); while ((n = tpoll(conf->tp, 1000)) < 0) { if (errno != EINTR) { log_err(errno, "Unable to multiplex I/O"); } else if (done || reconfig) { break; } } if (n <= 0) { continue; } if (tpoll_is_set(conf->tp, conf->ld, POLLIN)) { accept_client(conf); } if ((inevent_fd >= 0) && tpoll_is_set(conf->tp, inevent_fd, POLLIN)) { inevent_process(); } /* If read_from_obj() or write_to_obj() returns -1, * the obj's buffer has been flushed. If it is a telnet obj, * retain it and attempt to re-establish the connection; * o/w, give up and remove it from the master objs list. */ list_iterator_reset(i); while ((obj = list_next(i))) { if (obj->fd < 0) { continue; } if (is_telnet_obj(obj) && tpoll_is_set(conf->tp, obj->fd, POLLIN | POLLOUT) && (obj->aux.telnet.state == CONMAN_TELNET_PENDING)) { open_telnet_obj(obj); continue; } if (tpoll_is_set(conf->tp, obj->fd, POLLIN | POLLHUP | POLLERR)) { if (read_from_obj(obj, conf->tp) < 0) { list_delete(i); continue; } if (obj->fd < 0) { continue; } } if (tpoll_is_set(conf->tp, obj->fd, POLLOUT)) { if (write_to_obj(obj) < 0) { list_delete(i); continue; } if (obj->fd < 0) { continue; } } } } log_msg(LOG_NOTICE, "Exiting on signal=%d", done); list_iterator_destroy(i); return; }
obj_t * create_unixsock_obj(server_conf_t *conf, char *name, char *dev, char *errbuf, int errlen) { /* Creates a new unix domain object and adds it to the master objs list. * Returns the new objects, or NULL on error. */ int n; ListIterator i; obj_t *unixsock; int rv; assert(conf != NULL); assert((name != NULL) && (name[0] != '\0')); assert((dev != NULL) && (dev[0] != '\0')); assert(errbuf != NULL); assert(errlen > 0); /* Check length of device string. */ n = max_unixsock_dev_strlen(); if (strlen(dev) > n) { snprintf(errbuf, errlen, "console [%s] exceeds maximum device length of %d bytes", name, n); return(NULL); } /* Check for duplicate console and device names. */ i = list_iterator_create(conf->objs); while ((unixsock = list_next(i))) { if (is_console_obj(unixsock) && !strcmp(unixsock->name, name)) { snprintf(errbuf, errlen, "console [%s] specifies duplicate console name", name); break; } if (is_unixsock_obj(unixsock) && !strcmp(unixsock->aux.unixsock.dev, dev)) { snprintf(errbuf, errlen, "console [%s] specifies duplicate device \"%s\"", name, dev); break; } } list_iterator_destroy(i); if (unixsock != NULL) { return(NULL); } unixsock = create_obj(conf, name, -1, CONMAN_OBJ_UNIXSOCK); unixsock->aux.unixsock.dev = create_string(dev); unixsock->aux.unixsock.logfile = NULL; unixsock->aux.unixsock.timer = -1; unixsock->aux.unixsock.state = CONMAN_UNIXSOCK_DOWN; /* * Add obj to the master conf->objs list. */ list_append(conf->objs, unixsock); rv = inevent_add(unixsock->aux.unixsock.dev, (inevent_cb_f) open_unixsock_obj, unixsock); if (rv < 0) { log_msg(LOG_INFO, "Console [%s] unable to register device \"%s\" for inotify events", unixsock->name, unixsock->aux.unixsock.dev); } return(unixsock); }
static int connect_unixsock_obj(obj_t *unixsock) { /* Opens a connection to the specified (unixsock) obj. * Returns 0 if the connection is successfully completed; o/w, returns -1. */ unixsock_obj_t *auxp; struct stat st; struct sockaddr_un saddr; int rc; assert(unixsock != NULL); assert(is_unixsock_obj(unixsock)); assert(unixsock->aux.unixsock.state != CONMAN_UNIXSOCK_UP); assert(strlen(unixsock->aux.unixsock.dev) <= max_unixsock_dev_strlen()); auxp = &(unixsock->aux.unixsock); if (auxp->timer >= 0) { (void) tpoll_timeout_cancel(tp_global, auxp->timer); auxp->timer = -1; } if (stat(auxp->dev, &st) < 0) { log_msg(LOG_DEBUG, "Console [%s] cannot stat device \"%s\": %s", unixsock->name, auxp->dev, strerror(errno)); return(disconnect_unixsock_obj(unixsock)); } #ifdef S_ISSOCK /* Danger, Will Robinson! S_ISSOCK not in POSIX.1-1996. * * If this is not defined, connect() will detect the error of the device * not being a socket. */ if (!S_ISSOCK(st.st_mode)) { log_msg(LOG_INFO, "Console [%s] device \"%s\" is not a socket", unixsock->name, auxp->dev, strerror(errno)); return(disconnect_unixsock_obj(unixsock)); } #endif /* S_ISSOCK */ memset(&saddr, 0, sizeof(saddr)); saddr.sun_family = AF_UNIX; rc = strlcpy(saddr.sun_path, auxp->dev, sizeof(saddr.sun_path)); assert(rc < sizeof(saddr.sun_path)); if ((unixsock->fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { /* * This error should probably not be considered fatal, * but since it is elsewhere in the code, it is here as well. */ log_err(errno, "Unable to create console [%s] socket", unixsock->name); } set_fd_nonblocking(unixsock->fd); set_fd_closed_on_exec(unixsock->fd); /* FIXME: Check to see if connect() on a nonblocking unix domain socket * can return EINPROGRESS. I don't think it can. */ if (connect(unixsock->fd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) { log_msg(LOG_INFO, "Console [%s] cannot connect to device \"%s\": %s", unixsock->name, auxp->dev, strerror(errno)); return(disconnect_unixsock_obj(unixsock)); } /* Write-locking the unix domain socket appears ineffective. But since * create_unixsock_obj() already checks for duplicate devices, this is * only an issue if two daemons are trying to simultaneously use the * same local socket. */ unixsock->gotEOF = 0; auxp->state = CONMAN_UNIXSOCK_UP; /* Notify linked objs when transitioning into an UP state. */ write_notify_msg(unixsock, LOG_NOTICE, "Console [%s] connected to \"%s\"", unixsock->name, auxp->dev); DPRINTF((9, "Opened [%s] unixsock: fd=%d dev=%s.\n", unixsock->name, unixsock->fd, auxp->dev)); return(0); }
static void check_console_state(obj_t *console, obj_t *client) { /* Checks the state of the console and warns the client if needed. * Informs the newly-connected client if strange things are afoot. * Attempts an immediate reconnect if the console connection is down. */ char buf[MAX_LINE]; assert(is_console_obj(console)); assert(is_client_obj(client)); if (is_process_obj(console) && (console->fd < 0)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from \"%s\"%s", CONMAN_MSG_PREFIX, console->name, console->aux.process.prog, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); open_process_obj(console); } else if (is_serial_obj(console) && (console->fd < 0)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from \"%s\"%s", CONMAN_MSG_PREFIX, console->name, console->aux.serial.dev, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); open_serial_obj(console); } else if (is_telnet_obj(console) && (console->aux.telnet.state != CONMAN_TELNET_UP)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from <%s:%d>%s", CONMAN_MSG_PREFIX, console->name, console->aux.telnet.host, console->aux.telnet.port, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); console->aux.telnet.delay = TELNET_MIN_TIMEOUT; /* * Do not call connect_telnet_obj() while in the PENDING state since * it would be misinterpreted as the completion of the non-blocking * connect(). */ if (console->aux.telnet.state == CONMAN_TELNET_DOWN) { open_telnet_obj(console); } } else if (is_unixsock_obj(console) && (console->fd < 0)) { assert(console->aux.unixsock.state == CONMAN_UNIXSOCK_DOWN); snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from \"%s\"%s", CONMAN_MSG_PREFIX, console->name, console->aux.unixsock.dev, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); open_unixsock_obj(console); } #if WITH_FREEIPMI else if (is_ipmi_obj(console) && (console->aux.ipmi.state != CONMAN_IPMI_UP)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from <%s>%s", CONMAN_MSG_PREFIX, console->name, console->aux.ipmi.host, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); if (console->aux.ipmi.state == CONMAN_IPMI_DOWN) { open_ipmi_obj(console); } } #endif /* WITH_FREEIPMI */ return; }