/*
 * kilall.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:	1.01 23-Oct-1993 MvS
 *
 * Usage:	kilall [-][signal]
 *		pidof program [program..]
 *
 * Author:	Miquel van Smoorenburg, miquels@drinkel.nl.mugnet.org
 */
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <dirent.h>
#include <syslog.h>

/* Info about a process. */
typedef struct _proc_
{
  char *fullname;	/* Name as found out from argv[0] */
  char *basename;	/* Only the part after the last / */
  ino_t ino;		/* Inode number			  */
  dev_t dev;		/* Device it is on		  */
  pid_t pid;		/* Process ID.			  */
  int sid;		/* Session ID.			  */
  struct _proc_ *next;	/* Pinter to next struct. 	  */
} PROC;

/* List of processes. */
PROC *plist;

/* Did we stop a number of processes? */
int stopped;

/* Malloc space, barf if out of memory. */
void *xmalloc(bytes)
int bytes;
{
  void *p;

  if ((p = malloc(bytes)) == NULL) {
	if (stopped) kill(-1, SIGCONT);
	syslog(LOG_ERR, "out of memory");
	exit(1);
  }
  return(p);
}

/* See if the proc filesystem is there. Mount if needed. */
void getproc()
{
  struct stat st;
  int pid, wst;

  /* Stat /proc/version to see if /proc is mounted. */
  if (stat("/proc/version", &st) < 0) {

	/* It's not there, so mount it. */
	if ((pid = fork()) < 0) {
		syslog(LOG_ERR, "cannot fork");
		exit(1);
	}
	if (pid == 0) {
		/* Try a few mount binaries. */
		execl("/sbin/mount", "mount", "-t", "proc", "none", "/proc", NULL);
		execl("/etc/mount", "mount", "-t", "proc", "none", "/proc", NULL);
		execl("/bin/mount", "mount", "-t", "proc", "none", "/proc", NULL);

		/* Okay, I give up. */
		syslog(LOG_ERR, "cannot execute mount");
		exit(1);
	}
	/* Wait for child. */
	while(wait(&wst) != pid)
		;
	if (WEXITSTATUS(wst) != 0)
		syslog(LOG_ERR, "mount returned not-zero exit status");
  }

  /* See if mount succeeded. */
  if (stat("/proc/version", &st) < 0) {
	syslog(LOG_ERR, "/proc not mounted, failed to mount.");
	exit(1);
  }
}

/* Read the proc filesystem. */
int readproc()
{
  DIR *dir;
  struct dirent *d;
  char path[256];
  char buf[256];
  FILE *fp;
  int pid, f;
  PROC *p, *n;
  struct stat st;
  int c;

  /* Open the /proc directory. */
  if ((dir = opendir("/proc")) == NULL) {
	syslog(LOG_ERR, "cannot opendir(/proc)");
	return(-1);
  }

  /* Free the already existing process list. */
  n = NULL;
  for(p = plist; n; p = n) {
	n = p->next;
	if (p->fullname) free(p->fullname);
	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 and link it into the list. */
	p = (PROC *)xmalloc(sizeof(PROC));
	memset(p, 0, sizeof(PROC));
	p->next = plist;
	plist = p;
	p->pid = pid;

	/* Open the statistics file. */
	sprintf(path, "/proc/%s/stat", d->d_name);

	/* Read SID from it. */
 	if ((fp = fopen(path, "r")) != NULL) {
		buf[0] = 0;
		fgets(buf, 256, fp);
		if (sscanf(buf, "%*d %*s %*c %*d %*d %d", &p->sid) != 1) {
			p->sid = 0;
			fprintf(stderr, "can't read sid for process %d!\n", pid);
		}
		fclose(fp);
	} else
		fprintf(stderr, "can't read sid for process %d!\n", pid);

	/* Now read argv[0] */
	sprintf(path, "/proc/%s/cmdline", d->d_name);
	if ((fp = fopen(path, "r")) != NULL) {
		f = 0;
		while(f < 127 && (c = fgetc(fp)) != EOF && c) buf[f++] = c;
		buf[f] = 0;
		fclose(fp);

		/* Store the name into malloced memory. */
		if (f == 0) f++;
		p->fullname = (char *)xmalloc(f);
		strcpy(p->fullname, buf);

		/* Get a pointer to the basename. */
		if ((p->basename = strrchr(p->fullname, '/')) != NULL)
			p->basename++;
		else
			p->basename = p->fullname;
	} else
		fprintf(stderr, "can't get name of process %d\n", pid);

	/* Try to stat the executable. */
	sprintf(path, "/proc/%s/exe", d->d_name);
	if (stat(path, &st) == 0) {
		p->dev = st.st_dev;
		p->ino = st.st_ino;
	}
  }
  closedir(dir);

  /* Done. */
  return(0);
}

/* Try to get the process ID of a given process. */
int pidof(prog)
char *prog;
{
  struct stat st;
  int dostat = 0;
  int stat_ok_pid = -1;
  int name_ok_pid = -1;
  int bname_ok_pid = -1;
  PROC *p;
  char *s;

  /* Try to stat the executable. */
  if (stat(prog, &st) == 0) dostat++;

  /* Get basename of program. */
  if ((s = strrchr(prog, '/')) == NULL)
	s = prog;
  else
	s++;

  /* Walk through the list, comparing as we go. */
  for(p = plist; p; p = p->next) {
	/* See if the inode is the same. */
	if (dostat && p->dev == st.st_dev && p->ino == st.st_ino) {
		/* Yes. DEFINITELY a match. */
		stat_ok_pid = p->pid;
		break;
	}
	/* Compare the full name. */
	if (strcmp(p->fullname, prog) == 0) name_ok_pid = p->pid;

	/* Compare the base name. */
	if (strcmp(p->basename, s) == 0) bname_ok_pid = p->pid;
  }

  /* Now, see if we found it. */
  if (stat_ok_pid > 0) return(stat_ok_pid);
  if (name_ok_pid > 0) return(name_ok_pid);
  if (bname_ok_pid > 0) return(bname_ok_pid);

  return(-1);
}

/* Give usage message and exit. */
void usage()
{
  syslog(LOG_ERR, "only one argument, a signal number, allowed");
  closelog();
  exit(1);
}

/* Main for either killall or pidof. */
int main(argc, argv)
int argc;
char **argv;
{
  char *prog;
  PROC *p;
  int f;
  int pid, sid = -1, first = 1;
  int sig = SIGKILL;

  /* Get program name. */
  if ((prog = strrchr(argv[0], '/')) == NULL)
	prog = argv[0];
  else
	prog++;

  /* Now connect to syslog. */
  openlog(prog, LOG_CONS|LOG_PERROR, LOG_DAEMON);

  /* First get the /proc filesystem online. */
  getproc();

  /* See what we are. */
  if (strcmp(prog, "pidof") == 0) {

	/* Print out process-ID's one by one. */
	readproc();
	for(f = 1; f < argc; f++) {
		if ((pid = pidof(argv[f])) > 0) {
			if (!first) printf(" ");
			printf("%d", pid);
			first = 0;
		}
	}
	printf("\n");
	closelog();
	return(first ? 1 : 0);
  }

  /* Right, so we are "killall". */
  if (argc > 1) {
	if (argc != 2) usage();
	if (argv[1][0] == '-') (argv[1])++;
	if ((sig = atoi(argv[1])) <= 0 || sig > 31) usage();
  }

  /* Now stop all processes. */
  kill(-1, SIGSTOP);
  stopped = 1;

  /* Find out our own 'sid'. */
  if (readproc() < 0) {
	kill(-1, SIGCONT);
	exit(1);
  }

  pid = getpid();
  for(p = plist; p; p = p->next)
	if (p->pid == pid) {
		sid = p->sid;
		break;
	}

  /* Now kill all processes except our session. */
  for(p = plist; p; p = p->next)
	if (p->pid != pid && p->sid != sid)
		kill(p->pid, sig);

  /* And let them continue. */
  kill(-1, SIGCONT);

  /* Done. */
  closelog();
  return(0);
}
