diff options
Diffstat (limited to 'ksysguard/ksysguardd/Linux/ProcessList.c')
-rw-r--r-- | ksysguard/ksysguardd/Linux/ProcessList.c | 554 |
1 files changed, 554 insertions, 0 deletions
diff --git a/ksysguard/ksysguardd/Linux/ProcessList.c b/ksysguard/ksysguardd/Linux/ProcessList.c new file mode 100644 index 000000000..b267c7005 --- /dev/null +++ b/ksysguard/ksysguardd/Linux/ProcessList.c @@ -0,0 +1,554 @@ +/* + KSysGuard, the KDE System Guard + + Copyright (c) 1999 - 2001 Chris Schlaeger <cs@kde.org> + + This program is free software; you can redistribute it and/or + modify it under the terms of version 2 of the GNU General Public + License as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <time.h> +#include <unistd.h> + +#include "../../gui/SignalIDs.h" +#include "Command.h" +#include "PWUIDCache.h" +#include "ccont.h" +#include "ksysguardd.h" + +#include "ProcessList.h" + +#define BUFSIZE 1024 +#define TAGSIZE 32 +#define KDEINITLEN strlen( "kdeinit: " ) + +static CONTAINER ProcessList = 0; + +typedef struct { + /** + This flag is set for all found processes at the beginning of the + process list update. Processes that do not have this flag set will + be assumed dead and removed from the list. The flag is cleared after + each list update. + */ + int alive; + + /* The process ID */ + pid_t pid; + + /* The parent process ID */ + pid_t ppid; + + /* The real user ID */ + uid_t uid; + + /* The real group ID */ + gid_t gid; + + /* A character description of the process status */ + char status[ 16 ]; + + /* The number of the tty the process owns */ + int ttyNo; + + /** + The nice level. The range should be -20 to 20. I'm not sure + whether this is true for all platforms. + */ + int niceLevel; + + /* The scheduling priority. */ + int priority; + + /** + The total amount of memory the process uses. This includes shared and + swapped memory. + */ + unsigned int vmSize; + + /* The amount of physical memory the process currently uses. */ + unsigned int vmRss; + + /** + The number of 1/100 of a second the process has spend in user space. + If a machine has an uptime of 1 1/2 years or longer this is not a + good idea. I never thought that the stability of UNIX could get me + into trouble! ;) + */ + unsigned int userTime; + + /** + The number of 1/100 of a second the process has spend in system space. + If a machine has an uptime of 1 1/2 years or longer this is not a + good idea. I never thought that the stability of UNIX could get me + into trouble! ;) + */ + unsigned int sysTime; + + /* The system time as multime of 100ms */ + int centStamp; + + /* The current CPU load (in %) from user space */ + double userLoad; + + /* The current CPU load (in %) from system space */ + double sysLoad; + + /* The name of the process */ + char name[ 64 ]; + + /* The command used to start the process */ + char cmdline[ 256 ]; + + /* The login name of the user that owns this process */ + char userName[ 32 ]; +} ProcessInfo; + +static unsigned ProcessCount; + +static void validateStr( char* str ) +{ + char* s = str; + + /* All characters that could screw up the communication will be removed. */ + while ( *s ) { + if ( *s == '\t' || *s == '\n' || *s == '\r' ) + *s = ' '; + ++s; + } + + /* Make sure that string contains at least one character (blank). */ + if ( str[ 0 ] == '\0' ) + strcpy( str, " " ); +} + +static int processCmp( void* p1, void* p2 ) +{ + return ( ((ProcessInfo*)p1)->pid - ((ProcessInfo*)p2)->pid ); +} + +static ProcessInfo* findProcessInList( int pid ) +{ + ProcessInfo key; + long idx; + + key.pid = pid; + if ( ( idx = search_ctnr( ProcessList, processCmp, &key ) ) < 0 ) + return 0; + + return get_ctnr( ProcessList, idx ); +} + +static int updateProcess( int pid ) +{ + ProcessInfo* ps; + FILE* fd; + char buf[ BUFSIZE ]; + char tag[ TAGSIZE ]; + char format[ 32 ]; + char tagformat[ 32 ]; + int userTime, sysTime; + const char* uName; + char status; + + if ( ( ps = findProcessInList( pid ) ) == 0 ) { + struct timeval tv; + + ps = (ProcessInfo*)malloc( sizeof( ProcessInfo ) ); + ps->pid = pid; + ps->alive = 0; + + gettimeofday( &tv, 0 ); + ps->centStamp = tv.tv_sec * 100 + tv.tv_usec / 10000; + + push_ctnr( ProcessList, ps ); + bsort_ctnr( ProcessList, processCmp ); + } + + snprintf( buf, BUFSIZE - 1, "/proc/%d/status", pid ); + if ( ( fd = fopen( buf, "r" ) ) == 0 ) { + /* process has terminated in the mean time */ + return -1; + } + + sprintf( format, "%%%d[^\n]\n", (int)sizeof( buf ) - 1 ); + sprintf( tagformat, "%%%ds", (int)sizeof( tag ) - 1 ); + for ( ;; ) { + if ( fscanf( fd, format, buf ) != 1 ) + break; + buf[ sizeof( buf ) - 1 ] = '\0'; + sscanf( buf, tagformat, tag ); + tag[ sizeof( tag ) - 1 ] = '\0'; + if ( strcmp( tag, "Name:" ) == 0 ) { + sscanf( buf, "%*s %63s", ps->name ); + validateStr( ps->name ); + } else if ( strcmp( tag, "Uid:" ) == 0 ) + sscanf( buf, "%*s %d %*d %*d %*d", (int*)&ps->uid ); + } + + if ( fclose( fd ) ) + return -1; + + snprintf( buf, BUFSIZE - 1, "/proc/%d/stat", pid ); + buf[ BUFSIZE - 1 ] = '\0'; + if ( ( fd = fopen( buf, "r" ) ) == 0 ) + return -1; + + if ( fscanf( fd, "%*d %*s %c %d %d %*d %d %*d %*u %*u %*u %*u %*u %d %d" + "%*d %*d %*d %d %*u %*u %*d %u %u", + &status, (int*)&ps->ppid, (int*)&ps->gid, &ps->ttyNo, + &userTime, &sysTime, &ps->niceLevel, &ps->vmSize, + &ps->vmRss) != 9 ) { + fclose( fd ); + return -1; + } + + if ( fclose( fd ) ) + return -1; + + /* status decoding as taken from fs/proc/array.c */ + if ( status == 'R' ) + strcpy( ps->status, "running" ); + else if ( status == 'S' ) + strcpy( ps->status, "sleeping" ); + else if ( status == 'D' ) + strcpy( ps->status, "disk sleep" ); + else if ( status == 'Z' ) + strcpy( ps->status, "zombie" ); + else if ( status == 'T' ) + strcpy( ps->status, "stopped" ); + else if ( status == 'W' ) + strcpy( ps->status, "paging" ); + else + sprintf( ps->status, "Unknown: %c", status ); + + ps->vmRss = ( ps->vmRss + 3 ) * sysconf(_SC_PAGESIZE); + + { + int newCentStamp; + int timeDiff, userDiff, sysDiff; + struct timeval tv; + + gettimeofday( &tv, 0 ); + newCentStamp = tv.tv_sec * 100 + tv.tv_usec / 10000; + + timeDiff = newCentStamp - ps->centStamp; + userDiff = userTime - ps->userTime; + sysDiff = sysTime - ps->sysTime; + + if ( ( timeDiff > 0 ) && ( userDiff >= 0 ) && ( sysDiff >= 0 ) ) { + ps->userLoad = ( (double)userDiff / timeDiff ) * 100.0; + ps->sysLoad = ( (double)sysDiff / timeDiff ) * 100.0; + /** + During startup we get bigger loads since the time diff + cannot be correct. So we force it to 0. + */ + if ( ps->userLoad > 100.0 ) + ps->userLoad = 0.0; + if ( ps->sysLoad > 100.0 ) + ps->sysLoad = 0.0; + } else + ps->sysLoad = ps->userLoad = 0.0; + + ps->centStamp = newCentStamp; + ps->userTime = userTime; + ps->sysTime = sysTime; + } + + snprintf( buf, BUFSIZE - 1, "/proc/%d/cmdline", pid ); + if ( ( fd = fopen( buf, "r" ) ) == 0 ) + return -1; + + ps->cmdline[ 0 ] = '\0'; + sprintf( buf, "%%%d[^\n]", (int)sizeof( ps->cmdline ) - 1 ); + fscanf( fd, buf, ps->cmdline ); + ps->cmdline[ sizeof( ps->cmdline ) - 1 ] = '\0'; + validateStr( ps->cmdline ); + if ( fclose( fd ) ) + return -1; + + /* Ugly hack to "fix" program name for kdeinit launched programs. */ + if ( strcmp( ps->name, "kdeinit" ) == 0 && + strncmp( ps->cmdline, "kdeinit: ", KDEINITLEN ) == 0 && + strcmp( ps->cmdline + KDEINITLEN, "Running..." ) != 0 ) { + size_t len; + char* end = strchr( ps->cmdline + KDEINITLEN, ' ' ); + if ( end ) + len = ( end - ps->cmdline ) - KDEINITLEN; + else + len = strlen( ps->cmdline + KDEINITLEN ); + if ( len > 0 ) { + if ( len > sizeof( ps->name ) - 1 ) + len = sizeof( ps->name ) - 1; + strncpy( ps->name, ps->cmdline + KDEINITLEN, len ); + ps->name[ len ] = '\0'; + } + } + + /* find out user name with the process uid */ + uName = getCachedPWUID( ps->uid ); + strncpy( ps->userName, uName, sizeof( ps->userName ) - 1 ); + ps->userName[ sizeof( ps->userName ) - 1 ] = '\0'; + validateStr( ps->userName ); + + ps->alive = 1; + + return 0; +} + +static void cleanupProcessList( void ) +{ + ProcessInfo* ps; + + ProcessCount = 0; + /** + All processes that do not have the active flag set are assumed dead + and will be removed from the list. The alive flag is cleared. + */ + for ( ps = first_ctnr( ProcessList ); ps; ps = next_ctnr( ProcessList ) ) { + if ( ps->alive ) { + /* Process is still alive. Just clear flag. */ + ps->alive = 0; + ProcessCount++; + } else { + /** + Process has probably died. We remove it from the list and + destruct the data structure. i needs to be decremented so + that after i++ the next list element will be inspected. + */ + free( remove_ctnr( ProcessList ) ); + } + } +} + +int updateProcessList( void ) +{ + DIR* dir; + struct dirent* entry; + + /* read in current process list via the /proc filesystem entry */ + if ( ( dir = opendir( "/proc" ) ) == NULL ) { + print_error( "Cannot open directory \'/proc\'!\n" + "The kernel needs to be compiled with support\n" + "for /proc filesystem enabled!\n" ); + return 0; + } + + while ( ( entry = readdir( dir ) ) ) { + if ( isdigit( entry->d_name[ 0 ] ) ) { + int pid; + + pid = atoi( entry->d_name ); + updateProcess( pid ); + } + } + closedir( dir ); + + cleanupProcessList(); + return 0; +} + +/* +================================ public part ================================= +*/ + +void initProcessList( struct SensorModul* sm ) +{ + initPWUIDCache(); + + ProcessList = new_ctnr(); + + registerMonitor( "pscount", "integer", printProcessCount, printProcessCountInfo, sm ); + registerMonitor( "ps", "table", printProcessList, printProcessListInfo, sm ); + + if ( !RunAsDaemon ) { + registerCommand( "kill", killProcess ); + registerCommand( "setpriority", setPriority ); + } + + updateProcessList(); +} + +void exitProcessList( void ) +{ + removeMonitor( "ps" ); + removeMonitor( "pscount" ); + + if ( !RunAsDaemon ) { + removeCommand( "kill" ); + removeCommand( "setpriority" ); + } + + destr_ctnr( ProcessList, free ); + + exitPWUIDCache(); +} + +void printProcessListInfo( const char* cmd ) +{ + (void)cmd; + fprintf( CurrentClient, "Name\tPID\tPPID\tUID\tGID\tStatus\tUser%%\tSystem%%\tNice\tVmSize" + "\tVmRss\tLogin\tCommand\n" ); + fprintf( CurrentClient, "s\td\td\td\td\tS\tf\tf\td\tD\tD\ts\ts\n" ); +} + +void printProcessList( const char* cmd ) +{ + ProcessInfo* ps; + + (void)cmd; + + for ( ps = first_ctnr( ProcessList ); ps; ps = next_ctnr( ProcessList ) ) { + fprintf( CurrentClient, "%s\t%ld\t%ld\t%ld\t%ld\t%s\t%.2f\t%.2f\t%d\t%d\t%d" + "\t%s\t%s\n", ps->name, (long)ps->pid, (long)ps->ppid, + (long)ps->uid, (long)ps->gid, ps->status, ps->userLoad, + ps->sysLoad, ps->niceLevel, ps->vmSize / 1024, ps->vmRss / 1024, + ps->userName, ps->cmdline ); + } + + fprintf( CurrentClient, "\n" ); +} + +void printProcessCount( const char* cmd ) +{ + (void)cmd; + fprintf( CurrentClient, "%d\n", ProcessCount ); +} + +void printProcessCountInfo( const char* cmd ) +{ + (void)cmd; + fprintf( CurrentClient, "Number of Processes\t0\t0\t\n" ); +} + +void killProcess( const char* cmd ) +{ + int sig, pid; + + sscanf( cmd, "%*s %d %d", &pid, &sig ); + switch( sig ) { + case MENU_ID_SIGABRT: + sig = SIGABRT; + break; + case MENU_ID_SIGALRM: + sig = SIGALRM; + break; + case MENU_ID_SIGCHLD: + sig = SIGCHLD; + break; + case MENU_ID_SIGCONT: + sig = SIGCONT; + break; + case MENU_ID_SIGFPE: + sig = SIGFPE; + break; + case MENU_ID_SIGHUP: + sig = SIGHUP; + break; + case MENU_ID_SIGILL: + sig = SIGILL; + break; + case MENU_ID_SIGINT: + sig = SIGINT; + break; + case MENU_ID_SIGKILL: + sig = SIGKILL; + break; + case MENU_ID_SIGPIPE: + sig = SIGPIPE; + break; + case MENU_ID_SIGQUIT: + sig = SIGQUIT; + break; + case MENU_ID_SIGSEGV: + sig = SIGSEGV; + break; + case MENU_ID_SIGSTOP: + sig = SIGSTOP; + break; + case MENU_ID_SIGTERM: + sig = SIGTERM; + break; + case MENU_ID_SIGTSTP: + sig = SIGTSTP; + break; + case MENU_ID_SIGTTIN: + sig = SIGTTIN; + break; + case MENU_ID_SIGTTOU: + sig = SIGTTOU; + break; + case MENU_ID_SIGUSR1: + sig = SIGUSR1; + break; + case MENU_ID_SIGUSR2: + sig = SIGUSR2; + break; + } + + if ( kill( (pid_t)pid, sig ) ) { + switch ( errno ) { + case EINVAL: + fprintf( CurrentClient, "4\t%d\n", pid ); + break; + case ESRCH: + fprintf( CurrentClient, "3\t%d\n", pid ); + break; + case EPERM: + if(vfork() == 0) { + exit(0);/* Won't execute unless execve fails. Need this for the parent process to continue */ + } + fprintf( CurrentClient, "2\t%d\n", pid ); + break; + default: /* unknown error */ + fprintf( CurrentClient, "1\t%d\n", pid ); + break; + } + } else + fprintf( CurrentClient, "0\t%d\n", pid ); +} + +void setPriority( const char* cmd ) +{ + int pid, prio; + + sscanf( cmd, "%*s %d %d", &pid, &prio ); + if ( setpriority( PRIO_PROCESS, pid, prio ) ) { + switch ( errno ) { + case EINVAL: + fprintf( CurrentClient, "4\n" ); + break; + case ESRCH: + fprintf( CurrentClient, "3\n" ); + break; + case EPERM: + case EACCES: + fprintf( CurrentClient, "2\n" ); + break; + default: /* unknown error */ + fprintf( CurrentClient, "1\n" ); + break; + } + } else + fprintf( CurrentClient, "0\n" ); +} |