Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / ksysguard / ksysguardd / Linux / ProcessList.c
blobfa61415d20c3f49814b28695819ff1a4825e07da
1 /*
2 KSysGuard, the KDE System Guard
4 Copyright (c) 1999 - 2001 Chris Schlaeger <cs@kde.org>
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of version 2 of the GNU General Public
8 License as published by the Free Software Foundation.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include <ctype.h>
22 #include <dirent.h>
23 #include <errno.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/time.h>
29 #include <sys/resource.h>
30 #include <time.h>
31 #include <unistd.h>
32 #include <sys/ptrace.h>
33 #include <asm/unistd.h>
37 #include "../../gui/SignalIDs.h"
38 #include "Command.h"
39 #include "PWUIDCache.h"
40 #include "ccont.h"
41 #include "ksysguardd.h"
43 #include "ProcessList.h"
45 #define BUFSIZE 1024
46 #define TAGSIZE 32
47 #define KDEINITLEN sizeof( "kdeinit: " )
49 /* For ionice */
50 extern int sys_ioprio_set(int, int, int);
51 extern int sys_ioprio_get(int, int);
53 #define HAVE_IONICE
55 /* Check if this system has ionice */
56 #if !defined(SYS_ioprio_get) || !defined(SYS_ioprio_set)
57 /* All new kernels have SYS_ioprio_get and _set defined, but for the few that do not, here are the definitions */
58 #if defined(__i386__)
59 #define __NR_ioprio_set 289
60 #define __NR_ioprio_get 290
61 #elif defined(__ppc__) || defined(__powerpc__)
62 #define __NR_ioprio_set 273
63 #define __NR_ioprio_get 274
64 #elif defined(__x86_64__)
65 #define __NR_ioprio_set 251
66 #define __NR_ioprio_get 252
67 #elif defined(__ia64__)
68 #define __NR_ioprio_set 1274
69 #define __NR_ioprio_get 1275
70 #else
71 #ifdef __GNUC__
72 #warning "This architecture does not support IONICE. Disabling ionice feature."
73 #endif
74 #undef HAVE_IONICE
75 #endif
76 /* Map these to SYS_ioprio_get */
77 #define SYS_ioprio_get __NR_ioprio_get
78 #define SYS_ioprio_set __NR_ioprio_set
80 #endif /* !SYS_ioprio_get */
82 /* Set up ionice functions */
83 #ifdef HAVE_IONICE
84 #define IOPRIO_WHO_PROCESS 1
85 #define IOPRIO_CLASS_SHIFT 13
87 /* Expose the kernel calls to usespace via syscall
88 * See man ioprio_set and man ioprio_get for information on these functions */
89 static int ioprio_set(int which, int who, int ioprio)
91 return syscall(SYS_ioprio_set, which, who, ioprio);
94 static int ioprio_get(int which, int who)
96 return syscall(SYS_ioprio_get, which, who);
98 #endif
101 #ifndef bool
102 #define bool char
103 #define true 1
104 #define false 0
105 #endif
106 #include "config-ksysguardd.h" /*For HAVE_XRES*/
107 #ifdef HAVE_XRES
108 extern int setup_xres();
109 extern void xrestop_populate_client_data();
110 extern void printXres(FILE *CurrentClient);
111 static int have_xres = 0;
112 #endif
114 typedef struct {
116 /** The parent process ID */
117 pid_t ppid;
119 /** The real user ID */
120 uid_t uid;
122 /** The real group ID */
123 gid_t gid;
125 /** The process ID of any application that is debugging this one. 0 if none */
126 pid_t tracerpid;
128 /** A character description of the process status */
129 char status[ 16 ];
131 /** The tty the process owns */
132 char tty[10];
135 The nice level. The range should be -20 to 20. I'm not sure
136 whether this is true for all platforms.
138 int niceLevel;
140 /** The scheduling priority. */
141 int priority;
143 /** The i/o scheduling class and priority. */
144 int ioPriorityClass; /** 0 for none, 1 for realtime, 2 for best-effort, 3 for idle. -1 for error. */
145 int ioPriority; /** Between 0 and 7. 0 is highest priority, 7 is lowest. -1 for error. */
148 The total amount of virtual memory space that this process uses. This includes shared and
149 swapped memory, plus graphics memory and mmap'ed files and so on.
151 This is in KiB
153 unsigned long vmSize;
156 The amount of physical memory the process currently uses, including the physical memory used by any
157 shared libraries that it uses. Hence 2 processes sharing a library will both report their vmRss as including
158 this shared memory, even though it's only allocated once.
160 This is in KiB
163 unsigned long vmRss;
165 /** The amount of physical memory that is used by this process, not including any memory used by any shared libraries.
166 * This is in KiB */
167 unsigned long vmURss;
170 The number of 1/100 of a second the process has spend in user space.
171 If a machine has an uptime of 1 1/2 years or longer this is not a
172 good idea. I never thought that the stability of UNIX could get me
173 into trouble! ;)
175 unsigned long userTime;
178 The number of 1/100 of a second the process has spent in system space.
179 If a machine has an uptime of 1 1/2 years or longer this is not a
180 good idea. I never thought that the stability of UNIX could get me
181 into trouble! ;)
183 unsigned long sysTime;
185 /* NOTE: To get the user/system percentage, record the userTime and sysTime from between calls, then use the difference divided by the difference in time measure in 100th's of a second */
187 /** The name of the process */
188 char name[ 64 ];
190 /** The command used to start the process */
191 char cmdline[ 256 ];
193 /** The login name of the user that owns this process */
194 char userName[ 32 ];
196 } ProcessInfo;
198 void getIOnice( int pid, ProcessInfo *ps );
199 void ioniceProcess( const char* cmd );
201 static unsigned ProcessCount;
202 static DIR* procDir;
203 static void validateStr( char* str )
205 char* s = str;
207 /* All characters that could screw up the communication will be removed. */
208 while ( *s ) {
209 if ( *s == '\t' || *s == '\n' || *s == '\r' )
210 *s = ' ';
211 ++s;
214 /* Make sure that string contains at least one character (blank). */
215 if ( str[ 0 ] == '\0' )
216 strcpy( str, " " );
219 static bool getProcess( int pid, ProcessInfo *ps )
221 FILE* fd;
222 char buf[ BUFSIZE ];
223 char tag[ TAGSIZE ];
224 char format[ 32 ];
225 char tagformat[ 32 ];
226 const char* uName;
227 char status;
229 snprintf( buf, BUFSIZE - 1, "/proc/%d/status", pid );
230 if ( ( fd = fopen( buf, "r" ) ) == 0 ) {
231 /* process has terminated in the mean time */
232 return false;
234 ps->uid = 0;
235 ps->gid = 0;
236 ps->tracerpid = 0;
238 sprintf( format, "%%%d[^\n]\n", (int)sizeof( buf ) - 1 );
239 sprintf( tagformat, "%%%ds", (int)sizeof( tag ) - 1 );
240 for ( ;; ) {
241 if ( fscanf( fd, format, buf ) != 1 )
242 break;
243 buf[ sizeof( buf ) - 1 ] = '\0';
244 sscanf( buf, tagformat, tag );
245 tag[ sizeof( tag ) - 1 ] = '\0';
246 if ( strcmp( tag, "Name:" ) == 0 ) {
247 sscanf( buf, "%*s %63s", ps->name );
248 validateStr( ps->name );
249 } else if ( strcmp( tag, "Uid:" ) == 0 ) {
250 sscanf( buf, "%*s %d %*d %*d %*d", (int*)&ps->uid );
251 } else if ( strcmp( tag, "Gid:" ) == 0 ) {
252 sscanf( buf, "%*s %d %*d %*d %*d", (int*)&ps->gid );
253 } else if ( strcmp( tag, "TracerPid:" ) == 0 ) {
254 sscanf( buf, "%*s %d", (int*)&ps->tracerpid );
258 if ( fclose( fd ) )
259 return false;
261 snprintf( buf, BUFSIZE - 1, "/proc/%d/stat", pid );
262 buf[ BUFSIZE - 1 ] = '\0';
263 if ( ( fd = fopen( buf, "r" ) ) == 0 )
264 return false;
265 int ttyNo;
266 if ( fscanf( fd, "%*d %*s %c %d %*d %*d %d %*d %*u %*u %*u %*u %*u %lu %lu"
267 "%*d %*d %*d %d %*u %*u %*d %lu %lu",
268 &status, (int*)&ps->ppid, &ttyNo,
269 &ps->userTime, &ps->sysTime, &ps->niceLevel, &ps->vmSize,
270 &ps->vmRss) != 8 ) {
271 fclose( fd );
272 return false;
274 int major = ttyNo >> 8;
275 int minor = ttyNo & 0xff;
276 switch(major) {
277 case 136:
278 snprintf(ps->tty, sizeof(ps->tty)-1, "pts/%d", minor);
279 break;
280 case 4:
281 if(minor < 64)
282 snprintf(ps->tty, sizeof(ps->tty)-1, "tty/%d", minor);
283 else
284 snprintf(ps->tty, sizeof(ps->tty)-1, "ttyS/%d", minor-64);
285 break;
286 default:
287 ps->tty[0] = 0;
290 /*There was a "(ps->vmRss+3) * sysconf(_SC_PAGESIZE)" here originally. I have no idea why! After comparing it to
291 meminfo and other tools, this means we report the RSS by 12 bytes different compared to them. So I'm removing the +3
292 to be consistent. NEXT TIME COMMENT STRANGE THINGS LIKE THAT! :-)*/
293 ps->vmRss = ps->vmRss * sysconf(_SC_PAGESIZE) / 1024; /*convert to KiB*/
294 ps->vmSize /= 1024; /* convert to KiB */
296 if ( fclose( fd ) )
297 return false;
299 snprintf( buf, BUFSIZE - 1, "/proc/%d/statm", pid );
300 buf[ BUFSIZE - 1 ] = '\0';
301 ps->vmURss = -1;
302 if ( ( fd = fopen( buf, "r" ) ) != 0 ) {
303 unsigned long shared;
304 if ( fscanf( fd, "%*d %*u %lu",
305 &shared)==1) {
306 /* we use the rss - shared to find the amount of memory just this app uses */
307 ps->vmURss = ps->vmRss - (shared * sysconf(_SC_PAGESIZE) / 1024);
309 fclose( fd );
313 /* status decoding as taken from fs/proc/array.c */
314 if ( status == 'R' )
315 strcpy( ps->status, "running" );
316 else if ( status == 'S' )
317 strcpy( ps->status, "sleeping" );
318 else if ( status == 'D' )
319 strcpy( ps->status, "disk sleep" );
320 else if ( status == 'Z' )
321 strcpy( ps->status, "zombie" );
322 else if ( status == 'T' )
323 strcpy( ps->status, "stopped" );
324 else if ( status == 'W' )
325 strcpy( ps->status, "paging" );
326 else
327 sprintf( ps->status, "Unknown: %c", status );
330 snprintf( buf, BUFSIZE - 1, "/proc/%d/cmdline", pid );
331 if ( ( fd = fopen( buf, "r" ) ) == 0 )
332 return false;
334 ps->cmdline[ 0 ] = '\0';
336 unsigned int i =0;
337 while( (ps->cmdline[i] = fgetc(fd)) != EOF && i < sizeof(ps->cmdline)-3) {
338 if(ps->cmdline[i] == '\0')
339 ps->cmdline[i] = ' ';
340 i++;
344 if(i > 2) {
345 if(ps->cmdline[i-2] == ' ') ps->cmdline[i-2] = '\0';
346 else ps->cmdline[i-1] = '\0';
347 } else {
348 ps->cmdline[0] = '\0';
351 validateStr( ps->cmdline );
352 if ( fclose( fd ) )
353 return false;
355 /* Ugly hack to "fix" program name for kdeinit launched programs. */
356 if ( strcmp( ps->name, "kdeinit" ) == 0 &&
357 strncmp( ps->cmdline, "kdeinit: ", KDEINITLEN ) == 0 &&
358 strcmp( ps->cmdline + KDEINITLEN, "Running..." ) != 0 ) {
359 size_t len;
360 char* end = strchr( ps->cmdline + KDEINITLEN, ' ' );
361 if ( end )
362 len = ( end - ps->cmdline ) - KDEINITLEN;
363 else
364 len = strlen( ps->cmdline + KDEINITLEN );
365 if ( len > 0 ) {
366 if ( len > sizeof( ps->name ) - 1 )
367 len = sizeof( ps->name ) - 1;
368 strncpy( ps->name, ps->cmdline + KDEINITLEN, len );
369 ps->name[ len ] = '\0';
372 /* find out user name with the process uid */
373 uName = getCachedPWUID( ps->uid );
374 strncpy( ps->userName, uName, sizeof( ps->userName ) - 1 );
375 ps->userName[ sizeof( ps->userName ) - 1 ] = '\0';
376 validateStr( ps->userName );
378 getIOnice(pid, ps);
380 return true;
383 void printProcessList( const char* cmd)
385 (void)cmd;
386 struct dirent* entry;
388 ProcessInfo ps;
389 ProcessCount = 0;
390 rewinddir(procDir);
391 while ( ( entry = readdir( procDir ) ) ) {
392 if ( isdigit( entry->d_name[ 0 ] ) ) {
393 long pid;
394 pid = atol( entry->d_name );
395 if(getProcess( pid, &ps )) /* Print out the details of the process. Because of a stupid bug in kde3 ksysguard, make sure cmdline and tty are not empty */
396 fprintf( CurrentClient, "%s\t%ld\t%ld\t%lu\t%lu\t%s\t%lu\t%lu\t%d\t%lu\t%lu\t%lu\t%s\t%ld\t%s\t%s\t%d\t%d\n",
397 ps.name, pid, (long)ps.ppid,
398 (long)ps.uid, (long)ps.gid, ps.status, ps.userTime,
399 ps.sysTime, ps.niceLevel, ps.vmSize, ps.vmRss, ps.vmURss,
400 (ps.userName[0]==0)?" ":ps.userName, (long)ps.tracerpid,
401 (ps.tty[0]==0)?" ":ps.tty, (ps.cmdline[0]==0)?" ":ps.cmdline,
402 ps.ioPriorityClass, ps.ioPriority
406 fprintf( CurrentClient, "\n" );
407 return;
410 void getIOnice( int pid, ProcessInfo *ps ) {
411 #ifdef HAVE_IONICE
412 int ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); /* Returns from 0 to 7 for the iopriority, and -1 if there's an error */
413 if(ioprio == -1) {
414 ps->ioPriority = -1;
415 ps->ioPriorityClass = -1;
416 return; /* Error. Just give up. */
418 ps->ioPriority = ioprio & 0xff; /* Bottom few bits are the priority */
419 ps->ioPriorityClass = ioprio >> IOPRIO_CLASS_SHIFT; /* Top few bits are the class */
420 #else
421 return; /* Do nothing, if we do not support this architecture */
422 #endif
427 ================================ public part =================================
430 void initProcessList( struct SensorModul* sm )
432 initPWUIDCache();
434 registerMonitor( "pscount", "integer", printProcessCount, printProcessCountInfo, sm );
435 registerMonitor( "ps", "table", printProcessList, printProcessListInfo, sm );
437 if ( !RunAsDaemon ) {
438 registerCommand( "kill", killProcess );
439 registerCommand( "setpriority", setPriority );
440 #ifdef HAVE_IONICE
441 registerCommand( "ionice", ioniceProcess );
442 #endif
445 #ifdef HAVE_XRES
446 have_xres = setup_xres();
447 if(have_xres) {
448 registerLegacyMonitor( "xres", "table", printXresList, printXresListInfo, sm);
450 #endif
452 /*open /proc now in advance*/
453 /* read in current process list via the /proc file system entry */
454 if ( ( procDir = opendir( "/proc" ) ) == NULL ) {
455 print_error( "Cannot open directory \'/proc\'!\n"
456 "The kernel needs to be compiled with support\n"
457 "for /proc file system enabled!\n" );
458 return;
462 void exitProcessList( void )
464 removeMonitor( "ps" );
465 removeMonitor( "pscount" );
467 #ifdef HAVE_XRES
468 if(have_xres)
469 removeMonitor( "xres" );
470 #endif
471 if ( !RunAsDaemon ) {
472 removeCommand( "kill" );
473 removeCommand( "setpriority" );
476 exitPWUIDCache();
478 #ifdef HAVE_XRES
479 void printXresListInfo( const char *cmd)
481 (void)cmd;
482 fprintf(CurrentClient, "XPid\tXIdentifier\tXPxmMem\tXNumPxm\tXMemOther\n");
483 fprintf(CurrentClient, "d\ts\tD\td\tD\n");
486 void printXresList(const char*cmd)
488 (void)cmd;
489 printXres(CurrentClient);
492 #endif
494 void printProcessListInfo( const char* cmd )
496 (void)cmd;
497 fprintf( CurrentClient, "Name\tPID\tPPID\tUID\tGID\tStatus\tUser Time\tSystem Time\tNice\tVmSize"
498 "\tVmRss\tVmURss\tLogin\tTracerPID\tTTY\tCommand\tIO Priority Class\tIO Priority\n" );
499 fprintf( CurrentClient, "s\td\td\td\td\tS\td\td\td\tD\tD\tD\ts\td\ts\ts\td\td\n" );
502 void printProcessCount( const char* cmd )
504 (void)cmd;
505 struct dirent* entry;
506 ProcessCount = 0;
507 rewinddir(procDir);
508 while ( ( entry = readdir( procDir ) ) )
509 if ( isdigit( entry->d_name[ 0 ] ) )
510 ProcessCount++;
513 fprintf( CurrentClient, "%d\n", ProcessCount );
516 void printProcessCountInfo( const char* cmd )
518 (void)cmd;
519 fprintf( CurrentClient, "Number of Processes\t0\t0\t\n" );
522 void killProcess( const char* cmd )
524 /* Sends a signal (not neccessarily kill!) to the process. cmd is a string containing "kill <pid> <signal>" */
525 int sig, pid;
527 sscanf( cmd, "%*s %d %d", &pid, &sig );
528 switch( sig ) {
529 case MENU_ID_SIGABRT:
530 sig = SIGABRT;
531 break;
532 case MENU_ID_SIGALRM:
533 sig = SIGALRM;
534 break;
535 case MENU_ID_SIGCHLD:
536 sig = SIGCHLD;
537 break;
538 case MENU_ID_SIGCONT:
539 sig = SIGCONT;
540 break;
541 case MENU_ID_SIGFPE:
542 sig = SIGFPE;
543 break;
544 case MENU_ID_SIGHUP:
545 sig = SIGHUP;
546 break;
547 case MENU_ID_SIGILL:
548 sig = SIGILL;
549 break;
550 case MENU_ID_SIGINT:
551 sig = SIGINT;
552 break;
553 case MENU_ID_SIGKILL:
554 sig = SIGKILL;
555 break;
556 case MENU_ID_SIGPIPE:
557 sig = SIGPIPE;
558 break;
559 case MENU_ID_SIGQUIT:
560 sig = SIGQUIT;
561 break;
562 case MENU_ID_SIGSEGV:
563 sig = SIGSEGV;
564 break;
565 case MENU_ID_SIGSTOP:
566 sig = SIGSTOP;
567 break;
568 case MENU_ID_SIGTERM:
569 sig = SIGTERM;
570 break;
571 case MENU_ID_SIGTSTP:
572 sig = SIGTSTP;
573 break;
574 case MENU_ID_SIGTTIN:
575 sig = SIGTTIN;
576 break;
577 case MENU_ID_SIGTTOU:
578 sig = SIGTTOU;
579 break;
580 case MENU_ID_SIGUSR1:
581 sig = SIGUSR1;
582 break;
583 case MENU_ID_SIGUSR2:
584 sig = SIGUSR2;
585 break;
588 if ( kill( (pid_t)pid, sig ) ) {
589 switch ( errno ) {
590 case EINVAL:
591 fprintf( CurrentClient, "4\t%d\n", pid );
592 break;
593 case ESRCH:
594 fprintf( CurrentClient, "3\t%d\n", pid );
595 break;
596 case EPERM:
597 if(vfork() == 0) {
598 exit(0);/* Won't execute unless execve fails. Need this for the parent process to continue */
600 fprintf( CurrentClient, "2\t%d\n", pid );
601 break;
602 default: /* unknown error */
603 fprintf( CurrentClient, "1\t%d\n", pid );
604 break;
606 } else
607 fprintf( CurrentClient, "0\t%d\n", pid );
610 void setPriority( const char* cmd )
612 int pid, prio;
613 /** as: setpriority <pid> <priority> */
614 sscanf( cmd, "%*s %d %d", &pid, &prio );
615 if ( setpriority( PRIO_PROCESS, pid, prio ) ) {
616 switch ( errno ) {
617 case EINVAL:
618 fprintf( CurrentClient, "4\t%d\t%d\n", pid, prio );
619 break;
620 case ESRCH:
621 fprintf( CurrentClient, "3\t%d\t%d\nn", pid, prio );
622 break;
623 case EPERM:
624 case EACCES:
625 fprintf( CurrentClient, "2\t%d\t%d\n", pid, prio );
626 break;
627 default: /* unknown error */
628 fprintf( CurrentClient, "1\t%d\t%d\n", pid, prio );
629 break;
631 } else
632 fprintf( CurrentClient, "0\t%d\t%d\n",pid, prio );
635 void ioniceProcess( const char* cmd )
637 /* Re-ionice's a process. cmd is a string containing:
639 * ionice <pid> <class> <priority>
641 * where c = 1 for real time, 2 for best-effort, 3 for idle
642 * and priority is between 0 and 7, 0 being the highest priority, and ignored if c=3
644 * For more information, see: man ionice
647 int pid = 0;
648 int class = 2;
649 int priority = 0;
650 if(sscanf( cmd, "%*s %d %d %d", &pid, &class, &priority ) < 2) {
651 fprintf( CurrentClient, "4\t%d\n", pid ); /* 4 means error in values */
652 return; /* Error with input. */
655 #ifdef HAVE_IONICE
656 if(pid < 1 || class < 0 || class > 3) {
657 fprintf( CurrentClient, "4\t%d\n", pid ); /* 4 means error in values */
658 return; /* Error with input. Just ignore. */
661 if (ioprio_set(IOPRIO_WHO_PROCESS, pid, priority | class << IOPRIO_CLASS_SHIFT) == -1) {
662 switch ( errno ) {
663 case EINVAL:
664 fprintf( CurrentClient, "4\t%d\n", pid );
665 break;
666 case ESRCH:
667 fprintf( CurrentClient, "3\t%d\n", pid );
668 break;
669 case EPERM:
670 fprintf( CurrentClient, "2\t%d\n", pid );
671 break;
672 default: /* unknown error */
673 fprintf( CurrentClient, "1\t%d\n", pid );
674 break;
676 } else {
677 /* Successful */
678 fprintf( CurrentClient, "0\t%d\n", pid );
680 return;
681 #else
682 /** should never reach here */
683 fprintf( CurrentClient, "1\t%d\n", pid );
684 return;
685 #endif