/* (C) 2009-2011 Mika Ilmaranta License: GPLv2 */ #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "forkexec.h" static void sigchld_hdl(int sig); typedef struct exec_queue { pid_t pid; char **argv; char **envp; struct exec_queue *next; } EXEC_QUEUE; typedef struct exec_queues { char *name; EXEC_QUEUE *first; EXEC_QUEUE *last; struct exec_queues *next; } EXEC_QUEUES; static EXEC_QUEUES *exec_queues_first = NULL; static EXEC_QUEUES *exec_queues_last = NULL; pid_t forkexec(char **argv, char **envp) { pid_t pid; #if defined(DEBUG) int i; #endif if((pid = fork()) == -1) { syslog(LOG_ERR, "%s: %s: %d: fork() failed \"%s\"", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); return(0); } if(pid) { /* parent */ if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: %d: child process forked with pid: %d", __FILE__, __FUNCTION__, __LINE__, pid); return(pid); } /* child */ closelog(); /* openlog doesn't return a fd to set close on exec */ #if defined(DEBUG) for(i = 0; argv[i]; i++) { fprintf(stderr, "argv[%d] = \"%s\"\n", i, argv[i]); } for(i = 0; envp[i]; i++) { fprintf(stderr, "envp[%d] = \"%s\"\n", i, envp[i]); } #endif execve(argv[0], argv, envp); syslog(LOG_ERR, "%s: %s: %d: child process failed to execute external command", __FILE__, __FUNCTION__, __LINE__); exit(1); /* exec failed ... */ } /* Set up child signal handler to handle termination of children. This should be done once only for the main program. */ void create_sigchld_hdl(void) { struct sigaction act; memset (&act, 0, sizeof(act)); act.sa_handler = sigchld_hdl; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART | SA_NOCLDSTOP; if (sigaction(SIGCHLD, &act, 0)) { syslog(LOG_ERR, "%s: %s: %d: failed to set up child signal handler: %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); return; } else { if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: %d: successfully set up child signal handler", __FILE__, __FUNCTION__, __LINE__); } } /* SIGCHLD handler. Will be called for all children. */ static void sigchld_hdl(int sig) { /* Wait for the dead process. * We use a non-blocking call to be sure this signal handler will not * block if a child was cleaned up in another part of the program. */ int saved_errno = errno; int script_status; pid_t pid; while ((pid = waitpid(WAIT_ANY, &script_status, WNOHANG)) != 0) { if(pid == -1) { if(cfg.debug >= 9 && errno != ECHILD) syslog(LOG_ERR, "%s: %s: %d: waitpid failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); break; } else { if(cfg.debug >= 9 && WEXITSTATUS(script_status)) syslog(LOG_ERR, "%s: %s: %d: child script with pid %d exited with non null exit value %d", __FILE__, __FUNCTION__, __LINE__, pid, WEXITSTATUS(script_status)); else if(cfg.debug >= 9) syslog(LOG_ERR, "%s: %s: %d: child script with pid %d exited successfully", __FILE__, __FUNCTION__, __LINE__, pid); exec_queue_delete(pid); } } errno = saved_errno; } void exec_queue_add(char *queue, char **argv, char **envp) { EXEC_QUEUES *eqs; EXEC_QUEUE *eq; if(!exec_queues_first) { /* first queue */ if((eqs = malloc(sizeof(EXEC_QUEUES))) == NULL) { syslog(LOG_ERR, "%s: %s: %d: malloc failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); return; } eqs->name = strdup(queue); eqs->next = NULL; if((eq = malloc(sizeof(EXEC_QUEUE))) == NULL) { syslog(LOG_ERR, "%s: %s: %d: malloc failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); return; } eq->pid = 0; eq->argv = argv; eq->envp = envp; eq->next = NULL; eqs->first = eq; eqs->last = eq; exec_queues_first = eqs; exec_queues_last = eqs; } else { /* not first queue */ for(eqs = exec_queues_first; eqs; eqs = eqs->next) { if(!strcmp(eqs->name, queue)) { /* queue exists, add to it */ if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: %d: found queue %s", __FILE__, __FUNCTION__, __LINE__, eqs->name); if((eq = malloc(sizeof(EXEC_QUEUE))) == NULL) { syslog(LOG_ERR, "%s: %s: %d: malloc failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); return; } eq->pid = 0; eq->argv = argv; eq->envp = envp; eq->next = NULL; if(!eqs->first) { /* empty queue */ eqs->first = eq; eqs->last = eq; } else { /* add after last */ eqs->last->next = eq; eqs->last = eq; } break; } } if(!eqs) { /* not found, create a new queue and add to it */ if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: %d: queue %s not found adding new queue", __FILE__, __FUNCTION__, __LINE__, queue); if((eqs = malloc(sizeof(EXEC_QUEUES))) == NULL) { syslog(LOG_ERR, "%s: %s: %d: malloc failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); return; } eqs->name = strdup(queue); eqs->next = NULL; exec_queues_last->next = eqs; exec_queues_last = eqs; if((eq = malloc(sizeof(EXEC_QUEUE))) == NULL) { syslog(LOG_ERR, "%s: %s: %d: malloc failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); return; } eq->pid = 0; eq->argv = argv; eq->envp = envp; eq->next = NULL; eqs->first = eq; eqs->last = eq; } } return; } #if defined(DEBUG) void exec_queue_dump(void) { EXEC_QUEUES *eqs; EXEC_QUEUE *eq; int i; for(eqs = exec_queues_first; eqs; eqs = eqs->next) { syslog(LOG_INFO, "%s: %s: %d: eqs->name %s", __FILE__, __FUNCTION__, __LINE__, eqs->name); for(eq = eqs->first; eq; eq = eq->next) { syslog(LOG_INFO, "%s: %s: %d: eq->pid %d", __FILE__, __FUNCTION__, __LINE__, eq->pid); for(i = 0; eq->argv[i]; i++) { syslog(LOG_INFO, "%s: %s: %d: argv[%d] = %s", __FILE__, __FUNCTION__, __LINE__, i, eq->argv[i]); } } } } #endif void exec_queue_process(void) { EXEC_QUEUES *eqs; EXEC_QUEUE *eq; for(eqs = exec_queues_first; eqs; eqs = eqs->next) { eq = eqs->first; if(eq && eq->pid == 0) eq->pid = forkexec(eq->argv, eq->envp); } } void exec_queue_delete(pid_t pid) { EXEC_QUEUES *eqs; EXEC_QUEUE *eq; for(eqs = exec_queues_first; eqs; eqs = eqs->next) { EXEC_QUEUE *prev = NULL; for(eq = eqs->first; eq; eq = eq->next) { if(eq->pid == pid) { if(!prev) { /* this is first */ if(!eq->next) { /* last */ eqs->first = NULL; eqs->last = NULL; } else { /* not last */ eqs->first = eq->next; } } else { /* not first */ prev->next = eq->next; } exec_queue_argv_free(eq->argv); exec_queue_envp_free(eq->envp); free(eq); return; } prev = eq; } } if(cfg.debug >= 9) syslog(LOG_ERR, "%s: %s: %d: child pid %d not found", __FILE__, __FUNCTION__, __LINE__, pid); } void exec_queue_free(void) { EXEC_QUEUES *eqs; EXEC_QUEUE *eq; eqs = exec_queues_first; while(eqs) { EXEC_QUEUES *prev_eqs = eqs; eq = eqs->first; while(eq) { EXEC_QUEUE *prev_eq = eq; eq = eq->next; free(prev_eq); } eqs = eqs->next; free(prev_eqs); } exec_queues_first = NULL; exec_queues_last = NULL; } char **exec_queue_argv(char *fmt, ...) { va_list vl; char **argv; char *s; int fmt_cnt; char buf[BUFSIZ]; int i; s = fmt; fmt_cnt = 0; while(*s) { if(*s++ == '%') fmt_cnt++; } if((argv = malloc((fmt_cnt + 1) * sizeof(char *))) == NULL) { syslog(LOG_ERR, "%s: %s: failed to malloc %s", __FILE__, __FUNCTION__, strerror(errno)); return(NULL); } s = fmt; i = 0; va_start(vl, fmt); while(*s) { if(*s == '%') { s++; switch(*s++) { case 's': /* string */ argv[i] = strdup(va_arg(vl, char *)); i++; break; case 'd': /* int */ snprintf(buf, BUFSIZ - 1, "%d", va_arg(vl, int)); argv[i] = strdup(buf); i++; break; default: /* skip unknown directives */ break; } } else { s++; } } va_end(vl); argv[i] = NULL; return(argv); } void exec_queue_argv_free(char **argv) { int i; for(i = 0; argv[i]; i++) { free(argv[i]); } free(argv); } char **exec_queue_envp(void) { char **envp; char buf[BUFSIZ]; if((envp = malloc(4 * sizeof(char *))) == NULL) { syslog(LOG_ERR, "%s: %s: malloc failed %s", __FILE__, __FUNCTION__, strerror(errno)); return(NULL); } snprintf(buf, BUFSIZ - 1, "LANG=%s", getenv("LANG")); envp[0] = strdup(buf); snprintf(buf, BUFSIZ - 1, "PATH=%s", getenv("PATH")); envp[1] = strdup(buf); snprintf(buf, BUFSIZ - 1, "TERM=%s", getenv("TERM")); envp[2] = strdup(buf); envp[3] = NULL; return(envp); } void exec_queue_envp_free(char **envp) { int i; for(i = 0; envp[i]; i++) { free(envp[i]); } free(envp); } /* EOF */