/* * nkill.c: * Kill processes by name. Should work on Linux, SunOS 5.x, and anything with * a ps(1) which allows you to specify a list of properties to print for each * running process. * * Compiling this program: * * cc nkill.c -o nkill -O * * If you are using Linux, add -DSYS_LINUX. * * If you are using SunOS 5.x, add -DSYS_SUNOS. * * If you are using any other system, figure out how to make ps print a list * of all running processes in the format pid\s+command; on SysV this is * ps -A -o pid,comm, or perhaps ... -o pid,command. Then, modify the * definition of psargs[] below. * * If your system supports the POSIX regular expression functions regcomp, * regexec etc., then add -DUSE_REGEXPS; you may need to add -lre or similar * to make this work. * * Copyright (c) 2001 Chris Lightfoot. All rights reserved. * */ static const char rcsid[] = "$Id: nkill.c,v 1.7 2001/04/03 10:36:12 chris Exp $"; #include #include #include #include #include #include #include #include #include #ifdef SYS_SUNOS # include #endif #ifdef USE_REGEXPS # include #else # warning not compiling regular expressions support (use -DUSE_REGEXPS) #endif #if defined(SYS_LINUX) || defined(SYS_SUNOS) # define USE_PROCFS # include #endif /* List of signals. */ struct sigx { int sig; char *name; }; /* FIXME These are the signals about which Linux knows; probably woefully * incomplete. */ struct sigx known_signals[] = { #ifdef SIGHUP {SIGHUP, "SIGHUP"}, #endif #ifdef SIGINT {SIGINT, "SIGINT"}, #endif #ifdef SIGQUIT {SIGQUIT, "SIGQUIT"}, #endif #ifdef SIGILL {SIGILL, "SIGILL"}, #endif #ifdef SIGTRAP {SIGTRAP, "SIGTRAP"}, #endif #ifdef SIGIOT {SIGIOT, "SIGIOT"}, #endif #ifdef SIGBUS {SIGBUS, "SIGBUS"}, #endif #ifdef SIGFPE {SIGFPE, "SIGFPE"}, #endif #ifdef SIGKILL {SIGKILL, "SIGKILL"}, #endif #ifdef SIGUSR {SIGUSR, "SIGUSR"}, #endif #ifdef SIGSEGV {SIGSEGV, "SIGSEGV"}, #endif #ifdef SIGUSR {SIGUSR, "SIGUSR"}, #endif #ifdef SIGPIPE {SIGPIPE, "SIGPIPE"}, #endif #ifdef SIGALRM {SIGALRM, "SIGALRM"}, #endif #ifdef SIGTERM {SIGTERM, "SIGTERM"}, #endif #ifdef SIGCHLD {SIGCHLD, "SIGCHLD"}, #endif #ifdef SIGCONT {SIGCONT, "SIGCONT"}, #endif #ifdef SIGSTOP {SIGSTOP, "SIGSTOP"}, #endif #ifdef SIGTSTP {SIGTSTP, "SIGTSTP"}, #endif #ifdef SIGTTIN {SIGTTIN, "SIGTTIN"}, #endif #ifdef SIGTTOU {SIGTTOU, "SIGTTOU"}, #endif #ifdef SIGURG {SIGURG, "SIGURG"}, #endif #ifdef SIGXCPU {SIGXCPU, "SIGXCPU"}, #endif #ifdef SIGXFSZ {SIGXFSZ, "SIGXFSZ"}, #endif #ifdef SIGVTALRM {SIGVTALRM, "SIGVTALRM"}, #endif #ifdef SIGPROF {SIGPROF, "SIGPROF"}, #endif #ifdef SIGWINCH {SIGWINCH, "SIGWINCH"}, #endif #ifdef SIGIO {SIGIO, "SIGIO"}, #endif #ifdef SIGPWR {SIGPWR, "SIGPWR"}, #endif #ifdef SIGSYS {SIGSYS, "SIGSYS"}, #endif #ifdef SIGUSR1 {SIGUSR1, "SIGUSR1"}, #endif #ifdef SIGUSR2 {SIGUSR2, "SIGUSR2"} #endif }; /* Should we be verbose? */ int verbose; /* Should we kill based on all the arguments of the process? */ int useallargs; #if defined(SYS_LINUX) /* getcmd: (Linux) * Get the process's command line from /proc/$pid/cmdline. */ #define CMDLINE_SIZE 1024 char *getcmd(pid_t pid) { if (useallargs) { int fd; char buf[64], *ret = NULL, *p; ssize_t l; sprintf(buf, "/proc/%d/cmdline", (int)pid); fd = open(buf, O_RDONLY); if (fd == -1) goto fail; ret = (char*)malloc(CMDLINE_SIZE); memset(ret, 0, CMDLINE_SIZE); if ((l = read(fd, ret, CMDLINE_SIZE - 1)) == -1) goto fail; /* In Linux, the arguments in /proc/$pid/cmdline are separated by \0. */ for (p = ret; p < ret + l - 1; ++p) if (!*p) *p = ' '; close(fd); return ret; fail: if (fd != -1) close(fd); if (ret) free(ret); return NULL; } else { char buf[64], buf2[1024] = {0}; sprintf(buf, "/proc/%d/exe", (int)pid); if (readlink(buf, buf2, sizeof(buf2)) == -1) return NULL; else { /* Return only the file name of the executable. */ char *p = rindex(buf2, '/'); if (p) return strdup(p + 1); else return strdup(buf2); } } } #elif defined(SYS_SUNOS) /* getcmd: (SunOS) * Get the process's command line from /proc/$pid/psinfo. */ char *getcmd(pid_t pid) { int fd; char buf[64], *ret; psinfo_t psi; sprintf(buf, "/proc/%d/psinfo", (int)pid); fd = open(buf, O_RDONLY); if (fd == -1) return NULL; if (read(fd, &psi, sizeof(psi)) != sizeof(psi)) { close(fd); return NULL; } if (useallargs) return strdup(psi.pr_psargs); else { char *p = rindex(psi.pr_fname, '/'); if (p) return strdup(p + 1); else return strdup(psi.pr_fname); } } #endif #ifdef USE_PROCFS /* killall: (procfs version) * Go through all the processes in the system, and see if they match the * string or pattern given. */ int killall(const char *str, #ifdef USE_REGEXPS const regex_t *re, #endif int sig) { DIR *d; struct dirent *de; int killed = 0; d = opendir("/proc"); if (!d) { fprintf(stderr, "nkill: /proc: %s\n", strerror(errno)); return -1; } while (de = readdir(d)) { if (strlen(de->d_name) == strspn(de->d_name, "0123456789")) { /* Have a numeric pid-alike. */ pid_t pid = (pid_t)atoi(de->d_name); char *s = NULL; if (pid != 1 && pid != getpid()) s = getcmd(pid); if (s) { /* OK, now we have a command string. */ if ((str && strstr(s, str)) #ifdef USE_REGEXPS || (re && regexec(re, s, 0, NULL, 0) == 0) #endif ) { if (verbose) fprintf(stderr, "nkill: %d: %s: sending signal %d\n", (int)pid, s, sig); if (kill(pid, sig) == -1) fprintf(stderr, "nkill: %d: %s: %s\n", (int)pid, s, strerror(errno)); else ++killed; } free(s); } } } return killed; } #else # warning using lame ps(1) based algorithm (use -DSYS_LINUX or -DSYS_SUNOS) /* killall: (ps(1) version) * Run ps, with appropriate arguments, and kill the matching lines. */ /* argv array for invoking ps. */ char *psargs[] = {"/bin/ps", "-A", "-o", "pid,fname", NULL}; /* Probably correct for SysV-style machines. */ char *psargs_a[] = {"/bin/ps", "-A", "-o", "pid,comm", NULL}; /* Display all command arguments. */ /* If your system's ps is defective in the field of Being Any Good, you can * use something like this monstrosity. YMMV. */ /* char *psargs[] = {"/bin/sh", "-c", "ps -ax | sed 's/^ *\([0-9]*\) [^:]*:[0-9][0-9] /\1 /'", NULL }; */ char *psenv[] = {"PATH=/bin", NULL}; #define ERR_MAGIC 42 #define LINE_LEN 2048 /* Maximum plausible line length in PS. */ int killall(const char *str, #ifdef USE_REGEXPS const regex_t *re, #endif int sig) { int pp[2]; pid_t pschild; int killed = 0; if (pipe(pp) == -1) { fprintf(stderr, "nkill: %s\n", strerror(errno)); return -1; } pschild = fork(); if (pschild == -1) { fprintf(stderr, "nkill: fork: %s\n", strerror(errno)); return -1; } else if (pschild == 0) { int i; close(pp[0]); for (i = 0; i < getdtablesize(); ++i) if (i != pp[1]) close(i); dup2(pp[1], 1); /* Make the pipe our stdout. */ execve(psargs[0], useallargs ? psargs : psargs_a, psenv); exit(ERR_MAGIC); } else { int st; close(pp[1]); wait(&st); if (WIFEXITED(st) && WEXITSTATUS(st) != ERR_MAGIC) { /* ps ran according to plan. */ char *buf; FILE *fp; buf = (char*)malloc(LINE_LEN); fp = fdopen(pp[0], "rt"); while (fgets(buf, LINE_LEN - 1, fp)) { char *p = buf + strlen(buf) - 1, *q; pid_t pid; if (*p == '\n') *p = 0; /* Find PID and command line in arguments. */ q = buf; q += strspn(buf, " \t"); pid = atoi(q); q += strspn(q, "0123456789 \t"); if (pid != 1 && pid != getpid() && pid != pschild && ((str && strstr(q, str)) #ifdef USE_REGEXPS || (re && regexec(re, q, 0, NULL, 0) == 0) #endif )) { if (verbose) fprintf(stderr, "nkill: %d: %s: sending signal %d\n", (int)pid, q, sig); if (kill(pid, sig) == -1) fprintf(stderr, "nkill: %d: %s: %s\n", (int)pid, q, strerror(errno)); else ++killed; } } free(buf); } else { fprintf(stderr, "nkill: could not execute %s\n", psargs[0]); return -1; } } return killed; } #endif /* usage: * Display usage. */ void usage() { fprintf(stderr, "nkill: kill named processes\n%s\n\n", rcsid); #ifdef USE_REGEXPS fprintf(stderr, " nkill [-v] [-a] [-SIGNAL] (name | /regexp/) ...\n" " Send the given signal, or SIGTERM if not specified, to processes\n" " matching any of the names or regular expressions given. If -v is\n" " given, nkill prints more detailed information about what it is\n" " doing.\n"); #else fprintf(stderr, " nkill [-v] [-a] [-SIGNAL] name ...\n" " Send the given signal, or SIGTERM if not specified, to processes\n" " matching any of the names given. If -v is given, nkill prints more\n" " detailed information about what it is doing.\n"); #endif fprintf(stderr, "\n" " If the option -a is given, then the full arguments of the process\n" " are tested, rather than simply the process name\n"); fprintf(stderr, "\nCopyright (c) 2001 Chris Lightfoot . Redistribute freely.\n"); exit(1); } /* main: * Entry point. */ int main(int argc, char **argv) { int sig = SIGTERM; char **a; for (a = argv + 1; *a; ++a) { if (**a == '-') { /* An option or signal spec. */ if (*(*a + 1) == 'v') verbose = 1; else if (*(*a + 1) == 'a') useallargs = 1; else if (*(*a + 1) == 'h') usage(); else { /* Must be a signal specification. */ int s; s = atoi(*a + 1); if (s == 0) { struct sigx *p; for (p = known_signals; p < known_signals + sizeof(known_signals) / sizeof(struct sigx); ++p) if (!strcmp(*a + 1, p->name + 3)) { s = p->sig; break; } } if (s == 0) { fprintf(stderr, "nkill: signal specification `%s' makes no sense\n", *a); return 1; } else sig = s; } --argc; } } if (argc == 1) usage(); /* Now go through the arguments, killing processes. */ for (a = argv + 1; *a; ++a) { if (**a != '-') { int n = 0; #ifdef USE_REGEXPS int use_re = 0; regex_t re; /* Regular expressions are delimited by /.../. */ if (**a == '/') { char *q, *p; q = strdup(*a + 1); p = q + strlen(q) - 1; if (*p == '/') { use_re = 1; *p = 0; if (regcomp(&re, q, REG_EXTENDED | REG_NOSUB) != 0) { fprintf(stderr, "nkill: %s: syntax error in regular expression\n", *a); free(q); continue; } } free(q); } if (use_re) { n = killall(NULL, &re, sig); regfree(&re); } else n = killall(*a, NULL, sig); #else n = killall(*a, sig); #endif if (n == 0) fprintf(stderr, "nkill: %s: no process killed\n", *a); else if (verbose) fprintf(stderr, "nkill: %s: %d process%s killed\n", *a, n, n > 1 ? "es" : ""); } } return 0; }