enhanced libproc cgroup/cmdline support, exploited by top
[procps:crrodriguezs-procps.git] / skill.c
1 /*
2  * Copyright 1998-2002 by Albert Cahalan; all rights resered.
3  * This file may be used subject to the terms and conditions of the
4  * GNU Library General Public License Version 2, or any later version
5  * at your option, as published by the Free Software Foundation.
6  * This program is distributed in the hope that it will be useful,
7  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9  * GNU Library General Public License for more details.
10  */
11 #include <fcntl.h>
12 #include <pwd.h>
13 #include <dirent.h>
14 #include <errno.h>
15 #include <signal.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/resource.h>
20 #include <sys/stat.h>
21 #include <sys/time.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24 #include "proc/pwcache.h"
25 #include "proc/sig.h"
26 #include "proc/devname.h"
27 #include "proc/procps.h"  /* char *user_from_uid(uid_t uid) */
28 #include "proc/version.h" /* procps_version */
29
30 static int f_flag, i_flag, v_flag, w_flag, n_flag;
31
32 static int tty_count, uid_count, cmd_count, pid_count;
33 static int *ttys;
34 static uid_t *uids;
35 static const char **cmds;
36 static int *pids;
37
38 #define ENLIST(thing,addme) do{ \
39 if(!thing##s) thing##s = malloc(sizeof(*thing##s)*saved_argc); \
40 if(!thing##s) fprintf(stderr,"No memory.\n"),exit(2); \
41 thing##s[thing##_count++] = addme; \
42 }while(0)
43
44 static int my_pid;
45 static int saved_argc;
46
47 static int sig_or_pri;
48
49 static int program;
50 #define PROG_GARBAGE 0  /* keep this 0 */
51 #define PROG_KILL  1
52 #define PROG_SKILL 2
53 /* #define PROG_NICE  3 */ /* easy, but the old one isn't broken */
54 #define PROG_SNICE 4
55
56
57 /********************************************************************/
58
59 static void display_kill_version(void){
60   switch(program) {
61     case PROG_KILL:
62       fprintf(stdout, "kill (%s)\n",procps_version);
63       return;
64     case PROG_SKILL:
65       fprintf(stdout, "skill (%s)\n",procps_version);
66       return;
67     case PROG_SNICE:
68       fprintf(stdout, "snice (%s)\n",procps_version);
69       return;
70     default:
71       fprintf(stdout, "unknown (%s)\n",procps_version);
72       return;
73   }
74 }
75
76 /***** kill or nice a process */
77 static void hurt_proc(int tty, int uid, int pid, const char *restrict const cmd){
78   int failed;
79   int saved_errno;
80   char dn_buf[1000];
81   dev_to_tty(dn_buf, 999, tty, pid, ABBREV_DEV);
82   if(i_flag){
83     char buf[8];
84     fprintf(stderr, "%-8s %-8s %5d %-16.16s   ? ",
85       (char*)dn_buf,user_from_uid(uid),pid,cmd
86     );
87     if(!fgets(buf,7,stdin)){
88       printf("\n");
89       exit(0);
90     }
91     if(*buf!='y' && *buf!='Y') return;
92   }
93   /* do the actual work */
94   if(program==PROG_SKILL) failed=kill(pid,sig_or_pri);
95   else                    failed=setpriority(PRIO_PROCESS,pid,sig_or_pri);
96   saved_errno = errno;
97   if(w_flag && failed){
98     fprintf(stderr, "%-8s %-8s %5d %-16.16s   ",
99       (char*)dn_buf,user_from_uid(uid),pid,cmd
100     );
101     errno = saved_errno;
102     perror("");
103     return;
104   }
105   if(i_flag) return;
106   if(v_flag){
107     printf("%-8s %-8s %5d %-16.16s\n",
108       (char*)dn_buf,user_from_uid(uid),pid,cmd
109     );
110     return;
111   }
112   if(n_flag){
113    printf("%d\n",pid);
114    return;
115   }
116 }
117
118
119 /***** check one process */
120 static void check_proc(int pid){
121   char buf[128];
122   struct stat statbuf;
123   char *tmp;
124   int tty;
125   int fd;
126   int i;
127   if(pid==my_pid) return;
128   sprintf(buf, "/proc/%d/stat", pid); /* pid (cmd) state ppid pgrp session tty */
129   fd = open(buf,O_RDONLY);
130   if(fd==-1){  /* process exited maybe */
131     if(pids && w_flag) printf("WARNING: process %d could not be found.",pid);
132     return;
133   }
134   fstat(fd, &statbuf);
135   if(uids){  /* check the EUID */
136     i=uid_count;
137     while(i--) if(uids[i]==statbuf.st_uid) break;
138     if(i==-1) goto closure;
139   }
140   read(fd,buf,128);
141   buf[127] = '\0';
142   tmp = strrchr(buf, ')');
143   *tmp++ = '\0';
144   i = 5; while(i--) while(*tmp++!=' '); /* scan to find tty */
145   tty = atoi(tmp);
146   if(ttys){
147     i=tty_count;
148     while(i--) if(ttys[i]==tty) break;
149     if(i==-1) goto closure;
150   }
151   tmp = strchr(buf, '(') + 1;
152   if(cmds){
153     i=cmd_count;
154     /* fast comparison trick -- useful? */
155     while(i--) if(cmds[i][0]==*tmp && !strcmp(cmds[i],tmp)) break;
156     if(i==-1) goto closure;
157   }
158   /* This is where we kill/nice something. */
159 /*  fprintf(stderr, "PID %d, UID %d, TTY %d,%d, COMM %s\n",
160     pid, statbuf.st_uid, tty>>8, tty&0xf, tmp
161   );
162 */
163   hurt_proc(tty, statbuf.st_uid, pid, tmp);
164 closure:
165   close(fd); /* kill/nice _first_ to avoid PID reuse */
166 }
167
168
169 /***** debug function */
170 #if 0
171 static void show_lists(void){
172   int i;
173
174   fprintf(stderr, "%d TTY: ", tty_count);
175   if(ttys){
176     i=tty_count;
177     while(i--){
178       fprintf(stderr, "%d,%d%c", (ttys[i]>>8)&0xff, ttys[i]&0xff, i?' ':'\n');
179     }
180   }else fprintf(stderr, "\n");
181   
182   fprintf(stderr, "%d UID: ", uid_count);
183   if(uids){
184     i=uid_count;
185     while(i--) fprintf(stderr, "%d%c", uids[i], i?' ':'\n');
186   }else fprintf(stderr, "\n");
187   
188   fprintf(stderr, "%d PID: ", pid_count);
189   if(pids){
190     i=pid_count;
191     while(i--) fprintf(stderr, "%d%c", pids[i], i?' ':'\n');
192   }else fprintf(stderr, "\n");
193   
194   fprintf(stderr, "%d CMD: ", cmd_count);
195   if(cmds){
196     i=cmd_count;
197     while(i--) fprintf(stderr, "%s%c", cmds[i], i?' ':'\n');
198   }else fprintf(stderr, "\n");
199 }
200 #endif
201
202
203 /***** iterate over all PIDs */
204 static void iterate(void){
205   int pid;
206   DIR *d;
207   struct dirent *de;
208   if(pids){
209     pid = pid_count;
210     while(pid--) check_proc(pids[pid]);
211     return;
212   }
213 #if 0
214   /* could setuid() and kill -1 to have the kernel wipe out a user */
215   if(!ttys && !cmds && !pids && !i_flag){
216   }
217 #endif
218   d = opendir("/proc");
219   if(!d){
220     perror("/proc");
221     exit(1);
222   }
223   while(( de = readdir(d) )){
224     if(de->d_name[0] > '9') continue;
225     if(de->d_name[0] < '1') continue;
226     pid = atoi(de->d_name);
227     if(pid) check_proc(pid);
228   }
229   closedir (d);
230 }
231
232 /***** kill help */
233 static void kill_usage(void) NORETURN;
234 static void kill_usage(void){
235   fprintf(stderr,
236     "Usage:\n"
237     "  kill pid ...              Send SIGTERM to every process listed.\n"
238     "  kill signal pid ...       Send a signal to every process listed.\n"
239     "  kill -s signal pid ...    Send a signal to every process listed.\n"
240     "  kill -l                   List all signal names.\n"
241     "  kill -L                   List all signal names in a nice table.\n"
242     "  kill -l signal            Convert between signal numbers and names.\n"
243   );
244   exit(1);
245 }
246
247 /***** kill */
248 static void kill_main(int argc, const char *restrict const *restrict argv) NORETURN;
249 static void kill_main(int argc, const char *restrict const *restrict argv){
250   const char *sigptr;
251   int signo = SIGTERM;
252   int exitvalue = 0;
253   if(argc<2) kill_usage();
254   if(!strcmp(argv[1],"-V")|| !strcmp(argv[1],"--version")){
255     display_kill_version();
256     exit(0);
257   }
258   if(argv[1][0]!='-'){
259     argv++;
260     argc--;
261     goto no_more_args;
262   }
263
264   /* The -l option prints out signal names. */
265   if(argv[1][1]=='l' && argv[1][2]=='\0'){
266     if(argc==2){
267       unix_print_signals();
268       exit(0);
269     }
270     /* at this point, argc must be 3 or more */
271     if(argc>128 || argv[2][0] == '-') kill_usage();
272     exit(print_given_signals(argc-2, argv+2, 80));
273   }
274
275   /* The -L option prints out signal names in a nice table. */
276   if(argv[1][1]=='L' && argv[1][2]=='\0'){
277     if(argc==2){
278       pretty_print_signals();
279       exit(0);
280     }
281     kill_usage();
282   }
283   if(argv[1][1]=='-' && argv[1][2]=='\0'){
284     argv+=2;
285     argc-=2;
286     goto no_more_args;
287   }
288   if(argv[1][1]=='-') kill_usage(); /* likely --help */
289   // FIXME: "kill -sWINCH $$" not handled
290   if(argv[1][2]=='\0' && (argv[1][1]=='s' || argv[1][1]=='n')){
291     sigptr = argv[2];
292     argv+=3;
293     argc-=3;
294   }else{
295     sigptr = argv[1]+1;
296     argv+=2;
297     argc-=2;
298   }
299   signo = signal_name_to_number(sigptr);
300   if(signo<0){
301     fprintf(stderr, "ERROR: unknown signal name \"%s\".\n", sigptr);
302     kill_usage();
303   }
304 no_more_args:
305   if(!argc) kill_usage();  /* nothing to kill? */
306   while(argc--){
307     long pid;
308     char *endp;
309     pid = strtol(argv[argc],&endp,10);
310     if(!*endp){
311       if(!kill((pid_t)pid,signo)) continue;
312       // The UNIX standard contradicts itself. If at least one process
313       // is matched for each PID (as if processes could share PID!) and
314       // "the specified signal was successfully processed" (the systcall
315       // returned zero?) for at least one of those processes, then we must
316       // exit with zero. Note that an error might have also occured.
317       // The standard says we return non-zero if an error occurs. Thus if
318       // killing two processes gives 0 for one and EPERM for the other,
319       // we are required to return both zero and non-zero. Quantum kill???
320       exitvalue = 1;
321       continue;
322     }
323     fprintf(stderr, "ERROR: garbage process ID \"%s\".\n", argv[argc]);
324     kill_usage();
325   }
326   exit(exitvalue);
327 }
328
329 /***** skill/snice help */
330 static void skillsnice_usage(void) NORETURN;
331 static void skillsnice_usage(void){
332   if(program==PROG_SKILL){
333     fprintf(stderr,
334       "Usage:   skill [signal to send] [options] process selection criteria\n"
335       "Example: skill -KILL -v pts/*\n"
336       "\n"
337       "The default signal is TERM. Use -l or -L to list available signals.\n"
338       "Particularly useful signals include HUP, INT, KILL, STOP, CONT, and 0.\n"
339       "Alternate signals may be specified in three ways: -SIGKILL -KILL -9\n"
340     );
341   }else{
342     fprintf(stderr,
343       "Usage:   snice [new priority] [options] process selection criteria\n"
344       "Example: snice netscape crack +7\n"
345       "\n"
346       "The default priority is +4. (snice +4 ...)\n"
347       "Priority numbers range from +20 (slowest) to -20 (fastest).\n"
348       "Negative priority numbers are restricted to administrative users.\n"
349     );
350   }
351   fprintf(stderr,
352     "\n"
353     "General options:\n"
354     "-f  fast mode            This is not currently useful.\n"
355     "-i  interactive use      You will be asked to approve each action.\n"
356     "-v  verbose output       Display information about selected processes.\n"
357     "-w  warnings enabled     This is not currently useful.\n"
358     "-n  no action            This only displays the process ID.\n"
359     "\n"
360     "Selection criteria can be: terminal, user, pid, command.\n"
361     "The options below may be used to ensure correct interpretation.\n"
362     "-t  The next argument is a terminal (tty or pty).\n"
363     "-u  The next argument is a username.\n"
364     "-p  The next argument is a process ID number.\n"
365     "-c  The next argument is a command name.\n"
366   );
367   exit(1);
368 }
369
370 #if 0
371 static void _skillsnice_usage(int line){
372   fprintf(stderr,"Something at line %d.\n", line);
373   skillsnice_usage();
374 }
375 #define skillsnice_usage() _skillsnice_usage(__LINE__)
376 #endif
377
378 #define NEXTARG (argc?( argc--, ((argptr=*++argv)) ):NULL)
379
380 /***** common skill/snice argument parsing code */
381 #define NO_PRI_VAL ((int)0xdeafbeef)
382 static void skillsnice_parse(int argc, const char *restrict const *restrict argv){
383   int signo = -1;
384   int prino = NO_PRI_VAL;
385   int force = 0;
386   int num_found = 0;
387   const char *restrict argptr;
388   if(argc<2) skillsnice_usage();
389   if(argc==2 && argv[1][0]=='-'){
390     if(!strcmp(argv[1],"-L")){
391       pretty_print_signals();
392       exit(0);
393     }
394     if(!strcmp(argv[1],"-l")){
395       unix_print_signals();
396       exit(0);
397     }
398     if(!strcmp(argv[1],"-V")|| !strcmp(argv[1],"--version")){
399       display_kill_version();
400       exit(0);
401     }
402     skillsnice_usage();
403   }
404   NEXTARG;
405   /* Time for serious parsing. What does "skill -int 123 456" mean? */
406   while(argc){
407     if(force && !num_found){  /* if forced, _must_ find something */
408       fprintf(stderr,"ERROR: -%c used with bad data.\n", force);
409       skillsnice_usage();
410     }
411     force = 0;
412     if(program==PROG_SKILL && signo<0 && *argptr=='-'){
413       signo = signal_name_to_number(argptr+1);
414       if(signo>=0){      /* found a signal */
415         if(!NEXTARG) break;
416         continue;
417       }
418     }
419     if(program==PROG_SNICE && prino==NO_PRI_VAL
420     && (*argptr=='+' || *argptr=='-') && argptr[1]){
421       long val;
422       char *endp;
423       val = strtol(argptr,&endp,10);
424       if(!*endp && val<=999 && val>=-999){
425         prino=val;
426         if(!NEXTARG) break;
427         continue;
428       }
429     }
430     /* If '-' found, collect any flags. (but lone "-" is a tty) */
431     if(*argptr=='-' && argptr[1]){
432       argptr++;
433       do{
434         switch(( force = *argptr++ )){
435         default:  skillsnice_usage();
436         case 't':
437         case 'u':
438         case 'p':
439         case 'c':
440           if(!*argptr){ /* nothing left here, *argptr is '\0' */
441             if(!NEXTARG){
442               fprintf(stderr,"ERROR: -%c with nothing after it.\n", force);
443               skillsnice_usage();
444             }
445           }
446           goto selection_collection;
447         case 'f': f_flag++; break;
448         case 'i': i_flag++; break;
449         case 'v': v_flag++; break;
450         case 'w': w_flag++; break;
451         case 'n': n_flag++; break;
452         case 0:
453           NEXTARG;
454           /*
455            * If no more arguments, all the "if(argc)..." tests will fail
456            * and the big loop will exit.
457            */
458         } /* END OF SWITCH */
459       }while(force);
460     } /* END OF IF */
461 selection_collection:
462     num_found = 0; /* we should find at least one thing */
463     switch(force){ /* fall through each data type */
464     default: skillsnice_usage();
465     case 0: /* not forced */
466     case 't':
467       if(argc){
468         struct stat sbuf;
469         char path[32];
470         if(!argptr) skillsnice_usage(); /* Huh? Maybe "skill -t ''". */
471         snprintf(path,32,"/dev/%s",argptr);
472         if(stat(path, &sbuf)>=0 && S_ISCHR(sbuf.st_mode)){
473           num_found++;
474           ENLIST(tty,sbuf.st_rdev);
475           if(!NEXTARG) break;
476         }else if(!(argptr[1])){  /* if only 1 character */
477           switch(*argptr){
478           default:
479             if(stat(argptr,&sbuf)<0) break; /* the shell eats '?' */
480           case '-':
481           case '?':
482             num_found++;
483             ENLIST(tty,0);
484             if(!NEXTARG) break;
485           }
486         }
487       }
488       if(force) continue;
489     case 'u':
490       if(argc){
491         struct passwd *passwd_data;
492         passwd_data = getpwnam(argptr);
493         if(passwd_data){
494           num_found++;
495           ENLIST(uid,passwd_data->pw_uid);
496           if(!NEXTARG) break;
497         }
498       }
499       if(force) continue;
500     case 'p':
501       if(argc && *argptr>='0' && *argptr<='9'){
502         char *endp;
503         int num;
504         num = strtol(argptr, &endp, 0);
505         if(*endp == '\0'){
506           num_found++;
507           ENLIST(pid,num);
508           if(!NEXTARG) break;
509         }
510       }
511       if(force) continue;
512       if(num_found) continue; /* could still be an option */
513     case 'c':
514       if(argc){
515         num_found++;
516         ENLIST(cmd,argptr);
517         if(!NEXTARG) break;
518       }
519     } /* END OF SWITCH */
520   } /* END OF WHILE */
521   /* No more arguments to process. Must sanity check. */
522   if(!tty_count && !uid_count && !cmd_count && !pid_count){
523     fprintf(stderr,"ERROR: no process selection criteria.\n");
524     skillsnice_usage();
525   }
526   if((f_flag|i_flag|v_flag|w_flag|n_flag) & ~1){
527     fprintf(stderr,"ERROR: general flags may not be repeated.\n");
528     skillsnice_usage();
529   }
530   if(i_flag && (v_flag|f_flag|n_flag)){
531     fprintf(stderr,"ERROR: -i makes no sense with -v, -f, and -n.\n");
532     skillsnice_usage();
533   }
534   if(v_flag && (i_flag|f_flag)){
535     fprintf(stderr,"ERROR: -v makes no sense with -i and -f.\n");
536     skillsnice_usage();
537   }
538   /* OK, set up defaults */
539   if(prino==NO_PRI_VAL) prino=4;
540   if(signo<0) signo=SIGTERM;
541   if(n_flag){
542     program=PROG_SKILL;
543     signo=0; /* harmless */
544   }
545   if(program==PROG_SKILL) sig_or_pri = signo;
546   else sig_or_pri = prino;
547 }
548
549 /***** main body */
550 int main(int argc, const char *argv[]){
551   const char *tmpstr;
552   my_pid = getpid();
553   saved_argc = argc;
554   if(!argc){
555     fprintf(stderr,"ERROR: could not determine own name.\n");
556     exit(1);
557   }
558   tmpstr=strrchr(*argv,'/');
559   if(tmpstr) tmpstr++;
560   if(!tmpstr) tmpstr=*argv;
561   program = PROG_GARBAGE;
562   if(*tmpstr=='s'){
563     setpriority(PRIO_PROCESS,my_pid,-20);
564     if(!strcmp(tmpstr,"snice")) program = PROG_SNICE;
565     if(!strcmp(tmpstr,"skill")) program = PROG_SKILL;
566   }else{
567     if(!strcmp(tmpstr,"kill")) program = PROG_KILL;
568   }
569   switch(program){
570   case PROG_SNICE:
571   case PROG_SKILL:
572     skillsnice_parse(argc, argv);
573 /*    show_lists(); */
574     iterate(); /* this is it, go get them */
575     break;
576   case PROG_KILL:
577     kill_main(argc, argv);
578     break;
579   default:
580     fprintf(stderr,"ERROR: no \"%s\" support.\n",tmpstr);
581   }
582   return 0;
583 }
584
585