/* * Copyright (c) 2014 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthew Dillon * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Handle remote listen/connect and parsing operations. */ #include "svc.h" typedef struct SvcConnect { struct SvcConnect *next; command_t *cmd; char *label; pthread_t td; int active; int fd; int rc; FILE *fpr; FILE *fpw; } connect_t; static void *remote_connect_thread(void *arg); static void *remote_listener_thread(void *arg); static void *remote_accepted_thread(void *arg); static void remote_issue(connect_t *conn, command_t *cmd); static int decode_args(connect_t *conn, char ***avp, const char *ptr, size_t len); connect_t *CHead; connect_t **CNextP = &CHead; /* * Execute cmd on the running service by connecting to the service, passing-in * the command, and processing results. * * Called only by master process */ void remote_execute(command_t *cmd, const char *label) { connect_t *conn = calloc(sizeof(*conn), 1); conn->fd = -1; conn->cmd = cmd; conn->label = strdup(label); conn->active = 1; conn->next = *CNextP; *CNextP = conn; pthread_create(&conn->td, NULL, remote_connect_thread, conn); } /* * Threaded connect/execute */ static void * remote_connect_thread(void *arg) { connect_t *conn; command_t *cmd; struct sockaddr_un sou; size_t len; char *ptr; conn = arg; cmd = conn->cmd; bzero(&sou, sizeof(sou)); if ((conn->fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0) { sou.sun_family = AF_UNIX; snprintf(sou.sun_path, sizeof(sou.sun_path), "%s/service.%s.sk", cmd->piddir, conn->label); len = strlen(sou.sun_path); len = offsetof(struct sockaddr_un, sun_path[len+1]); if (connect(conn->fd, (void *)&sou, len) < 0) { close(conn->fd); conn->fd = -1; } } if (conn->fd >= 0) { /* * Issue command */ conn->fpr = fdopen(conn->fd, "r"); conn->fpw = fdopen(dup(conn->fd), "w"); conn->fd = -1; setvbuf(conn->fpr, NULL, _IOFBF, 0); setvbuf(conn->fpw, NULL, _IOFBF, 0); remote_issue(conn, cmd); while ((ptr = fgetln(conn->fpr, &len)) != NULL) { if (len == 2 && ptr[0] == '.' && ptr[1] == '\n') break; /* * de-escape .. */ if (len == 3 && ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '\n') { ++ptr; --len; } fwrite(ptr, 1, len, cmd->fp); fflush(cmd->fp); } conn->rc = 0; conn->active = 0; fclose(conn->fpr); fclose(conn->fpw); conn->fpr = NULL; conn->fpw = NULL; } else { /* * Connection failed */ fprintf(cmd->fp, "Unable to connect to service %s\n", conn->label); conn->rc = 1; conn->active = 0; if (cmd->force_remove_files) { fprintf(cmd->fp, "Removing pid and socket files for %s\n", conn->label); remove_pid_and_socket(cmd, conn->label); } } return NULL; } /* * Called only by master process * * Collect status from all remote commands. */ int remote_wait(void) { connect_t *scan; int rc = 0; while ((scan = CHead) != NULL) { if (pthread_join(scan->td, NULL) < 0) continue; assert(scan->active == 0); rc += scan->rc; CHead = scan->next; if (scan->fpr) { fclose(scan->fpr); scan->fpr = NULL; } if (scan->fpw) { fclose(scan->fpw); scan->fpw = NULL; } if (scan->fd >= 0) { close(scan->fd); scan->fd = -1; } if (scan->label) { free(scan->label); scan->label = NULL; } scan->cmd = NULL; free(scan); } return rc; } /* * Create the unix domain socket and pid file for the service * and start a thread to accept and process connections. * * Return 0 on success, non-zero if the socket could not be created. */ void remote_listener(command_t *cmd, int lfd) { connect_t *conn; /* * child, create our unix domain socket listener thread. */ conn = calloc(sizeof(*conn), 1); conn->fd = lfd; conn->cmd = cmd; conn->label = strdup(cmd->label); conn->active = 1; conn->next = *CNextP; *CNextP = conn; pthread_create(&conn->td, NULL, remote_listener_thread, conn); } static void * remote_listener_thread(void *arg) { connect_t *lconn = arg; connect_t *conn; struct sockaddr_un sou; socklen_t len; conn = calloc(sizeof(*conn), 1); for (;;) { len = sizeof(sou); conn->fd = accept(lconn->fd, (void *)&sou, &len); conn->label = strdup(lconn->label); if (conn->fd < 0) { if (errno == EINTR) continue; break; } pthread_create(&conn->td, NULL, remote_accepted_thread, conn); conn = calloc(sizeof(*conn), 1); } free(conn); return NULL; } static void * remote_accepted_thread(void *arg) { connect_t *conn = arg; command_t cmd; char *ptr; size_t len; int rc; int ac; char **av; pthread_detach(conn->td); conn->fpr = fdopen(conn->fd, "r"); conn->fpw = fdopen(dup(conn->fd), "w"); conn->fd = -1; setvbuf(conn->fpr, NULL, _IOFBF, 0); setvbuf(conn->fpw, NULL, _IOFBF, 0); while ((ptr = fgetln(conn->fpr, &len)) != NULL) { ac = decode_args(conn, &av, ptr, len); rc = process_cmd(&cmd, conn->fpw, ac, av); cmd.cmdline = 0; /* we are the remote */ cmd.commanded = 1; /* commanded action (vs automatic) */ sreplace(&cmd.label, conn->label); if (rc == 0) { pthread_mutex_lock(&serial_mtx); rc = execute_cmd(&cmd); pthread_mutex_unlock(&serial_mtx); } free_cmd(&cmd); afree(&av); fwrite(".\n", 2, 1, conn->fpw); fflush(conn->fpw); } fclose(conn->fpr); fclose(conn->fpw); conn->fpr = NULL; conn->fpw = NULL; free(conn->label); free(conn); return NULL; } /* * Issue the command to the remote, encode the arguments. */ static void remote_issue(connect_t *conn, command_t *cmd) { int i; for (i = 1; i < cmd->orig_ac; ++i) { const char *str = cmd->orig_av[i]; if (i != 1) putc(' ', conn->fpw); while (*str) { if (*str == ' ' || *str == '\\' || *str == '\n') putc('\\', conn->fpw); putc(*str, conn->fpw); ++str; } } putc('\n', conn->fpw); fflush(conn->fpw); } /* * Decode arguments */ static int decode_args(connect_t *conn __unused, char ***avp, const char *ptr, size_t len) { char **av; char *arg; size_t i; size_t j; int acmax; int ac; if (len && ptr[len-1] == '\n') --len; acmax = 3; /* av[0], first arg, terminating NULL */ for (i = 0; i < len; ++i) { if (ptr[i] == ' ') ++acmax; } av = calloc(sizeof(char *), acmax); av[0] = NULL; ac = 1; i = 0; while (i < len) { for (j = i; j < len; ++j) { if (ptr[j] == ' ') break; } arg = malloc(j - i + 1); /* worst case arg size */ j = 0; while (i < len) { if (ptr[i] == ' ') break; if (ptr[i] == '\\' && i + 1 < len) { arg[j++] = ptr[i+1]; i += 2; } else { arg[j++] = ptr[i]; i += 1; } } arg[j] = 0; av[ac++] = arg; if (i < len && ptr[i] == ' ') ++i; } av[ac] = NULL; #if 0 fprintf(conn->fpw, "DECODE ARGS: "); for (i = 1; i < (size_t)ac; ++i) fprintf(conn->fpw, " \"%s\"", av[i]); fprintf(conn->fpw, "\n"); fflush(conn->fpw); #endif *avp = av; return ac; }