blob: 309b7ecde790953d6945fb56633e353ce257fa54 [file] [log] [blame] [raw]
/*
* kilall5.c Kill all processes except processes that have the
* same session id, so that the shell that called us
* won't be killed. Typically used in shutdown scripts.
*
* pidof.c Tries to get the pid of the process[es] named.
*
* Version: 2.86 30-Jul-2004 MvS
*
* Usage: killall5 [-][signal]
* pidof [-s] [-o omitpid [-o omitpid]] program [program..]
*
* Authors: Miquel van Smoorenburg, miquels@cistron.nl
*
* Riku Meskanen, <mesrik@jyu.fi>
* - return all running pids of given program name
* - single shot '-s' option for backwards combatibility
* - omit pid '-o' option and %PPID (parent pid metavariable)
* - syslog() only if not a connected to controlling terminal
* - swapped out programs pids are caught now
*
* This file is part of the sysvinit suite,
* Copyright 1991-2004 Miquel van Smoorenburg.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <dirent.h>
#include <syslog.h>
#include <getopt.h>
#include <stdarg.h>
#include <sys/mman.h>
char *Version = "@(#)killall5 2.86 31-Jul-2004 miquels@cistron.nl";
#define STATNAMELEN 15
#define DO_STAT 1
#define NO_STAT 0
/* Info about a process. */
typedef struct proc {
char *argv0; /* Name as found out from argv[0] */
char *argv0base; /* `basename argv[1]` */
char *argv1; /* Name as found out from argv[1] */
char *argv1base; /* `basename argv[1]` */
char *statname; /* the statname without braces */
ino_t ino; /* Inode number */
dev_t dev; /* Device it is on */
pid_t pid; /* Process ID. */
int sid; /* Session ID. */
int kernel; /* Kernel thread or zombie. */
struct proc *next; /* Pointer to next struct. */
} PROC;
/* pid queue */
typedef struct pidq {
PROC *proc;
struct pidq *next;
} PIDQ;
typedef struct {
PIDQ *head;
PIDQ *tail;
PIDQ *next;
} PIDQ_HEAD;
/* List of processes. */
PROC *plist;
/* Did we stop all processes ? */
int sent_sigstop;
int scripts_too = 0;
char *progname; /* the name of the running program */
#ifdef __GNUC__
__attribute__ ((format (printf, 2, 3)))
#endif
void nsyslog(int pri, char *fmt, ...);
/*
* Malloc space, barf if out of memory.
*/
void *xmalloc(int bytes)
{
void *p;
if ((p = malloc(bytes)) == NULL) {
if (sent_sigstop) kill(-1, SIGCONT);
nsyslog(LOG_ERR, "out of memory");
exit(1);
}
return p;
}
/*
* See if the proc filesystem is there. Mount if needed.
*/
int mount_proc(void)
{
struct stat st;
char *args[] = { "mount", "-t", "proc", "proc", "/proc", 0 };
pid_t pid, rc;
int wst;
int did_mount = 0;
/* Stat /proc/1 to see if /proc is mounted. */
if (stat("/proc/1", &st) < 0 && errno == ENOENT) {
/* It's not there, so mount it. */
if ((pid = fork()) < 0) {
nsyslog(LOG_ERR, "cannot fork");
exit(1);
}
if (pid == 0) {
/* Try a few mount binaries. */
execv("/sbin/mount", args);
execv("/bin/mount", args);
execv("/usr/bin/mount", args);
/* Okay, I give up. */
nsyslog(LOG_ERR, "cannot execute mount");
exit(1);
}
/* Wait for child. */
while ((rc = wait(&wst)) != pid)
if (rc < 0 && errno == ECHILD)
break;
if (rc != pid || WEXITSTATUS(wst) != 0)
nsyslog(LOG_ERR, "mount returned non-zero exit status");
did_mount = 1;
}
/* See if mount succeeded. */
if (stat("/proc/1", &st) < 0) {
if (errno == ENOENT)
nsyslog(LOG_ERR, "/proc not mounted, failed to mount.");
else
nsyslog(LOG_ERR, "/proc unavailable.");
exit(1);
}
return did_mount;
}
int readarg(FILE *fp, char *buf, int sz)
{
int c = 0, f = 0;
while (f < (sz-1) && (c = fgetc(fp)) != EOF && c)
buf[f++] = c;
buf[f] = 0;
return (c == EOF && f == 0) ? c : f;
}
/*
* Read the proc filesystem.
* CWD must be /proc to avoid problems if / is affected by the killing (ie depend on fuse).
*/
int readproc(int do_stat)
{
DIR *dir;
FILE *fp;
PROC *p, *n;
struct dirent *d;
struct stat st;
char path[256];
char buf[256];
char *s, *q;
unsigned long startcode, endcode;
int pid, f;
/* Open the /proc directory. */
if (chdir("/proc") == -1) {
nsyslog(LOG_ERR, "chdir /proc failed");
return -1;
}
if ((dir = opendir(".")) == NULL) {
nsyslog(LOG_ERR, "cannot opendir(/proc)");
return -1;
}
/* Free the already existing process list. */
n = plist;
for (p = plist; n; p = n) {
n = p->next;
if (p->argv0) free(p->argv0);
if (p->argv1) free(p->argv1);
free(p);
}
plist = NULL;
/* Walk through the directory. */
while ((d = readdir(dir)) != NULL) {
/* See if this is a process */
if ((pid = atoi(d->d_name)) == 0) continue;
/* Get a PROC struct . */
p = (PROC *)xmalloc(sizeof(PROC));
memset(p, 0, sizeof(PROC));
/* Open the status file. */
snprintf(path, sizeof(path), "%s/stat", d->d_name);
/* Read SID & statname from it. */
if ((fp = fopen(path, "r")) != NULL) {
buf[0] = 0;
fgets(buf, sizeof(buf), fp);
/* See if name starts with '(' */
s = buf;
while (*s != ' ') s++;
s++;
if (*s == '(') {
/* Read program name. */
q = strrchr(buf, ')');
if (q == NULL) {
p->sid = 0;
nsyslog(LOG_ERR,
"can't get program name from /proc/%s\n",
path);
free(p);
continue;
}
s++;
} else {
q = s;
while (*q != ' ') q++;
}
*q++ = 0;
while (*q == ' ') q++;
p->statname = (char *)xmalloc(strlen(s)+1);
strcpy(p->statname, s);
/* Get session, startcode, endcode. */
startcode = endcode = 0;
if (sscanf(q, "%*c %*d %*d %d %*d %*d %*u %*u "
"%*u %*u %*u %*u %*u %*d %*d "
"%*d %*d %*d %*d %*u %*u %*d "
"%*u %lu %lu",
&p->sid, &startcode, &endcode) != 3) {
p->sid = 0;
nsyslog(LOG_ERR, "can't read sid from %s\n",
path);
free(p);
continue;
}
if (startcode == 0 && endcode == 0)
p->kernel = 1;
fclose(fp);
} else {
/* Process disappeared.. */
free(p);
continue;
}
snprintf(path, sizeof(path), "%s/cmdline", d->d_name);
if ((fp = fopen(path, "r")) != NULL) {
/* Now read argv[0] */
f = readarg(fp, buf, sizeof(buf));
if (buf[0]) {
/* Store the name into malloced memory. */
p->argv0 = (char *)xmalloc(f + 1);
strcpy(p->argv0, buf);
/* Get a pointer to the basename. */
p->argv0base = strrchr(p->argv0, '/');
if (p->argv0base != NULL)
p->argv0base++;
else
p->argv0base = p->argv0;
}
/* And read argv[1] */
while ((f = readarg(fp, buf, sizeof(buf))) != EOF)
if (buf[0] != '-') break;
if (buf[0]) {
/* Store the name into malloced memory. */
p->argv1 = (char *)xmalloc(f + 1);
strcpy(p->argv1, buf);
/* Get a pointer to the basename. */
p->argv1base = strrchr(p->argv1, '/');
if (p->argv1base != NULL)
p->argv1base++;
else
p->argv1base = p->argv1;
}
fclose(fp);
} else {
/* Process disappeared.. */
free(p);
continue;
}
/* Try to stat the executable. */
snprintf(path, sizeof(path), "/proc/%s/exe", d->d_name);
if (do_stat && stat(path, &st) == 0) {
p->dev = st.st_dev;
p->ino = st.st_ino;
}
/* Link it into the list. */
p->next = plist;
plist = p;
p->pid = pid;
}
closedir(dir);
/* Done. */
return 0;
}
PIDQ_HEAD *init_pid_q(PIDQ_HEAD *q)
{
q->head = q->next = q->tail = NULL;
return q;
}
int empty_q(PIDQ_HEAD *q)
{
return (q->head == NULL);
}
int add_pid_to_q(PIDQ_HEAD *q, PROC *p)
{
PIDQ *tmp;
tmp = (PIDQ *)xmalloc(sizeof(PIDQ));
tmp->proc = p;
tmp->next = NULL;
if (empty_q(q)) {
q->head = tmp;
q->tail = tmp;
} else {
q->tail->next = tmp;
q->tail = tmp;
}
return 0;
}
PROC *get_next_from_pid_q(PIDQ_HEAD *q)
{
PROC *p;
PIDQ *tmp = q->head;
if (!empty_q(q)) {
p = q->head->proc;
q->head = tmp->next;
free(tmp);
return p;
}
return NULL;
}
/* Try to get the process ID of a given process. */
PIDQ_HEAD *pidof(char *prog)
{
PROC *p;
PIDQ_HEAD *q;
struct stat st;
char *s;
int dostat = 0;
int foundone = 0;
int ok = 0;
if (! prog)
return NULL;
/* Get basename of program. */
if ((s = strrchr(prog, '/')) == NULL)
s = prog;
else
s++;
if (! *s)
return NULL;
q = (PIDQ_HEAD *)xmalloc(sizeof(PIDQ_HEAD));
q = init_pid_q(q);
/* Try to stat the executable. */
if (prog[0] == '/' && stat(prog, &st) == 0)
dostat++;
/* First try to find a match based on dev/ino pair. */
if (dostat) {
for (p = plist; p; p = p->next) {
if (p->dev == st.st_dev && p->ino == st.st_ino) {
add_pid_to_q(q, p);
foundone++;
}
}
}
/* If we didn't find a match based on dev/ino, try the name. */
if (!foundone) for (p = plist; p; p = p->next) {
ok = 0;
/* matching nonmatching
* proc name prog name prog name
* --- ----------- ------------
* b b, p/b, q/b
* p/b b, p/b q/b
*
* Algorithm: Match if:
* cmd = arg
* or cmd = base(arg)
* or base(cmd) = arg
*
* Specifically, do not match just because base(cmd) = base(arg)
* as was done in earlier versions of this program, since this
* allows /aaa/foo to match /bbb/foo .
*/
ok |=
(p->argv0 && strcmp(p->argv0, prog) == 0)
|| (p->argv0 && s != prog && strcmp(p->argv0, s) == 0)
|| (p->argv0base && strcmp(p->argv0base, prog) == 0);
/* For scripts, compare argv[1] as well. */
if (
scripts_too && p->statname && p->argv1base
&& !strncmp(p->statname, p->argv1base, STATNAMELEN)
) {
ok |=
(p->argv1 && strcmp(p->argv1, prog) == 0)
|| (p->argv1 && s != prog && strcmp(p->argv1, s) == 0)
|| (p->argv1base && strcmp(p->argv1base, prog) == 0);
}
/*
* if we have a space in argv0, process probably
* used setproctitle so try statname.
*/
if (strlen(s) <= STATNAMELEN &&
(p->argv0 == NULL ||
p->argv0[0] == 0 ||
strchr(p->argv0, ' '))) {
ok |= (strcmp(p->statname, s) == 0);
}
if (ok) add_pid_to_q(q, p);
}
return q;
}
/* Give usage message and exit. */
void usage(void)
{
nsyslog(LOG_ERR, "only one argument, a signal number, allowed");
closelog();
exit(1);
}
/* write to syslog file if not open terminal */
#ifdef __GNUC__
__attribute__ ((format (printf, 2, 3)))
#endif
void nsyslog(int pri, char *fmt, ...)
{
va_list args;
va_start(args, fmt);
if (ttyname(0) == NULL) {
vsyslog(pri, fmt, args);
} else {
fprintf(stderr, "%s: ",progname);
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
}
va_end(args);
}
#define PIDOF_SINGLE 0x01
#define PIDOF_OMIT 0x02
#define PIDOF_OMITSZ 5
/*
* Pidof functionality.
*/
int main_pidof(int argc, char **argv)
{
PIDQ_HEAD *q;
PROC *p;
pid_t opid[PIDOF_OMITSZ], spid;
int f;
int first = 1;
int i, oind, opt, flags = 0;
int chroot_check = 0;
struct stat st;
char tmp[512];
for (oind = PIDOF_OMITSZ-1; oind > 0; oind--)
opid[oind] = 0;
opterr = 0;
while ((opt = getopt(argc,argv,"hco:sx")) != EOF) switch (opt) {
case '?':
nsyslog(LOG_ERR,"invalid options on command line!\n");
closelog();
exit(1);
case 'c':
if (geteuid() == 0) chroot_check = 1;
break;
case 'o':
if (oind >= PIDOF_OMITSZ -1) {
nsyslog(LOG_ERR,"omit pid buffer size %d "
"exceeded!\n", PIDOF_OMITSZ);
closelog();
exit(1);
}
if (strcmp("%PPID",optarg) == 0)
opid[oind] = getppid();
else if ((opid[oind] = atoi(optarg)) < 1) {
nsyslog(LOG_ERR,
"illegal omit pid value (%s)!\n",
optarg);
closelog();
exit(1);
}
oind++;
flags |= PIDOF_OMIT;
break;
case 's':
flags |= PIDOF_SINGLE;
break;
case 'x':
scripts_too++;
break;
default:
/* Nothing */
break;
}
argc -= optind;
argv += optind;
/* Check if we are in a chroot */
if (chroot_check) {
snprintf(tmp, 512, "/proc/%d/root", (int)getpid());
if (stat(tmp, &st) < 0) {
nsyslog(LOG_ERR, "stat failed for %s!\n", tmp);
closelog();
exit(1);
}
}
/* Print out process-ID's one by one. */
readproc(DO_STAT);
for(f = 0; f < argc; f++) {
if ((q = pidof(argv[f])) != NULL) {
spid = 0;
while ((p = get_next_from_pid_q(q))) {
if (flags & PIDOF_OMIT) {
for (i = 0; i < oind; i++)
if (opid[i] == p->pid)
break;
/*
* On a match, continue with
* the for loop above.
*/
if (i < oind)
continue;
}
if (flags & PIDOF_SINGLE) {
if (spid)
continue;
else
spid = 1;
}
if (chroot_check) {
struct stat st2;
snprintf(tmp, 512, "/proc/%d/root",
(int)p->pid);
if (stat(tmp, &st2) < 0 ||
st.st_dev != st2.st_dev ||
st.st_ino != st2.st_ino) {
continue;
}
}
if (!first)
printf(" ");
printf("%d", (int)p->pid);
first = 0;
}
}
}
if (!first)
printf("\n");
closelog();
return(first ? 1 : 0);
}
#define KILLALL_OMITSZ 16
/* Main for either killall or pidof. */
int main(int argc, char **argv)
{
PROC *p;
int pid, sid = -1;
pid_t opid[KILLALL_OMITSZ];
int i, oind, omit = 0;
int sig = SIGKILL;
/* return non-zero if no process was killed */
int retval = 2;
/* Get program name. */
if ((progname = strrchr(argv[0], '/')) == NULL)
progname = argv[0];
else
progname++;
/* Now connect to syslog. */
openlog(progname, LOG_CONS|LOG_PID, LOG_DAEMON);
/* Were we called as 'pidof' ? */
if (strcmp(progname, "pidof") == 0)
return main_pidof(argc, argv);
/* Right, so we are "killall". */
for (oind = KILLALL_OMITSZ-1; oind > 0; oind--)
opid[oind] = 0;
if (argc > 1) {
for (i = 1; i < argc; i++) {
if (argv[i][0] == '-') (argv[i])++;
if (argv[i][0] == 'o') {
if (++i >= argc) usage();
if (oind >= KILLALL_OMITSZ -1) {
nsyslog(LOG_ERR,"omit pid buffer size "
"%d exceeded!\n",
KILLALL_OMITSZ);
closelog();
exit(1);
}
if ((opid[oind] = atoi(argv[i])) < 1) {
nsyslog(LOG_ERR,
"illegal omit pid value "
"(%s)!\n", argv[i]);
closelog();
exit(1);
}
oind++;
omit = 1;
}
else if ((sig = atoi(argv[1])) <= 0 || sig > 31)
usage();
}
}
/* First get the /proc filesystem online. */
mount_proc();
/*
* Ignoring SIGKILL and SIGSTOP do not make sense, but
* someday kill(-1, sig) might kill ourself if we don't
* do this. This certainly is a valid concern for SIGTERM-
* Linux 2.1 might send the calling process the signal too.
*/
signal(SIGTERM, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
signal(SIGKILL, SIG_IGN);
#if !defined __CYGWIN__ && !defined __INTERIX
/* lock us into memory */
mlockall(MCL_CURRENT | MCL_FUTURE);
#endif
/* Now stop all processes. */
kill(-1, SIGSTOP);
sent_sigstop = 1;
/* Read /proc filesystem */
if (readproc(NO_STAT) < 0) {
kill(-1, SIGCONT);
return(1);
}
/* Now kill all processes except init (pid 1) and our session. */
#ifdef __INTERIX
sid = getpgrp();
#else
sid = (int)getsid(0);
#endif
pid = (int)getpid();
for (p = plist; p; p = p->next) {
if (p->pid == 1 || p->pid == pid || p->sid == sid || p->kernel)
continue;
if (omit) {
for (i = 0; i < oind; i++)
if (opid[i] == p->pid)
break;
/* On a match, continue with the for loop above. */
if (i < oind)
continue;
}
kill(p->pid, sig);
retval = 0;
}
/* And let them continue. */
kill(-1, SIGCONT);
/* Done. */
closelog();
/* Force the kernel to run the scheduler */
usleep(1);
return retval;
}