/* killall.c - kill processes by name */

/* Copyright 1993-1995 Werner Almesberger. See file COPYING for details. */


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "comm.h"
#include "signals.h"


#define PROC_BASE "/proc"
#define MAX_NAMES (sizeof(unsigned long)*8)


static int verbose = 0,interactive = 0;


static int ask(char *name,pid_t pid)
{
    int ch,c;

    do {
        printf("Kill %s(%d) ? (y/n) ",name,pid);
        fflush(stdout);
	do if ((ch = getchar()) == EOF) exit(0);
	while (ch == '\n' || ch == '\t' || ch == ' ');
	do if ((c = getchar()) == EOF) exit(0);
	while (c != '\n');
    }
    while (ch != 'y' && ch != 'n' && ch != 'Y' && ch != 'N');
    return ch == 'y' || ch == 'Y';
}


static int kill_all(int signal,int names,char **namelist)
{
    DIR *dir;
    struct dirent *de;
    FILE *file;
    struct stat st,sts[MAX_NAMES];
    char path[PATH_MAX+1],comm[COMM_LEN+1];
    pid_t pid,self;
    int empty,dummy,i;
    unsigned long found;

    for (i = 0; i < names; i++)
	if (!strchr(namelist[i],'/')) sts[i].st_dev = 0;
	else if (stat(namelist[i],&sts[i]) < 0) {
		perror(namelist[i]);
		exit(1);
	    }
    self = getpid();
    found = 0;
    if (!(dir = opendir(PROC_BASE))) {
	perror(PROC_BASE);
	exit(1);
    }
    empty = 1;
    while (de = readdir(dir))
	if ((pid = atoi(de->d_name)) && pid != self) {
	    sprintf(path,"%s/%d/stat",PROC_BASE,pid);
	    if (file = fopen(path,"r")) {
		empty = 0;
		if (fscanf(file,"%d (%[^)]",&dummy,comm) == 2)
		    for (i = 0; i < names; i++) {
			if (!sts[i].st_dev) {
			    if (strcmp(namelist[i],comm)) continue;
			}
			else {
			    sprintf(path,"%s/%d/exe",PROC_BASE,pid);
			    if (stat(path,&st) < 0) continue;
			    if (sts[i].st_dev != st.st_dev || sts[i].st_ino !=
			      st.st_ino) continue;
			}
			if (interactive && !ask(comm,pid)) continue;
			if (kill(pid,signal) >= 0) {
			    if (verbose)
				fprintf(stderr,"Killed %s(%d)\n",comm,pid);
			    found |= 1 << i;
			}
			else if (errno != ESRCH || interactive)
				fprintf(stderr,"%s(%d): %s\n",comm,pid,
				  strerror(errno));
		    }
		(void) fclose(file);
	    }
	}
    (void) closedir(dir);
    if (empty) {
	fprintf(stderr,PROC_BASE " is empty (not mounted ?)\n");
	exit(1);
    }
    for (i = 0; i < names; i++)
	if (!(found & (1 << i)))
	    fprintf(stderr,"%s: no process killed\n",namelist[i]);
    return found == ((1 << (names-1)) | ((1 << (names-1))-1)) ? 0 : 1;
}


static void usage(void)
{
    fprintf(stderr,"usage: killall [ -iv ] [ -signal ] name ...\n");
    fprintf(stderr,"       killall -l\n");
    fprintf(stderr,"       killall -V\n\n");
    fprintf(stderr,"    -i      ask for confirmation before killing\n");
    fprintf(stderr,"    -l      list all known signal names\n");
    fprintf(stderr,"    -signal send signal instead of SIGTERM\n");
    fprintf(stderr,"    -v      report if the signal was successfully sent\n");
    fprintf(stderr,"    -V      display version information\n\n");
    exit(1);
}


int main(int argc,char **argv)
{
    int sig_num;

    if (argc == 2 && !strcmp(argv[1],"-l")) {
	list_signals();
	return 0;
    }
    sig_num = SIGTERM;
    while (argc > 1 && *argv[1] == '-') {
	argc--;
	argv++;
	    if (!strcmp(*argv,"-v")) verbose = 1;
	else if (!strcmp(*argv,"-i")) interactive = 1;
	else if (!strcmp(*argv,"-vi") || !strcmp(*argv,"-iv"))
		verbose = interactive = 1;
	else if (!strcmp(*argv,"-V")) {
		fprintf(stderr,"killall from psmisc version " PSMISC_VERSION
		  "\n");
		return 0;
	    }
	else sig_num = get_signal(*argv+1,"killall");
    }
    if (argc < 2) usage();
    if (argc > MAX_NAMES+1) {
	fprintf(stderr,"Maximum number of names is %d\n",MAX_NAMES);
	exit(1);
    }
    return kill_all(sig_num,argc-1,argv+1);
}
