/* * ep - extended pipelining * * proctable.c - process table implementation * ____________________________________________________________ * * Developed by Ondrej Jombik * and Lubomir Host * Copyright (c) 2000-2003 Platon SDG, http://platon.sk/ * All rights reserved. * * See README file for more information about this software. * See COPYING file for license information. * * Download the latest version from * http://platon.sk/projects/ep/ */ /* $Platon: ep/src/proctable.c,v 1.33 2003/05/03 09:58:19 nepto Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #ifdef STDC_HEADERS # include # include #endif #if HAVE_UNISTD_H # include # include #endif #if HAVE_SYS_WAIT_H # include #endif #include #include #include /* S_IFCHR */ /* #include PRIO_PROCESS */ #include /* PRIO_PROCESS */ #include "platon-str.h" #include "proctable.h" #include "process.h" #include "message.h" /* msg_*_print() */ #include "conf.h" /* cfg */ static char buf[BUFSIZE]; int proctable_get_size(ptbl) PROCTABLE ptbl; { register int i; for (i = 0; ptbl[i] != NULL; i++) ; return i; } PROCTABLE proctable_create(size) const int size; { PROCTABLE ptbl; register int i; ptbl = (PROCTABLE) malloc((size + 1) * sizeof(struct process *)); if (ptbl == NULL) return NULL; ptbl[size] = NULL; for (i = 0; i < size; i++) { ptbl[i] = (struct process *) malloc(sizeof(struct process)); if (ptbl[i] == NULL) { proctable_destroy(&ptbl); return NULL; } process_init(ptbl[i]); } return ptbl; } void proctable_destroy(p_ptbl) PROCTABLE *p_ptbl; { if (p_ptbl != NULL) { register int i; /* TODO: * Here was written *p_ptbl[i] instead (*p_ptbl)[i] on several places. * I supposed that it is the same. LEARN OPERATOR PRIORITIES ASAP!! * -- Nepto [2/12/2001] */ for (i = 0; (*p_ptbl)[i] != NULL; i++) { process_destroy((*p_ptbl)[i]); free((*p_ptbl)[i]); } free(*p_ptbl); *p_ptbl = NULL; } } int proctable_join(p_dest_ptbl, src_ptbl) PROCTABLE *p_dest_ptbl; PROCTABLE src_ptbl; { register int i, dest_size, src_size; if (p_dest_ptbl == NULL) return 0; for (i = 0; *p_dest_ptbl != NULL && (*p_dest_ptbl)[i] != NULL; i++) ; dest_size = i; for (i = 0; src_ptbl != NULL && src_ptbl[i] != NULL; i++) ; src_size = i; *p_dest_ptbl = (PROCTABLE) realloc(*p_dest_ptbl, sizeof(struct process *) * (dest_size + src_size + 1)); if (*p_dest_ptbl == NULL) return 0; for (i = 0; i < src_size; i++) (*p_dest_ptbl)[dest_size + i] = src_ptbl[i]; (*p_dest_ptbl)[dest_size + src_size] = NULL; return 1; } int proctable_get_process_index_by_pid(ptbl, pid) PROCTABLE ptbl; int pid; { register int i = 0; for (i = 0; ptbl[i] != NULL; i++) if (ptbl[i]->pid == pid) return i; return -1; } int proctable_get_process_index_by_name(ptbl, name) PROCTABLE ptbl; char *name; { register int i = 0; for (i = 0; ptbl[i] != NULL; i++) if (ptbl[i]->name != NULL && ! PLATON_FUNC(strdyn_casecmp)(ptbl[i]->name, name)) return i; return -1; } int proctable_add_std_process(p_ptbl) PROCTABLE *p_ptbl; { register int i; i = proctable_get_size(*p_ptbl); /* 1 for ending NULL, 1 for new std_process */ *p_ptbl = (PROCTABLE) realloc(*p_ptbl, (i + 2) * sizeof(struct process *)); if (*p_ptbl == NULL) { msg_error_print("realloc() failure\n"); return -1; } for (; i >= 0; i--) (*p_ptbl)[i + 1] = (*p_ptbl)[i]; (*p_ptbl)[0] = (struct process *) malloc(sizeof(struct process)); if ((*p_ptbl)[0] == NULL) { msg_error_print("malloc() failure\n"); for (i = 1; *p_ptbl[i] != NULL; i++) { process_destroy(*p_ptbl[i]); free(*p_ptbl[i]); } free(*p_ptbl); return -1; } process_init((*p_ptbl)[0]); return 0; } int proctable_update(ptbl) PROCTABLE ptbl; { register int i, j, k; struct proclink link; for (i = 0; ptbl[i] != NULL; i++) { link.proc_id = i; for (k = 0; k < N_FD; k++) { link.fd_id = k; for (j = 0; j < ptbl[i]->fd[k].n_link; j++) { /* Invalid link? */ if (ptbl[i]->fd[k].link[j].proc_id >= proctable_get_size(ptbl)) { msg_error_print("invalid process number '%d'\n", ptbl[i]->fd[k].link[j].proc_id); return -1; } /* Selflink? */ if (i == ptbl[i]->fd[k].link[j].proc_id) { msg_error_print("cannot self link from %d.%s to %d.%s\n", i, fd_id_to_fd_str(k), ptbl[i]->fd[k].link[j].proc_id, fd_id_to_fd_str(ptbl[i]->fd[k].link[j].fd_id)); return -1; } /* Duplicated link? */ if (fd_is_proclink(&ptbl[i]->fd[k], &ptbl[i]->fd[k].link[j]) - 1 != j) { msg_error_print( "duplicated link from %d.%s to %d.%s\n", i, fd_id_to_fd_str(k), ptbl[i]->fd[k].link[j].proc_id, fd_id_to_fd_str(ptbl[i]->fd[k].link[j].fd_id)); return -1; } /* Linking input to input or output to output? */ while (1) { if (k == 0) { if (i == 0) { if (ptbl[i]->fd[k].link[j].fd_id == 0) break; } else if (ptbl[i]->fd[k].link[j].fd_id == 0) { if (ptbl[i]->fd[k].link[j].proc_id == 0) break; } else if (ptbl[i]->fd[k].link[j].proc_id != 0) break; } else { if (i == 0) { if (ptbl[i]->fd[k].link[j].fd_id != 0) break; } else if (ptbl[i]->fd[k].link[j].fd_id != 0) { if (ptbl[i]->fd[k].link[j].proc_id == 0) break; } else if (ptbl[i]->fd[k].link[j].proc_id != 0) break; } msg_error_print( "incorrect link from %d.%s to %d.%s\n", i, fd_id_to_fd_str(k), ptbl[i]->fd[k].link[j].proc_id, fd_id_to_fd_str(ptbl[i]->fd[k].link[j].fd_id)); return -1; } /* If equivalent link doesn't exist, create it. */ if (fd_is_proclink(&ptbl[ptbl[i]->fd[k].link[j].proc_id]-> fd[ptbl[i]->fd[k].link[j].fd_id], &link)) ; else fd_add_proclink(&ptbl[ptbl[i]->fd[k].link[j].proc_id]-> fd[ptbl[i]->fd[k].link[j].fd_id], &link); } } } return 0; } int proctable_create_pipes(ptbl) PROCTABLE ptbl; { register int i, k; for (i = 0; ptbl[i] != NULL; i++) { for (k = 0; k < N_FD; k++) { /* XXX: We want to create pipes anyway. */ if (1 || ptbl[i]->fd[k].n_link != 0) { if (i == 0) { /* Set the standart (basic) descriptors. */ ptbl[0]->fd[k].pipe[0] = ptbl[0]->fd[k].pipe[1] = k; } else { #if DEBUG msg_debug_print("creating pipe ptbl[%d]->fd[%d].pipe", i, k); #endif if (pipe(ptbl[i]->fd[k].pipe) < 0) { #if DEBUG msg_print(" - failed\n"); #endif return -1; } #if DEBUG msg_print(" = (%d, %d)\n", ptbl[i]->fd[k].pipe[0], ptbl[i]->fd[k].pipe[1]); #endif } } } } return 0; } int proctable_exec(ptbl) PROCTABLE ptbl; { register int i, j, k; for (i = 1; ptbl[i] != NULL; i++) { switch (ptbl[i]->pid = fork()) { /* Parent process. */ default: break; /* Error. */ case -1: return -1; /* Child process. */ case 0: for (k = 0; k < N_FD; k++) /* XXX: We want to dup pipe anyway. */ if (1 || ptbl[i]->fd[k].n_link != 0) { dup2(ptbl[i]->fd[k].pipe[k ? 1 : 0], k); /* See the '1 ||' below. close(ptbl[i]->fd[k].pipe[0]); close(ptbl[i]->fd[k].pipe[1]); */ } else { #if DEBUG msg_debug_print("proctable_exec(): " "process %d, fd %d, no links defined\n", i, k); #endif } for (j = 1; ptbl[j] != NULL; j++) { if (1 || i != j) for (k = 0; k < N_FD; k++) { close(ptbl[j]->fd[k].pipe[0]); close(ptbl[j]->fd[k].pipe[1]); } } #if 0 /* setvbuf(stdout, NULL, _IONBF, 0); */ /* setbuf(stdout, NULL); */ for (k = 0; k < N_FD; k++) { if (k > 0) { fchmod(k, 030620); fcntl(k, F_SETFL, O_NONBLOCK|O_NDELAY); } } #endif if (ptbl[i]->nice != 0 && setpriority(PRIO_PROCESS, PRIO_PROCESS, ptbl[i]->nice) == -1 && ! cfg.quiet) /* this is probably written to wrong place; fd 2 (stderr) must not be real stderr */ msg_warn_print("cannot set priority %d for process %d\n", ptbl[i]->nice, i); execvp(ptbl[i]->argv[0], ptbl[i]->argv); /* * RETURN VALUES * If any of the exec functions returns, an error will have * occurred. The return value is -1, and the global variable * errno will be set to indicate the error. */ /* msg_error_print("can't exec: %s\n", strerror(errno)); */ close(1); close(2); close(0); /* execvp() fails, exiting */ exit(errno == 0 ? 255 : (errno < 0 ? -errno : errno)); } } /* * All fork()-s were OK. * Returning successfully. */ return 0; } void proctable_close_2nd_sides_of_pipes(ptbl) PROCTABLE ptbl; { register int i; for (i = 1; ptbl[i] != NULL; i++) process_close_2nd_sides_of_pipes(ptbl[i]); } int proctable_remove_process(p_ptbl, proc_id) PROCTABLE *p_ptbl; const int proc_id; { register int i, j, k; /* * Pri odstraneni procesu je nutne: * - odstranit process z tabulky PROCTABLE a posunut vsetky za nim * - prepisat vsetky linky v PROCTABLE * - zrusit vsetky linky odkazujuce na dany process * * Nepto [3/4/2001] */ #if DEBUG msg_debug_print("proctable_remove_process(%p [%p], %d)\n", p_ptbl, *p_ptbl, proc_id); #endif for (k = 0; k < N_FD; k++) { if (proctable_remove_process_fd_links(*p_ptbl, proc_id, k) == -1) return -1; } for (i = 0; (*p_ptbl)[i] != NULL; i++) { for (k = 0; k < N_FD; k++) { #if DEBUG msg_debug_print(" process %d fd %d n_link %d\n", i, k, (*p_ptbl)[i]->fd[k].n_link); #endif for (j = 0; j < (*p_ptbl)[i]->fd[k].n_link; /* empty */) { if ((*p_ptbl)[i]->fd[k].link[j].proc_id == proc_id) { /* This removing stuff was moved to begin of function. */ /* fd_remove_proclink_by_index(&(*p_ptbl)[i]->fd[k], j); */ } else { if ((*p_ptbl)[i]->fd[k].link[j].proc_id > proc_id) (*p_ptbl)[i]->fd[k].link[j].proc_id--; j++; } } } } process_destroy((*p_ptbl)[proc_id]); for (i = proc_id; (*p_ptbl)[i] != NULL; i++) { (*p_ptbl)[i] = (*p_ptbl)[i + 1]; } /* Process table is a list of process ended with NULL. */ /* Resizing process table: '-1' is for size down by 1 */ /* '+1' cause we already go down by 1 */ /* see the for () above */ /* '+1' is for ending NULL */ /* So the final is '+1'. */ *p_ptbl = (PROCTABLE) realloc(*p_ptbl, (proctable_get_size(*p_ptbl) + 1) * sizeof(struct process *)); if (*p_ptbl == NULL) { msg_error_print("proctable_remove_process(): realloc() failure\n"); return -1; } #if DEBUG msg_debug_print("proctable_remove_process(%p [%p])\n", p_ptbl, *p_ptbl); #endif return 0; } int proctable_remove_process_fd_links(ptbl, proc_id, fd_id) PROCTABLE ptbl; int proc_id; int fd_id; { while (ptbl[proc_id]->fd[fd_id].n_link > 0) { register int a_proc_id, a_fd_id; /* 0 means the first proclink; we want remove them all continuosly */ a_proc_id = ptbl[proc_id]->fd[fd_id].link[0].proc_id; a_fd_id = ptbl[proc_id]->fd[fd_id].link[0].fd_id; /* Process 1: id: i, fd: k */ /* Process 2: id: proc_id, fd: fd_id */ if ( fd_remove_proclink2( &((ptbl[a_proc_id])->fd[a_fd_id]), proc_id, fd_id) == 0 || fd_remove_proclink2( &((ptbl[proc_id])->fd[fd_id]), a_proc_id, a_fd_id) == 0 ) return -1; } return 0; } void proctable_print(ptbl) PROCTABLE ptbl; { register int i, j, k; for (i = 0; ptbl[i] != NULL; i++) { msg_print("Process %d (", i); process_print_names(ptbl[i]); msg_print("): "); process_print_cmdline(ptbl[i]); msg_print("\n"); for (k = 0; k < N_FD; k++) { msg_print(" %s = (%d, %d)\n", fd_id_to_fd_str(k), ptbl[i]->fd[k].pipe[0], ptbl[i]->fd[k].pipe[1]); for (j = 0; j < ptbl[i]->fd[k].n_link; j++) msg_print(" [%d].%s %s [%d].%s\n", i, fd_id_to_fd_str(k), ((i&&k) || (!i&&!k)) ? "->" : "<-", ptbl[i]->fd[k].link[j].proc_id, fd_id_to_fd_str(ptbl[i]->fd[k].link[j].fd_id)); } } } int proctable_read(p_ptbl, p_rfds, p_wfds, p_efds) PROCTABLE *p_ptbl; fd_set *p_rfds; fd_set *p_wfds; fd_set *p_efds; { register int i, k, datasize = 1; for (i = 0; (*p_ptbl)[i] != NULL; i++) { for (k = 0; k < N_FD; k++) { if (FD_ISSET((*p_ptbl)[i]->fd[k].pipe[0], p_efds)) { FD_CLR((*p_ptbl)[i]->fd[k].pipe[0], p_efds); #if DEBUG msg_debug_print("proctable_read(): exception:" " process %d, fd %d, real fd %d\n", i, k, (*p_ptbl)[i]->fd[k].pipe[0]); #endif } } for (k = (i ? 1 : 0); k < (i ? N_FD : 1); k++) { if (FD_ISSET((*p_ptbl)[i]->fd[k].pipe[0], p_rfds)) { register int j; FD_CLR((*p_ptbl)[i]->fd[k].pipe[0], p_rfds); datasize = read((*p_ptbl)[i]->fd[k].pipe[0], buf, BUFSIZE); #if DEBUG msg_debug_print("proctable_read(): descriptor change:" " process %d, fd %d, datasize %d\n", i, k, datasize); #endif if (datasize == 0) { /* TODO: multi reads from one fd doesn't work!!! */ close((*p_ptbl)[i]->fd[k].pipe[0]); #if DEBUG msg_debug_print("proctable_read(): close(%d)\n", (*p_ptbl)[i]->fd[k].pipe[0]); #endif } if (datasize < 0) { #if DEBUG msg_debug_print("proctable_read(): " "read() failure, %d returned\n", datasize); #endif msg_errno_print(); msg_print("Terminating..."); exit(1); } if (datasize == 0) { /* Variable datasize is 0, filedescriptor is closed. Now we are going to remove all proclinks from/to this filedescriptor. */ proctable_remove_process_fd_links(*p_ptbl, i, k); } else { /* Variable datasize is not 0. Data were recieved. We are going to transfer datasize bytes to linked filedescriptors. */ for (j = 0; j < (*p_ptbl)[i]->fd[k].n_link; j++) { register int proc_id, fd_id; register int fd_write; proc_id = (*p_ptbl)[i]->fd[k].link[j].proc_id; fd_id = (*p_ptbl)[i]->fd[k].link[j].fd_id; fd_write = (*p_ptbl)[proc_id]->fd[fd_id].pipe[1]; #if 0 && DEBUG msg_debug_print("proctable_read(): descriptor write:" " process %d, fd %d, datasize %d\n", proc_idx, fd_write, datasize); #endif switch (write(fd_write, buf, datasize)) { case -1: /* This is some special case. We are writting to filedescriptor,which probably desn't exists (ie. due to child exit). With signal handling and appropriate testing after select() return we should never get here. But if signals are not used, this situation is quite common ep: debug: proctable_read(): write() failed: Broken pipe So that descriptor is not usable anymore. We will remove its proclinks. */ #if DEBUG msg_debug_print("proctable_read(): " "write() failed: %s\n", strerror(errno)); #endif proctable_remove_process_fd_links(*p_ptbl, proc_id, fd_id); break; case 0: /* Similar situation as -1 above */ #if DEBUG msg_debug_print("proctable_read(): " "write() returns 0: %s\n", strerror(errno)); #endif proctable_remove_process_fd_links(*p_ptbl, proc_id, fd_id); break; default: break; } } } } /* if (FD_ISSET()) */ else datasize = 1; if (! datasize) break; } if (! datasize) break; } /* Returns 0 if process was removed, 1 elsewhere. */ return datasize; } int proctable_wait(p_ptbl) PROCTABLE *p_ptbl; { int pid = 0; int status; #if DEBUG msg_debug_print("proctable_wait(): f_wait_call == 1, calling wait()\n"); #endif /* * Parameters in waitpid(): * 1. Value -1 means wait for any child process * 2. Constant WNOHANG means return immediately if no child has exited. */ pid = waitpid(-1, &status, WNOHANG); switch (pid) { case -1: #if 0 msg_error_print("proctable_wait(): " "waitpid() failure: %s\n", strerror(errno)); #endif break; case 0: #if DEBUG msg_warn_print("proctable_wait(): " "waitpid() warning: no child available\n"); #endif /* We will check if there is a process which should be terminated. If it is, we will return 1 and than main program will call us again after select() timeout. */ { register int i, k, proc_n_link = 1; for (i = 1; (*p_ptbl)[i] != NULL; i++) { for (proc_n_link = 0, k = 0; k < N_FD; k++) proc_n_link += (*p_ptbl)[i]->fd[k].n_link; if (proc_n_link == 0) { #if DEBUG msg_debug_print("proctable_wait(): candidate index = %d\n", i); #endif /* Don't need to browse the process table more. */ /* THIS IS WRONG! */ break; } } if (proc_n_link == 0) pid = 1; } break; default: { register int index; index = proctable_get_process_index_by_pid(*p_ptbl, pid); #if DEBUG msg_debug_print("proctable_wait(): " "pid = %d, index = %d\n", pid, index); msg_debug_print("proctable_wait(): " "proctable_get_size() = %d\n", proctable_get_size(*p_ptbl)); #endif if (! cfg.quiet) { msg_print("[%d] ", pid); if (WIFEXITED(status)) { msg_print("Exit"); if (WEXITSTATUS(status)) msg_print(" %d", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { register int sig_n = WTERMSIG(status); switch (sig_n) { case SIGKILL: msg_print("Killed"); break; case SIGTERM: msg_print("Terminated"); break; case SIGSEGV: msg_print("Segmentation fault"); break; default: msg_print("Received signal %d", sig_n); break; }; } else { msg_print("Aborted", pid); } msg_print("\t\t\t\t"); msg_array_print((*p_ptbl)[index]->argv); msg_print("\n"); } /* if (! cfg.quiet) */ #if DEBUG msg_debug_print("proctable_wait(): " "removing process from process table\n"); #endif proctable_remove_process(p_ptbl, index); #if DEBUG msg_debug_print("proctable_wait(): " "proctable_get_size() = %d\n", proctable_get_size(*p_ptbl)); #endif } break; } /* switch (pid) */ return pid; } int proctable_wait2(p_ptbl) PROCTABLE *p_ptbl; { register int i, k, proc_n_link = 1; int pid = 0; int status; for (i = 1; (*p_ptbl)[i] != NULL; i++) { for (proc_n_link = 0, k = 0; k < N_FD; k++) proc_n_link += (*p_ptbl)[i]->fd[k].n_link; if (proc_n_link == 0) { #if DEBUG msg_debug_print("proctable_wait2(): candidate index = %d\n", i); #endif pid = waitpid((*p_ptbl)[i]->pid, &status, WNOHANG); switch (pid) { case -1: #if 0 msg_error_print("proctable_wait2(): " "waitpid() failure: %s\n", strerror(errno)); #endif return pid; break; case 0: #if DEBUG msg_warn_print("proctable_wait2(): " "waitpid() warning: no child available\n"); #endif break; default: if (! cfg.quiet) { msg_print("[%d] ", pid); if (WIFEXITED(status)) { msg_print("Exit"); if (WEXITSTATUS(status)) msg_print(" %d", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { register int sig_n = WTERMSIG(status); switch (sig_n) { case SIGKILL: msg_print("Killed"); break; case SIGTERM: msg_print("Terminated"); break; case SIGSEGV: msg_print("Segmentation fault"); break; default: msg_print("Received signal %d", sig_n); break; }; } else { msg_print("Aborted", pid); } msg_print("\t\t\t\t"); msg_array_print((*p_ptbl)[i]->argv); msg_print("\n"); } /* if (! cfg.quiet) */ proctable_remove_process(p_ptbl, i); return pid; break; } } } return 0; }