ips-4.0/0000755000175000017500000000000011364750237011106 5ustar dbelldbellips-4.0/ips.init0000644000175000017500000000424210740005447012561 0ustar dbelldbell#ips# # The special command macro run by default option SysInit -my -noself Cols # Different formats of interest option Cols -col pid parent user ttyname summary runtime command option Pack -col pid parent uid gid stat ttyd stdio residentset runtime \ age program -sep 1 option All Cols Nocond option Servers All -cond Server option Users All -cond User option Cmd -col pid command -sep 1 option Env -col pid environment -sep 1 option Files -col pid Files option Vert -vert -sep 1 -col All option Cpu Nocond -col pid user summary runtime percentcpu command \ -sep 1 -revsort percentcpu -cond percentcpu -initsleep 2 option Top Cpu -revsort runorder -curses -default cond -active option Wide -width 999999 option Widerun -colwidth systemtime 12 -colwidth usertime 12 \ -colwidth runtime 12 -colwidth childruntime 12 # Different conditions of interest option Nocond -default noself noroot my pid user group active top cond option Tty -cond ttydev.test option Stop -cond Stop option Mytty -cond Mytty # Definitions for related groups of columns column Run runtime idletime percentcpu column Regs eip esp column Sigs signalcatch signalignore signalblock column Size residentsetsize percentmemory size pageswaps column Stdio stdin stdout stderr column Files openfiles Stdio currentdirectory rootdirectory # All columns column All pid parentpid uid user gid group \ processgroup ttyprocessgroup \ state flags priority processor threads \ nice priority realtimepriority policy \ systemtime usertime runtime childruntime \ percentcpu runorder \ residentsetsize size percentmemory \ active idletime starttime deadtime age realtimer \ eip esp waitchannel waitsymbol \ pagefaults minorpagefaults majorpagefaults \ pageswaps childpageswaps \ signalcatch signalignore signalblock \ ttyname ttydevice \ openfiles stdin stdout stderr stdio \ currentdirectory rootdirectory executable \ summary program command environment # Special definition to identify the first user id expr SysFirstUserId 100 # Other definitions to use in conditions expr Me (uid == my(uid)) expr Mytty (ttydev == my(ttydev)) expr Server (uid < SysFirstUserId) expr User !Server expr Stop (state == 'T') ips-4.0/file.c0000644000175000017500000001265311360557332012175 0ustar dbelldbell/* * Configurable ps-like program. * Initialisation file parsing routines. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include #include "ips.h" #define ALLOC_SIZE 100 /* allocation size for lines */ /* * Local procedures. */ static char * ReadLine(FILE * fp); /* * Try to access the initial file in the user's home directory. * The file may or may not exist. */ BOOL ParseUserInitFile(void) { const char * home; char * fullName; BOOL result; /* * Get the user's home directory and then build the full * path from it and the user init file name. */ home = getenv("HOME"); if (home == NULL) return TRUE; fullName = AllocMemory(strlen(home) + sizeof(USER_INIT_FILE) + 2); strcpy(fullName, home); strcat(fullName, "/"); strcat(fullName, USER_INIT_FILE); /* * Try to parse the file. */ result = ParseFile(fullName, TRUE); free(fullName); return result; } /* * Try to parse the system initialisation file. * The file may or may not exist. */ BOOL ParseSystemInitFile(void) { /* * Try to parse the system initialisation file. */ return ParseFile(SYSTEM_INIT_FILE, TRUE); } /* * Try to parse the definitions within a specified file. * If the isOptional flag is set, then the file does not have to exist. * Returns TRUE if the file was successfully parsed. */ BOOL ParseFile(const char * name, BOOL isOptional) { FILE * fp; char * cp; char * cmd; BOOL status; MACRO_TYPE macroType; status = TRUE; /* * If the file is optional then check whether it exists. * In not, then return success without doing anything. */ if (isOptional && (access(name, F_OK) != 0) && (errno == ENOENT)) return TRUE; /* * Check our permissions for reading the file before using it, * in case we are running suid as root. */ if (access(name, R_OK) != 0) { fprintf(stderr, "Cannot access \"%s\" for reading: %s\n", name, strerror(errno)); return FALSE; } fp = fopen(name, "r"); if (fp == NULL) { fprintf(stderr, "Cannot open \"%s\" for reading: %s\n", name, strerror(errno)); return FALSE; } /* * For further security, make sure that the first line contains * the magic string identifying an ips configuration file. */ cmd = ReadLine(fp); if ((cmd == NULL) || (strcmp(cmd, FIRST_LINE) != 0)) { fclose(fp); fprintf(stderr, "The file \"%s\" does not begin with \"%s\".\n", name, FIRST_LINE); return FALSE; } /* * OK, read each line of the file and handle it. */ while (TRUE) { cmd = ReadLine(fp); if (cmd == NULL) break; while (isBlank(*cmd)) cmd++; /* * Skip blank lines or lines starting with hash marks. */ if ((*cmd == '#') || (*cmd == '\0')) continue; cp = cmd; while ((*cp != '\0') && !isBlank(*cp)) cp++; if (*cp) *cp++ = '\0'; while (isBlank(*cp)) cp++; /* * If a macro is being defined, then do that. */ macroType = MACRO_TYPE_NONE; if (strcmp(cmd, "option") == 0) macroType = MACRO_TYPE_OPTION; else if (strcmp(cmd, "column") == 0) macroType = MACRO_TYPE_COLUMN; else if (strcmp(cmd, "expr") == 0) macroType = MACRO_TYPE_EXPRESSION; if (macroType != MACRO_TYPE_NONE) { cmd = cp; while ((*cp != '\0') && !isBlank(*cp)) cp++; if (*cp) *cp++ = '\0'; while (isBlank(*cp)) cp++; if (!DefineMacro(macroType, cmd, cp)) status = FALSE; continue; } /* * The command is unknown. */ fprintf(stderr, "%s: Unknown command \"%s\"\n", name, cmd); status = FALSE; break; } if (ferror(fp)) { fprintf(stderr, "Error reading \"%s\": %s\n", name, strerror(errno)); fclose(fp); return FALSE; } (void) fclose(fp); return status; } /* * Read the next line from the opened file. * Returns a pointer to a static buffer which contains the null * terminated line with the newline removed, or a NULL pointer on * end of file or error. This handles backslashes at the end of lines * to indicate that the line is continued on the next line. */ static char * ReadLine(FILE * fp) { char * tmpBuf; static char * buf; static int avail; static int used; used = 0; while (TRUE) { /* * Grow the buffer if we need more room. */ if (used + ALLOC_SIZE > avail) { if (avail == 0) tmpBuf = malloc(ALLOC_SIZE); else tmpBuf = realloc(buf, avail + ALLOC_SIZE); if (tmpBuf == NULL) { fprintf(stderr, "Cannot allocate memory\n"); return NULL; } buf = tmpBuf; avail += ALLOC_SIZE; } /* * Read some more of the file into the buffer after any * part of the line which may have already been read. */ if (fgets(&buf[used], avail - used, fp) == NULL) { if (used) return buf; return NULL; } /* * Add to the size of the line read so far. */ used += strlen(&buf[used]); /* * If the buffer doesn't end in a newline, then we didn't * read the whole line, so go back and read some more. */ if ((used > 0) && (buf[used - 1] != '\n')) continue; /* * Remove the newline from the end of the data. */ buf[--used] = '\0'; /* * See if the last character of the line is now a backslash. * If so, then replace it with a blank and continue reading. */ if ((used > 0) && (buf[used - 1] == '\\')) { buf[used - 1] = ' '; continue; } /* * The line is completely read in, so return it. */ return buf; } } /* END CODE */ ips-4.0/sort.c0000644000175000017500000001747411360561147012252 0ustar dbelldbell/* * Configurable ps-like program. * Sorting support routines. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include "ips.h" #include "expr.h" #define MAX_SORT 50 /* maximum items to allow sorting by */ #define SORT_ALLOC_SIZE 100 /* reallocation size for sorting */ /* * Types of values to sort on. */ typedef int SORT_TYPE; #define SORT_NORMAL 1 /* normal sort on column value */ #define SORT_REVERSE 2 /* reverse sort on column value */ #define SORT_NORMAL_EXPR 3 /* sort on expression evaluation */ #define SORT_REVERSE_EXPR 4 /* reverse sort on expression */ /* * One item of sorting. */ typedef struct { SORT_TYPE type; /* type of sort */ COLUMN * column; /* column to sort by */ TREE * tree; /* expression tree to sort by */ } SORT_ITEM; /* * Data to specify sorting conditions. */ static int sortCount; /* number of items in table */ static SORT_ITEM sortTable[MAX_SORT]; /* table of sorting items */ /* * Data used during the actual sorting. */ static int procSortSize; /* entries in sort table */ static PROC ** procSortTable; /* proc sorting table */ /* * Local procedures. */ static int SortFunction(const void * e1, const void * e2); static int CompareSortingItems(const PROC * proc1, const PROC * proc2); /* * Get the use flags for all the columns that we sort by. * This just means find all references to columns, and OR together * the use flags for each column. */ USEFLAG GetSortingUseFlags(void) { const SORT_ITEM * item; USEFLAG flags; flags = USE_NONE; for (item = sortTable; item < &sortTable[sortCount]; item++) { switch (item->type) { case SORT_NORMAL: case SORT_REVERSE: flags |= item->column->useFlag; break; case SORT_NORMAL_EXPR: case SORT_REVERSE_EXPR: flags |= GetNodeUseFlags(item->tree->root); break; default: break; } } return flags; } /* * Clear all sorting. */ void ClearSorting(void) { sortCount = 0; } /* * Append items to do sorting according to the indicated column names. * The sorting can be the normal forwards sort, or else a reverse sort. * The arguments are column names, no expressions are allowed here. */ BOOL AppendColumnSort(ARGS * ap, BOOL reverse) { SORT_TYPE type; SORT_ITEM * item; const char * name; COLUMN * column; int count; int i; char * table[MAX_WORDS]; type = (reverse ? SORT_REVERSE : SORT_NORMAL); count = ExpandArguments(ap, table, MAX_WORDS); if (count < 0) return FALSE; for (i = 0; i < count; i++) { if (sortCount >= MAX_SORT) { fprintf(stderr, "Too many sort items\n"); return FALSE; } name = table[i]; column = FindColumn(name); if (column == NULL) { fprintf(stderr, "Bad sorting column name %s\n", name); return FALSE; } item = &sortTable[sortCount]; item->type = type; item->column = column; item->tree = NULL; sortCount++; } return TRUE; } /* * Routine to append a sort expression to the sorting list. * The sorting can be the normal forwards sort, or else a reverse sort. */ BOOL AppendExpressionSort(ARGS * ap, BOOL reverse) { SORT_TYPE type; SORT_ITEM * item; char * str; type = (reverse ? SORT_REVERSE_EXPR : SORT_NORMAL_EXPR); if (ap->count <= 0) { fprintf(stderr, "Missing sort expression\n"); return FALSE; } ap->count--; str = *ap->table++; if (sortCount >= MAX_SORT) { fprintf(stderr, "Too many sort items\n"); return FALSE; } item = &sortTable[sortCount]; item->type = type; item->column = NULL; item->tree = (TREE *) malloc(sizeof(TREE)); if (item->tree == NULL) { fprintf(stderr, "Cannot allocate memory\n"); return FALSE; } if (!ParseTree(item->tree, str, 0)) return FALSE; sortCount++; return TRUE; } /* * Sort the process list. */ void SortProcesses(void) { int count; PROC * proc; PROC ** procPtr; /* * If we need to grow the sorting table to include new processes, * then do so. */ if (procAllocCount >= procSortSize) { procSortSize = procAllocCount + SORT_ALLOC_SIZE; procSortTable = (PROC **) realloc(procSortTable, (sizeof(PROC *) * (procSortSize + 1))); if (procSortTable == NULL) { fprintf(stderr, "Cannot allocate memory\n"); exit(1); } } /* * Put the process entries into the sort table. */ count = 0; for (proc = processList; proc; proc = proc->next) procSortTable[count++] = proc; procSortTable[count] = NULL_PROC; /* * Sort the entries in the table. */ qsort((void *) procSortTable, count, sizeof(PROC *), SortFunction); /* * Relink the processes into a new list according to the sorted * table pointers. */ procPtr = procSortTable; processList = *procPtr; while (*procPtr) { procPtr[0]->next = procPtr[1]; procPtr++; } } /* * Function called by qsort to compare two processes for the correct * sorting order. Returns -1 if the first process is first, 1 if the * second process is first, or 0 if they are equal. (But there should * be no case where two different processes actually sort as equal.) */ static int SortFunction(const void * e1, const void * e2) { const PROC ** procPtr1; const PROC ** procPtr2; const PROC * proc1; const PROC * proc2; int status; procPtr1 = (const PROC **) e1; procPtr2 = (const PROC **) e2; proc1 = *procPtr1; proc2 = *procPtr2; /* * Check for processes which are not to be shown so as to * avoid any hard work comparing them to sort them. */ if (!proc1->isShown || !proc2->isShown) { if (proc1->isShown) return -1; if (proc2->isShown) return 1; return 0; } /* * Do sorting based on any specified sorting items, * which can be column names or arbitrary expressions. */ status = CompareSortingItems(proc1, proc2); if (status != 0) return status; /* * They appear equal for all sorting items, so do a sort on * their process ids. */ if (proc1->pid < proc2->pid) return -1; if (proc1->pid > proc2->pid) return 1; /* * The process ids match, so sort on their thread ids. * Always put the main thread first. */ if ((proc1->tid == NO_THREAD_ID) && (proc2->tid != NO_THREAD_ID)) return -1; if ((proc1->tid != NO_THREAD_ID) && (proc2->tid == NO_THREAD_ID)) return 1; if ((proc1->tid == proc1->pid) && (proc2->tid != proc2->pid)) return -1; if ((proc1->tid != proc1->pid) && (proc2->tid == proc2->pid)) return 1; if (proc1->tid < proc2->tid) return -1; if (proc1->tid > proc2->tid) return 1; /* * There are multiple processes with the same ids. * Sort on their start times (newer processes first). */ if (proc1->startTimeTicks > proc2->startTimeTicks) return -1; if (proc1->startTimeTicks < proc2->startTimeTicks) return -1; return 0; } /* * Compare two processes using the defined list of sorting items. * The comparison can be on column values or on arbitrary expressions. * Returns -1 if the first process is less, 1 if the first process is * more, or 0 if they are identical. */ static int CompareSortingItems(const PROC * proc1, const PROC * proc2) { SORT_ITEM * item; TREE * tree; int status; VALUE value1; VALUE value2; if (proc1 == proc2) return 0; for (item = sortTable; item < &sortTable[sortCount]; item++) { switch (item->type) { case SORT_NORMAL: status = item->column->sortFunc(proc1, proc2); break; case SORT_REVERSE: status = -item->column->sortFunc(proc1, proc2); break; case SORT_NORMAL_EXPR: case SORT_REVERSE_EXPR: tree = item->tree; tree->proc = proc1; value1 = EvaluateNode(tree, tree->root); tree->proc = proc2; value2 = EvaluateNode(tree, tree->root); if (!CompareValues(value1, value2, &status)) return 0; if (item->type == SORT_REVERSE_EXPR) status = -status; break; default: status = 0; break; } if (status != 0) return status; } return 0; } /* END CODE */ ips-4.0/ips.h0000644000175000017500000005425111364726167012066 0ustar dbelldbell/* * Configurable ps-like program. * Global definitions. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #ifndef IPS_H #define IPS_H #include #include #include #include #include #include #include #include #include #ifndef SYSTEM_INIT_FILE #define SYSTEM_INIT_FILE "/usr/local/lib/ips.init" #endif #ifndef USER_INIT_FILE #define USER_INIT_FILE ".ipsrc" #endif #ifndef SYSTEM_INIT_MACRO #define SYSTEM_INIT_MACRO "SysInit" #endif #ifndef USER_INIT_MACRO #define USER_INIT_MACRO "UserInit" #endif #define FIRST_LINE "#ips#" /* first line of init files */ /* * Some constants related to the system. */ #define CPU_SCALE 10000 /* scaling factor for cpu usage */ #define MEMORY_SCALE 1000 /* scaling factor for memory usage */ #define BASE_USER_UID 100 /* first "user" uid */ /* * Constants for calculating CPU percentages. */ #define PERCENT_RESOLUTION 5 /* max number of CPU samples per second */ #define PERCENT_MAX_SECONDS 20 /* max seconds for collecting CPU samples */ #define PERCENT_DEFAULT_SECONDS 10 /* default seconds for cpu percentage */ #define PERCENT_SAMPLES ((PERCENT_RESOLUTION) * (PERCENT_MAX_SECONDS) + 2) /* total samples */ /* * Define widths of numbers which depend on the size of a long. */ #if 2147483647 + 1 > 0 #define LONG_HEX_DIGITS 16 #define LONG_DIGITS 21 #define LONG_HEX_FORMAT "%016lx" #else #define LONG_HEX_DIGITS 8 #define LONG_DIGITS 11 #define LONG_HEX_FORMAT "%08lx" #endif /* * Some default values. */ #define DEFAULT_INIT_SEC 1 /* default initialization time */ #define DEFAULT_SLEEP_SEC 10 /* default sleep between loops */ #define DEFAULT_SCROLL_SEC 30 /* default time between autoscrolls */ #define DEFAULT_OVERLAP_LINES 1 /* default overlapping lines */ #define DEFAULT_SYNC_SEC 300 /* default time to force data sync */ #define DEFAULT_ACTIVE_SEC 60 /* default time to consider active */ #define DEFAULT_DEATH_SEC 30 /* default time to preserve death */ #define DEFAULT_WIDTH 80 /* default width if not known */ #define DEFAULT_GEOMETRY "150x50" /* geometry for X11 */ #define DEFAULT_GEOMETRY_ROWS 50 /* row part of geometry */ #define DEFAULT_GEOMETRY_COLS 150 /* col part of geometry */ #define DEFAULT_FONT "fixed" /* font name for X11 */ #define DEFAULT_FOREGROUND "black" /* foreground color for X11 */ #define DEFAULT_BACKGROUND "white" /* background color for X11 */ /* * Some limits on the program. */ #define MAX_PIDS 100 #define MAX_USERS 100 #define MAX_GROUPS 100 #define MAX_PROGRAMS 20 #define MAX_WORDS 1000 #define MAX_COLUMNS 50 #define MAX_SEPARATION 20 #define MAX_MACRO_LEN 16 #define MAX_OPTION_DEPTH 20 #define MAX_EXPR_DEPTH 20 #define MAX_WIDTH (1024 * 31) #define MAX_COMMAND_LEN (1024 * 10) #define MAX_ENVIRON_LEN (1024 * 20) #define MAX_INFO_LEN 256 #define MAX_PATH_LEN 1024 #define BUF_COMMAND_LEN 128 #define MAX_PROGRAM_LEN 32 #define MAX_WCHAN_LEN 32 #define MAX_STATES_LEN 16 #define MAX_COLORS 20 #define MAX_ROW_COLORS 50 /* * Macros to help parse strings. */ #define isBlank(ch) (((ch) == ' ') || ((ch) == '\t')) #define isDigit(ch) (((ch) >= '0') && ((ch) <= '9')) #define isUpper(ch) (((ch) >= 'A') && ((ch) <= 'Z')) #define isLower(ch) (((ch) >= 'a') && ((ch) <= 'z')) #define isMacro(ch) isUpper(ch) /* * Boolean values. */ typedef int BOOL; #define FALSE ((BOOL) 0) #define TRUE ((BOOL) 1) /* * Some other typedefs. */ typedef unsigned long ULONG; typedef struct COLUMN COLUMN; /* * Special values for unknown user, group, or device ids. */ #define BAD_UID ((uid_t) -1) #define BAD_GID ((gid_t) -1) #define BAD_DEVID ((dev_t) -1) /* * Argument structure. * This is for parsing command line arguments. */ typedef struct { int count; /* number of arguments */ char ** table; /* table of arguments */ } ARGS; /* * Structure which holds information about a process. * This is used for main process information and also for threads * making up the main process. The main process entry is a summary * of all the threads for a process and has a tid of NO_THREAD_ID. * For a multi-threaded process each thread has its own entry, * including the main starting thread. Threads are linked together * and know their main process owner entry. */ typedef struct PROC PROC; struct PROC { PROC * next; /* next process or thread in list */ PROC * nextThread; /* first or next thread belonging to owner */ PROC * owner; /* main process owning this thread */ BOOL isValid; /* the process is existent */ BOOL isAncient; /* the process is before we started */ BOOL isNew; /* the process is just created */ BOOL isActive; /* the process is active */ BOOL isChanged; /* the process state has changed */ BOOL isShown; /* the process is to be shown */ BOOL hasCommand; /* there is a real command line */ BOOL isThread; /* this is a thread process entry */ ULONG liveCounter; /* counter to mark existant procs */ ULONG runOrder; /* counter value when last active */ time_t lastSavedTime; /* time status was copied to old */ time_t lastActiveTime; /* time process last was active */ time_t lastSyncTime; /* time status was last synchonized */ time_t deathTime; /* time process was seen to be dead */ uid_t uid; /* effective user id */ gid_t gid; /* effective group id */ pid_t pid; /* process id */ pthread_t tid; /* thread id (or NO_THREAD_ID) */ pid_t parentPid; /* parent pid */ pid_t processGroup; /* process group */ pid_t sessionId; /* session id */ dev_t ttyDevice; /* controlling terminal's device */ pid_t ttyProcessGroup; /* process group of terminal */ int state; /* process state character */ int processor; /* processor process is on */ int exitSignal; /* signal causing exit */ ULONG flags; /* kernel flags */ long minorFaults; /* minor page faults */ long majorFaults; /* major page faults */ long childMinorFaults; /* child minor page faults */ long childMajorFaults; /* child major page faults */ long userRunTime; /* user runtime in jiffies */ long systemRunTime; /* system runtime in jiffies */ long childUserRunTime; /* child user runtime */ long childSystemRunTime; /* child system runtime */ long nice; /* nice value */ long priority; /* scheduling priority */ long realTimePriority; /* real time priority */ long timeout; /* timeout */ long itRealValue; /* jiffies sleeping for */ long virtualSize; /* virtual size of process in bytes */ long rss; /* resident size in clicks */ long rssLimit; /* resident size limit */ long policy; /* policy */ time_t startTimeClock; /* clock time when started */ ULONG startTimeTicks; /* jiffies uptime when started */ ULONG firstCpuTime; /* cpu runtime when first examined */ ULONG startCode; /* beginning address of code */ ULONG endCode; /* ending address of code */ ULONG startStack; /* starting address of stack */ ULONG esp; /* stack pointer */ ULONG eip; /* instruction pointer */ ULONG waitChan; /* wait channel address */ ULONG signal; /* current signal */ ULONG sigBlock; /* signals to block */ ULONG sigIgnore; /* signals to ignore */ ULONG sigCatch; /* signals to catch */ ULONG pagesSwapped; /* pages swapped out */ ULONG childPagesSwapped; /* child pages swapped out */ int percentCpu; /* percentage of cpu used */ int percentMemory; /* percentage of memory used */ int openFiles; /* number of open files */ int threadCount; /* number of threads */ int commandLength; /* length of command line */ int environmentLength; /* length of environment */ int cwdPathLength; /* length of current working dir path */ int rootPathLength; /* length of root directory path */ int execPathLength; /* length of executable path */ char * command; /* command line */ char * environment; /* environment */ char * cwdPath; /* path of current working directory */ char * rootPath; /* path of root directory */ char * execPath; /* path of executable */ char * stdioPaths[3]; /* paths of stdin, stdout, stderr */ char program[MAX_PROGRAM_LEN + 2]; /* program name */ char commandBuffer[BUF_COMMAND_LEN + 2]; /* command buffer */ char waitChanSymbol[MAX_WCHAN_LEN + 2]; /* wait channel */ char states[MAX_STATES_LEN + 2]; /* thread states */ long cpuTable[PERCENT_SAMPLES]; /* cpu runtime sample table */ /* * Status which is saved in order to determine active processes * even if they are currently sleeping for this snapshot. * A process is active if any of this differs from the current * value. */ int oldState; int oldFlags; long oldMinorFaults; long oldMajorFaults; long oldUserRunTime; long oldSystemRunTime; ULONG oldStartTimeTicks; ULONG oldEndCode; ULONG oldEsp; ULONG oldEip; ULONG oldWaitChan; }; #define NULL_PROC ((PROC *) 0) /* * The thread id for the main process entry. */ #define NO_THREAD_ID ((pthread_t) -1) /* * Column justification definitions. */ typedef int JUSTIFY; #define LEFT ((JUSTIFY) 0) #define RIGHT ((JUSTIFY) 1) #define CENTER ((JUSTIFY) 2) /* * Values returned by expressions. */ typedef struct { int type; /* type of value */ long intVal; /* integer value */ const char * strVal; /* string value */ COLUMN * column; /* column value */ } VALUE; #define VALUE_BAD 0 #define VALUE_NONE 1 #define VALUE_NUMBER 2 #define VALUE_STRING 3 #define VALUE_COLUMN 4 #define VALUE_BOOLEAN 5 #define TWOVAL(first, second) (((first) * 10) + (second)) /* * Flags for what a column requires to be collected. * These flags are OR'd together. */ typedef unsigned int USEFLAG; #define USE_NONE ((USEFLAG) 0x0000) #define USE_INIT ((USEFLAG) 0x0001) #define USE_DEV_NAME ((USEFLAG) 0x0002) #define USE_OPEN_FILE ((USEFLAG) 0x0004) #define USE_CURR_DIR ((USEFLAG) 0x0008) #define USE_COMMAND ((USEFLAG) 0x0010) #define USE_SELF ((USEFLAG) 0x0020) #define USE_STDIN ((USEFLAG) 0x0040) #define USE_STDOUT ((USEFLAG) 0x0080) #define USE_STDERR ((USEFLAG) 0x0100) #define USE_ENVIRON ((USEFLAG) 0x0200) #define USE_ROOT_DIR ((USEFLAG) 0x0400) #define USE_EXEC_INODE ((USEFLAG) 0x0800) #define USE_USER_NAME ((USEFLAG) 0x1000) #define USE_GROUP_NAME ((USEFLAG) 0x2000) #define USE_WCHAN ((USEFLAG) 0x4000) #define USE_THREADS ((USEFLAG) 0x8000) /* * Structure for one column that can be displayed. */ struct COLUMN { char * name; /* column name for commands */ char * heading; /* heading string for column */ int minWidth; /* absolute minimum width of column */ int initWidth; /* initial minimum width of column */ int width; /* actual minimum width of column */ JUSTIFY justify; /* how value is justified in column */ USEFLAG useFlag; /* what data column uses */ const char * (*showFunc)(const PROC * proc); int (*sortFunc)(const PROC * proc1, const PROC * proc2); void (*evalFunc)(const PROC * proc, VALUE * retval); BOOL (*testFunc)(const PROC * proc); }; /* * Identifiers for the different types of macros. * Some features of these macro types are built-in. * These definitions cannot be changed in isolation. */ typedef int MACRO_TYPE; #define MACRO_TYPE_NONE ((MACRO_TYPE) -1) #define MACRO_TYPE_OPTION ((MACRO_TYPE) 0) #define MACRO_TYPE_COLUMN ((MACRO_TYPE) 1) #define MACRO_TYPE_EXPRESSION ((MACRO_TYPE) 2) /* * Interface to communicate with an input/output display device. * This can be, for example, a dumb terminal, a curses terminal, * or an X11 window. */ typedef struct DISPLAY DISPLAY; struct DISPLAY { BOOL (*open)(DISPLAY *); /* open display */ BOOL (*defineColor)(DISPLAY *, int, const char *, const char *, int); void (*createWindow)(DISPLAY *); /* create window on display */ void (*close)(DISPLAY *); /* close display */ void (*setColor)(DISPLAY *, int); /* set color for output */ void (*refresh)(DISPLAY *); /* refresh display */ void (*beginPage)(DISPLAY *); /* begin output of page */ void (*putChar)(DISPLAY *, int); /* output character */ void (*putString)(DISPLAY *, const char *); /* output string */ void (*putBuffer)(DISPLAY *, const char *, int); /* buffer */ void (*endPage)(DISPLAY *); /* end output of page */ BOOL (*eventWait)(DISPLAY *, int); /* handle events and wait */ BOOL (*inputReady)(DISPLAY *); /* check if input is ready */ int (*readChar)(DISPLAY *); /* read input character */ void (*ringBell)(DISPLAY *); /* ring the bell */ int (*getRows)(DISPLAY *); /* get number of rows */ int (*getCols)(DISPLAY *); /* get number of columns */ BOOL (*doesScroll)(DISPLAY *); /* whether display scrolls */ }; #define DISPLAY_TYPE_TTY "tty" #define DISPLAY_TYPE_CURSES "curses" #define DISPLAY_TYPE_X11 "x11" /* * Color related definitions. */ #define DEFAULT_COLOR_ID 0 #define BAD_COLOR_ID -1 #define DEFAULT_COLOR_NAME "default" /* * Flags which can be set in addition to the foreground and background * to modify the text slightly. */ #define COLOR_FLAG_NONE 0x00 #define COLOR_FLAG_BOLD 0x01 #define COLOR_FLAG_UNDERLINE 0x02 /* * List of columns being shown. */ extern int showCount; extern COLUMN *showList[MAX_COLUMNS]; /* * Other global variables. */ extern ULONG startUptime; /* uptime jiffies at start */ extern time_t startTime; /* clock time at start */ extern time_t currentTime; /* current clock time */ extern long totalMemoryClicks; /* amount of total memory */ extern long ticksPerSecond; /* number of clock ticks per second */ extern long pageSize; /* number of bytes in a page */ extern ULONG liveCounter; /* counter for live procs */ extern int newCpuIndex; /* new CPU sample index */ extern int oldCpuIndex; /* old CPU sample index */ extern BOOL ancientFlag; /* seeing pre-existing procs */ extern BOOL showThreads; /* show threads */ extern BOOL noCopy; /* don't copy data for threads */ extern BOOL noSelf; /* don't show myself */ extern BOOL noRoot; /* don't show root procs */ extern BOOL noHeader; /* don't show column header */ extern BOOL isInfoShown; /* show info line at top */ extern BOOL myProcs; /* only show my procs */ extern BOOL activeOnly; /* only show active procs */ extern BOOL clearScreen; /* clear screen each loop */ extern BOOL isLooping; /* loop showing status */ extern BOOL isRunning; /* we still want to run */ extern BOOL isFrozen; /* data collection is frozen */ extern BOOL isUpdateForced; /* update once even if frozen */ extern BOOL isRefreshNeeded; /* need to refresh display */ extern BOOL isVertical; /* vertical output format */ extern BOOL isTopMode; /* top option was used */ extern BOOL isTopAuto; /* autosize height for top */ extern BOOL useOpenFiles; /* using open file info */ extern BOOL useCurrentDirectory; /* using current dir info */ extern BOOL useRootDirectory; /* using root dir info */ extern BOOL useExecInode; /* using executable info */ extern BOOL useDeviceNames; /* using device name info */ extern BOOL useUserNames; /* using user name info */ extern BOOL useGroupNames; /* using group name info */ extern BOOL useInitSleep; /* using initial sleep */ extern BOOL useCommand; /* using command line info */ extern BOOL useSelf; /* using my own proc info */ extern BOOL useEnvironment; /* using environment info */ extern BOOL useWaitChan; /* using wait channel symbol */ extern BOOL useThreads; /* use thread data */ extern BOOL useStdioTable[3]; /* using various stdio info */ extern pid_t myPid; /* my pid */ extern uid_t myUid; /* my real user id */ extern gid_t myGid; /* my real group id */ extern dev_t nullDevice; /* device of /dev/null */ extern ino_t nullInode; /* inode of /dev/null */ extern int procAllocCount; /* allocated proc structures */ extern int deathTime; /* seconds for dead processes */ extern int activeTime; /* seconds for active procs */ extern int pidCount; /* pids in pidList */ extern int userCount; /* users in userList */ extern int groupCount; /* groups in groupList */ extern int programCount; /* programs in programList */ extern int outputWidth; /* width of output */ extern int outputHeight; /* height of output */ extern int separation; /* blanks between columns */ extern int sleepTimeMs; /* milliseconds between loops */ extern int syncTime; /* seconds between syncs */ extern int initSleepTime; /* seconds for initial sleep */ extern int topCount; /* number of procs for top */ extern int percentSeconds; /* seconds for cpu percentages */ extern int scrollSeconds; /* seconds between scrolling */ extern int overlapLines; /* lines of overlap */ extern int skipCount; /* lines to skip in display */ extern int procShowCount; /* processes wanting showing */ extern int procTotalCount; /* count of all processes */ extern int threadShowCount; /* threads wanting showing */ extern int threadTotalCount; /* count of all threads */ extern int infoColorId; /* color id for info line */ extern int headerColorId; /* color id for header line */ extern PROC * processList; /* list of existent procs */ extern PROC * freeProcessList; /* free proc structure list */ extern char * geometry; /* window geometry string */ extern char * fontName; /* font name */ extern char * foregroundName; /* foreground color name */ extern char * backgroundName; /* background color name */ extern char * displayName; /* display name */ extern const char * displayType; /* display type */ extern char emptyString[4]; /* empty string */ extern char rootString[4]; /* root path string */ extern pid_t pidList[MAX_PIDS]; /* pids to be shown */ extern uid_t userList[MAX_USERS]; /* user ids to be shown */ extern gid_t groupList[MAX_GROUPS]; /* group ids to be shown */ extern char programList[MAX_PROGRAMS][MAX_PROGRAM_LEN + 2]; /* * Global procedures. */ extern BOOL PatternMatch(const char * text, const char * pattern); extern char * AllocateSharedString(const char * str, int len); extern void FreeSharedString(char * str); extern char * AllocTempString(int len); extern char * CopyTempString(const char * oldcp); extern void FreeTempStrings(void); extern void * AllocMemory(int len); extern void * ReallocMemory(void * oldBuffer, int len); extern char * CopyString(const char *); extern void ReplaceString(char **, const char *); extern void DefaultAllOptions(void); extern void DefaultColumns(void); extern void DefaultColumnWidths(void); extern ULONG GetUptime(void); extern BOOL ParseSystemInitFile(void); extern BOOL ParseUserInitFile(void); extern BOOL ParseFile(const char * name, BOOL isOptional); extern BOOL ParseOptions(ARGS * ap, int depth); extern BOOL ExpandOptionName(const char * name, int depth); extern long GetDecimalNumber(const char ** cpp); extern double GetFloatingNumber(const char ** cpp); extern void ClearCondition(void); extern BOOL ParseCondition(const char * str); extern USEFLAG GetConditionUseFlags(void); extern USEFLAG GetSortingUseFlags(void); extern void ClearSorting(void); extern void SortProcesses(void); extern void UpdateProcessCounts(void); extern BOOL AppendColumnSort(ARGS * ap, BOOL reverse); extern BOOL AppendExpressionSort(ARGS * ap, BOOL reverse); extern PROC * FindProcess(pid_t pid, pthread_t tid); extern BOOL InitializeProcessData(void); extern BOOL InitializeDisplay(void); extern void TopPage(void); extern void BottomPage(void); extern void NextPage(void); extern void PreviousPage(void); extern void ScanProcesses(void); extern void CheckActiveProcess(PROC * proc); extern void RemoveDeadProcesses(void); extern void InitialProcessScan(void); extern BOOL IsShownProcess(const PROC * proc); extern void ShowSelectedProcesses(void); extern void ListMacros(void); extern COLUMN *FindColumn(const char * name); extern void ListColumns(void); extern void CollectUserNames(void); extern void CollectGroupNames(void); extern void CollectDeviceNames(void); extern void CollectStaticSystemInfo(void); extern void CollectDynamicSystemInfo(void); extern uid_t FindUserId(const char * name); extern gid_t FindGroupId(const char * name); extern const char * FindUserName(uid_t uid); extern const char * FindGroupName(gid_t gid); extern const char * FindDeviceName(dev_t devid); extern const char * FindDeviceFromInode(dev_t dev, ino_t inode); extern void CalculateCpuPercentage(PROC * proc); extern void UpdateTimes(void); extern void SetCommandLine(PROC * proc, const char * str, int len); extern BOOL SetSharedString(char ** saveStr, int * saveLen, const char * str, int len); extern void ResetScrollTime(void); extern void WaitForCommands(long milliSeconds); extern BOOL ReadCommands(void); extern int ExpandArguments(ARGS * ap, char ** table, int tablelen); extern BOOL MacroExists(MACRO_TYPE id, const char * name); extern BOOL DefineMacro(MACRO_TYPE id, const char * name, const char * str); extern BOOL ExpandMacro(MACRO_TYPE id, const char * name, ARGS * retargs); extern void MakePrintable(char * cp, int len); extern void GetTimeOfDay(struct timeval * retTimeVal); extern long ElapsedMilliSeconds(const struct timeval * oldTime, const struct timeval * newTime); extern BOOL CompareValues(const VALUE leftval, const VALUE rightval, int * result); extern ULONG GetRunOrder(const PROC * proc); extern time_t GetLastActiveTime(const PROC * proc); extern BOOL GetIsActive(const PROC * proc); extern int GetState(const PROC * proc); extern void BuildStates(PROC * proc); extern int PickBestState(int state1, int state2); extern void InitializeColors(void); extern int AllocateColor(const char * color); extern BOOL DefineColors(void); extern void ClearRowColorConditions(void); extern BOOL ParseRowColorCondition(const char * color, const char * condition); extern int EvaluateRowColor(const PROC * proc); extern USEFLAG GetRowColorUseFlags(void); extern BOOL DpySetDisplay(const char * type); extern BOOL DpyOpen(void); extern BOOL DpyDefineColor(int colorId, const char * foreground, const char * background, int flags); extern void DpyCreateWindow(void); extern void DpyClose(void); extern void DpySetColor(int colorId); extern void DpyRefresh(void); extern void DpyBeginPage(void); extern void DpyChar(int ch); extern void DpyString(const char * str); extern void DpyBuffer(const char * buffer, int length); extern void DpyEndPage(void); extern BOOL DpyInputReady(void); extern int DpyReadChar(void); extern void DpyRingBell(void); extern int DpyGetRows(void); extern int DpyGetCols(void); extern BOOL DpyDoesScroll(void); extern BOOL DpyEventWait(int milliSeconds); extern DISPLAY * GetTtyDisplay(void); extern DISPLAY * GetCursesDisplay(void); extern DISPLAY * GetX11Display(void); #endif ips-4.0/cursesdisplay.c0000644000175000017500000002162011360271655014143 0ustar dbelldbell/* * Configurable ps-like program. * Display device which uses curses for fancy output to the terminal. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include #include #include #include #undef FALSE #undef TRUE #include "ips.h" /* * A structure holding a color name and its index. */ typedef struct { const char * name; short index; } COLOR_INFO; /* * The table of color names and their indexes. */ static const COLOR_INFO colorInfoTable[] = { {"black", COLOR_BLACK}, {"red", COLOR_RED}, {"green", COLOR_GREEN}, {"yellow", COLOR_YELLOW}, {"blue", COLOR_BLUE}, {"magenta", COLOR_MAGENTA}, {"cyan", COLOR_CYAN}, {"white", COLOR_WHITE}, {NULL, 0} }; static BOOL CursesOpen(DISPLAY *); static BOOL CursesDefineColor(DISPLAY *, int, const char *, const char *, int); static void CursesCreateWindow(DISPLAY *); static void CursesClose(DISPLAY *); static void CursesSetColor(DISPLAY *, int); static void CursesRefresh(DISPLAY *); static void CursesBeginPage(DISPLAY *); static void CursesPutChar(DISPLAY *, int); static void CursesPutString(DISPLAY *, const char *); static void CursesPutBuffer(DISPLAY *, const char *, int); static void CursesEndPage(DISPLAY *); static BOOL CursesEventWait(DISPLAY *, int); static BOOL CursesInputReady(DISPLAY *); static int CursesReadChar(DISPLAY *); static void CursesRingBell(DISPLAY *); static int CursesGetRows(DISPLAY *); static int CursesGetCols(DISPLAY *); static BOOL CursesDoesScroll(DISPLAY *); static DISPLAY cursesDisplay = { CursesOpen, CursesDefineColor, CursesCreateWindow, CursesClose, CursesSetColor, CursesRefresh, CursesBeginPage, CursesPutChar, CursesPutString, CursesPutBuffer, CursesEndPage, CursesEventWait, CursesInputReady, CursesReadChar, CursesRingBell, CursesGetRows, CursesGetCols, CursesDoesScroll }; /* * The main window. */ static WINDOW * mainWindow; /* * Terminal size data. */ static BOOL sizeChanged; /* terminal size has changed */ /* * The attributes for the colors. */ static int attributeTable[MAX_COLORS]; /* * Static routines. */ static void HandleResize(int arg); static void GetTerminalSize(void); static int FindColorNameIndex(const char * name); /* * Return the instance of the curses display device. */ DISPLAY * GetCursesDisplay(void) { return &cursesDisplay; } /* * Open the display device. */ static BOOL CursesOpen(DISPLAY * display) { SCREEN * screen; screen = newterm(NULL, stdout, stdin); if (screen == NULL) return FALSE; set_term(screen); cbreak(); noecho(); if (has_colors()) { start_color(); use_default_colors(); } mainWindow = newwin(0, 0, 0, 0); /* * If output is to a terminal, then get its current size and * set up to handle resize signals. */ if (isatty(STDOUT_FILENO)) { signal(SIGWINCH, HandleResize); GetTerminalSize(); } return TRUE; } /* * Create the window. * We don't do anything here. */ static void CursesCreateWindow(DISPLAY * display) { } /* * Close the display device. */ static void CursesClose(DISPLAY * display) { refresh(); endwin(); } /* * Define a color for the specified color id. */ static BOOL CursesDefineColor(DISPLAY * display, int colorId, const char * foreground, const char * background, int colorFlags) { int foregroundIndex = -1; int backgroundIndex = -1; int attribute = A_NORMAL; int status; if ((colorId < 0) || (colorId >= MAX_COLORS) || !has_colors() || (colorId >= COLOR_PAIRS)) { return FALSE; } /* * Validate that the flags are only the ones we know. */ if (colorFlags & ~(COLOR_FLAG_UNDERLINE|COLOR_FLAG_BOLD)) return FALSE; /* * If the foreground color name is non-empty then parse * it to get the index. */ if (*foreground) { foregroundIndex = FindColorNameIndex(foreground); if (foregroundIndex < 0) return FALSE; } /* * If the background color name is non-empty then parse * it to get the index. */ if (*background) { backgroundIndex = FindColorNameIndex(background); if (backgroundIndex < 0) return FALSE; } /* * Tell curses about the color. * This differs depending on whether color index 0 is used. */ if (colorId == 0) status = assume_default_colors(foregroundIndex, backgroundIndex); else status = init_pair(colorId, foregroundIndex, backgroundIndex); if (status == ERR) return FALSE; /* * Calculate and save the attributes for the color id. */ if (colorFlags & COLOR_FLAG_UNDERLINE) attribute |= A_UNDERLINE; if (colorFlags & COLOR_FLAG_BOLD) attribute |= A_BOLD; attribute |= COLOR_PAIR(colorId); attributeTable[colorId] = attribute; return TRUE; } /* * Find the color name in the table of colors and return the * index value of the color, or else parse a numeric color index. * This is a value from 0 to 255. Returns -1 if the color name * is not known and is not a valid number. */ static int FindColorNameIndex(const char * name) { int index; if (*name == '\0') return -1; /* * Look for a real name. */ for (index = 0; colorInfoTable[index].name; index++) { if (strcmp(name, colorInfoTable[index].name) == 0) { index = colorInfoTable[index].index; if (index >= COLORS) return -1; return index; } } /* * The name wasn't known. * Try parsing the string as a number. */ index = 0; while ((*name >= '0') && (*name <= '9')) index = index * 10 + (*name++ - '0'); /* * If the name wasn't numeric or the index is out of range then fail. */ if (*name || (index < 0) || (index >= COLORS)) return -1; return index; } static void CursesSetColor(DISPLAY * display, int colorId) { if ((colorId < 0) || (colorId >= MAX_COLORS) || (colorId >= COLOR_PAIRS)) { return; } wattrset(mainWindow, attributeTable[colorId]); } static void CursesRefresh(DISPLAY * display) { wrefresh(curscr); } static void CursesBeginPage(DISPLAY * display) { wmove(mainWindow, 0, 0); } static void CursesPutChar(DISPLAY * display, int ch) { waddch(mainWindow, ch); } static void CursesPutString(DISPLAY * display, const char * str) { waddstr(mainWindow, str); } static void CursesPutBuffer(DISPLAY * display, const char * str, int len) { while (len-- > 0) { waddch(mainWindow, *str); str++; } } static void CursesEndPage(DISPLAY * display) { wclrtobot(mainWindow); wmove(mainWindow, 0, 0); wrefresh(mainWindow); } /* * Handle events for the display while waiting for the specified amount * of time. Returns early if there are input characters to be read. * Returns TRUE if the window was resized and so needs to be updated soon. */ static BOOL CursesEventWait(DISPLAY * display, int milliSeconds) { struct timeval timeOut; fd_set readFds; if (milliSeconds <= 0) return sizeChanged; FD_ZERO(&readFds); FD_SET(STDIN_FILENO, &readFds); timeOut.tv_sec = milliSeconds / 1000; timeOut.tv_usec = (milliSeconds % 1000) * 1000; (void) select(STDIN_FILENO + 1, &readFds, NULL, NULL, &timeOut); return sizeChanged; } /* * See if input is ready from the terminal. */ static BOOL CursesInputReady(DISPLAY * display) { struct timeval timeOut; fd_set readFds; FD_ZERO(&readFds); FD_SET(STDIN_FILENO, &readFds); timeOut.tv_sec = 0; timeOut.tv_usec = 0; return (select(STDIN_FILENO + 1, &readFds, NULL, NULL, &timeOut) > 0); } /* * Read the next character from the terminal. */ static int CursesReadChar(DISPLAY * display) { char data; if (read(STDIN_FILENO, &data, 1) < 1) return EOF; return data & 0xff; } static void CursesRingBell(DISPLAY * display) { fflush(stdout); fputc('\007', stderr); fflush(stderr); } static int CursesGetRows(DISPLAY * display) { if (sizeChanged) GetTerminalSize(); return LINES; } /* * Return the number of columns for display. * Note: We reduce curses's value by one since it will always * auto-line-wrap if the last column is written into, and handling * that misfeature for correct output is otherwise painful. */ static int CursesGetCols(DISPLAY * display) { if (sizeChanged) GetTerminalSize(); return COLS - 1; } static BOOL CursesDoesScroll(DISPLAY * display) { return FALSE; } /* * Signal handler for resizing of window. * This only sets a flag so that we can handle the resize later. * (Changing the size at unpredictable times would be dangerous.) */ static void HandleResize(int arg) { sizeChanged = TRUE; signal(SIGWINCH, HandleResize); } /* * Routine called to get the new terminal size from the kernel. * We inform curses of this change and let it resize its window. */ static void GetTerminalSize(void) { struct winsize size; int rows; int cols; sizeChanged = FALSE; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) return; rows = size.ws_row; cols = size.ws_col; if (rows <= 0) rows = 1; if (cols <= 0) cols = 1; /* * If the values have changed then inform curses. */ if ((rows != LINES) || (cols != COLS)) resize_term(rows, cols); } /* END CODE */ ips-4.0/macro.c0000644000175000017500000001460411360557755012366 0ustar dbelldbell/* * Configurable ps-like program. * Macro definition routines. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include "ips.h" /* * Number of different macro lists. */ #define MACRO_TYPE_COUNT 3 /* * Macro definition. * This is a variable sized structure, with all its string data allocated * within it. So the structure must be treated as a unit, and its elements * cannot be individually freed. Only the complete structure can be freed. */ typedef struct MACRO MACRO; struct MACRO { MACRO * next; /* next macro in list */ char * name; /* macro name */ char ** values; /* replacement name list */ int count; /* number of elements in list */ char buf[1]; /* string buffer (variable length) */ }; #define NULL_MACRO ((MACRO *) 0) /* * Header for macro lists. */ typedef struct { char * define; /* string used in configuration files */ BOOL words; /* whether macro is expanded by words */ MACRO * list; /* list of macros */ } MACRO_HEADER; /* * The headers for all type of macros. * These definitions cannot be changed in isolation. */ static MACRO_HEADER macroLists[MACRO_TYPE_COUNT] = { {"option", TRUE, NULL}, {"column", TRUE, NULL}, {"expr", FALSE, NULL} }; /* * Local procedures. */ static MACRO_HEADER * FindMacroHeader(MACRO_TYPE type); static MACRO * FindMacro(MACRO_HEADER * head, const char * name); /* * Determine whether or not the specified macro name exists for the * specified type of macro. */ BOOL MacroExists(MACRO_TYPE type, const char * name) { MACRO_HEADER * head; head = FindMacroHeader(type); if (head == NULL) return FALSE; return (FindMacro(head, name) != NULL_MACRO); } /* * Expand a macro name of the specified type into its replacement word list. * The supplied ARGS structure is filled in with the count and table of words. * Returns TRUE if successful, or FALSE with an error message on error. */ BOOL ExpandMacro(MACRO_TYPE type, const char * name, ARGS * retargs) { MACRO_HEADER * head; MACRO * macro; retargs->table = NULL; retargs->count = 0; if (!isMacro(*name)) { fprintf(stderr, "Macro name \"%s\" is not upper case\n", name); return FALSE; } head = FindMacroHeader(type); if (head == NULL) return FALSE; macro = FindMacro(head, name); if (macro == NULL_MACRO) { fprintf(stderr, "Macro name \"%s\" is undefined\n", name); return FALSE; } retargs->table = macro->values; retargs->count = macro->count; return TRUE; } /* * Define the specified macro name of the specified type to have the * following expansion. The expansion is either in terms of space-separated * words, or just as a complete line. Macro names must begin with an upper * case letter to distinguish them from non-macros. Redefining of existing * macros is allowed; the new macros will simply be found first. * Returns TRUE if successful. */ BOOL DefineMacro(MACRO_TYPE type, const char * name, const char * str) { MACRO_HEADER * head; /* header of list */ MACRO * macro; /* allocated macro */ char * bp; /* pointer into buffer for alloc */ const char * word; /* beginning of current word */ int wordCount; /* number of words */ int size; /* total size of allocated macro */ int i; int wordLengths[MAX_WORDS]; const char * words[MAX_WORDS]; if (*name == '\0') { fprintf(stderr, "Missing macro name for define\n"); return FALSE; } if (!isMacro(*name)) { fprintf(stderr, "Macro name \"%s\" is not upper case\n", name); return FALSE; } if (strlen(name) > MAX_MACRO_LEN) { fprintf(stderr, "Macro name \"%s\" is too long\n", name); return FALSE; } head = FindMacroHeader(type); if (head == NULL) return FALSE; size = sizeof(MACRO) + strlen(name) + 1; wordCount = 0; /* * See if the input string is to be broken up into words. * If so, then do that, otherwise leave the string as it is. */ if (head->words) { while (TRUE) { while (isBlank(*str)) str++; if (*str == '\0') break; if (wordCount >= MAX_WORDS) { fprintf(stderr, "Too many words defined for macro \"%s\"\n", name); return FALSE; } word = str; while ((*str != '\0') && !isBlank(*str)) str++; words[wordCount] = word; wordLengths[wordCount] = (str - word); size += (wordLengths[wordCount] + 1); wordCount++; } } else { if (wordCount >= MAX_WORDS) { fprintf(stderr, "Too many words defined for macro \"%s\"\n", name); return FALSE; } words[wordCount] = str; wordLengths[wordCount] = strlen(str); size += (wordLengths[wordCount] + 1); wordCount++; } size += (sizeof(char *) * (wordCount + 1)); macro = (MACRO *) malloc(size); if (macro == NULL) { fprintf(stderr, "Cannot allocate macro structure\n"); return FALSE; } bp = macro->buf; macro->values = (char **) bp; bp += (sizeof(char *) * (wordCount + 1)); macro->name = bp; strcpy(bp, name); bp += (strlen(bp) + 1); for (i = 0; i < wordCount; i++) { macro->values[i] = bp; memcpy(bp, words[i], wordLengths[i]); bp += wordLengths[i]; *bp++ = '\0'; } macro->values[wordCount] = NULL; macro->count = wordCount; macro->next = head->list; head->list = macro; return TRUE; } static MACRO * FindMacro(MACRO_HEADER * head, const char * name) { MACRO * macro; for (macro = head->list; macro; macro = macro->next) { if (strcmp(name, macro->name) == 0) return macro; } return NULL_MACRO; } /* * Find the header for the specified type of macro. * Returns NULL if the macro type is unknown. */ static MACRO_HEADER * FindMacroHeader(MACRO_TYPE type) { if ((type < 0) || (type >= MACRO_TYPE_COUNT)) { fprintf(stderr, "Illegal macro type %d\n", type); return NULL; } return ¯oLists[type]; } /* * Display the definition of all the macros. * This description is suitable for reading back in as an initialization file. */ void ListMacros(void) { MACRO_TYPE type; const MACRO_HEADER * head; const MACRO * macro; int i; printf("%s\n", FIRST_LINE); printf("# System initialization file is \"%s\".\n", SYSTEM_INIT_FILE); for (type = 0; type < MACRO_TYPE_COUNT; type++) { head = ¯oLists[type]; for (macro = head->list; macro; macro = macro->next) { printf("%s %s", head->define, macro->name); for (i = 0; i < macro->count; i++) printf(" %s", macro->values[i]); printf("\n"); } } } /* END CODE */ ips-4.0/columns.c0000644000175000017500000012631711360556245012743 0ustar dbelldbell/* * Configurable ps-like program. * Column data and display routines. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include #include #include "ips.h" /* * Macro to define the column functions and to fill in the COLUMN * structure with values. */ #define DEFCOL(symbol, name, header, width, justify, flags) \ static const char * Show##symbol(const PROC * proc); \ static int Sort##symbol(const PROC * proc1, const PROC * proc2); \ static void Eval##symbol(const PROC * proc, VALUE * val); \ static BOOL Test##symbol(const PROC * proc); \ \ static COLUMN Column##symbol = \ { \ #name, #header, sizeof(#header) - 1, width, width, \ justify, flags, \ Show##symbol, Sort##symbol, \ Eval##symbol, Test##symbol \ }; /* * The column definitions. */ DEFCOL(Pid, pid, Pid, 5, RIGHT, USE_NONE) DEFCOL(Tid, tid, Tid, 5, RIGHT, USE_NONE) DEFCOL(State, state, Stat, 4, CENTER, USE_NONE) DEFCOL(ThreadCount, threads, Threads, 7, RIGHT, USE_NONE) DEFCOL(ParentPid, parentpid, PPid, 5, RIGHT, USE_NONE) DEFCOL(SystemTime, systemtime, System Time, 10, RIGHT, USE_NONE) DEFCOL(UserTime, usertime, User Time, 10, RIGHT, USE_NONE) DEFCOL(RunTime, runtime, Runtime, 10, RIGHT, USE_NONE) DEFCOL(ChildRunTime, childruntime, ChildRuntime, 10, RIGHT, USE_NONE) DEFCOL(PageSwaps, pageswaps, PageSwaps, 10, RIGHT, USE_NONE) DEFCOL(Policy, policy, Policy, 6, RIGHT, USE_NONE) DEFCOL(Priority, priority, Pri, 3, RIGHT, USE_NONE) DEFCOL(RealTimePriority, realtimepriority, RTPri, 5, RIGHT, USE_NONE) DEFCOL(ChildPageSwaps, childpageswaps, ChildPgSwp, 10, RIGHT, USE_NONE) DEFCOL(Eip, eip, EIP, LONG_HEX_DIGITS, RIGHT, USE_NONE) DEFCOL(Esp, esp, ESP, LONG_HEX_DIGITS, RIGHT, USE_NONE) DEFCOL(WaitChannel, waitchannel, WaitChan, LONG_HEX_DIGITS, RIGHT, USE_NONE) DEFCOL(WaitSymbol, waitsymbol, WaitSymbol, 16, LEFT, USE_WCHAN) DEFCOL(Program, program, Program, 12, LEFT, USE_NONE) DEFCOL(Command, command, Command, 20, LEFT, USE_COMMAND) DEFCOL(Environment, environment, Environment, 25, LEFT, USE_ENVIRON) DEFCOL(ProcessGroup, processgroup, PGrp, 5, RIGHT, USE_NONE) DEFCOL(TtyProcessGroup, ttyprocessgroup,TtyPG, 5, RIGHT, USE_NONE) DEFCOL(IdleTime, idletime, Idle, 8, RIGHT, USE_INIT) DEFCOL(DeadTime, deadtime, Dead, 8, RIGHT, USE_NONE) DEFCOL(TtyName, ttyname, TtyName, 8, LEFT, USE_DEV_NAME) DEFCOL(TtyDevice, ttydevice, TtyD, 4, RIGHT, USE_NONE) DEFCOL(UserName, user, User, 8, LEFT, USE_USER_NAME) DEFCOL(UserId, uid, Uid, 5, RIGHT, USE_NONE) DEFCOL(GroupName, group, Group, 8, LEFT, USE_GROUP_NAME) DEFCOL(GroupId, gid, Gid, 5, RIGHT, USE_NONE) DEFCOL(PercentCpu, percentcpu, %Cpu, 6, RIGHT, USE_INIT) DEFCOL(PercentMemory, percentmemory, %Mem, 4, RIGHT, USE_NONE) DEFCOL(Rss, residentsetsize,Rss, 5, RIGHT, USE_NONE) DEFCOL(StartTime, starttime, Start Time, 10, LEFT, USE_NONE) DEFCOL(Age, age, Age, 8, RIGHT, USE_NONE) DEFCOL(Flags, flags, Flags, 8, RIGHT, USE_NONE) DEFCOL(PageFaults, pagefaults, Faults, 10, RIGHT, USE_NONE) DEFCOL(MinorPageFaults, minorpagefaults,MinorFault, 10, RIGHT, USE_NONE) DEFCOL(MajorPageFaults, majorpagefaults,MajorFault, 10, RIGHT, USE_NONE) DEFCOL(SignalCatch, signalcatch, SigCatch, 8, RIGHT, USE_NONE) DEFCOL(SignalIgnore, signalignore, SigIgnor, 8, RIGHT, USE_NONE) DEFCOL(SignalBlock, signalblock, SigBlock, 8, RIGHT, USE_NONE) DEFCOL(OpenFileCount, openfiles, Files, 5, RIGHT, USE_OPEN_FILE) DEFCOL(RunOrder, runorder, RunOrder, 10, RIGHT, USE_NONE) DEFCOL(CurrentDirectory,currentdirectory,Current Dir, 25, LEFT, USE_CURR_DIR) DEFCOL(RootDirectory, rootdirectory, Root Dir, 25, LEFT, USE_ROOT_DIR) DEFCOL(Executable, executable, Executable, 25, LEFT, USE_EXEC_INODE) DEFCOL(Summary, summary, Summary, 14, LEFT, USE_NONE) DEFCOL(Nice, nice, Nic, 3, RIGHT, USE_NONE) DEFCOL(Size, size, Size, 5, RIGHT, USE_NONE) DEFCOL(RealTimer, realtimer, RealTimer, 10, RIGHT, USE_NONE) DEFCOL(Stdin, stdin, Stdin, 20, LEFT, USE_STDIN) DEFCOL(Stdout, stdout, Stdout, 20, LEFT, USE_STDOUT) DEFCOL(Stderr, stderr, Stderr, 20, LEFT, USE_STDERR) DEFCOL(Stdio, stdio, Stdio, 5, CENTER, USE_STDIN | USE_STDOUT | USE_STDERR) DEFCOL(Active, active, Active, 5, CENTER, USE_INIT) DEFCOL(Processor, processor, Proc, 4, RIGHT, USE_NONE) DEFCOL(States, states, States, 10, LEFT, USE_THREADS) /* * Table of all columns. * The table is NULL terminated. */ static COLUMN * columnTable[] = { &ColumnPid, &ColumnTid, &ColumnState, &ColumnParentPid, &ColumnSystemTime, &ColumnUserTime, &ColumnRunTime, &ColumnChildRunTime, &ColumnPageSwaps, &ColumnChildPageSwaps, &ColumnEip, &ColumnEsp, &ColumnWaitChannel, &ColumnWaitSymbol, &ColumnProgram, &ColumnCommand, &ColumnEnvironment, &ColumnProcessGroup, &ColumnTtyProcessGroup, &ColumnThreadCount, &ColumnIdleTime, &ColumnDeadTime, &ColumnTtyName, &ColumnTtyDevice, &ColumnUserName, &ColumnGroupName, &ColumnUserId, &ColumnGroupId, &ColumnPercentCpu, &ColumnPercentMemory, &ColumnRss, &ColumnStartTime, &ColumnAge, &ColumnFlags, &ColumnPageFaults, &ColumnMinorPageFaults, &ColumnMajorPageFaults, &ColumnSignalCatch, &ColumnSignalIgnore, &ColumnSignalBlock, &ColumnOpenFileCount, &ColumnRunOrder, &ColumnCurrentDirectory, &ColumnRootDirectory, &ColumnExecutable, &ColumnSummary, &ColumnNice, &ColumnPriority, &ColumnRealTimePriority, &ColumnPolicy, &ColumnProcessor, &ColumnSize, &ColumnRealTimer, &ColumnStdin, &ColumnStdout, &ColumnStderr, &ColumnStdio, &ColumnActive, &ColumnStates, NULL }; /* * Default columns when nothing else has been set. */ static COLUMN * defaultColumns[] = { &ColumnPid, &ColumnParentPid, &ColumnTtyName, &ColumnUserName, &ColumnSummary, &ColumnRunTime, &ColumnCommand, NULL }; /* * Temporary buffer to contain constructed column strings. */ static char showBuffer[256]; /* * Other static routines. */ static void TicksToString(char * buf, long ticks); /* * Default the widths of all the columns. */ void DefaultColumnWidths(void) { COLUMN ** columnPtr; COLUMN * column; for (columnPtr = columnTable; *columnPtr; columnPtr++) { column = *columnPtr; column->width = column->initWidth; } } /* * List the available columns to stdout. */ void ListColumns(void) { COLUMN ** columnPtr; const COLUMN * column; printf("Column Name Displayed As Width\n"); printf("----------- ------------ -----\n"); for (columnPtr = columnTable; *columnPtr; columnPtr++) { column = *columnPtr; printf("%-20s %-15s %3d\n", column->name, column->heading, column->width); } printf("\n"); printf("Note: Column names may be abbreviated. They are used in\n"); printf("several ways: displaying, sorting, and for the cond option.\n"); } /* * Set the default columns. */ void DefaultColumns(void) { COLUMN ** src; COLUMN ** dest; dest = showList; src = defaultColumns; showCount = 0; while (*src) { *dest++ = *src++; showCount++; } } /* * Find the column with the given name. * Abbreviations are allowed. * Returns NULL if there was no match or it was ambiguous. */ COLUMN * FindColumn(const char * name) { COLUMN ** columnPtr; COLUMN * column; COLUMN * match; int count; int len; int nameLength; match = NULL; count = 0; nameLength = strlen(name); for (columnPtr = columnTable; *columnPtr; columnPtr++) { column = *columnPtr; len = strlen(column->name); if ((len < nameLength) || ((memcmp(name, column->name, nameLength) != 0))) continue; if (len == nameLength) return column; match = column; count++; } if (count == 1) return match; return NULL; } /* * Functions to sort various columns based on comparing two processes. * This returns a negative value if the item for the first process is less * than the second process, a positive value if the item is greater, or * zero if the item is the same for both processes. */ static int SortPid(const PROC * proc1, const PROC * proc2) { return proc1->pid - proc2->pid; } static int SortTid(const PROC * proc1, const PROC * proc2) { if ((proc1->tid == NO_THREAD_ID) && (proc2->tid != NO_THREAD_ID)) return -1; if ((proc1->tid != NO_THREAD_ID) && (proc2->tid == NO_THREAD_ID)) return 1; if ((proc1->tid == proc1->pid) && (proc2->tid != proc2->pid)) return -1; if ((proc1->tid != proc1->pid) && (proc2->tid == proc2->pid)) return 1; if (proc1->tid < proc2->tid) return -1; if (proc1->tid > proc2->tid) return 1; return 0; } static int SortState(const PROC * proc1, const PROC * proc2) { return GetState(proc1) - GetState(proc2); } static int SortStates(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->states, proc2->states); } static int SortParentPid(const PROC * proc1, const PROC * proc2) { return proc1->parentPid - proc2->parentPid; } static int SortThreadCount(const PROC * proc1, const PROC * proc2) { return proc1->threadCount - proc2->threadCount; } static int SortSystemTime(const PROC * proc1, const PROC * proc2) { return proc1->systemRunTime - proc2->systemRunTime; } static int SortUserTime(const PROC * proc1, const PROC * proc2) { return proc1->userRunTime - proc2->userRunTime; } static int SortRunTime(const PROC * proc1, const PROC * proc2) { return (proc1->systemRunTime + proc1->userRunTime) - (proc2->systemRunTime + proc2->userRunTime); } static int SortChildRunTime(const PROC * proc1, const PROC * proc2) { return (proc1->childSystemRunTime + proc1->childUserRunTime) - (proc2->childSystemRunTime + proc2->childUserRunTime); } static int SortPageSwaps(const PROC * proc1, const PROC * proc2) { return proc1->pagesSwapped - proc2->pagesSwapped; } static int SortChildPageSwaps(const PROC * proc1, const PROC * proc2) { return proc1->childPagesSwapped - proc2->childPagesSwapped; } static int SortEip(const PROC * proc1, const PROC * proc2) { return proc1->eip - proc2->eip; } static int SortEsp(const PROC * proc1, const PROC * proc2) { return proc1->esp - proc2->esp; } static int SortWaitChannel(const PROC * proc1, const PROC * proc2) { return proc1->waitChan - proc2->waitChan; } static int SortWaitSymbol(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->waitChanSymbol, proc2->waitChanSymbol); } static int SortProgram(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->program, proc2->program); } static int SortCommand(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->command, proc2->command); } static int SortEnvironment(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->environment, proc2->environment); } static int SortProcessGroup(const PROC * proc1, const PROC * proc2) { return proc1->processGroup - proc2->processGroup; } static int SortTtyProcessGroup(const PROC * proc1, const PROC * proc2) { return proc1->ttyProcessGroup - proc2->ttyProcessGroup; } static int SortIdleTime(const PROC * proc1, const PROC * proc2) { return GetLastActiveTime(proc2) - GetLastActiveTime(proc1); } static int SortDeadTime(const PROC * proc1, const PROC * proc2) { return proc2->deathTime - proc1->deathTime; } static int SortTtyName(const PROC * proc1, const PROC * proc2) { const char * name1; const char * name2; if (proc1->ttyDevice == proc2->ttyDevice) return 0; name1 = FindDeviceName(proc1->ttyDevice); name2 = FindDeviceName(proc2->ttyDevice); if (name1 && name2) return strcmp(name1, name2); if (!name1 && name2) return -1; if (name1 && !name2) return 1; return ((long) proc1->ttyDevice) - ((long) proc2->ttyDevice); } static int SortTtyDevice(const PROC * proc1, const PROC * proc2) { return ((long) proc1->ttyDevice) - ((long) proc2->ttyDevice); } static int SortUserName(const PROC * proc1, const PROC * proc2) { const char * name1; const char * name2; if (proc1->uid == proc2->uid) return 0; name1 = FindUserName(proc1->uid); name2 = FindUserName(proc2->uid); if (name1 && name2) return strcmp(name1, name2); if (!name1 && name2) return -1; if (name1 && !name2) return 1; return ((long) proc1->uid) - ((long) proc2->uid); } static int SortUserId(const PROC * proc1, const PROC * proc2) { return ((long) proc1->uid) - ((long) proc2->uid); } static int SortGroupName(const PROC * proc1, const PROC * proc2) { const char * name1; const char * name2; if (proc1->gid == proc2->gid) return 0; name1 = FindGroupName(proc1->gid); name2 = FindGroupName(proc2->gid); if (name1 && name2) return strcmp(name1, name2); if (!name1 && name2) return -1; if (name1 && !name2) return 1; return ((long) proc1->gid) - ((long) proc2->gid); } static int SortGroupId(const PROC * proc1, const PROC * proc2) { return ((long) proc1->gid) - ((long) proc2->gid); } static int SortPercentCpu(const PROC * proc1, const PROC * proc2) { return proc1->percentCpu - proc2->percentCpu; } static int SortPercentMemory(const PROC * proc1, const PROC * proc2) { return proc1->percentMemory - proc2->percentMemory; } static int SortRss(const PROC * proc1, const PROC * proc2) { return proc1->rss - proc2->rss; } static int SortStartTime(const PROC * proc1, const PROC * proc2) { return proc1->startTimeTicks - proc2->startTimeTicks; } static int SortAge(const PROC * proc1, const PROC * proc2) { return proc2->startTimeTicks - proc1->startTimeTicks; } static int SortFlags(const PROC * proc1, const PROC * proc2) { return proc1->flags - proc2->flags; } static int SortPageFaults(const PROC * proc1, const PROC * proc2) { return (proc1->minorFaults + proc1->majorFaults) - (proc2->minorFaults + proc2->majorFaults); } static int SortMinorPageFaults(const PROC * proc1, const PROC * proc2) { return proc1->minorFaults - proc2->minorFaults; } static int SortMajorPageFaults(const PROC * proc1, const PROC * proc2) { return proc1->majorFaults - proc2->majorFaults; } static int SortSignalCatch(const PROC * proc1, const PROC * proc2) { return proc1->sigCatch - proc2->sigCatch; } static int SortSignalIgnore(const PROC * proc1, const PROC * proc2) { return proc1->sigIgnore - proc2->sigIgnore; } static int SortSignalBlock(const PROC * proc1, const PROC * proc2) { return proc1->sigBlock - proc2->sigBlock; } static int SortOpenFileCount(const PROC * proc1, const PROC * proc2) { if (proc1->openFiles < proc2->openFiles) return -1; if (proc1->openFiles > proc2->openFiles) return 1; return 0; } static int SortRunOrder(const PROC * proc1, const PROC * proc2) { ULONG runOrder1 = GetRunOrder(proc1); ULONG runOrder2 = GetRunOrder(proc2); if (runOrder1 < runOrder2) return -1; if (runOrder1 > runOrder2) return 1; return 0; } static int SortCurrentDirectory(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->cwdPath, proc2->cwdPath); } static int SortRootDirectory(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->rootPath, proc2->rootPath); } static int SortExecutable(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->execPath, proc2->execPath); } static int SortStdin(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->stdioPaths[0], proc2->stdioPaths[0]); } static int SortStdout(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->stdioPaths[1], proc2->stdioPaths[1]); } static int SortStderr(const PROC * proc1, const PROC * proc2) { return strcmp(proc1->stdioPaths[2], proc2->stdioPaths[2]); } static int SortStdio(const PROC * proc1, const PROC * proc2) { char buf[8]; strcpy(buf, ShowStdio(proc1)); return strcmp(buf, ShowStdio(proc2)); } static int SortSummary(const PROC * proc1, const PROC * proc2) { char buf[30]; strcpy(buf, ShowSummary(proc1)); return strcmp(buf, ShowSummary(proc2)); } static int SortPriority(const PROC * proc1, const PROC * proc2) { if (proc1->priority < proc2->priority) return -1; if (proc1->priority > proc2->priority) return 1; return 0; } static int SortPolicy(const PROC * proc1, const PROC * proc2) { if (proc1->policy < proc2->policy) return -1; if (proc1->policy > proc2->policy) return 1; return 0; } static int SortRealTimePriority(const PROC * proc1, const PROC * proc2) { if (proc1->realTimePriority < proc2->realTimePriority) return -1; if (proc1->realTimePriority > proc2->realTimePriority) return 1; return 0; } static int SortNice(const PROC * proc1, const PROC * proc2) { if (proc1->nice < proc2->nice) return -1; if (proc1->nice > proc2->nice) return 1; return 0; } static int SortProcessor(const PROC * proc1, const PROC * proc2) { if (proc1->processor < proc2->processor) return -1; if (proc1->processor > proc2->processor) return 1; return 0; } static int SortSize(const PROC * proc1, const PROC * proc2) { if (proc1->virtualSize < proc2->virtualSize) return -1; if (proc1->virtualSize > proc2->virtualSize) return 1; return 0; } static int SortRealTimer(const PROC * proc1, const PROC * proc2) { if (proc1->itRealValue < proc2->itRealValue) return -1; if (proc1->itRealValue > proc2->itRealValue) return 1; return 0; } static int SortActive(const PROC * proc1, const PROC * proc2) { BOOL isActive1 = GetIsActive(proc1); BOOL isActive2 = GetIsActive(proc2); if (!isActive1 && isActive2) return -1; if (isActive1 && !isActive2) return 1; return 0; } /* * Routines to evaluate a column, and return it with the specified type. * (This is either a string or a number.) */ static void EvalPid(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->pid; } static void EvalTid(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->tid; } static void EvalState(const PROC * proc, VALUE * val) { char * cp; cp = AllocTempString(2); cp[0] = GetState(proc); cp[1] = '\0'; val->type = VALUE_STRING; val->strVal = cp; } static void EvalStates(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->states; } static void EvalParentPid(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->parentPid; } static void EvalThreadCount(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->threadCount; } static void EvalSystemTime(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->systemRunTime; } static void EvalUserTime(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->userRunTime; } static void EvalRunTime(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->systemRunTime + proc->userRunTime; } static void EvalChildRunTime(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->childSystemRunTime + proc->childUserRunTime; } static void EvalPageSwaps(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->pagesSwapped; } static void EvalChildPageSwaps(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->childPagesSwapped; } static void EvalEip(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->eip; } static void EvalEsp(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->eip; } static void EvalWaitChannel(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->waitChan; } static void EvalWaitSymbol(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->waitChanSymbol; } static void EvalProgram(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->program; } static void EvalCommand(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->command; } static void EvalEnvironment(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->environment; } static void EvalProcessGroup(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->processGroup; } static void EvalTtyProcessGroup(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->ttyProcessGroup; } static void EvalIdleTime(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = (currentTime - GetLastActiveTime(proc)) / 60; } static void EvalDeadTime(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = 0; if (proc->deathTime > 0) val->intVal = (currentTime - proc->deathTime) / 60; } static void EvalTtyName(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = FindDeviceName(proc->ttyDevice); if (val->strVal == NULL) val->strVal = ""; } static void EvalTtyDevice(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = (long) proc->ttyDevice; } static void EvalUserName(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = FindUserName(proc->uid); if (val->strVal == NULL) val->strVal = ""; } static void EvalGroupName(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = FindGroupName(proc->uid); if (val->strVal == NULL) val->strVal = ""; } static void EvalUserId(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = (long) proc->uid; } static void EvalGroupId(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = (long) proc->gid; } static void EvalPercentCpu(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->percentCpu; } static void EvalPercentMemory(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->percentMemory; } static void EvalRss(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->rss * 4; } static void EvalStartTime(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->startTimeClock; } static void EvalAge(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = currentTime - proc->startTimeClock; if (val->intVal < 0) val->intVal = 0; } static void EvalFlags(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->flags; } static void EvalPageFaults(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->minorFaults + proc->majorFaults; } static void EvalMinorPageFaults(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->minorFaults; } static void EvalMajorPageFaults(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->majorFaults; } static void EvalSignalCatch(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->sigCatch; } static void EvalSignalIgnore(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->sigIgnore; } static void EvalSignalBlock(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->sigBlock; } static void EvalOpenFileCount(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->openFiles; } static void EvalRunOrder(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = GetRunOrder(proc); } static void EvalCurrentDirectory(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->cwdPath; } static void EvalRootDirectory(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->rootPath; } static void EvalExecutable(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->execPath; } static void EvalStdin(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->stdioPaths[0]; } static void EvalStdout(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->stdioPaths[1]; } static void EvalStderr(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = proc->stdioPaths[2]; } static void EvalStdio(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = CopyTempString(ShowStdio(proc)); } static void EvalSummary(const PROC * proc, VALUE * val) { val->type = VALUE_STRING; val->strVal = CopyTempString(ShowSummary(proc)); } static void EvalPolicy(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->policy; } static void EvalPriority(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->priority; } static void EvalRealTimePriority(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->realTimePriority; } static void EvalNice(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->nice; } static void EvalProcessor(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->processor; } static void EvalSize(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = (proc->virtualSize + 1023) / 1024; } static void EvalRealTimer(const PROC * proc, VALUE * val) { val->type = VALUE_NUMBER; val->intVal = proc->itRealValue; } static void EvalActive(const PROC * proc, VALUE * val) { val->type = VALUE_BOOLEAN; val->intVal = GetIsActive(proc); } /* * Routines to test whether a column value is "TRUE" in some sense. * The meaning differs from column to column, but generally means that * the column value is nonzero or non-null. These functions are used when * the "test" attribute is used on a column name. */ static BOOL TestPid(const PROC * proc) { return (proc->pid != 0); } static BOOL TestTid(const PROC * proc) { return (proc->tid != NO_THREAD_ID); } static BOOL TestState(const PROC * proc) { int state = GetState(proc); return ((state != 'Z') && (state != ' ')); } static BOOL TestStates(const PROC * proc) { /* * The state of the main process should be good * enough for all of the threads since they are * all zombies or non-existant together. */ return TestState(proc); } static BOOL TestThreadCount(const PROC * proc) { return (proc->threadCount > 1); } static BOOL TestParentPid(const PROC * proc) { return (proc->parentPid && (proc->parentPid != 1)); } static BOOL TestSystemTime(const PROC * proc) { return (proc->systemRunTime != 0); } static BOOL TestUserTime(const PROC * proc) { return (proc->userRunTime != 0); } static BOOL TestRunTime(const PROC * proc) { return ((proc->systemRunTime != 0) || (proc->userRunTime != 0)); } static BOOL TestChildRunTime(const PROC * proc) { return ((proc->childSystemRunTime != 0) || (proc->childUserRunTime != 0)); } static BOOL TestPageSwaps(const PROC * proc) { return (proc->pagesSwapped != 0); } static BOOL TestChildPageSwaps(const PROC * proc) { return (proc->childPagesSwapped != 0); } static BOOL TestEip(const PROC * proc) { return (proc->eip != 0); } static BOOL TestEsp(const PROC * proc) { return (proc->esp != 0); } static BOOL TestWaitChannel(const PROC * proc) { return (proc->waitChan != 0); } static BOOL TestWaitSymbol(const PROC * proc) { return (proc->waitChanSymbol[0] != '\0'); } static BOOL TestProgram(const PROC * proc) { return (proc->program[0] != '\0'); } static BOOL TestCommand(const PROC * proc) { return proc->hasCommand; } static BOOL TestEnvironment(const PROC * proc) { return (proc->environment[0] != '\0'); } static BOOL TestProcessGroup(const PROC * proc) { return (proc->processGroup != 0); } static BOOL TestTtyProcessGroup(const PROC * proc) { return ((proc->ttyProcessGroup != 0) && (proc->ttyProcessGroup != -1)); } static BOOL TestIdleTime(const PROC * proc) { return (GetLastActiveTime(proc) != currentTime); } static BOOL TestDeadTime(const PROC * proc) { return (proc->deathTime != 0); } static BOOL TestTtyName(const PROC * proc) { return ((proc->ttyDevice != 0) && (proc->ttyDevice != BAD_DEVID)); } static BOOL TestTtyDevice(const PROC * proc) { return ((proc->ttyDevice != 0) && (proc->ttyDevice != BAD_DEVID)); } static BOOL TestUserName(const PROC * proc) { return (FindUserName(proc->uid) != NULL); } static BOOL TestGroupName(const PROC * proc) { return (FindGroupName(proc->gid) != NULL); } static BOOL TestUserId(const PROC * proc) { return (proc->uid != 0); } static BOOL TestGroupId(const PROC * proc) { return (proc->gid != 0); } static BOOL TestPercentCpu(const PROC * proc) { return (proc->percentCpu != 0); } static BOOL TestPercentMemory(const PROC * proc) { return (proc->percentMemory != 0); } static BOOL TestRss(const PROC * proc) { return (proc->rss != 0); } static BOOL TestStartTime(const PROC * proc) { return (proc->startTimeTicks != 0); } static BOOL TestAge(const PROC * proc) { return (proc->startTimeClock < currentTime); } static BOOL TestFlags(const PROC * proc) { return (proc->flags != 0); } static BOOL TestPageFaults(const PROC * proc) { return ((proc->minorFaults != 0) || (proc->majorFaults != 0)); } static BOOL TestMinorPageFaults(const PROC * proc) { return (proc->minorFaults != 0); } static BOOL TestMajorPageFaults(const PROC * proc) { return (proc->majorFaults != 0); } static BOOL TestSignalCatch(const PROC * proc) { return (proc->sigCatch != 0); } static BOOL TestSignalIgnore(const PROC * proc) { return (proc->sigIgnore != 0); } static BOOL TestSignalBlock(const PROC * proc) { return (proc->sigBlock != 0); } static BOOL TestOpenFileCount(const PROC * proc) { return (proc->openFiles > 0); } static BOOL TestRunOrder(const PROC * proc) { return (GetRunOrder(proc) > 0); } static BOOL TestCurrentDirectory(const PROC * proc) { return (proc->cwdPath != emptyString); } static BOOL TestRootDirectory(const PROC * proc) { return (proc->rootPath != emptyString); } static BOOL TestExecutable(const PROC * proc) { return (proc->execPath != emptyString); } static BOOL TestStdin(const PROC * proc) { return (proc->stdioPaths[0] != emptyString); } static BOOL TestStdout(const PROC * proc) { return (proc->stdioPaths[1] != emptyString); } static BOOL TestStderr(const PROC * proc) { return (proc->stdioPaths[2] != emptyString); } static BOOL TestStdio(const PROC * proc) { return ((proc->stdioPaths[0] != '\0') || (proc->stdioPaths[1] != '\0') || (proc->stdioPaths[2] != '\0')); } static BOOL TestSummary(const PROC * proc) { return TRUE; } static BOOL TestPolicy(const PROC * proc) { return (proc->policy != 0); } static BOOL TestPriority(const PROC * proc) { return (proc->priority != 0); } static BOOL TestRealTimePriority(const PROC * proc) { return (proc->realTimePriority != 0); } static BOOL TestNice(const PROC * proc) { return (proc->nice != 0); } static BOOL TestProcessor(const PROC * proc) { return (proc->processor != 0); } static BOOL TestSize(const PROC * proc) { return (proc->virtualSize != 0); } static BOOL TestRealTimer(const PROC * proc) { return (proc->itRealValue != 0); } static BOOL TestActive(const PROC * proc) { return GetIsActive(proc); } /* * Functions to show various columns. * This just means return a pointer to a string containing the desired * information. The string should be of minimal length, since left or * right space padding is done elsewhere. The string only has to be valid * until the next ShowXXX function is called, and so a common buffer can * be used for many routines. */ static const char * ShowPid(const PROC * proc) { sprintf(showBuffer, "%ld", (long) proc->pid); return showBuffer; } static const char * ShowTid(const PROC * proc) { if (proc->tid == NO_THREAD_ID) return "-"; sprintf(showBuffer, "%ld", (long) proc->tid); return showBuffer; } static const char * ShowState(const PROC * proc) { showBuffer[0] = GetState(proc); showBuffer[1] = '\0'; return showBuffer; } static const char * ShowStates(const PROC * proc) { return proc->states; } static const char * ShowThreadCount(const PROC * proc) { sprintf(showBuffer, "%d", proc->threadCount); return showBuffer; } static const char * ShowParentPid(const PROC * proc) { sprintf(showBuffer, "%ld", (long) proc->parentPid); return showBuffer; } static const char * ShowSystemTime(const PROC * proc) { TicksToString(showBuffer, proc->systemRunTime); return showBuffer; } static const char * ShowUserTime(const PROC * proc) { TicksToString(showBuffer, proc->userRunTime); return showBuffer; } static const char * ShowRunTime(const PROC * proc) { TicksToString(showBuffer, proc->systemRunTime + proc->userRunTime); return showBuffer; } static const char * ShowChildRunTime(const PROC * proc) { TicksToString(showBuffer, proc->childSystemRunTime + proc->childUserRunTime); return showBuffer; } static const char * ShowPageSwaps(const PROC * proc) { sprintf(showBuffer, "%ld", proc->pagesSwapped); return showBuffer; } static const char * ShowChildPageSwaps(const PROC * proc) { sprintf(showBuffer, "%ld", proc->childPagesSwapped); return showBuffer; } static const char * ShowEip(const PROC * proc) { sprintf(showBuffer, LONG_HEX_FORMAT, proc->eip); return showBuffer; } static const char * ShowEsp(const PROC * proc) { sprintf(showBuffer, LONG_HEX_FORMAT, proc->esp); return showBuffer; } static const char * ShowWaitChannel(const PROC * proc) { if (proc->waitChan == 0) return ""; sprintf(showBuffer, LONG_HEX_FORMAT, proc->waitChan); return showBuffer; } static const char * ShowWaitSymbol(const PROC * proc) { return proc->waitChanSymbol; } static const char * ShowProgram(const PROC * proc) { return proc->program; } static const char * ShowCommand(const PROC * proc) { return proc->command; } static const char * ShowEnvironment(const PROC * proc) { return proc->environment; } static const char * ShowProcessGroup(const PROC * proc) { sprintf(showBuffer, "%ld", (long) proc->processGroup); return showBuffer; } static const char * ShowTtyProcessGroup(const PROC * proc) { sprintf(showBuffer, "%ld", (long) proc->ttyProcessGroup); return showBuffer; } static const char * ShowIdleTime(const PROC * proc) { char * cp; long idle; cp = showBuffer; if (proc->isAncient) *cp++ = '+'; idle = (currentTime - GetLastActiveTime(proc)) / 60; if (idle < 60) sprintf(cp, "%ld", idle); else sprintf(cp, "%ld:%02ld", idle / 60, idle % 60); return showBuffer; } static const char * ShowDeadTime(const PROC * proc) { char * cp; long minutes; if (proc->deathTime == 0) return "-"; cp = showBuffer; minutes = (currentTime - proc->deathTime) / 60; if (minutes < 60) sprintf(cp, "%ld", minutes); else sprintf(cp, "%ld:%02ld", minutes / 60, minutes % 60); return showBuffer; } static const char * ShowTtyName(const PROC * proc) { const char * name; name = FindDeviceName(proc->ttyDevice); if (name != NULL) return name; sprintf(showBuffer, "%lx", (long) proc->ttyDevice); return showBuffer; } static const char * ShowTtyDevice(const PROC * proc) { sprintf(showBuffer, "%lx", (long) proc->ttyDevice); return showBuffer; } static const char * ShowUserName(const PROC * proc) { const char * name; name = FindUserName(proc->uid); if (name) return name; sprintf(showBuffer, "%ld", (long) proc->uid); return showBuffer; } static const char * ShowGroupName(const PROC * proc) { const char * name; name = FindGroupName(proc->gid); if (name) return name; sprintf(showBuffer, "%ld", (long) proc->gid); return showBuffer; } static const char * ShowUserId(const PROC * proc) { sprintf(showBuffer, "%ld", (long) proc->uid); return showBuffer; } static const char * ShowGroupId(const PROC * proc) { sprintf(showBuffer, "%ld", (long) proc->gid); return showBuffer; } static const char * ShowPercentCpu(const PROC * proc) { sprintf(showBuffer, "%d.%02d", proc->percentCpu / 100, proc->percentCpu % 100); return showBuffer; } static const char * ShowPercentMemory(const PROC * proc) { sprintf(showBuffer, "%d.%d", proc->percentMemory / 10, proc->percentMemory % 10); return showBuffer; } static const char * ShowRss(const PROC * proc) { sprintf(showBuffer, "%ld", proc->rss * 4); return showBuffer; } static const char * ShowStartTime(const PROC * proc) { int procAgeDays; char * cp; /* * Get the formatted string for the process's start time: * "Wed Jun 30 21:49:08 1993\n" */ cp = ctime(&proc->startTimeClock); /* * Show the start time as just the hour and minute, unless the * process started more than one day ago, in which case also give * the number of days it has been around. */ memcpy(showBuffer, cp + 11, 5); showBuffer[5] = '\0'; procAgeDays = (currentTime - proc->startTimeClock) / (60 * 60 * 24); if (procAgeDays > 0) sprintf(showBuffer + 5, "-%dd", procAgeDays); return showBuffer; } static const char * ShowAge(const PROC * proc) { long minutes; long hours; long days; minutes = (currentTime - proc->startTimeClock) / 60; if (minutes < 0) minutes = 0; hours = minutes / 60; minutes %= 60; days = hours / 24; hours %= 24; if (days) sprintf(showBuffer, "%ldd%02ld:%02ld", days, hours, minutes); else if (hours) sprintf(showBuffer, "%ld:%02ld", hours, minutes); else sprintf(showBuffer, "%ld", minutes); return showBuffer; } static const char * ShowFlags(const PROC * proc) { sprintf(showBuffer, "%lx", proc->flags); return showBuffer; } static const char * ShowPageFaults(const PROC * proc) { sprintf(showBuffer, "%ld", proc->minorFaults + proc->majorFaults); return showBuffer; } static const char * ShowMinorPageFaults(const PROC * proc) { sprintf(showBuffer, "%ld", proc->minorFaults); return showBuffer; } static const char * ShowMajorPageFaults(const PROC * proc) { sprintf(showBuffer, "%ld", proc->majorFaults); return showBuffer; } static const char * ShowSignalCatch(const PROC * proc) { sprintf(showBuffer, "%08lx", proc->sigCatch); return showBuffer; } static const char * ShowSignalIgnore(const PROC * proc) { sprintf(showBuffer, "%08lx", proc->sigIgnore); return showBuffer; } static const char * ShowSignalBlock(const PROC * proc) { sprintf(showBuffer, "%08lx", proc->sigBlock); return showBuffer; } static const char * ShowOpenFileCount(const PROC * proc) { if (proc->openFiles < 0) return "-"; sprintf(showBuffer, "%d", proc->openFiles); return showBuffer; } static const char * ShowRunOrder(const PROC * proc) { sprintf(showBuffer, "%lu", GetRunOrder(proc)); return showBuffer; } static const char * ShowCurrentDirectory(const PROC * proc) { return proc->cwdPath; } static const char * ShowRootDirectory(const PROC * proc) { return proc->rootPath; } static const char * ShowExecutable(const PROC * proc) { return proc->execPath; } static const char * ShowStdin(const PROC * proc) { return proc->stdioPaths[0]; } static const char * ShowStdout(const PROC * proc) { return proc->stdioPaths[1]; } static const char * ShowStderr(const PROC * proc) { return proc->stdioPaths[2]; } static const char * ShowStdio(const PROC * proc) { char * path; char * tag; int fd; strcpy(showBuffer, "---"); /* * Loop over the stdio file descriptors. */ for (fd = 0; fd <= 2; fd++) { path = proc->stdioPaths[fd]; tag = &showBuffer[fd]; /* * If there is no path then leave the dash. */ if (*path == '\0') continue; /* * If this is a pipe then say so. */ if (memcmp(path, "pipe:", 5) == 0) { *tag = 'P'; continue; } /* * If this is a socket then say so. */ if (memcmp(path, "socket:", 7) == 0) { *tag = 'S'; continue; } /* * If this is the null device say so. */ if (strcmp(path, "/dev/null") == 0) { *tag = 'N'; continue; } /* * If this is a terminal say so. */ if ((memcmp(path, "/dev/tty", 8) == 0) || (memcmp(path, "/dev/pts/", 9) == 0) || (memcmp(path, "/dev/vc/", 8) == 0)) { *tag = 'T'; continue; } /* * Don't know what it is, say it is just some file. */ *tag = 'F'; } return showBuffer; } static const char * ShowSummary(const PROC * proc) { int state = GetState(proc); long age; char * cp; cp = showBuffer; strcpy(cp, "--------------"); if (proc->deathTime != 0) cp[0] = '-'; else if ((state == 'R') || (state == 'Z') || (state == 'T') || (state == 'D')) { cp[0] = state; /* copy state */ } else if (GetIsActive(proc)) cp[0] = 'A'; /* active */ else cp[0] = 'I'; /* or idle */ if ((proc->rss == 0) && (proc->virtualSize != 0) && (state != 'Z')) cp[1] = 'W'; if (proc->nice > 0) cp[2] = 'N'; else if (proc->nice < 0) cp[2] = 'H'; if (proc->sessionId == proc->pid) cp[3] = 'S'; /* session id leader */ if (proc->processGroup == proc->pid) cp[4] = 'P'; /* process group leader */ if ((proc->ttyDevice != 0) && (proc->ttyDevice != BAD_DEVID)) cp[5] = 'T'; /* on a terminal */ if (proc->ttyProcessGroup == proc->processGroup) cp[6] = 'F'; /* foreground process */ if (proc->parentPid == 1) cp[7] = 'I'; /* owned by init */ if (proc->sigIgnore & (1 << (SIGHUP - 1))) cp[8] = 'H'; /* ignores SIGHUP */ else if (proc->sigCatch & (1 << (SIGHUP - 1))) cp[8] = 'h'; /* catches SIGHUP */ if (proc->sigIgnore & (1 << (SIGTERM - 1))) cp[9] = 'T'; /* ignores SIGTERM */ else if (proc->sigCatch & (1 << (SIGTERM - 1))) cp[9] = 't'; /* catches SIGTERM */ if (proc->uid == myUid) cp[10] = 'U'; /* has my user id */ if (proc->gid == myGid) cp[11] = 'G'; /* has my group id */ if (proc->uid == 0) cp[12] = 'R'; /* root process */ else if (proc->uid < BASE_USER_UID) cp[12] = 'S'; /* server process */ age = currentTime - proc->startTimeClock; if (age >= (60 * 60 * 24 * 7)) cp[13] = 'W'; /* week old */ else if (age >= (60 * 60 * 24)) cp[13] = 'D'; /* day old */ else if (age >= (60 * 60)) cp[13] = 'H'; /* hour old */ else if (age >= (60 * 10)) cp[13] = 'T'; /* ten minutes old */ else if (age >= (60 * 5)) cp[13] = 'F'; /* five minutes old */ else if (age >= 60) cp[13] = 'M'; /* minute old */ else cp[13] = 'N'; /* new process */ return showBuffer; } static const char * ShowPolicy(const PROC * proc) { sprintf(showBuffer, "%ld", proc->policy); return showBuffer; } static const char * ShowPriority(const PROC * proc) { sprintf(showBuffer, "%ld", proc->priority); return showBuffer; } static const char * ShowRealTimePriority(const PROC * proc) { sprintf(showBuffer, "%ld", proc->realTimePriority); return showBuffer; } static const char * ShowNice(const PROC * proc) { sprintf(showBuffer, "%ld", proc->nice); return showBuffer; } static const char * ShowProcessor(const PROC * proc) { sprintf(showBuffer, "%ld", (long) proc->processor); return showBuffer; } static const char * ShowSize(const PROC * proc) { sprintf(showBuffer, "%ld", (proc->virtualSize + 1023) / 1024); return showBuffer; } static const char * ShowRealTimer(const PROC * proc) { long intPart; long fracPart; intPart = proc->itRealValue / ticksPerSecond; fracPart = proc->itRealValue % ticksPerSecond; if (fracPart) { sprintf(showBuffer, "%ld.%02ld", intPart, ((fracPart * 100) / ticksPerSecond)); } else sprintf(showBuffer, "%ld", intPart); return showBuffer; } static const char * ShowActive(const PROC * proc) { return (GetIsActive(proc) ? "active" : "idle"); } /* * Convert a time in ticks into hours, minutes, seconds and hundreths of * seconds in the form: * hhh:mm:ss.hh * and store that into the supplied buffer. */ static void TicksToString(char * buf, long ticks) { int hundreths; int seconds; int minutes; long hours; hundreths = ticks % ticksPerSecond; ticks /= ticksPerSecond; seconds = ticks % 60; ticks /= 60; minutes = ticks % 60; ticks /= 60; hours = ticks; if (hours > 0) { sprintf(buf, "%ld:%02d:%02d.%02d", hours, minutes, seconds, hundreths); } else if (minutes > 0) sprintf(buf, "%d:%02d.%02d", minutes, seconds, hundreths); else sprintf(buf, "%d.%02d", seconds, hundreths); } /* END CODE */ ips-4.0/README0000644000175000017500000000311711364747161011772 0ustar dbelldbellThis is the 'ips' program which stands for 'intelligent ps'. It displays process status in a versatile and efficient manner. This has been tested under Linux kernel version 2.6.28, but should work reasonably for other kernel versions too. The collection of process data in ips is dependent on the /proc layout so it will not work on other operating systems without modification. Almost all of the system-dependent code should be in linux.c. To build ips you can probably just type 'make' with no problems. The only major configuration option is to disable the use of X11 if you don't need or want it. Modify the Makefile to do this. There is a man page called ips.man which explains the features and use of the program. There is a system-wide default initialization file called ips.init which you might want to install in /usr/local/lib. If you would rather put the initialization file elsewhere, the definition for it is in ips.h. If you trust the program, you can make it suid root so that it can display all of the columns for all users. Note that it might be a security problem for some process information to be known by everybody (especially the environment variables of a process). You must decide if that is a problem for your site. I have tried to avoid any other security problems, but of course I can't guarantee that there isn't a problem. So running ips suid root is at your own risk. The ips program will run fine without being suid root, but in this case some of the data it attempts to display will not be available for normal users. David I. Bell dbell@canb.auug.org.au 25 April 2010 ips-4.0/show.c0000644000175000017500000003732411363014130012223 0ustar dbelldbell/* * Configurable ps-like program. * Routines to display the results. * All output goes through the currently set DISPLAY object. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include "ips.h" /* * Static variables. */ static int headerWidth; /* width of longest header string */ static BOOL isShown; /* whether anything has been shown */ static BOOL haveScrollTime; /* have last scroll time */ static struct timeval lastScrollTime; /* time auto scrolling was last done */ /* * Local procedures. */ static void ShowVerticalProcessStatus(const PROC * proc); static const char * ReturnInfoString(void); static const char * ReturnHeaderString(void); static const char * ReturnProcessString(const PROC * proc); static BOOL CanShowColumn(const COLUMN * column, int col); static int GetSkipCountDelta(void); static int GetUsefulRows(void); static int GetPageCount(void); static int GetShowCount(void); /* * Initialize for displaying the process status. * This opens the display device. * Returns TRUE if successful. */ BOOL InitializeDisplay(void) { COLUMN ** columnPtr; const COLUMN * column; int len; /* * Set the display devicetype. */ if (!DpySetDisplay(displayType)) { fprintf(stderr, "Display type \"%s\" is not available\n", displayType); return FALSE; } /* * Try to open the display device. */ if (!DpyOpen()) { fprintf(stderr, "Failed to open display type \"%s\"\n", displayType); return FALSE; } /* * Try to define the colors the user specified. */ if (!DefineColors()) { DpyClose(); fprintf(stderr, "Failed to define colors\n"); return FALSE; } /* * Now create the window. */ DpyCreateWindow(); headerWidth = 0; if (noHeader || !isVertical) return TRUE; /* * Iterate over all of the desired columns to be displayed * and find the longest column header string. */ for (columnPtr = showList; *columnPtr; columnPtr++) { column = *columnPtr; len = strlen(column->heading); if (len > headerWidth) headerWidth = len; } return TRUE; } /* * Show all of the selected processes for one iteration of the program. * This clears the screen or prints a blank line, prints the header string * if required, then prints the process status for each selected process. * The status can be printed horizontally or vertically. */ void ShowSelectedProcesses(void) { const PROC * proc; int seenCount; int skippedCount; int usefulRows; struct timeval newScrollTime; isShown = FALSE; seenCount = 0; skippedCount = 0; /* * If auto scrolling is enabled then see if we need to do that now. */ if (!isFrozen && (scrollSeconds > 0) && !DpyDoesScroll()) { GetTimeOfDay(&newScrollTime); if (haveScrollTime && (ElapsedMilliSeconds(&lastScrollTime, &newScrollTime) / 1000 > scrollSeconds)) { NextPage(); } if (!haveScrollTime) { haveScrollTime = TRUE; lastScrollTime = newScrollTime; } } /* * If the display needs refreshing the do that. */ if (isRefreshNeeded) { DpyRefresh(); isRefreshNeeded = FALSE; } /* * Begin display of a new page with the default color. */ DpySetColor(DEFAULT_COLOR_ID); DpyBeginPage(); /* * Show the info line if needed. */ if (isLooping && isInfoShown) { DpySetColor(infoColorId); DpyString(ReturnInfoString()); DpyChar('\n'); DpySetColor(DEFAULT_COLOR_ID); } /* * Get how many rows of data we can show. * If the display device scrolls then there is no limit. */ if (DpyDoesScroll()) usefulRows = 0; else usefulRows = GetUsefulRows(); /* * Loop over all processes in the list. */ for (proc = processList; proc; proc = proc->next) { /* * If we have filled the lines of the display then stop. */ if (usefulRows && (seenCount >= usefulRows)) break; /* * If we are restricting the output to the top few process, * then stop when we reach the limit. */ if (topCount && (seenCount >= topCount)) break; /* * If the process is not to be shown, then skip it. */ if (!proc->isShown) continue; /* * If we haven't skipped enough processes yet then skip it. */ if (skippedCount < skipCount) { skippedCount++; continue; } /* * We want to display this process's status. * If we haven't displayed any processes yet this iteration, * then print the header string if required. */ if (seenCount == 0) { if (!noHeader && !isVertical) { DpySetColor(headerColorId); DpyString(ReturnHeaderString()); DpyChar('\n'); DpySetColor(DEFAULT_COLOR_ID); } } /* * Set the color to the one which the process gets due to any * row conditions that have been specified. */ DpySetColor(EvaluateRowColor(proc)); /* * If we want a vertical display of the columns, * then do that, otherwise do the normal horizontal output. */ if (isVertical) ShowVerticalProcessStatus(proc); else { DpyString(ReturnProcessString(proc)); DpyChar('\n'); } seenCount++; isShown = TRUE; } /* * Reset the colors to the default and end the page to flush output. */ DpySetColor(DEFAULT_COLOR_ID); DpyEndPage(); } /* * Return the information string which gives a quick overview. */ static const char * ReturnInfoString(void) { int delta; static char buf[MAX_INFO_LEN + 1]; delta = GetSkipCountDelta(); sprintf(buf, "[Procs: %d Threads: %d Shown: %d Sleep: %g Scroll: %d Overlap: %d Page: %d/%d%s]", procTotalCount, threadTotalCount, GetShowCount(), ((double) sleepTimeMs) / 1000.0, scrollSeconds, overlapLines, (skipCount / delta) + 1, GetPageCount(), (isFrozen ? " FROZEN" : "")); return buf; } /* * Return the header line as determined by the currently defined columns. * This returns a pointer to a static buffer with the stored information. * The column headers are justified appropriately. This is only used for * horizontal column displays. */ static const char * ReturnHeaderString(void) { COLUMN ** columnPtr; const COLUMN * column; int col; int len; int padding; int tmp; char * cp; char * bufCp; static char buf[MAX_WIDTH + 1]; bufCp = buf; col = 0; padding = 0; columnPtr = showList; column = *columnPtr++; while (CanShowColumn(column, col)) { while (padding-- > 0) *bufCp++ = ' '; cp = column->heading; len = strlen(cp); if (len > column->width) len = column->width; padding = column->width - len; if (column->justify == RIGHT) { while (padding-- > 0) *bufCp++ = ' '; } if (column->justify == CENTER) { tmp = padding / 2; padding -= tmp; while (tmp-- > 0) *bufCp++ = ' '; } memcpy(bufCp, cp, len); bufCp += len; col += column->width + separation; if (padding < 0) padding = 0; padding += separation; column = *columnPtr++; } *bufCp = '\0'; return buf; } /* * Return a line of information about the specified process based on the * currently defined columns. This returns a pointer to a static buffer * with the stored information. The columns are justified appropriately. * This is only used for horizontal column displays. */ static const char * ReturnProcessString(const PROC * proc) { COLUMN ** columnPtr; COLUMN * column; BOOL overflow; int len; int padding; int tmp; int col; int goalCol; const char * value; char * bufCp; static char buf[MAX_WIDTH + 1]; bufCp = buf; col = 0; goalCol = 0; columnPtr = showList; column = *columnPtr++; while (CanShowColumn(column, goalCol)) { /* * Generate the column's value. */ value = column->showFunc(proc); len = strlen(value); /* * Calculate the required padding to make the returned * value fill up the width of the column. */ padding = column->width - len; if (padding < 0) padding = 0; /* * Assume the column's value hasn't overflowed the * allowed width until we know otherwise. */ overflow = FALSE; /* * If this is not the last column on the line then * truncate its value if necessary to the width of the * column minus one (to allow for the vertical bar). */ if (*columnPtr && (len > column->width)) { len = column->width - 1; overflow = TRUE; padding = 0; } /* * If the column won't fit within the remaining width * of the line then truncate its value to the remaining * width minus one (to allow for the vertical bar). */ if (goalCol + len > outputWidth) { len = outputWidth - goalCol - 1; overflow = TRUE; padding = 0; } if (len < 0) len = 0; /* * If there is any padding to be applied before the * column's value is printed then apply it. */ if (padding > 0) { if (column->justify == RIGHT) { goalCol += padding; padding = 0; } if (column->justify == CENTER) { tmp = padding / 2; padding -= tmp; goalCol += tmp; } } /* * Now actually space over to the goal column where the * value is printed. */ while (col < goalCol) { col++; *bufCp++ = ' '; } /* * Copy as much of the value as we allow. */ memcpy(bufCp, value, len); bufCp += len; goalCol += len; col += len; /* * If the value overflowed then append a vertical bar. */ if (overflow) { *bufCp++ = '|'; goalCol++; col++; } /* * If there is any remaining padding then add it to * the goal column for the next iteration to use. */ if (padding > 0) goalCol += padding; /* * Add in the separation value to the goal column. */ goalCol += separation; column = *columnPtr++; } *bufCp = '\0'; return buf; } /* * Return whether or not a column can be displayed starting at the * specified column value. A left justified column can be displayed * as long as its heading value will fit (the minimum width). But a * right or center justified column must fit its defined width. * This returns FALSE if a NULL column is given. */ static BOOL CanShowColumn(const COLUMN * column, int col) { if (column == NULL) return FALSE; if (column->justify == LEFT) return (col + column->minWidth <= outputWidth); return (col + column->width <= outputWidth); } /* * Print the status of a process in a vertical format. * In this format, each process status requires multiple lines, one per * item to be displayed. The beginning of each line can give the item name, * and the rest of each line contains the value. No column justification * is done here so that all values are left justified. */ static void ShowVerticalProcessStatus(const PROC * proc) { COLUMN ** columnPtr; const COLUMN * column; int columnWidth; int len; char * cp; const char * str; char buf[128]; /* * Separate the process status from the previous process status. */ if (isShown) DpyChar('\n'); /* * Calculate how many columns are available for each value. * Make sure there is at least one column available, even if * that means we will overflow the line. */ if (noHeader) columnWidth = outputWidth; else columnWidth = outputWidth - headerWidth - separation - 1; if (columnWidth <= 0) columnWidth = 1; /* * Iterate over all of the desired columns to be displayed. */ for (columnPtr = showList; *columnPtr; columnPtr++) { column = *columnPtr; /* * Construct and output the header string for the line. * The string contains the column header and a colon, * followed by padding out to the maximum width of the * header strings plus the column separation value. */ if (!noHeader) { cp = buf; len = strlen(column->heading); memcpy(cp, column->heading, len); cp += len; *cp++ = ':'; len = headerWidth + separation - len; if (len > 0) { memset(cp, ' ', len); cp += len; } *cp = '\0'; DpyString(buf); } /* * Now get the value for the data item and get its length. */ str = column->showFunc(proc); len = strlen(str); /* * If the column is too wide, then truncate it and change * the last character into a vertical bar. Otherwise print * the whole value. */ if (len > columnWidth) { DpyBuffer(str, columnWidth - 1); DpyString("|\n"); } else { DpyString(str); DpyChar('\n'); } } } /* * Adjust variables so that on the next update of the process list the * next page of processes will be shown. If there are no more pages * then the first page will be shown. */ void NextPage(void) { /* * If the rest of the processes from the current skip count * fit in the display then move to the first page. */ if (skipCount + GetUsefulRows() >= GetShowCount()) { TopPage(); return; } skipCount += GetSkipCountDelta(); haveScrollTime = FALSE; } /* * Adjust variables so that on the next update of the process list the * previous page of processes will be shown. If there are no more pages * then the last page will be shown. */ void PreviousPage(void) { skipCount -= GetSkipCountDelta(); if (skipCount < 0) BottomPage(); haveScrollTime = FALSE; } /* * Adjust variables so that on the next update of the process list the * first page of processes will be shown. */ void TopPage(void) { skipCount = 0; haveScrollTime = FALSE; } /* * Adjust variables so that on the next update of the process list the * last page of processes will be shown. */ void BottomPage(void) { int delta; int modulo; /* * Calculate the number of lines to skip where the last page * of status would completely fill the screen. */ skipCount = GetShowCount() - GetUsefulRows(); /* * If no lines would be skipped then the last page is the same * as the first page. */ if (skipCount <= 0) { skipCount = 0; haveScrollTime = FALSE; return; } /* * If the skip count is not an exact multiple of the skip delta * value then round it up to the next exact multiple. */ delta = GetSkipCountDelta(); modulo = skipCount % delta; if (modulo > 0) skipCount += (delta - modulo); haveScrollTime = FALSE; } /* * Reset the scroll time so that it won't happen for a while. * This is called, for example, when the display has been unfrozen. */ void ResetScrollTime(void) { haveScrollTime = FALSE; } /* * Get the delta value for changing the number of lines to skip for a page. * This takes into account whether a header or info line is being shown * and how many lines of overlap there are between pages. This will always * return a value of at least 1. */ static int GetSkipCountDelta(void) { int count; count = GetUsefulRows() - overlapLines; if (count <= 0) count = 1; return count; } /* * Return the number of useful rows for showing actual process status. * This accounts for the header and information lines at the top if present. * This will always return a value of at least 1. */ static int GetUsefulRows(void) { int count; count = DpyGetRows(); if (!noHeader) count--; if (isInfoShown) count--; if (count <= 0) count = 1; return count; } /* * Return the number of pages required for the display. */ static int GetPageCount(void) { int skip; int delta; /* * Calculate the number of lines to skip where the last page * of status would completely fill the screen. */ skip = GetShowCount() - GetUsefulRows(); /* * If no lines would be skipped then there is only one page. */ if (skip <= 0) return 1; /* * Divide the skip value by the amount we skip by to get * the number of pages (and while rounding up). */ delta = GetSkipCountDelta(); return 1 + (skip + (delta - 1)) / delta; } /* * Get the number of processes or threads to actually be shown. * This is the number of processes or processes that want showing, * but limited to the topCount if such a limit is in effect. */ static int GetShowCount(void) { int count; if (showThreads) count = threadShowCount; else count = procShowCount; if (topCount && (count > topCount)) count = topCount; return count; } /* END CODE */ ips-4.0/ips.man0000644000175000017500000016541211364747173012414 0ustar dbelldbell.TH IPS 1 \" -*- nroff -*- .SH NAME ips \- intelligent process status .SH SYNOPSIS .B ips [column-options] [select-options] [sort-options] .B [other-options] [macro-names] .SH DESCRIPTION .B Ips is an intelligent ps-like program which displays process or thread status obtained from the .I /proc filesystem. It has features to make tracking of active, semi-active, and transient processes easy. It is extremely configurable, but is still efficient. .B Ips tries to consume as little runtime as possible by only collecting as much information as is needed for the particular display specified. .PP .B Ips normally displays the process status once and then exits, but it can also act like a .I top program to display process status repeatedly. The output can be displayed line by line as for a dumb terminal, displayed through the .I curses library using cursor addressing, or displayed in a raw X11 window. The output can be colored to highlight rows of interest. .PP The information to be displayed about processes can be selected on a column-by-column basis. Each column displayes one piece of information about the processes. The set of columns to be displayed and their order may be changed. .PP Processes can be selected for displaying based on the values of one or more columns. Some selection criteria are pre-defined for efficiency and convenience, such as the process id and user name. Other selection criteria can be defined using general expressions which refer to any combination of the column values. .PP The order that processes are displayed is based on sorting the values of one or more columns. The set of columns to sort by, the order of the columns for sorting, and whether each sorting is normal or reversed can be changed. Arbitrary expressions based on the values of the columns can also be used for sorting. .PP Process lines can be colored based on arbitrary expressions so as to highlight the processes of interest. The foreground color, background color, underlining, and boldness can be set for the output. The header lines can also be colored. .PP .B Ips reads initilization files to define macros which make it easy to specify useful combinations of configuration options. Therefore many different output formats and short-cuts to common option combinations can be used. .PP Options to .B ips are minus signs followed by short words or phrases. Multiple options cannot be combined together following one minus sign (unlike the case with many other utilities). Options are processed in the order that they are given on the command line. Combinations of options which appear to do conflicting actions are permitted. This is because each option merely modifies the state left from the previous options. The state left after all the options have been processed is the one which is actually executed. .SH SPECIFYING COLUMNS FOR OUTPUT There are many columns of information which can be selected for display. Each column displays one item of information about the displayed processes. The set of columns and their order can be specified by the user. .PP Each column has a defined width, which is usually adequate to hold the widest possible data item for that column. This width is just a default and can be changed if desired. The data items shown within a column are left justified, right justified, or centered within the column width according to the type of column. In some cases the column width might not be adequate to show the complete data item, and in this case the item is truncated to the column width. Truncation is indicated by a vertical bar at the right edge of the column. (The usual columns which require truncation are the .I command and .I environment columns, which displays the full command line or environment string for a process.) .PP The .I ips program enforces a limit on the total width used for displaying of columns. If too many columns are selected for display, then one or more columns from the right are removed until the remaining columns fit within the total width. The width limit is usually implicitly set by the terminal or window's width. But if desired, the width limit can be explicitly specified by the user. (This is convenient if the .I ips program's output is being piped to another process, for example.) .PP If the final displayed column does not extend out to the total width limit, then that column's width is extended to include the remaining columns. This allows more of the data item to be seen before it requires truncation. (Typically, the .I command column is the rightmost column so as to take advantage of these extra columns.) .PP The options for manipulating columns are .IR -col , .IR -addcol , .IR -remcol , .IR -sep , .IR -width , .IR -colwidth , .IR -vert , and .IR -listcolumns . .PP The .I -col option first clears any existing list of column names for display, and then sets the new list of column names to be displayed as specified. The columns are displayed in the order specified in the option. If there is a duplicate column name in the list, then only the last use of the column name is effective. .PP The .I -addcol option adds the specified columns to the existing list of column names to be displayed. The new columns are added in the order specified, and by default are appended after previously existing columns in the list. If any of the column names are already in the existing list, then they are removed from the list before being added back into it. An argument can be a number, in which case any later column names are inserted into the list starting at the specified column number. Out of range column numbers are silently changed to the nearest legal value. For example, .B ips -addcol 2 uid gid 999 percentcpu adds the user id column as column 2, the group id column as column 3, and appends the percentage cpu column after all other columns. .PP The .I -remcol option removes the specified columns from the list of column names, without caring whether or not the columns were in the list. .PP The .I -sep option specifies the separation between adjacent columns in the display. It has one argument, which is the number of spaces to insert between each pair of columns. The default separation is 2 spaces. .PP The .I -width option specifies the total width available for the display of columns. It has one argument, which is the number of columns available. If this option is not given and the output is to .BR stdout , then the width is obtained from the kernel if .B stdout is a terminal, or else is set to 80 columns if .B stdout is not a terminal. .PP The .I -colwidth option specifies the width of a particular column. It has one or two arguments. The first argument is the name of the column whose width is to be set. The second argument is the desired width of the column. If the second argument is not given, then the column width is set to its default value. .PP The .I -vert option changes the output format from the default horizontal one into a vertical one. In vertical format, the status for each process is multi-line where each displayed value uses a complete line. The beginning of each line contains the column heading and a colon character, unless the .I -noheader option was used. Each value is left justified to the same position on the line and can use the rest of the available output width. The .I -sep option sets the number of spaces between the widest column header and the beginning of the values. If multiple processes are being displayed, then a blank line separates their status lines. .PP The .I -listcolumns option simply lists the names of the available columns and then exits. The heading for the column and the default width of the column is also shown. .SH SELECTION OF PROCESSES FOR DISPLAY The set of processes to be shown can be specified by a number of options. Each of these options specifies a condition to be met. Processes will only be shown which meet all of the specified conditions. .PP The options which specify conditions to be met are .IR -pid , .IR -user , .IR -group , .IR -my , .IR -noroot , .IR -noself , .IR -active , .IR -top , and .IR -cond . .PP The .I -pid option is followed by one or more process ids, and restricts the display to only the specified processes if they exist. Using this option multiple times adds to the list of process ids to be shown. .PP The .I -user option is followed by one or more user names or user ids, and restricts the display to processes with those user ids if they exist. Using this option multiple times adds to the list of users to be shown. .PP The .I -group option is followed by one or more group names or group ids, and restricts the display to processes with those group ids if they exist. Using this option multiple times adds to the list of groups to be shown. .PP The .I -program option is followed by one or more program names, and restricts the display to processes having those program names if they exist. A program name is the name of the executable file which started the process (as displayed in the .I program column). This is not always the same name as shown in the command line arguments. Using this option multiple times adds to the list of programs to be shown. .PP The .I -my option only selects process which have my user id. .PP The .I -noroot option disables selection of processes which run as root. .PP The .I -noself option removes the .B ips process from the display. .PP The .I -active option only shows processes which are either running or which have run recently. .PP The .I -top option limits the display to a specified number of processes. After displaying the specified number of processes, further ones are ignored. If no argument is given to the option, then the height of the terminal or window is used to limit the number of displayed processes. .PP The previous options can only select processes which match a small set of possible conditions. The .IR -cond option is different, and understands general expressions. The expression is specified in the argument following the option. (The argument usually needs quoting to avoid being split into multiple arguments or having its tokens interpreted by the shell.) .PP You can select processes matching a condition which is any combination of the column values for the process. This is done by specifying an expression to be evaluated for each process. If the result of the expression is non-zero or non-null, then the process is selected. If the expression cannot be evaluated (such as an attempt to divide by zero), then no error is generated but the process will not be selected. .PP Most of the expression syntax from C can be applied to the column values, such as arithmetic, comparisons, logical ANDs and ORs, the use of parentheses, the question mark operator, and some built-in functions. Numeric and string constants can be used within expressions. Numbers are usually decimal, but are octal if started with a leading 0, and hex if started with a leading 0x. Strings are enclosed in a pair of matching single or double quotes. Generally, string values must be compared with string values, and numeric values compared with numeric values. But in some cases numeric values can be converted to strings for comparison. .PP Column values are represented in the expressions by their column names as listed by the .I -listcolumns option, where unique abbreviations are allowed. Values from multiple columns can be used in the same expression, and can be compared against each other. Some column values are numeric, whereas other column values are strings. .PP The value obtained from using a column name is usually its .I base value, which is the unformatted primitive unit of information for the column. For example, for runtimes, this is the number of .I jiffies of runtime the process has used (i.e., 100's of seconds). A base value can be either a numeric or string value, depending on the column. .PP You can apply qualifiers to the column names to use alternate representations of a column value. A qualifier is a word following the column name which is separated from it by a period. The allowed qualifiers are .IR base , .IR show , and .IR test . .PP Using the .I base qualifier is the same thing as using the column name by itself (the base value). .PP Using the .I show qualifier returns the column value as a string value which is the same as is displayed for the column. So for example, for runtimes the .I show value contains colons and periods separating hours, minutes, and parts of seconds. .PP Using the .I test qualifier returns a boolean value (1 for TRUE and 0 for FALSE) indicating whether some useful aspect of the column is true. The meaning of this test varies depending on the column. For example, for the column showing the parent pid, the test returns whether or not the process has a parent (i.e., not 0 or 1). .PP There are several functions that can be used within expressions. These are .IR min , .IR max , .IR abs , .IR strlen , .IR match , .IR cmp , .IR str , and .IR my . .PP The .IR min , .IR max , and .I abs functions take numeric arguments, and take the minimum of two numbers, the maximum of two numbers, or the absolute value of a number. .PP The .I strlen function returns length of the string argument, or if a number was given, the length of the string representation of that number. .PP The .I cmp function compares two arguments and returns -1, 0, or 1 according to whether the first argument is less than, equal to, or greater than the second argument. If both arguments are numeric, then the comparison is done on their values. Otherwise, the comparison is done as a string, converting a numeric argument to a string value if required. .PP The .I match function takes two arguments which may be string or numeric values. Numeric values are converted into the corresponding string value. The first argument is a string value to be tested. The second argument is a wildcard pattern to match against. The wildcard syntax is like filename matching, so '?' means any single character, '*' means any sequence of characters, and '[]' matches single occurances of the enclosed characters. The function returns 1 if the string matches, and 0 if it does not. .PP The .I -str function converts its argument to a string value. .PP The .I my function takes one argument, which is a column name (possibly qualified). It returns the value of that column for the .B ips process .IR itself . For example, .I my(ttyname) returns a string which is my terminal name. In order to be of maximum use, the .IR uid , .IR user , .IR gid , and .I group columns return the user's real group and user ids for the .I my function, even if the .B ips program has been made setuid. .PP Upper case names can be used within expressions, which are macro names to be expanded into sub-expressions. These macro names are defined in the initialization files. The expansion of the macro must be a complete expression on its own, with proper use of parenthesis and operators. The macro name is replaced with the result of evaluating the sub-expression, and so can be a number or a string. The definition of a sub-expression can also contain macro names which will also be evaluated. .SH SORTING OF DISPLAYED PROCESSES The default sorting order of displayed processes is by their process id. But the list of displayed processes can be sorted based on any combination of the column values. The columns to be sorted by do not have to be restricted to the set of columns which are being displayed. .PP The first specified sorting column is used to sort the processes. If two or more processes have the same value for the first sorting column, then they are sorted by the second specified sorting column (if specified). This process continues as long as there are sorting columns specified and any processes still need sorting. If any processes are still left with matching sorting values after all the sorting columns have been used, then the process ids are used for a final sort. .PP Sorting on a column can be either a normal sort, or a reverse sort. In a normal sort, processes with smaller values will be displayed first. In a reverse sort, processes with larger values will be displayed first. Values are compared based on the type of column used for sorting. Some columns sort based on integer values, and some sort based on string values. Even if the displayed value is a string, the sorting may be based on the underlying integral .I base value. (The .I start-time column is an example.) .PP The .IR -sort , .IR -revsort , .IR -sortexpr , .IR -revsortexpr , and .I -nosort options are used to specify sorting values. .PP The .I -sort and .I -revsort options are used to append columns to the sorting list, either for normal sorting or for reverse sorting. They are followed by the list of columns to be added for sorting. .PP The .I -sortexpr and .I -revsortexpr options append an arbitrary expression to the sorting list, either for normal sorting or for reverse sorting. The expression can be made up of column names, numbers, strings, and operators, as in the .I -cond option. Sorting is done on the result of the expression which may be a numeric or string value. .PP The .I -nosort removes all columns from the sorting list, leaving only the default sort based on process id. .SH COLORING OF THE OUTPUT By default, all of the output text from .B ips is shown in the normal foreground and background colors of the output method (e.g., black on white for X11 output). .PP The information line, the header line, and the process rows can be individually colored by specifying foreground colors, background colors, and attributes for them. .PP The specification of a color is most generally given by string consisting of three parts which are separated by slash characters. These three parts are a foreground color name, a background color name, and attribute letters. .PP If only one slash is present then only a foreground and background color name is given, with no attributes. If no slash is present then only a foreground color name is given with no background name or attributes. .PP If a color name is empty or has the special value .IR default , then that color is the default color of the output method. .PP The attribute letters can be either .BI ' b ' to indicate bold (or bright) text, or else .BI ' u ' to indicated underlined text, or else both. .PP Examples of color specifications are: .BR red , .BR /blue , .BR green/yellow , .BR default/default , .BR //u , and .BR red//bu . These set a foreground of red with a default background, a default foreground with a blue background, a foreground of green with a yellow background, a default foreground and background, a default foreground and background with the text underlined, and a red foreground with a default background with the text underlined and made bold. .PP The available colors depends on the output method, as well as the naming convention of the colors. .PP For X11 output, many colors are available and can be named explicitly or else specified using 3 or 6 hexadecimal digits following a hash mark to give the red, green, and blue components. .PP For curses and terminal output, up to 256 colors can be used (according to the capabilities of the terminal). The colors are numeric values from 0 to 255, with the first 8 being the primary colors, the next 8 being the secondary colors, the last 20 or so being gray scale colors, and the others an arbitrary color. Alternatively, the names of the eight primary colors can be used. .PP The information line can be colored using the .I -infocolor option. The header line can be colored using the .I -headercolor option. The process rows being output can be colored using one or more uses of the .I -rowcolor option. This option takes two arguments. The first argument is a color specification. The second argument is an expression to be evaluated for the process being shown in the row, as in the .I -cond option. If the condition is true then the row will be colored in the specified color. .PP If multiple .I -rowcolor options are used and multiple conditions match a row, then the color of the last matching condition is used for the row. .PP Rows which are not matched by the conditions in any .I -rowcolor option are colored in the default foreground and background colors. .SH SPECIFYING THE DISPLAY METHOD The output from .I ips can be displayed using one of several different methods. The .IR -once , .IR -loop , .IR -curses , and .I -x11 options are used to specify which of the display methods are used. The default option is .IR -once . .PP Both of the .I -once and .I -loop options specifies a display method which writes the process status to .B stdout line by line using no cursor addressing sequences. Such output is suitable for saving to a file using redirection of standard output or for processing in a pipeline. The difference between the two options indicates whether or not the output is a once-only snapshot or is to be repeated indefinitely in a loop. There is no limit to the number of lines that can be written. The .I -clear option can be used with either of these options to write the standard ANSI clear screen escape sequence before each display of the process status. .PP The .I -curses option specifies a display method which uses the .IR curses (3) library for efficient updating of the screen using cursor addressing sequences. This display uses the whole terminal screen. The screen can be resized if desired. The number of lines of information is limited by the size of the screen so that only a subset of the status might be visible at one time. However, the display can be scrolled automatically or manually so that eventually all of the status can be seen. The .I ips program is in looping mode for this display method. The program can be terminated by typing the .B q or .B ESCAPE characters into the terminal. .PP The .I -x11 option specifies a display method which uses a raw X11 window (i.e., without using a terminal emulator such as .IR xterm ). The window can be resized if desired. The number of lines of information is limited by the number of rows in the window so that only a subset of the status might be visible at one time. However, the display can be scrolled automatically or manually so that eventually all of the status can be seen. The .I ips program is in looping mode for this display method. The program can be terminated by typing the .B q or .B ESCAPE characters into the window or by closing the window using the window manager. .PP The .IR -display , .IR -geometry , .IR -font , .IR -foreground , and .IR -background options can be used to set the display name, window geometry, font name, foreground color, and background color for the X11 window. If no display name is set then the default one using the .B DISPLAY environment variable is used. The default window geometry is 150x50. The default font is the .B fixed font, which is a mono-space (i.e., fixed-width) font. If a different font is specified then it should also be a mono-space font. The default foreground and background colors are .B black and .BR white . .PP Note: The X11 display mode is optional and must have been compiled into .I ips when it was built. This allows .I ips to be built for systems which have no X11 libraries installed. If your version of .I ips does not have X11 support, then the use of the .I -x11 option will produce an error message and fail. .PP For all of the looping display methods, the .I -sleep option can be used to set the sleep time in seconds between updates. (If not given, the default sleep time is 10 seconds.) The argument to this option can be a fixed point value, so that for example, a value of 0.5 specifies a sleep of 1/2 second. .PP The .I -scroll and .I -overlap options can be used for the curses and X11 display modes. The .I -scroll option sets the time interval in seconds for automatic scolling of the display if more processes are displayed than will fit. The default scroll time is 30 seconds. Note that the scrolling interval does not affect how often the display is updated (use .I -sleep for that). It just means that when the display is next updated, if the required time since the last scrolling had elapsed, then scrolling occurs for that update. It might take many update cycles before scrolling allows all of the process status to be seen. Scrolling wraps around, so that after the last process has been seen in the display, then the next scrolled display will return to the first process again. A scroll time of zero disables automatic scrolling completely. .PP The .I -overlap option specifies the number of lines of process status which are duplicated when scrolling occurs. The default overlap is one line. .SH THREAD HANDLING Depending on the options used, the .B ips program shows either the status of the processes in the system or the status of the threads in the system. Without any options only processes are shown. In order to show thread information, the .I -showthreads option must be used. .PP Some processes only consist of one thread of execution, which is the case for most simple programs which have no use for multi-threading. For these processes, the showing of processes or threads gives the same results and there are no problems in interpreting their status. .PP However, some processes contain more than one thread of execution. Threads share many of their attributes with each other, such as their memory and opened files, but have distinct program counters, stack pointers, runtime, and process state. The threads of a process all have the same process id, but have another id called the thread id (tid) which distinguishes them. One of the threads is called the main thread and has a thread id which is the same as the process id. .PP When .B ips shows only processes, then the status shown for a process consisting of multiple threads can be slightly misleading. The shared attributes are shown correctly for the process. However, some of the distinct status values are only those of the main thread, while those values for the other threads are ignored. Examples of these values are the program counter and the process state. .PP In particular, the process state can give very misleading status of the process. If the main thread is sleeping, but another thread is constantly running, the state of the process can be misleadingly reported as 'S'. In this case, the runtime of the process increases quickly and is shown as active, however it never appears to be running. .PP The runtime of a process is the sum of all of the runtimes of the individual threads, and so is generally meaningful. Note that in a multi-cpu system where multiple threads can simultaneously run, the runtime of a process can appear to increase faster than the clock rate since multiple threads can contribute the full elapsed time to the process runtime. .PP When .B ips is showing thread status then all of the above problems are avoided. Each thread of a process is then shown with its correct status. This includes the program counter, the process state, and the runtime. In this case, threads which are running will show their state as 'R' as expected. Also note that when threads are shown, the display of the main thread is only that of that particular thread, so that its runtime is no longer the sum of all of the threads. .PP Even when only processes are being shown, the state information for the process can optionally be more accurate than indicated above. If the .I -usethreads option is used or if the .I states column is used, then the .B ips program will examine the states of all of the theads of a process, and select the most important state among all of the threads as the state to show for the process as a whole. For example, the priority order of the states starts with the 'R', 'D', and 'S' states so that, for example, if any thread is running, then the state of the process is 'R' as expected. .PP The .I states column shows all of the states of the threads of a process using multiple letters and numeric counts. For example, a value of 'R3DS2' indicates that there are three running threads, one thread in a disk I/O wait, and two sleeping threads. .SH COMMAND INPUT WHILE RUNNING The curses and X11 display modes allow commands to be typed while they are running. Commands are not visible as they are typed to the screen or window. The commands are read character by character so that they are executed immediately when complete without requiring a terminating newline. If the command is one which affects the display then the current sleep is canceled so that the display can show the result. .PP Some commands accept an optional numeric argument which is typed just prior to the command. This numeric argument can be a non-negative integer value or a non-negative fixed point number. Commands which only accept an integer value ignore any fractional part. If a numeric argument is not given, the commands will use a default value. If a numeric argument is typed, but you no longer want to use it (as when you have made a typing mistake), then the backspace or delete keys will totally remove any partially typed numeric argument. At this point you can type in a new numeric argument (if desired). .PP The .B s command sets the sleep time to the number of seconds specified in the preceeding numeric argument. The command accepts a fixed point value so that sleeps less than one second are possible. If no argument is given then the sleep time is set to the default value of 10 seconds. .PP The .B a command sets the automatic scrolling time to the number of seconds specified in the preceeding numeric argument. If no argument is given then the autoscroll time is set to the default value of 30 seconds. A value of 0 disables autoscrolling. .PP The .B t and .B b commands change the display to show the top or the bottom of the process list. (These are the first and last pages of the display.) .PP The .B n and .B p commands change the display to show the next or previous page of the process list. If the next page is past the end of the list then the first page is displayed. Similarly, if the previous page is before the beginning of the list then the last page is displayed. .PP The .B o command sets the number of lines of overlap between pages of data to the value specified in the preceeding numeric argument. If no argument is given then the overlap value is set to the default value of 1 line. .PP The .B i command enables or disables an information line at the top of the display which shows the total number of process and threads in the system, the number of threads or processes which are currently being shown, the sleep time, the currently displayed page number, and if the display is frozen, an indication of that fact. Without any arguments, the display of the information line is toggled. A zero argument disables the line. A nonzero argument enables the line. .PP The .B h command enables or disables the column header line at the top of the display. Without any arguments, the display of the header line is toggled. A zero argument disables the header. A nonzero argument enables the header. .PP The .B 'f' command enables or disables the frozen state of the display. Without any arguments, the frozen state is toggled. A nonzero argument freezes the display. A zero argument unfreezes the display. While the display is frozen, the .I ips program simply waits for further commands (ignoring the normal sleep and autoscroll times). The automatic collection of new process data is disabled. Automatic scrolling is also disabled. However, commands can still be typed while the display is frozen to perform scrolling or process status updating on demand. .PP A .B SPACE or .B RETURN character updates the display immediately. New process data will be collected for the display. This occurs even if the display is currently frozen. .PP The .B r command refreshes the contents of the display to fix any glitches. This is mostly intended for curses use when other programs output to the screen, or when the terminal emulator misbehaves. .PP A .B q or .B ESCAPE character quits .IR ips . .PP All other characters are illegal and ring the bell. .SH INITIALIZATION FILES AND MACROS For convenience and to allow users to configure the output to their liking, .I ips reads two initialization files on startup. The first of the files to be read is the system initialization file .I /usr/local/lib/ips.init which is used to set system defaults for .BR ips . .PP The second initialization file to be read is the user initialization file .I $HOME/.ipsrc located in each user's home directory. This allows each user to modify the system defaults for their own use. The reading of the user's initialization file can be disabled by using the .I -noinit option. If used, this option must be the first option after the command name. .PP The contents of the initialization files are very simple. Each line of the file can be blank, be a comment, or be a macro definition. If any line ends in a backslash, then the backslash is replaced by a space and the next line is appended to it. Comment lines have a hash mask character as their first non-blank character. Comment lines and blank lines are ignored. .PP The first line of initialization files must consist of the word .BR #ips# , otherwise an error message will be generated and the program will exit. .PP Macro definitions are used to replace single arguments on the command line with possibly large replacement strings with many arguments. The replacement strings can themselves use macros, and these new macros are also removed and replaced. Macro replacement continues until either no more macros remain to be replaced, or until the allowed macro depth is exceeded. .PP Macro names are usually distinguished from non-macros by the fact that macros begin with upper case letters. Since column names are all in lower case, there is no problem distinguishing between a column name and a macro name. .PP There are three different types of macros in .IR ips . These types are distinguished by the location of the macro usage within the command line. The three types of macros are commands, columns, and expressions. Command macros define a list of command line options and their arguments. Column macros define a list of column names. Expression macros define a sub-expression for the .IR -cond , .IR -sortexpr , and .I -revsortexpr options. .PP Because the meaning of these three types of macros differs so much, and the replacement strings for the macros would generally make no sense if used for a different type of macro, the three types of macros have independent name spaces. This means that the same macro name could be defined three times, once for each type of macro. (But this is probably bad practice). .PP To define a macro in an initialization file, you use one of the keywords .IR option , .IR column , or .IR expr , followed by the macro name and the replacement strings for the macro, all on one line (taking into account the use of backslashes to continue lines). The macro names must begin with an upper case letter. .PP The .I option keyword defines a macro as being one or more command line options. The replacement string consists of a number of space separated options and arguments as used on the command line, including the leading hyphens for the options. Arguments for options must be contained within the macro expansion itself. The macro expansion can itself contain macros which will also be expanded into more options. .PP As the single exception to the requirement that macro names are in upper case, if a word appears on the .B ips command line which is not an option, and which cannot be an argument for an option, then that word with its initial letter converted to upper case is treated as an option macro to be expanded. .PP An important special case of this is a word typed immediately after the .B ips program name. This is typically a macro name which defines a particular format of display. For example, the command .B ips top would expand the option macro named .B Top which could be defined to emulate the output of the .B top program. .PP The .I column keyword defines a macro as being a list of column names. The replacement string consists of a number of space separated column names. The macro expansion can itself contain macros which will also be expanded into more column names. .PP The .I expr keyword defines a macro which is an expression used for the .IR -cond , .IR -sortexpr , or .I -revsortexpr options. The replacement string consists of a complete expression using numbers, strings, column names, and possibly other macros which will also be expanded. .PP Here is an example of a valid initialization file: .sp .nf #ips# # The special command macro run by default option SysInit -col pid parent user summary runtime command # Definitions for other commands of interest option Stop -cond Stop option Cmd -col pid command -sep 1 option Env -col pid environment -sep 1 option Vert -vert -sep 1 -col All option Mytty -cond Mytty option Top -sep 1 -col pid user summary runtime \\ percentcpu command -revsort percentcpu \\ -revsort runorder -curses -clear -active # Definitions for groups of columns column Run runtime idletime percentcpu column Regs eip esp column Sigs signalcatch signalignore signalblock column Size residentsetsize percentmemory size column Stdio stdin stdout stderr # All columns column All pid parentpid uid user gid group \\ processgroup ttyprocessgroup \\ state flags nice priority realtimepriority policy \\ systemtime usertime runtime childruntime \\ threads percentcpu runorder \\ residentsetsize size percentmemory \\ active idletime starttime age realtimer \\ eip esp waitchannel waitsymbol \\ pagefaults minorpagefaults majorpagefaults \\ pageswaps childpageswaps \\ signalcatch signalignore signalblock \\ ttyname ttydevice \\ openfiles stdin stdout stderr stdio \\ currentdirectory rootdirectory executable \\ summary program command environment # Definitions for expressions used in conditions expr Me (uid == my(uid)) expr Server (uid < 100) expr User !Server expr Stop (state == 'T') expr Mytty (ttydev == my(ttydev)) .fi .PP The special option macro names of .B SysInit and .B UserInit are automatically expanded (if they are defined) at the start of every run of .BR ips . These macros are used to initialize parameters to default values. Examples of this initialization is to specify the default list of columns to be displayed and the default sleep time when looping. The .B SysInit macro definition is usually contained in the system initialization file, while the .B UserInit macro definition is usually contained in the user's initialization file. Parameters set by these macros can be modified by using options on the command line. .SH USEFUL MACROS The standard supplied system initialization file .I /usr/local/lib/ips.init contains many macros of interest. This section describes some of the standard macros which are provided. Remember that these macros can be used in lower case on the command line. .PP Warning: These macros might not actually work on your system as described here since they can be changed by the system administrator. The system administrator may also have added other useful macros which are not described here. You should examine the macro definitions in the initialization file in order to make full use of .BR ips . .PP The default macro .I SysInit adds a condition to only show your own processes. So in order to see other user's processes, you must disable that condition explicitly or else use a macro which disables it. The .I Nocond macro removes all conditions on the selection of processes allowing you to see all processes. .PP The user name column is not shown by default. The .I Long macro changes the displayed columns to include the user name and the parent pid. .PP The .I All macro combines the .I Nocond and .I Long macros to show all processes in a nice display. .PP The .I Pack macro shows many useful columns together including the user and group ids, the state of stdio, and the process age. .PP The .I Cmd and .I Env macros show only the process id and the command line or environment so that you can see much more of these columns than is usual. .PP The .I Files macro shows columns related to files, such as the number of open files, the status of stdio, and the current and root directories. .PP The .I Cpu macro shows a snapshot display of the currently active processes. It has a two second sleep in order to detect running processes. The .I Top macro shows the same display format, but in a looping manner using .I curses and including recently active processes. .PP The width of the runtime columns is not adequate to hold really large runtimes. The .I Widerun macro increases the width of these columns to show larger runtimes. .PP The .I Wide macro makes the output width be as large as possible, allowing the showing of very long command lines or environments. .PP The .I Vert macro sets the output format to vertical and shows every column value. .PP The .I Tty macro adds a condition to only show processes which are on a terminal. .PP The .I Mytty macro adds a condition to only show processes which are on your own terminal. .PP The .I Stop macro adds a condition to show stopped processes. .SH OTHER FEATURES There are several other features of .B ips which can be specified using command line options. These options are .IR -default , .IR -read , .IR -initsleep , .IR -noheader , .IR -activetime , .IR -deathtime , .IR -synctime , .IR -listmacros , .IR -listcolumns , .IR -version , .IR -end , and .IR -help . .PP The .I -default option is useful to reset parameters that have been set by previous options. In particular, it is useful to reset parameters that have been set by the initialization files. It accepts one or more option names (without the leading hyphens). Any parameter set by the indicated option is restored to its initial state as when the .B ips program started. For example, .B -default pid removes any previous restriction on the process ids that can be shown. .PP The output from the .I -help option will briefly describe the use of the remaining options. .SH COLUMN DESCRIPTIONS Some of the columns for displaying are self-evident. But many of them need an explanation, and this is done here. Due to the permissions on /proc, some of the column values may not be available for every process. Columns marked as .B restricted are only available if the process has your own user id, you are running as root, or the .B ips program itself is setuid to root. .PP The .I state column shows the current state of the process. This is a single letter, where 'R' is runnable, 'D' is disk I/O, 'T' is stopped, 'S' is sleeping, 'Z' is zombie, and ' ' is dead (nonexistent). .PP The .I eip and .I esp columns show the instruction pointer and stack pointer of the process. The instruction pointer is also known as the program counter, or PC. .PP The .I waitchannel column shows the hex address within the kernel that the process is sleeping on. This is zero if the process is not sleeping. Usually, different reasons for sleeping use different addresses. .PP The .I waitsymbol column shows the symbolic address within the kernel that the process is sleeping on. This is blank if the process is not sleeping. .PP The .I program and .I command columns show the program name and command line of the process. The program name is just the name of the executable file without any arguments. The command line shows the arguments that the program was started with. If no command line arguments were supplied to the program, then this column shows the program name enclosed in parenthesis. .PP The .I idletime column shows the number of minutes that the process has been idle. An idle process is one which has not (detectably) run at all in the indicated interval. The idle time is only known by examining processes over time, and so the true idle time of a process which existed before .B ips was run is not known. In these cases, the idle time is simply the amount of time that .B ips has been running, and the times are marked with a leading plus sign. .PP The .I active column shows whether or not the process has been active. It shows one of the values "active" or "idle". This column is provided mainly for use in sorting and selecting. .PP The .I ttyname and .I ttydevice columns show the controlling terminal of the process, which is usually the terminal where the user logged into. The device is the kernel's id for the terminal, and is just a number. The name is found by searching /dev for a character device which has that same id and then displaying the device name with the /dev removed. .PP The .IR user , .IR uid , .IR group , and .I gid columns show the user ids and group ids of a process. The uid and gid are the numeric ids as used by the kernel. The user and group are the conversion of those ids to user names and group names, as found in the /etc/passwd and /etc/group files. .PP The .I percentcpu column shows the percentage of CPU time that the process has used in a certain recent time interval called the sample interval. The samples are taken at a maximum rate of five times a second according to the current sleep time of the .B ips program. The sample interval is a sliding value so as to give an average cpu percentage over a specified number of seconds. This makes the values less 'jumpy' than instantaneous cpu percentages would give and act more like the system load averages. The sample interval is set using the .I -percentseconds option, which can have a value from 0 to 20. The default sample interval is 10 seconds. The percentage runtime is 100 times the quotient of the runtime used during the sample interval by the sample interval itself. Note that for a multi-threaded process on a multi-cpu system, the percentage runtime can reach multiples of 100. .PP The .I residentsetsize column is the number of K of memory used by the process. Pages of a process which are not in memory are not counted by this column. .PP The .I starttime and .I age columns show the time at which the process was created. The start time is the time of day the process started, and if the process was in existence for over one day, then the number of days previously that the process was started. The age is the number of minutes that the process has existed, and is the difference between the current time and the time that the process started. .PP The .I flags column shows some kernel flags associated with the process, in hex. .PP The .IR minorpagefaults , .IR majorpagefaults , and .I pagefaults columns show the number of minor page faults, major page faults, and the total page faults of the process. Minor page faults are faults on pages that do not require any disk I/O, which are copy on write or touching empty pages. Major page faults are faults which require disk I/O, such as reading in of text file pages or swap pages. .PP The .IR signalcatch , .IR signalignore , and .IR signalblock columns show the state of signal handling for the process. Each of these value is a hex value, where signal N is bit number N-1 (counting from bit 0 at the right). Caught signals are those for which a signal handler is installed. Ignored signals are those for which the process is ignoring signals. Blocked signals are those which are pending delivery, but which the process has blocked from being delivered. .PP The .I openfiles column displays the number of open files that the process has. This column is restricted. .PP The .I runorder column shows the relative run order of the processes. The run order is a monotonically increasing value representing the number of process samplings that .B ips has made since it started. Processes are assigned the current run order value whenever they are seen to have been active since the last sample. Processes with a larger run order value have run more recently. .PP The .I currentdirectory column gives the current working directory of the process in the kernel's internal values of device number and inode number, separated by a colon. The device number is in hex, and the inode number is in decimal. This column is restricted. .PP The .I rootdirectory column gives the root directory of the process in the kernel's internal values of device number and inode number, separated by a colon. The device number is in hex, and the inode number is in decimal. This column is restricted. .PP The .I executable column gives the device number and inode number of the executable file for the process, separated by a colon. The device number is in hex, and the inode number is in decimal. This column is restricted. .PP The .I realtimer column shows the amount of time that the process wants to sleep before being woken up. This is either just the number of seconds, or else is the number of seconds and parts of seconds. This value does not decrement as time passes, so you don't know when the sleep time will expire. .PP The .IR stdin , .IR stdout , and .I stderr columns show the file names associated with the stdin, stdout, or stderr file descriptors of the process. These columns are restricted. .PP The .I stdio column shows a summary of the files associated with the stdin, stdout, or stderr file descriptors of the process. This is in the form of a three character string with one character for each of the .BR stdin , .BR stdout , and .B stderr file descriptors. The character is 'T' for a terminal, 'P' for a pipe, 'S' for a socket, 'N' for /dev/null, 'F' for some other file, and '-' for a closed file descriptor (or if the information is unavailable). This column is restricted. .PP The .I summary column shows many flag characters which summarize some of the state of the process. This consists of a string of 14 characters, where each character is either a dash or a letter. A letter indicates the specified condition is true for that character position, whereas a dash indicates that the condition is false for that character position. .PP Character 1 is the state of the process, except that if the process is sleeping, then it is 'A' for recently active, or 'I' for idle, and if the process has died (i.e., no longer existent), then it is '-'. Character 2 is 'W' if the process has no resident memory, and is therefore swapped out. Character 3 is 'N' if the process has been niced, and is 'H' if the process has been given as higher priority than normal. Character 4 is 'S' if the process is a session id leader. Character 5 is 'P' if the process is a process group leader. Character 6 is 'T' if the process has a controlling terminal. Character 7 is 'F' if the process is a foreground process, which means that its process group matches its controlling terminal's process group. Character 8 is 'I' if the process has no parent, meaning it is owned by .BR init . Character 9 is 'h' if the process is catching SIGHUP or 'H' if the process is ignoring SIGHUP. Character 10 is 't' if the process is catching SIGTERM or 'T' if the process is ignoring SIGTERM. Character 11 is 'U' if the process has your user id. Character 12 is 'G' if the process has your group id. Character 13 is 'R' if the process is running as root. Character 14 shows the age of the process. It is 'N' for a new process, 'M' for a process one minute old, 'F' for a process five minutes old, 'T' for a process ten minutes old, 'H' for a process one hour old, 'D' for a process one day old, and 'W' for a process one week old. .SH PERFORMANCE Some data is only collected if the columns using that data are used. Here 'used' means either displaying, selecting on, or sorting by the column. Avoiding columns when they are not required will save the time used to collect that data. .PP Most process status is obtained by scanning the /proc directory looking for filenames which are numeric (which are the process ids). For each of these processes, the file /proc//stat must be opened and read to collect most of the process status. .PP If detailed thread information is requested, then the directories /proc//task must be scanned for filenames which are numeric (which are the thread ids). For each of these threads, the file /proc//task//stat must be opened and read to collect the thread status. .PP Additional files in /proc might need to be read to get the full status that is required. .PP Using the .I -pid option will save much work, since then the scan of /proc is avoided and only the specified process ids will be examined. Using .I -noself avoids looking at our own process. .PP Using the .IR -my , .IR -user , .IR -group , and .I -noroot options will save time reading and parsing of the process status for the eliminated processes, and stop collection of other data for the eliminated processes. .PP The .I -top and .I -cond options may save time by eliminating the display of process information. But the information is still collected. .PP The .I -synctime option changes the interval on which the full process status is collected for inactive processes. (See the RISKS section below.) Setting this to a shorter time interval will increase the runtime. .PP The .I command column requires the opening and reading of /proc//cmdline whenever the process has changed state or when the synctime has expired. .PP The .I environment column requires the opening and reading of /proc//environ whenver the process has changed state or when the synctime has expired. .PP The .IR active , .IR idletime , and .I percentcpu columns and the .I -active option require that the .B ips program sample the processes twice before displaying anything, with a small sleep between the two samples. So there will be a delay before seeing anything. .PP The .IR ttyname column requires the reading of /dev to find the list of character devices. This work adds a delay to the program before anything is displayed. It is only required once per run. .PP The .I openfiles column requires the reading of all the files in /proc//fd whenever the process has changed state or when the synctime has expired. .PP The .IR stdin , .IR stdout , .IR stderr , and .I stdio columns require the link values of one or more of the /proc//fd/ files to obtain their information whenever the process has changed state or when the synctime has expired. .PP The .I currentdirectory column requires the reading of the /proc//cwd file whenever the process has changed state or when the synctime has expired. .PP The .I rootdirectory column requires the reading of the /proc//root file whenever the process has changed state or when the synctime has expired. .PP The .I waitsymbol column requires the reading of the /proc//wchan file whenever the process has changed state or when the synctime has expired. .PP The .I executable column requires the reading of the /proc//exe file whenever the process has changed state or when the synctime has expired. .SH RISKS The determination of whether a process has been active since the last sample is not completely foolproof. Some of the process data is only collected when a process has been active, or else has not been collected for a while, and so there is a small risk that the data is obsolete. The columns which are not necessarily collected on every update are the ones which require examining /proc files other than the main status file. These columns include the command line, the environment, the current directory, and the number of opened files. .PP The .B ips program checks many process status values to determine whether or not a process has been active since the last sampling. If any of these differ from the last sampling, then the process is active. These values are the process state, runtime, flags, page faults, start time, stack pointer, instruction pointer, and wait channel. New process are always active, and processes whose state is 'R' or 'D' are always active. .PP It is possible that a process which wakes up for only a short time, does very little and then goes back to sleep will appear to be inactive. (The kernel only has a 1/100 second runtime resolution, and so the small runtime of the process might not have been seen by the kernel.) .PP The .I -synctime option can be used to reduce or expand this risk of showing obsolete data. It accepts the number of seconds at which the complete status of the process is collected even when it is idle. It defaults to one minute. Setting the synctime to zero produces a status with no obsolete data. .PP The list of user names, group names, and device names are only collected when .B ips is first started. Changes to the password file, group files, or device files will not be seen while the program is running. .PP The data collected by .B ips is dynamic. It can change even while the status is being collected for a single process. So the data shown is only a snapshot and is never absolutely consistent. .SH LIMITS The following are some limits to the operation of .BR ips . These are compile-time constants, and could be increased if required by recompiling the program. .PP You can only specify 100 process ids for the .I -pid option. .PP You can only specify 100 user names or ids for the .I -user option. .PP You can only specify 100 group names or ids for the .I -group option. .PP You can only have 1000 arguments on a command line. .PP The maximum output width is 31K characters, where K is 1024. .PP The maximum command string length is 10K. .PP The maximum environment string length is 20K. .PP The maximum program name string length is 32. This length is imposed by the kernel which only has a buffer of this size. .PP The maximum separation between columns is 20 spaces. .PP The maximum depth of expansion of option macros is 20. .PP The maximum depth of expansion of expression macros is 20. .PP The maximum number of seconds for calculating cpu percentages is 20 seconds. .SH BUGS The .I -clear option clears the screen by outputting the ANSI escape sequence for clearing the screen. If your terminal does not understand this escape sequence then this option will not work correctly. .PP Proportional spaced fonts do not work correctly in the X11 display mode. .PP Using both of the .I -vert and .I -top options together without any argument does nothing useful. The number of processes shown will be dependent on the screen height, but the output will not be limited to the screen height since each process status prints on multiple lines. .PP Pagination of output when using the .I -vert option is not correct. .PP There are no quoting characters for macro definitions, so you cannot create single arguments which contain blanks. This means that if you use the .IR -cond , .IR -sortexpr , or .I -revsortexpr options in the macro definition file, then the following expression must not contain any blanks. However, you .I can use blanks in the definition of an expression macro. .PP The specification of a window position for X11 using the .I -geometry option does not work correctly. .PP This program is dependent on the layout of the /proc file system which changes depending on the kernel version. This particular version of .I ips works for kernel version 2.6.13. .SH FUTURES I would like to allow macros to accept arguments enclosed in parenthesis, and have those arguments substituted into the replacement string at the locations matching parameter names for the macro. .PP I would like to allow user-defined columns where the user can define the format of the data to be displayed using the results of expressions on other column data. .SH CREDITS Some of the knowledge on how to process and display the data from /proc was obtained by reading the procps version 0.97 code by Michael K. Johnson. .PP The pattern matching code was adapted from code written by Ingo Wilken. .SH AUTHOR .nf David I. Bell dbell@canb.auug.org.au 25 April 2010 .fi ips-4.0/commands.c0000644000175000017500000001516011363014721013044 0ustar dbelldbell/* * Configurable ps-like program. * Routines to sleep while reading commands while ips is in loop mode. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include "ips.h" /* * Some state for numeric argument parsing. */ static double numberValue; static double scaleValue; static BOOL isNumberSeen; static BOOL isPeriodSeen; /* * Local routines. */ static void ClearParsedNumber(void); static double GetParsedNumber(double); /* * Sleep for the required amount of time while handling any commands. * Returns only when the sleep time is used up or a command was read * which wants us to start the next loop immediately. */ void WaitForCommands(long milliSeconds) { long remainingMilliSeconds; long elapsedMilliSeconds; struct timeval beginTime; /* * If there is no sleep then just collect any keyboard events * that are ready right now and execute commands for them. */ if (milliSeconds <= 0) { DpyEventWait(0); ReadCommands(); return; } /* * We want to sleep. Get the beginning time so we can tell * when our sleep has finished. */ GetTimeOfDay(&beginTime); remainingMilliSeconds = milliSeconds; /* * Loop until either the remaining time has elapsed, the display * needs redrawing, or until a command was executed which wants * the sleep to terminate early. */ do { /* * Handle any events for the display device while sleeping * for the remaining amount of time. If the routine says * that an update is needed now, then arrange to leave * the loop (after checking for commands). */ if (DpyEventWait(remainingMilliSeconds)) milliSeconds = 0; /* * Handle any commands that may have been typed while we * were sleeping. If the command is one which wants us to * return early, then do so. */ if (ReadCommands()) break; /* * No command that needed to terminate the sleep was read. * Get the elapsed time since the beginning time so we can * tell how long we slept for. */ elapsedMilliSeconds = ElapsedMilliSeconds(&beginTime, NULL); /* * If the time went negative (e.g., was reset) then leave. * The time should behave better on the next call. */ if (elapsedMilliSeconds < 0) break; /* * Calculate the amount of time left to sleep. */ remainingMilliSeconds = milliSeconds - elapsedMilliSeconds; } while (remainingMilliSeconds > 0); } /* * Read any commands that the user may have typed while we were sleeping. * Reading is done in character mode so that multi-character commands * might not be complete in one call. Also, reading is non-blocking. * Returns TRUE if the command is one which requires our sleep to terminate. */ BOOL ReadCommands(void) { BOOL isWakeup; BOOL isClear; BOOL isError; int ch; isWakeup = FALSE; isClear = FALSE; isError = FALSE; /* * Loop reading commands until there are no more characters. */ while (DpyInputReady()) { ch = DpyReadChar(); if (ch == EOF) break; switch (ch) { case 'q': case '\033': /* * Quit from the program. */ isRunning = FALSE; isWakeup = TRUE; isClear = TRUE; break; case '\r': case '\n': case ' ': /* * Do an immediate update. */ isWakeup = TRUE; isUpdateForced = TRUE; isClear = TRUE; break; case 'a': /* * Set autoscroll time. */ scrollSeconds = (int) GetParsedNumber(DEFAULT_SCROLL_SEC); if (isInfoShown) isWakeup = TRUE; break; case 't': /* * Move to top page. */ TopPage(); isWakeup = TRUE; isClear = TRUE; break; case 'h': /* * Turn on or off the header line. */ noHeader = (GetParsedNumber(noHeader) == 0); isWakeup = TRUE; break; case 'f': /* * Freeze or unfreeze the display. */ isFrozen = (GetParsedNumber(!isFrozen) != 0); ResetScrollTime(); isWakeup = TRUE; break; case 'i': /* * Turn on or off the info line. */ isInfoShown = (GetParsedNumber(!isInfoShown) != 0); isWakeup = TRUE; break; case 'b': /* * Move to bottom page. */ BottomPage(); isWakeup = TRUE; isClear = TRUE; break; case 'n': /* * Move to next page of data. */ NextPage(); isWakeup = TRUE; isClear = TRUE; break; case 'o': /* * Set overlap lines between screens. */ overlapLines = (int) GetParsedNumber(DEFAULT_OVERLAP_LINES); isWakeup = TRUE; break; case 'p': /* * Move to previous page of data. */ PreviousPage(); isWakeup = TRUE; isClear = TRUE; break; case 's': /* * Set sleep time. */ sleepTimeMs = (int) (1000.0 * GetParsedNumber(DEFAULT_SLEEP_SEC)); isWakeup = TRUE; break; case '\177': case '\b': /* * Clear the numeric argument. */ isClear = TRUE; break; case 'r': /* * Refresh the screen. */ isRefreshNeeded = TRUE; isWakeup = TRUE; isClear = TRUE; break; case '.': /* * Parse part of a floating point argument. */ if (isPeriodSeen) isError = TRUE; isPeriodSeen = TRUE; if (!isNumberSeen) { isNumberSeen = TRUE; numberValue = 0.0; scaleValue = 1.0; } break; default: /* * If the character isn't a digit then it is * an error. */ if ((ch < '0') || (ch > '9')) { isError = TRUE; break; } /* * Parse part of a floating point value. */ if (!isNumberSeen) { isNumberSeen = TRUE; numberValue = 0.0; scaleValue = 1.0; } numberValue = numberValue * 10.0 + (ch - '0'); if (isPeriodSeen) scaleValue *= 10.0; break; } } /* * If there was a command error then ring the bell. */ if (isError) DpyRingBell(); /* * Clear any parsed number if we need to. */ if (isError || isClear) ClearParsedNumber(); /* * Return indication of whether we need to wake up. */ return isWakeup; } /* * Get the numeric value (if any) that had previously been parsed and saved, * defaulting its value to the specifed one if it hadn't been entered, * and clearing the saved value so that a new number can be entered for * the next command. */ static double GetParsedNumber(double value) { if (isNumberSeen) value = numberValue / scaleValue; ClearParsedNumber(); return value; } /* * Clear any parsed number. */ static void ClearParsedNumber(void) { numberValue = 0; scaleValue = 0; isNumberSeen = FALSE; isPeriodSeen = FALSE; } /* END CODE */ ips-4.0/linux.c0000644000175000017500000006071711364731663012426 0ustar dbelldbell/* * Configurable ps-like program. * Linux-specific process information collection routines. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include #include #include #include "ips.h" #define PROCDIR "/proc" /* path for proc filesystem */ #define BEGNAMECHAR '(' /* begin char for program name */ #define ENDNAMECHAR ')' /* end char for program name */ /* * A generous size of a buffer which will hold the /proc file names * that we are interested in. These file names only contain small fixed * components along with one or two possible integer values. */ #define PROC_FILE_LEN 80 /* * Static variables. */ static DIR * procDir; /* opendir for /proc */ /* * Local procedures. */ static void ExamineProcessId(pid_t pid, pthread_t tid); static void GetProcessCommandLine(PROC * proc); static void GetProcessEnvironment(PROC * proc); static void GetProcessOpenFileCount(PROC * proc); static void GetProcessCurrentDirectory(PROC * proc); static void GetProcessRootDirectory(PROC * proc); static void GetProcessExecInode(PROC * proc); static void GetProcessStdioDescriptors(PROC * proc); static void GetProcessWaitSymbol(PROC * proc); static void ScanThreads(PROC * proc); static BOOL IsCopyAllowed(const PROC * proc); static BOOL ReadLinkPath(const char * name, char ** retpath, int * retpathlength); /* * Initialize things that we need up front for process status collection. * Returns TRUE if successful. */ BOOL InitializeProcessData(void) { CollectStaticSystemInfo(); if (useUserNames) CollectUserNames(); if (useGroupNames) CollectGroupNames(); if (useDeviceNames) CollectDeviceNames(); /* * Open the /proc directory so that the names in it can be found. */ procDir = opendir(PROCDIR); if (procDir == NULL) { fprintf(stderr, "Cannot open %s\n", PROCDIR); return FALSE; } ancientFlag = TRUE; return TRUE; } /* * Collect system information that we need. * This information collected here is static. */ void CollectStaticSystemInfo(void) { const char * cp; int fd; int cc; char buf[256]; /* * Get the number of ticks per second. */ ticksPerSecond = sysconf(_SC_CLK_TCK); if (ticksPerSecond <= 0) ticksPerSecond = 100; /* * Get the page size. */ pageSize = sysconf(_SC_PAGESIZE); if (pageSize <= 0) pageSize = 4096; /* * Collect the amount of memory on the system. */ fd = open(PROCDIR "/meminfo", O_RDONLY); if (fd < 0) return; cc = read(fd, buf, sizeof(buf) - 1); (void) close(fd); if (cc <= 0) return; buf[cc] = '\0'; cp = strstr(buf, "MemTotal:"); if (cp == NULL) return; cp += 9; totalMemoryClicks = GetDecimalNumber(&cp) / (pageSize / 1024); /* * Get the starting uptime and time of day. * This will be used to determine the age of processes. */ startUptime = GetUptime(); startTime = time(NULL); } /* * Collect system information that we need. * This information collected here is dynamic. * For now all we need is the process and thread counts. */ void CollectDynamicSystemInfo(void) { const struct dirent * dp; const char * cp; int fd; int cc; char buf[256]; /* * Read the load average information. * This has a format like: 0.03 0.06 0.07 1/214 12496 */ fd = open(PROCDIR "/loadavg", O_RDONLY); if (fd < 0) return; cc = read(fd, buf, sizeof(buf) - 1); (void) close(fd); if (cc <= 0) return; buf[cc] = '\0'; /* * The number of threads on the system follows a slash. */ cp = strchr(buf, '/'); if (cp == 0) return; cp++; threadTotalCount = GetDecimalNumber(&cp); /* * Get the number of processes by counting the number of * filenames in the /proc directory which are numeric. */ procTotalCount = 0; seekdir(procDir, 0); while ((dp = readdir(procDir)) != NULL) { cp = dp->d_name; while (isDigit(*cp)) cp++; if (*cp == '\0') procTotalCount++; } } /* * Get the uptime of the system in jiffies. Since the /proc data format * is floating point seconds, we have to convert it. * Returns 0 if something is wrong. */ ULONG GetUptime(void) { const char * cp; int fd; int cc; ULONG intVal; ULONG fracVal; ULONG fracScale; char buf[128]; fd = open(PROCDIR "/uptime", O_RDONLY); if (fd < 0) return 0; cc = read(fd, buf, sizeof(buf) - 1); (void) close(fd); if (cc <= 0) return 0; buf[cc] = '\0'; cp = buf; while (isBlank(*cp)) cp++; intVal = 0; fracVal = 0; fracScale = 1; while (isDigit(*cp)) intVal = intVal * 10 + *cp++ - '0'; if (*cp == '.') cp++; while (isDigit(*cp)) { fracVal = fracVal * 10 + *cp++ - '0'; fracScale = fracScale * 10; } if ((*cp != ' ') && (*cp != '\n')) return 0; return (intVal * ticksPerSecond) + ((fracVal * ticksPerSecond) / fracScale); } /* * Scan all processes and set their new state. */ void ScanProcesses(void) { const struct dirent * dp; const char * name; pid_t pid; int i; UpdateTimes(); CollectDynamicSystemInfo(); /* * If we require our own process information, then get that now. */ if (useSelf) ExamineProcessId(myPid, NO_THREAD_ID); /* * If there were no specific pids given, then scan them all by * reading the numeric entries from the "/proc" directory. * Otherwise, examine just the specified processes. */ if (pidCount == 0) { seekdir(procDir, 0); while ((dp = readdir(procDir)) != NULL) { name = dp->d_name; pid = 0; while (isDigit(*name)) pid = pid * 10 + *name++ - '0'; if (*name) continue; if ((pid != myPid) || !useSelf) ExamineProcessId(pid, NO_THREAD_ID); } } else { for (i = 0; i < pidCount; i++) { if ((pidList[i] != myPid) || !useSelf) ExamineProcessId(pidList[i], NO_THREAD_ID); } } RemoveDeadProcesses(); SortProcesses(); UpdateProcessCounts(); ancientFlag = FALSE; } /* * Collect data about the specified process and thread id. * A thread id of NO_THREAD_ID collects the main process data, * possibly along with the process data of all of its threads. * This allocates a new PROC structure if necessary. * If the process is successfully examined, the valid flag is set. */ static void ExamineProcessId(pid_t pid, pthread_t tid) { PROC * proc; int fd; int cc; int i; char * begName; char * endName; const char * cp; long ticksFromStart; long secondsFromStart; BOOL okSkip; struct stat statBuf; char buf[512]; char name[PROC_FILE_LEN]; proc = FindProcess(pid, tid); proc->isValid = FALSE; proc->isShown = FALSE; okSkip = ((pid != myPid) || !useSelf); if (okSkip && noSelf && (pid == myPid)) return; if (proc->isThread) sprintf(name, "%s/%ld/task/%ld/stat", PROCDIR, (long) pid, (long) tid); else sprintf(name, "%s/%ld/stat", PROCDIR, (long) pid); fd = open(name, O_RDONLY); if (fd < 0) { if (errno != ENOENT) perror(name); return; } if (fstat(fd, &statBuf) < 0) { (void) close(fd); return; } proc->deathTime = 0; if (okSkip) { if (myProcs && (statBuf.st_uid != myUid)) { (void) close(fd); return; } if (noRoot && (statBuf.st_uid == 0)) return; if ((userCount > 0)) { for (i = 0; i < userCount; i++) { if (statBuf.st_uid == userList[i]) break; } if (i == userCount) { (void) close(fd); return; } } if ((groupCount > 0)) { for (i = 0; i < groupCount; i++) { if (statBuf.st_gid == groupList[i]) break; } if (i == groupCount) { (void) close(fd); return; } } } proc->uid = statBuf.st_uid; proc->gid = statBuf.st_gid; /* * Read the process status into a buffer. */ cc = read(fd, buf, sizeof(buf)); if (cc < 0) { (void) close(fd); return; } (void) close(fd); if (cc == sizeof(buf)) { fprintf(stderr, "status buffer overflow"); return; } buf[cc] = '\0'; /* * The program name begins after a left parenthesis. * Break the status string into two at that point. */ begName = strchr(buf, BEGNAMECHAR); if (begName == NULL) { fprintf(stderr, "Cannot find start of program name\n"); return; } *begName++ = '\0'; /* * The program name ends after a right parenthesis. * But, look for the rightmost one in case the program name * itself contains a parenthesis! */ endName = strchr(begName, ENDNAMECHAR); if (endName == NULL) { fprintf(stderr, "Cannot find end of program name\n"); return; } while ((cp = strchr(endName + 1, ENDNAMECHAR)) != NULL) endName = begName + (cp - begName); MakePrintable(begName, endName - begName); *endName++ = '\0'; strncpy(proc->program, begName, MAX_PROGRAM_LEN); proc->program[MAX_PROGRAM_LEN] = '\0'; /* * Find the process state character, and then parse the numbers on * the rest of the line to collect the remaining state information. */ cp = endName; while (isBlank(*cp)) cp++; if ((*cp == '\0') || (*cp == '\n') || isDigit(*cp)) { fprintf(stderr, "Bad proc state character\n"); return; } proc->state = *cp++; proc->states[0] = proc->state; proc->states[1] = '\0'; proc->parentPid = GetDecimalNumber(&cp); proc->processGroup = GetDecimalNumber(&cp); proc->sessionId = GetDecimalNumber(&cp); proc->ttyDevice = GetDecimalNumber(&cp); proc->ttyProcessGroup = GetDecimalNumber(&cp); proc->flags = GetDecimalNumber(&cp); proc->minorFaults = GetDecimalNumber(&cp); proc->childMinorFaults = GetDecimalNumber(&cp); proc->majorFaults = GetDecimalNumber(&cp); proc->childMajorFaults = GetDecimalNumber(&cp); proc->userRunTime = GetDecimalNumber(&cp); proc->systemRunTime = GetDecimalNumber(&cp); proc->childUserRunTime = GetDecimalNumber(&cp); proc->childSystemRunTime = GetDecimalNumber(&cp); proc->priority = GetDecimalNumber(&cp); proc->nice = GetDecimalNumber(&cp); proc->threadCount = GetDecimalNumber(&cp); proc->itRealValue = GetDecimalNumber(&cp); proc->startTimeTicks = GetDecimalNumber(&cp); proc->virtualSize = GetDecimalNumber(&cp); proc->rss = GetDecimalNumber(&cp); proc->rssLimit = GetDecimalNumber(&cp); proc->startCode = GetDecimalNumber(&cp); proc->endCode = GetDecimalNumber(&cp); proc->startStack = GetDecimalNumber(&cp); proc->esp = GetDecimalNumber(&cp); proc->eip = GetDecimalNumber(&cp); proc->signal = GetDecimalNumber(&cp); proc->sigBlock = GetDecimalNumber(&cp); proc->sigIgnore = GetDecimalNumber(&cp); proc->sigCatch = GetDecimalNumber(&cp); proc->waitChan = GetDecimalNumber(&cp); proc->pagesSwapped = GetDecimalNumber(&cp); proc->childPagesSwapped = GetDecimalNumber(&cp); proc->exitSignal = GetDecimalNumber(&cp); proc->processor = GetDecimalNumber(&cp); proc->realTimePriority = GetDecimalNumber(&cp); proc->policy = GetDecimalNumber(&cp); /* * Convert the processes start time into clock time and age. * Get the number of ticks after we started when the specified * process started and convert that to elapsed seconds. * This can be positive or negative according to whether the * process started before or after us. */ ticksFromStart = proc->startTimeTicks - startUptime; if (ticksFromStart >= 0) secondsFromStart = ticksFromStart / ticksPerSecond; else secondsFromStart = -((-ticksFromStart) / ticksPerSecond); /* * Add the elapsed seconds to the clock time when we started * to get the clock time the process started. */ proc->startTimeClock = startTime + secondsFromStart; /* * If the process is newly seen then save its starting runtime. * This is used for the cpu percentage calculation. */ if (proc->isNew) proc->firstCpuTime = proc->userRunTime + proc->systemRunTime; /* * See if the process has changed state, and is therefore active. */ CheckActiveProcess(proc); /* * Get several pieces of extra data, but only if the process * has changed state, and only if these data are required. * However, get the extra data always if it is older than * the specified sync time. */ if (proc->isChanged || ((proc->lastSyncTime + syncTime) <= currentTime)) { proc->lastSyncTime = currentTime; GetProcessOpenFileCount(proc); GetProcessStdioDescriptors(proc); GetProcessCurrentDirectory(proc); GetProcessRootDirectory(proc); GetProcessExecInode(proc); GetProcessCommandLine(proc); GetProcessEnvironment(proc); GetProcessWaitSymbol(proc); } proc->liveCounter = liveCounter; proc->isValid = TRUE; if (IsShownProcess(proc)) proc->isShown = TRUE; /* * If we are showing or using threads and this process has more than * one thread then collect information on them. */ if (showThreads || useThreads) ScanThreads(proc); /* * Store the states string for threads if any. */ BuildStates(proc); } /* * Scan the list of threads for the indicated process. */ static void ScanThreads(PROC * proc) { DIR * dir; const struct dirent * dp; const char * cp; pthread_t tid; char name[PROC_FILE_LEN]; /* * If this is a thread or there aren't multiple threads then * do nothing. */ if (proc->isThread || (proc->threadCount <= 1)) return; /* * Read the list of thread ids from the task directory of the * main process and examine each one. */ sprintf(name, "%s/%ld/task", PROCDIR, (long) proc->pid); dir = opendir(name); if (dir == NULL) return; while ((dp = readdir(dir)) != NULL) { cp = dp->d_name; tid = 0; while (isDigit(*cp)) tid = tid * 10 + (*cp++ - '0'); if (*cp == '\0') ExamineProcessId(proc->pid, tid); } closedir(dir); } /* * Get the wait channel symbol name for the process. */ static void GetProcessWaitSymbol(PROC * proc) { int fd; int len; char name[PROC_FILE_LEN]; /* * If the wait channel is 0 or is not used then * set an empty symbol name. */ if (!useWaitChan || (proc->waitChan == 0)) { proc->waitChanSymbol[0] = '\0'; return; } /* * Open the wait channel file and read it into a buffer * including trying to read one extra character. */ sprintf(name, "%s/%ld/waitChan", PROCDIR, (long) proc->pid); fd = open(name, O_RDONLY); len = -1; if (fd >= 0) { len = read(fd, proc->waitChanSymbol, MAX_WCHAN_LEN + 1); (void) close(fd); } /* * If the symbol wasn't found then store a dash for the symbol. */ if (len < 0) { proc->waitChanSymbol[0] = '-'; proc->waitChanSymbol[1] = '\0'; return; } /* * If we read one more character than our limit, then we missed * some of the symbol name line, so flag that by replacing the * last character of the allowed length with a vertical bar. */ if (len > MAX_WCHAN_LEN) { len = MAX_WCHAN_LEN; proc->waitChanSymbol[MAX_WCHAN_LEN - 1] = '|'; } /* * Null terminate the wait symbol name and make it printable. */ proc->waitChanSymbol[len] = '\0'; MakePrintable(proc->waitChanSymbol, len); } /* * Get the command line for the specified process. * If there isn't one, then use the program name as the command line, * surrounded by parenthesis. If the command line is small, then it * fits within the proc structure, otherwise we have to malloc it. * Threads copy the command line string from the main process. */ static void GetProcessCommandLine(PROC * proc) { int fd; int len; char name[PROC_FILE_LEN]; char buffer[MAX_COMMAND_LEN + 2]; len = 0; if (!useCommand) { proc->hasCommand = FALSE; proc->commandLength = 0; proc->command[0] = '\0'; return; } proc->hasCommand = TRUE; /* * If we are a thread process and have an owner then copy the * command from the owner structure. */ if (IsCopyAllowed(proc)) { SetCommandLine(proc, proc->owner->command, proc->owner->commandLength); return; } /* * Open the command line file and read it into a large buffer, * including trying to read one extra character. */ sprintf(name, "%s/%ld/cmdline", PROCDIR, (long) proc->pid); fd = open(name, O_RDONLY); if (fd >= 0) { len = read(fd, buffer, MAX_COMMAND_LEN + 1); (void) close(fd); } /* * If we could not get the command line, or if there was none * there, then use the program name surrounded by parenthesis. * Remember that there is no real command line for user tests. */ if ((fd < 0) || (len <= 0)) { proc->hasCommand = FALSE; len = strlen(proc->program); buffer[0] = '('; memcpy(&buffer[1], proc->program, len); buffer[len + 1] = ')'; len += 2; } /* * If we read one more character than our limit, then we missed * some of the command line, so flag that by replacing the last * character of the allowed length with a vertical bar. */ if (len > MAX_COMMAND_LEN) { len = MAX_COMMAND_LEN; buffer[MAX_COMMAND_LEN - 1] = '|'; } /* * Null terminate the command line and make it printable. */ buffer[len] = '\0'; MakePrintable(buffer, len); /* * Store the command line into the structure. */ SetCommandLine(proc, buffer, len); } /* * Get the environment for the specified process. * This could be very large, so it is allocated dynamically and we * attempt to share strings among processes. Threads copy the * environment string value from the main process. */ static void GetProcessEnvironment(PROC * proc) { int fd; int len; char name[PROC_FILE_LEN]; char buffer[MAX_ENVIRON_LEN + 2]; if (!useEnvironment) return; /* * If we are a thread process and have an owner then copy the * environment from the owner structure. */ if (IsCopyAllowed(proc)) { SetSharedString(&proc->environment, &proc->environmentLength, proc->owner->environment, proc->owner->environmentLength); return; } /* * Open the environment file and read it into a large buffer, * including trying to read one extra character. */ sprintf(name, "%s/%ld/environ", PROCDIR, (long) proc->pid); fd = open(name, O_RDONLY); len = 0; if (fd >= 0) { len = read(fd, buffer, MAX_ENVIRON_LEN + 1); (void) close(fd); } /* * If we could not open the file, or if there was nothing there, * then free any old environment string and point to a null string. */ if ((fd < 0) || (len <= 0)) { FreeSharedString(proc->environment); proc->environment = emptyString; proc->environmentLength = 0; return; } /* * If we read one more character than our limit, then we missed * some of the environment, so flag that by replacing the last * character of the allowed length with a vertical bar. */ if (len > MAX_ENVIRON_LEN) { len = MAX_ENVIRON_LEN; buffer[MAX_ENVIRON_LEN - 1] = '|'; } /* * Null terminate the environment string and make it printable. */ buffer[len] = '\0'; MakePrintable(buffer, len); /* * Store the environment line into the structure. */ SetSharedString(&proc->environment, &proc->environmentLength, buffer, len); } /* * Get the number of open files for the process. * This is expensive and so is only gotten if the column is actually in use. * The permissions only allow this information to be gotten for the same user * id or if you are running as root. */ static void GetProcessOpenFileCount(PROC * proc) { DIR * dir; const struct dirent * dp; const char * cp; int count; char name[PROC_FILE_LEN]; proc->openFiles = -1; if (!useOpenFiles) return; /* * If we are a thread process and have an owner then copy the * open file count from the main process. */ if (IsCopyAllowed(proc)) { proc->openFiles = proc->owner->openFiles; return; } /* * Open the fd directory in the process status and count the * number of numeric file names. */ sprintf(name, "%s/%ld/fd", PROCDIR, (long) proc->pid); dir = opendir(name); if (dir == NULL) return; count = 0; while ((dp = readdir(dir)) != NULL) { cp = dp->d_name; while (isDigit(*cp)) cp++; if (*cp == '\0') count++; } closedir(dir); proc->openFiles = count; } /* * Get the current working directory of the specified process. * This is expensive and so is only gotten if the column is actually in use. * The permissions only allow this information to be gotten for the same user * id or if you are running as root. */ static void GetProcessCurrentDirectory(PROC * proc) { char name[PROC_FILE_LEN]; if (!useCurrentDirectory) return; /* * If we are a thread process and have an owner then copy the * current directory path from the main process. */ if (IsCopyAllowed(proc)) { SetSharedString(&proc->cwdPath, &proc->cwdPathLength, proc->owner->cwdPath, proc->owner->cwdPathLength); return; } sprintf(name, "%s/%ld/cwd", PROCDIR, (long) proc->pid); ReadLinkPath(name, &proc->cwdPath, &proc->cwdPathLength); } /* * Get the current root directory of the specified process. * This is expensive and so is only gotten if the column is actually in use. * The permissions only allow this information to be gotten for the same user * id or if you are running as root. */ static void GetProcessRootDirectory(PROC * proc) { char name[PROC_FILE_LEN]; if (!useRootDirectory) return; /* * If we are a thread process and have an owner then copy the * root directory path from the main process. */ if (IsCopyAllowed(proc)) { SetSharedString(&proc->rootPath, &proc->rootPathLength, proc->owner->rootPath, proc->owner->rootPathLength); return; } sprintf(name, "%s/%ld/root", PROCDIR, (long) proc->pid); ReadLinkPath(name, &proc->rootPath, &proc->rootPathLength); } /* * Get the device and inode of the executable file for specified process. * This is expensive and so is only gotten if the column is actually in use. * The permissions only allow this information to be gotten for the same user * id or if you are running as root. */ static void GetProcessExecInode(PROC * proc) { char name[PROC_FILE_LEN]; if (!useExecInode) return; /* * If we are a thread process and have an owner then copy the * executable path from the main process. */ if (IsCopyAllowed(proc)) { SetSharedString(&proc->execPath, &proc->execPathLength, proc->owner->execPath, proc->owner->execPathLength); return; } sprintf(name, "%s/%ld/exe", PROCDIR, (long) proc->pid); ReadLinkPath(name, &proc->execPath, &proc->execPathLength); } /* * Get information about processes three standard file descriptors. * This is expensive and so is only gotten if the columns are actually in use. * The permissions only allow this information to be gotten for the same user * id or if you are running as root. */ static void GetProcessStdioDescriptors(PROC * proc) { int fd; int dummy; char name[PROC_FILE_LEN]; for (fd = 0; fd <= 2; fd++) { if (!useStdioTable[fd]) continue; /* * If we are a thread process and have an owner then copy the * stdio path from the main process. */ if (IsCopyAllowed(proc)) { SetSharedString(&proc->stdioPaths[fd], &dummy, proc->owner->stdioPaths[fd], strlen(proc->owner->stdioPaths[fd])); continue; } sprintf(name, "%s/%ld/fd/%d", PROCDIR, (long) proc->pid, fd); ReadLinkPath(name, &proc->stdioPaths[fd], &dummy); } } /* * Get the destination path and length for the specified symbolic link. * The path is returned into the indicated variables, which are updated. * The returned string is allocated within a shared pool of strings and * so can only be freed by calling the appropriate routine. Returns TRUE * if the information was able to be obtained. */ static BOOL ReadLinkPath(const char * name, char ** retpath, int * retpathlength) { char * newPath; char * oldPath; int len; int oldLength; char buffer[MAX_PATH_LEN]; /* * Save the values for the old path. */ oldPath = *retpath; oldLength = *retpathlength; /* * Read the value of the symbolic link. */ len = readlink(name, buffer, sizeof(buffer)); if ((len <= 0) || (len == sizeof(buffer))) { FreeSharedString(oldPath); *retpath = emptyString; *retpathlength = 0; return FALSE; } buffer[len] = '\0'; /* * If the path is the same as the existing one then do nothing. */ if ((len == oldLength) && (strcmp(oldPath, buffer) == 0)) return TRUE; /* * The value has changed. Delete the old string. */ FreeSharedString(oldPath); *retpath = emptyString; *retpathlength = 0; /* * Allocate a new shared string to store the new value. */ newPath = AllocateSharedString(buffer, len); if (newPath == NULL) return FALSE; /* * Return the new string value. */ *retpath = newPath; *retpathlength = len; return TRUE; } /* * Return whether or now we are a thread process that is allowed to * copy data from the owning main process. */ static BOOL IsCopyAllowed(const PROC * proc) { if (noCopy) return FALSE; return (proc->isThread && (proc->owner != 0)); } /* * Pick the best state character of the two states which most indicates * what the multiple threads of a process are doing. */ int PickBestState(int state1, int state2) { if ((state1 == 'R') || (state2 == 'R')) return 'R'; if ((state1 == 'D') || (state2 == 'D')) return 'D'; if ((state1 == 'S') || (state2 == 'S')) return 'S'; return state1; } /* END CODE */ ips-4.0/ttydisplay.c0000644000175000017500000002552711360543642013467 0ustar dbelldbell/* * Configurable ps-like program. * Dumb terminal display device which just sends text to stdout. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include #include #include #include "ips.h" /* * The ANSI escape sequence for clearing the terminal screen. */ #define CLEAR_SCREEN "\033[H\033[2J" /* * A structure holding a color name and its numeric foreground * and background codes in the ANSI escape sequence. */ typedef struct { const char * name; short foreground; short background; } COLOR_INFO; /* * The table of color names and their numeric values for the * basic ANSI terminal escape sequence. */ static const COLOR_INFO colorInfoTable[] = { {"black", 30, 40}, {"red", 31, 41}, {"green", 32, 42}, {"yellow", 33, 43}, {"blue", 34, 44}, {"magenta", 35, 45}, {"cyan", 36, 46}, {"white", 37, 47}, {NULL, 0, 0} }; /* * Maximum color indices for the basic terminal colors and the * extended terminal colors. */ #define MAX_BASIC_INDEX 7 #define MAX_EXTENDED_INDEX 255 static BOOL TtyOpen(DISPLAY *); static BOOL TtyDefineColor(DISPLAY *, int, const char *, const char *, int); static void TtyCreateWindow(DISPLAY *); static void TtyClose(DISPLAY *); static void TtySetColor(DISPLAY *, int); static void TtyRefresh(DISPLAY *); static void TtyBeginPage(DISPLAY *); static void TtyPutChar(DISPLAY *, int); static void TtyPutString(DISPLAY *, const char *); static void TtyPutBuffer(DISPLAY *, const char *, int); static void TtyEndPage(DISPLAY *); static BOOL TtyEventWait(DISPLAY *, int); static BOOL TtyInputReady(DISPLAY *); static int TtyReadChar(DISPLAY *); static void TtyRingBell(DISPLAY *); static int TtyGetRows(DISPLAY *); static int TtyGetCols(DISPLAY *); static BOOL TtyDoesScroll(DISPLAY *); static DISPLAY ttyDisplay = { TtyOpen, TtyDefineColor, TtyCreateWindow, TtyClose, TtySetColor, TtyRefresh, TtyBeginPage, TtyPutChar, TtyPutString, TtyPutBuffer, TtyEndPage, TtyEventWait, TtyInputReady, TtyReadChar, TtyRingBell, TtyGetRows, TtyGetCols, TtyDoesScroll }; /* * The table of the indexes into the color table for the * foreground and background colors (indexed by the color id). * The value of -1 means to use the default color. */ static int foregroundColorTable[MAX_COLORS]; static int backgroundColorTable[MAX_COLORS]; static int flagsColorTable[MAX_COLORS]; /* * The current color in effect. */ static int currentColorId; static int currentForegroundIndex; static int currentBackgroundIndex; static int currentColorFlags; /* * Terminal size data. */ static BOOL shown; /* whether output has been shown */ static BOOL sizeChanged; /* terminal size has changed */ static int rows = 99999; /* number of rows in terminal */ static int cols = 80; /* number of columns in terminal */ static void HandleResize(int arg); static void GetTerminalSize(void); static int FindColorNameIndex(const char * name); /* * Return the instance of the terminal display device. */ DISPLAY * GetTtyDisplay(void) { return &ttyDisplay; } /* * Open the display device. */ static BOOL TtyOpen(DISPLAY * display) { int colorId; /* * If output is to a terminal, then get its current size and * set up to handle resize signals. */ if (isatty(STDOUT_FILENO)) { signal(SIGWINCH, HandleResize); GetTerminalSize(); } /* * Set buffering for block mode for efficiency. */ setvbuf(stdout, NULL, _IOFBF, BUFSIZ); shown = FALSE; /* * Initialize the color table. */ for (colorId = 0; colorId < MAX_COLORS; colorId++) { foregroundColorTable[colorId] = -1; backgroundColorTable[colorId] = -1; flagsColorTable[colorId] = 0; } currentColorId = DEFAULT_COLOR_ID; currentForegroundIndex = -1; currentBackgroundIndex = -1; currentColorFlags = 0; return TRUE; } /* * Create the output window. * This is a no-op for us. */ static void TtyCreateWindow(DISPLAY * display) { } /* * Close the display device. */ static void TtyClose(DISPLAY * display) { fflush(stdout); } /* * Define a color for the specified color id. */ static BOOL TtyDefineColor(DISPLAY * display, int colorId, const char * foreground, const char * background, int colorFlags) { int foregroundIndex = -1; int backgroundIndex = -1; /* * Validate the color id. */ if ((colorId < 0) || (colorId >= MAX_COLORS)) return FALSE; /* * Validate that the flags are only the ones we know. */ if (colorFlags & ~(COLOR_FLAG_UNDERLINE|COLOR_FLAG_BOLD)) return FALSE; /* * If the foreground color name is non-empty then parse * it to get the index. */ if (*foreground) { foregroundIndex = FindColorNameIndex(foreground); if (foregroundIndex < 0) return FALSE; } /* * If the background color name is non-empty then parse * it to get the index. */ if (*background) { backgroundIndex = FindColorNameIndex(background); if (backgroundIndex < 0) return FALSE; } /* * If a high color index is used with a bold attribute * then return an error. */ if (((foregroundIndex > MAX_BASIC_INDEX) || (backgroundIndex > MAX_BASIC_INDEX)) && (colorFlags & COLOR_FLAG_BOLD)) { return FALSE; } /* * Set the foreground and background color indexes and the * flags for this color id. */ foregroundColorTable[colorId] = foregroundIndex; backgroundColorTable[colorId] = backgroundIndex; flagsColorTable[colorId] = colorFlags; return TRUE; } /* * Find the color name in the table of colors and return the * index value of the color, or else parse a numeric color index. * This is a value from 0 to 255. Returns -1 if the color name * is not known and is not a valid number. */ static int FindColorNameIndex(const char * name) { int index; if (*name == '\0') return -1; /* * Look for a real name. */ for (index = 0; colorInfoTable[index].name; index++) { if (strcmp(name, colorInfoTable[index].name) == 0) return index; } /* * The name wasn't known. * Try parsing the string as a number. */ index = 0; while ((*name >= '0') && (*name <= '9')) index = index * 10 + (*name++ - '0'); /* * If the name wasn't numeric or the index is out of range then fail. */ if (*name || (index < 0) || (index > MAX_EXTENDED_INDEX)) return -1; return index; } /* * Set the color for further output. */ static void TtySetColor(DISPLAY * display, int colorId) { int foregroundIndex; int backgroundIndex; int colorFlags; char * cp; char * endIntroCp; char buffer[64]; if ((colorId < 0) || (colorId >= MAX_COLORS)) return; if (colorId == currentColorId) return; currentColorId = colorId; foregroundIndex = foregroundColorTable[colorId]; backgroundIndex = backgroundColorTable[colorId]; colorFlags = flagsColorTable[colorId]; if ((foregroundIndex == currentForegroundIndex) && (backgroundIndex == currentBackgroundIndex) && (colorFlags == currentColorFlags)) { return; } currentForegroundIndex = foregroundIndex; currentBackgroundIndex = backgroundIndex; currentColorFlags = colorFlags; /* * Store the beginning of the escape sequence. */ cp = buffer; *cp++ = '\033'; *cp++ = '['; endIntroCp = cp; /* * Reset the color if the defaults are used. */ if ((foregroundIndex < 0) || (backgroundIndex < 0)) *cp++ = '0'; /* * Set the attributes. */ if (currentColorFlags & COLOR_FLAG_UNDERLINE) { if (cp != endIntroCp) *cp++ = ';'; *cp++ = '4'; } if (currentColorFlags & COLOR_FLAG_BOLD) { if (cp != endIntroCp) *cp++ = ';'; *cp++ = '1'; } /* * Set the foreground color if needed. */ if (foregroundIndex >= 0) { if (cp != endIntroCp) *cp++ = ';'; /* * The string depends on whether the index is in the basic * or the extended range. */ if (foregroundIndex > MAX_BASIC_INDEX) sprintf(cp, "38;5;%d", foregroundIndex); else sprintf(cp, "%d", colorInfoTable[foregroundIndex].foreground); cp += strlen(cp); } /* * Set the background color if needed. */ if (backgroundIndex >= 0) { if (cp != endIntroCp) *cp++ = ';'; /* * The string depends on whether the index is in the basic * or the extended range. */ if (backgroundIndex > MAX_BASIC_INDEX) sprintf(cp, "48;5;%d", backgroundIndex); else sprintf(cp, "%d", colorInfoTable[backgroundIndex].background); cp += strlen(cp); } /* * Terminate the escape sequence and send it. */ *cp++ = 'm'; *cp = '\0'; fputs(buffer, stdout); } static void TtyRefresh(DISPLAY * display) { } static void TtyBeginPage(DISPLAY * display) { if (clearScreen) fputs(CLEAR_SCREEN, stdout); else if (shown) putchar('\n'); } static void TtyPutChar(DISPLAY * display, int ch) { /* * Before writing any newline clear any current coloring. * This is necessary to prevent display bugs on the next line. */ if (ch == '\n') TtySetColor(display, DEFAULT_COLOR_ID); putchar(ch); shown = TRUE; } static void TtyPutString(DISPLAY * display, const char * str) { fputs(str, stdout); } static void TtyPutBuffer(DISPLAY * display, const char * str, int len) { while (len-- > 0) { putchar(*str); str++; } } static void TtyEndPage(DISPLAY * display) { fflush(stdout); } /* * Handle events for the display while waiting for the specified amount * of time. There are no input events to handle except for a window * size change, which will cause a signal to terminate the select. */ static BOOL TtyEventWait(DISPLAY * display, int milliSeconds) { struct timeval timeOut; if (milliSeconds <= 0) return FALSE; timeOut.tv_sec = milliSeconds / 1000; timeOut.tv_usec = (milliSeconds % 1000) * 1000; (void) select(0, NULL, NULL, NULL, &timeOut); return FALSE; } /* * See if input is ready from the terminal. * This display device never returns any input. */ static BOOL TtyInputReady(DISPLAY * display) { return FALSE; } /* * Read the next character from the terminal. * This display device always returns EOF. */ static int TtyReadChar(DISPLAY * display) { return EOF; } static void TtyRingBell(DISPLAY * display) { fflush(stdout); fputc('\007', stderr); fflush(stderr); } static int TtyGetRows(DISPLAY * display) { if (sizeChanged) GetTerminalSize(); return rows; } static int TtyGetCols(DISPLAY * display) { if (sizeChanged) GetTerminalSize(); return cols; } static BOOL TtyDoesScroll(DISPLAY * display) { return TRUE; } /* * Signal handler for resizing of window. * This only sets a flag so that we can handle the resize later. * (Changing the size at unpredictable times would be dangerous.) */ static void HandleResize(int arg) { sizeChanged = TRUE; signal(SIGWINCH, HandleResize); } /* * Routine called to get the new terminal size from the kernel. */ static void GetTerminalSize(void) { struct winsize size; sizeChanged = FALSE; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) return; rows = size.ws_row; cols = size.ws_col; if (rows <= 0) rows = 1; if (cols <= 0) cols = 1; } /* END CODE */ ips-4.0/cond.c0000644000175000017500000011431011360557153012173 0ustar dbelldbell/* * Configurable ps-like program. * Conditional display routines. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include "ips.h" #include "expr.h" /* * Function ids for expressions. */ #define FUNC_MATCH FUNCTION_BUILD(1, 2) #define FUNC_STRLEN FUNCTION_BUILD(2, 1) #define FUNC_MIN FUNCTION_BUILD(3, 2) #define FUNC_MAX FUNCTION_BUILD(4, 2) #define FUNC_ABS FUNCTION_BUILD(5, 1) #define FUNC_MY FUNCTION_BUILD(6, 1) #define FUNC_CMP FUNCTION_BUILD(7, 2) #define FUNC_STR FUNCTION_BUILD(8, 1) /* * A conservative buffer size which is guaranteed to hold the string * representation of a long integer value and its terminating null. */ #define LONG_STR_LENGTH (sizeof(long) * 3 + 2) /* * Static variables. */ static BOOL condFlag; /* TRUE if have condition tree */ static TREE condTree; /* condition tree */ static NODE badNode; /* special node for errors */ static VALUE badValue; /* special value for errors */ /* * Local procedures. */ static NODE * ParseAlternation(TREE * tree); static NODE * ParseOrOr(TREE * tree); static NODE * ParseAndAnd(TREE * tree); static NODE * ParseRelation(TREE * tree); static NODE * ParseSum(TREE * tree); static NODE * ParseProduct(TREE * tree); static NODE * ParseOr(TREE * tree); static NODE * ParseAnd(TREE * tree); static NODE * ParseShift(TREE * tree); static NODE * ParseUnary(TREE * tree); static NODE * ParseTerm(TREE * tree); static NODE * ParseSymbol(TREE * tree, char * str); static NODE * ParseMacro(TREE * tree, char * str); static NODE * ParseFunction(TREE * tree, char * str); static NODE * NewNode(TREE * tree, OP op, NODE * left, NODE * right); static NODE * BadNode(TREE * tree, char * message); static TOKEN ParseNewToken(TREE * tree); static TOKEN ParseToken(TREE * tree); static TOKEN ParseNumber(TREE * tree); static TOKEN BadToken(TREE * tree, char * msg); static VALUE EvaluateComparison(TREE * tree, const NODE * node); static VALUE EvaluateFunction(TREE * tree, const NODE * node); static BOOL ConvertType(VALUE val, int type, VALUE * retval); static VALUE FunctionMatch(TREE * tree, VALUE arg1, VALUE arg2); static VALUE FunctionMin(TREE * tree, VALUE arg1, VALUE arg2); static VALUE FunctionMax(TREE * tree, VALUE arg1, VALUE arg2); static VALUE FunctionStrlen(TREE * tree, VALUE arg); static VALUE FunctionAbs(TREE * tree, VALUE arg); static VALUE FunctionStr(TREE * tree, VALUE arg); static VALUE FunctionCmp(TREE * tree, VALUE arg1, VALUE arg2); static VALUE FunctionMy(TREE * tree, const NODE *node); /* * Check if the specified process should be shown based on the conditional * criteria. Returns TRUE if so. This works by possibly evaluating an * expression tree. */ BOOL IsShownProcess(const PROC * proc) { TREE * tree; int i; VALUE value; /* * If this is a thread and threads are not being shown then * we don't want it. */ if (proc->isThread && !showThreads) return FALSE; /* * If we are showing threads and this is a main process and * there is more than 1 thread then we don't want it. */ if (showThreads && !proc->isThread && (proc->threadCount > 1)) return FALSE; /* * If we are showing threads and this is a thread but there is * only one thread then we don't want it (since we will show * the main process in that case). */ if (showThreads && proc->isThread && (proc->threadCount <= 1)) return FALSE; if (myProcs && (proc->uid != myUid)) return FALSE; if (noSelf && (proc->pid == myPid)) return FALSE; if (pidCount) { for (i = 0; i < pidCount; i++) { if (pidList[i] == proc->pid) break; } if (i == pidCount) return FALSE; } if (userCount) { for (i = 0; i < userCount; i++) { if (userList[i] == proc->uid) break; } if (i == userCount) return FALSE; } if (groupCount) { for (i = 0; i < groupCount; i++) { if (groupList[i] == proc->gid) break; } if (i == groupCount) return FALSE; } if (programCount) { for (i = 0; i < programCount; i++) { if (strcmp(programList[i], proc->program) == 0) break; } if (i == programCount) return FALSE; } if (activeOnly && !GetIsActive(proc)) return FALSE; if (!condFlag) return TRUE; tree = &condTree; tree->proc = proc; value = EvaluateNode(tree, tree->root); if ((value.type == VALUE_NUMBER) || (value.type == VALUE_BOOLEAN)) return (value.intVal != 0); if (value.type == VALUE_STRING) return (*value.strVal != '\0'); return FALSE; } /* * Get the use flags for the current condition expression. * This just means find all references to columns, and OR together * the use flags for each column. */ USEFLAG GetConditionUseFlags(void) { if (!condFlag) return USE_NONE; return GetNodeUseFlags(condTree.root); } /* * Recursive routine to search down all nodes to collect use flags * from those nodes which reference column values. */ USEFLAG GetNodeUseFlags(const NODE * node) { int i; USEFLAG flags; flags = USE_NONE; /* * If this is a column opcode, then get the use flags for the column. */ switch (node->op) { case OP_COLUMN_BASE: case OP_COLUMN_SHOW: case OP_COLUMN_TEST: flags |= node->column->useFlag; break; case OP_FUNCTION: if (node->intVal == FUNC_MY) flags |= USE_SELF; default: break; } /* * Now look at all the child nodes, and recurse on them. */ for (i = 0; i < CHILDS; i++) { if (node->child[i]) flags |= GetNodeUseFlags(node->child[i]); } /* * Return the final use flags. */ return flags; } /* * Recursive routine to calculate the result of an expression tree * concerning the specified process starting with the specified node. */ VALUE EvaluateNode(TREE * tree, const NODE * node) { NODE * left; NODE * right; int leftType; int rightType; int valType; VALUE value; VALUE leftVal; VALUE rightVal; const char * cp; if (node == NULL) return badValue; left = node->child[0]; right = node->child[1]; leftType = VALUE_NONE; rightType = VALUE_NONE; valType = VALUE_NONE; /* * First specify the required types of subTree expressions * and whether we need to evaluate them. */ switch (node->op) { case OP_NOT: leftType = VALUE_BOOLEAN; valType = VALUE_BOOLEAN; break; case OP_NEGATE: case OP_COMPLEMENT: leftType = VALUE_NUMBER; valType = VALUE_NUMBER; break; case OP_ADD: case OP_SUBTRACT: case OP_MULTIPLY: case OP_DIVIDE: case OP_MODULO: case OP_AND: case OP_XOR: leftType = VALUE_NUMBER; rightType = VALUE_NUMBER; valType = VALUE_NUMBER; break; case OP_OROR: case OP_ANDAND: leftType = VALUE_BOOLEAN; rightType = VALUE_BOOLEAN; valType = VALUE_BOOLEAN; break; case OP_ALTERNATION: leftType = VALUE_BOOLEAN; break; case OP_STRING: value.type = VALUE_STRING; value.strVal = node->strVal; return value; case OP_NUMBER: value.type = VALUE_NUMBER; value.intVal = node->intVal; return value; case OP_COLUMN_TEST: value.type = VALUE_BOOLEAN; value.intVal = node->column->testFunc(tree->proc); return value; case OP_COLUMN_BASE: node->column->evalFunc(tree->proc, &value); return value; case OP_COLUMN_SHOW: cp = node->column->showFunc(tree->proc); /* * This is a bit of a hack. * Copy the string unless it points to the program * name, command line, or environment, in which case * we know it is not temporary and so we don't need * to copy it. This also helps avoid running out of * string space. */ if ((cp != tree->proc->command) && (cp != tree->proc->environment) && (cp != tree->proc->program)) { cp = CopyTempString(cp); } /* * Store the string value for the column. * If this was copied, then it will be freed when * we end the current display of processes. */ value.type = VALUE_STRING; value.strVal = cp; return value; case OP_EQUAL: case OP_NOTEQUAL: case OP_LESS: case OP_LESSEQUAL: case OP_GREATER: case OP_GREATEREQUAL: return EvaluateComparison(tree, node); case OP_FUNCTION: return EvaluateFunction(tree, node); default: return badValue; } /* * Initialize the values to make the compiler happy. */ leftVal.type = VALUE_NONE; leftVal.intVal = 0; leftVal.strVal = 0; leftVal.column = 0; rightVal.type = VALUE_NONE; rightVal.intVal = 0; rightVal.strVal = 0; rightVal.column = 0; /* * Evaluate the left and right subtrees if requested and then * convert their value into the requested type. */ if (leftType != VALUE_NONE) { leftVal = EvaluateNode(tree, left); if (!ConvertType(leftVal, leftType, &leftVal)) return badValue; } if (rightType != VALUE_NONE) { rightVal = EvaluateNode(tree, right); if (!ConvertType(rightVal, rightType, &rightVal)) return badValue; } value.type = valType; /* * Now operate on the converted values. * For most of these, their input arguments have already validated. */ switch (node->op) { case OP_AND: value.intVal = leftVal.intVal & rightVal.intVal; break; case OP_OR: value.intVal = leftVal.intVal | rightVal.intVal; break; case OP_XOR: value.intVal = leftVal.intVal ^ rightVal.intVal; break; case OP_COMPLEMENT: value.intVal = ~leftVal.intVal; break; case OP_NEGATE: value.intVal = -leftVal.intVal; break; case OP_ADD: value.intVal = leftVal.intVal + rightVal.intVal; break; case OP_SUBTRACT: value.intVal = leftVal.intVal - rightVal.intVal; break; case OP_MULTIPLY: value.intVal = leftVal.intVal * rightVal.intVal; break; case OP_DIVIDE: if (rightVal.intVal == 0) return badValue; value.intVal = leftVal.intVal / rightVal.intVal; break; case OP_MODULO: if (rightVal.intVal == 0) return badValue; value.intVal = leftVal.intVal % rightVal.intVal; break; case OP_NOT: value.intVal = !leftVal.intVal; break; case OP_ANDAND: value.intVal = leftVal.intVal && rightVal.intVal; break; case OP_OROR: value.intVal = leftVal.intVal || rightVal.intVal; break; case OP_ALTERNATION: if (leftVal.intVal != 0) value = EvaluateNode(tree, right); else value = EvaluateNode(tree, node->child[2]); break; default: return badValue; } return value; } /* * Evaluate a function call. */ static VALUE EvaluateFunction(TREE * tree, const NODE * node) { VALUE value; VALUE arg1; VALUE arg2; if (node->op != OP_FUNCTION) return badValue; /* * Check for the magic "my" function, which acts specially. * We cannot evaluate its argument before calling it since * its argument is special. */ if (node->intVal == FUNC_MY) return FunctionMy(tree, node->child[0]); /* * Evaluate the number of arguments indicated by the function id. */ switch (FUNCTION_ARGS(node->intVal)) { case 2: arg2 = EvaluateNode(tree, node->child[1]); case 1: arg1 = EvaluateNode(tree, node->child[0]); case 0: break; default: return badValue; } /* * Call the proper function indicated by the function id. */ switch (node->intVal) { case FUNC_MATCH: return FunctionMatch(tree, arg1, arg2); case FUNC_MIN: return FunctionMin(tree, arg1, arg2); case FUNC_MAX: return FunctionMax(tree, arg1, arg2); case FUNC_STRLEN: return FunctionStrlen(tree, arg1); case FUNC_ABS: return FunctionAbs(tree, arg1); case FUNC_STR: return FunctionStr(tree, arg1); case FUNC_CMP: return FunctionCmp(tree, arg1, arg2); default: return badValue; } return value; } /* * Function to do a pattern match between two values interpreted as strings. * Returns the value of the comparison. */ static VALUE FunctionMatch(TREE * tree, VALUE arg1, VALUE arg2) { VALUE value; char buf1[LONG_STR_LENGTH]; char buf2[LONG_STR_LENGTH]; /* * Convert the input arguments to strings if they were numbers. */ if ((arg1.type == VALUE_BOOLEAN) || (arg1.type == VALUE_NUMBER)) { sprintf(buf1, "%ld", arg1.intVal); arg1.type = VALUE_STRING; arg1.strVal = buf1; } if ((arg2.type == VALUE_BOOLEAN) || (arg2.type == VALUE_NUMBER)) { sprintf(buf2, "%ld", arg2.intVal); arg2.type = VALUE_STRING; arg2.strVal = buf2; } /* * If they are not both strings now, then return a bad value. */ if ((arg1.type != VALUE_STRING) || (arg2.type != VALUE_STRING)) return badValue; /* * They are both strings now so do the pattern match. */ value.type = VALUE_BOOLEAN; value.intVal = PatternMatch(arg1.strVal, arg2.strVal); return value; } /* * Function to find the minimum of two numbers. * This is not defined for strings. */ static VALUE FunctionMin(TREE * tree, VALUE arg1, VALUE arg2) { VALUE value; /* * Make sure the arguments are numbers. */ if (((arg1.type != VALUE_BOOLEAN) && (arg1.type != VALUE_NUMBER)) || ((arg2.type != VALUE_BOOLEAN) && (arg2.type != VALUE_NUMBER))) { return badValue; } value.type = VALUE_NUMBER; value.intVal = (arg1.intVal < arg2.intVal) ? arg1.intVal : arg2.intVal; return value; } /* * Function to find the maximum of two numbers. * This is not defined for strings. */ static VALUE FunctionMax(TREE * tree, VALUE arg1, VALUE arg2) { VALUE value; /* * Make sure the arguments are numbers. */ if (((arg1.type != VALUE_BOOLEAN) && (arg1.type != VALUE_NUMBER)) || ((arg2.type != VALUE_BOOLEAN) && (arg2.type != VALUE_NUMBER))) { return badValue; } value.type = VALUE_NUMBER; value.intVal = (arg1.intVal > arg2.intVal) ? arg1.intVal : arg2.intVal; return value; } /* * Function to find the absolute value of a number. * This is not defined for strings. */ static VALUE FunctionAbs(TREE * tree, VALUE arg) { VALUE value; if ((arg.type != VALUE_BOOLEAN) && (arg.type != VALUE_NUMBER)) return badValue; value.type = VALUE_NUMBER; value.intVal = (arg.intVal < 0) ? -arg.intVal : arg.intVal; return value; } /* * Function to convert a value to a string. */ static VALUE FunctionStr(TREE * tree, VALUE arg) { VALUE value; char buf[LONG_STR_LENGTH]; if (arg.type == VALUE_STRING) return arg; if ((arg.type != VALUE_BOOLEAN) && (arg.type != VALUE_NUMBER)) return badValue; value.type = VALUE_STRING; /* * Check a few common values specially, and if it is not one * of those, then allocate a temporary string for the value. */ if (arg.intVal == 0) value.strVal = "0"; else if (arg.intVal == 1) value.strVal = "1"; else if (arg.intVal == -1) value.strVal = "-1"; else { sprintf(buf, "%ld", arg.intVal); value.strVal = CopyTempString(buf); } return value; } /* * Function to return the length of a string. */ static VALUE FunctionStrlen(TREE * tree, VALUE arg) { VALUE value; char buf[LONG_STR_LENGTH]; /* * Convert the input argument a string if it was a number. */ if ((arg.type == VALUE_BOOLEAN) || (arg.type == VALUE_NUMBER)) { sprintf(buf, "%ld", arg.intVal); arg.type = VALUE_STRING; arg.strVal = buf; } /* * If it is not a string now, then return a bad value. */ if ((arg.type != VALUE_STRING)) return badValue; /* * Return the length of the string. */ value.type = VALUE_NUMBER; value.intVal = strlen(arg.strVal); return value; } /* * Function to compare two values returning -1, 0, or 1. * The values can be numbers or strings. */ static VALUE FunctionCmp(TREE * tree, VALUE arg1, VALUE arg2) { VALUE value; char buf1[LONG_STR_LENGTH]; char buf2[LONG_STR_LENGTH]; /* * If both arguments are numbers then compare them that way. */ if (((arg1.type == VALUE_BOOLEAN) || (arg1.type == VALUE_NUMBER)) && ((arg2.type == VALUE_BOOLEAN) || (arg2.type == VALUE_NUMBER))) { value.type = VALUE_NUMBER; value.intVal = 0; if (arg1.intVal < arg2.intVal) value.intVal = -1; if (arg1.intVal > arg2.intVal) value.intVal = 1; return value; } /* * Convert the input argument to strings if they were numbers. */ if ((arg1.type == VALUE_BOOLEAN) || (arg1.type == VALUE_NUMBER)) { sprintf(buf1, "%ld", arg1.intVal); arg1.type = VALUE_STRING; arg1.strVal = buf1; } if ((arg2.type == VALUE_BOOLEAN) || (arg2.type == VALUE_NUMBER)) { sprintf(buf2, "%ld", arg2.intVal); arg2.type = VALUE_STRING; arg2.strVal = buf2; } /* * Make sure the arguments are strings now. */ if ((arg1.type != VALUE_STRING) || (arg2.type != VALUE_STRING)) return badValue; /* * OK, now do a string compare and normalize the result. */ value.type = VALUE_NUMBER; value.intVal = strcmp(arg1.strVal, arg2.strVal); if (value.intVal < 0) value.intVal = -1; if (value.intVal > 0) value.intVal = 1; return value; } /* * Function to return information about MY own process, * instead of the process currently being examined. * This function is special and doesn't work like the other functions. */ static VALUE FunctionMy(TREE * tree, const NODE * node) { const PROC * savedProc; PROC * proc; int savedUid; int savedGid; VALUE value; /* * Save the process pointer of the current process being used for * evaluation, and replace it with a pointer to our own process. * Save our effective user and group ids and replace them with our * real ones. */ proc = FindProcess(myPid, NO_THREAD_ID); savedUid = proc->uid; savedGid = proc->gid; proc->uid = myUid; proc->gid = myGid; savedProc = tree->proc; tree->proc = proc; /* * Evaluate the node (which should be a column name) using * our own process information instead of the current one. */ value = EvaluateNode(tree, node); /* * Restore our effective user and group ids, then replace the * process pointer in the tree to what it should be. */ proc->uid = savedUid; proc->gid = savedGid; tree->proc = savedProc; return value; } /* * Perform a comparison operation between two arbitrary values. * Sets the resulting value to either a boolen value, or else * the bad value if the comparison is illegal. */ static VALUE EvaluateComparison(TREE * tree, const NODE * node) { int compare; VALUE leftVal; VALUE rightVal; VALUE value; leftVal = EvaluateNode(tree, node->child[0]); if (leftVal.type == VALUE_BAD) return badValue; rightVal = EvaluateNode(tree, node->child[1]); if (rightVal.type == VALUE_BAD) return badValue; if (!CompareValues(leftVal, rightVal, &compare)) return badValue; /* * Now set the result according to the comparison required * and the result of the comparison. */ value.type = VALUE_BOOLEAN; switch (node->op) { case OP_EQUAL: value.intVal = (compare == 0); break; case OP_NOTEQUAL: value.intVal = (compare != 0); break; case OP_LESS: value.intVal = (compare < 0); break; case OP_LESSEQUAL: value.intVal = (compare <= 0); break; case OP_GREATER: value.intVal = (compare > 0); break; case OP_GREATEREQUAL: value.intVal = (compare >= 0); break; default: return badValue; } return value; } /* * Compare two values and determine their relative sizes. * Indirectly eturns -1 if the first value is less, 1 if the first value * is more, and 0 if they are the same. * Returns TRUE if the comparison is valid. */ BOOL CompareValues(const VALUE leftVal, const VALUE rightVal, int * result) { int compare = 0; switch (TWOVAL(leftVal.type, rightVal.type)) { case TWOVAL(VALUE_NUMBER, VALUE_NUMBER): case TWOVAL(VALUE_NUMBER, VALUE_BOOLEAN): case TWOVAL(VALUE_BOOLEAN, VALUE_NUMBER): case TWOVAL(VALUE_BOOLEAN, VALUE_BOOLEAN): if (leftVal.intVal < rightVal.intVal) compare = -1; if (leftVal.intVal > rightVal.intVal) compare = 1; break; case TWOVAL(VALUE_STRING, VALUE_STRING): compare = strcmp(leftVal.strVal, rightVal.strVal); break;; default: return FALSE; } *result = compare; return TRUE; } /* * Try to convert the specified input value into the specified type * value and return that value. Returns TRUE if successful. */ static BOOL ConvertType(VALUE val, int type, VALUE * retval) { const char * cp; long intVal; BOOL isNeg; /* * If the type is already correct, then just return the same value. */ if (val.type == type) { *retval = val; return TRUE; } /* * We actually need a conversion. * Switch on the pair of "from" and "to" types. */ switch (TWOVAL(val.type, type)) { case TWOVAL(VALUE_STRING, VALUE_BOOLEAN): retval->type = VALUE_BOOLEAN; retval->intVal = (val.strVal[0] != '\0'); break; case TWOVAL(VALUE_NUMBER, VALUE_BOOLEAN): retval->type = VALUE_BOOLEAN; retval->intVal = (val.intVal != 0); break; case TWOVAL(VALUE_BOOLEAN, VALUE_NUMBER): retval->type = VALUE_NUMBER; retval->intVal = val.intVal; break; case TWOVAL(VALUE_STRING, VALUE_NUMBER): cp = val.strVal; intVal = 0; isNeg = FALSE; while (isBlank(*cp)) cp++; if (*cp == '-') { cp++; isNeg = TRUE; } if (!isDigit(*cp)) { *retval = badValue; return FALSE; } while (isDigit(*cp)) intVal = intVal * 10 + *cp++ - '0'; while (isBlank(*cp)) cp++; if (*cp) { *retval = badValue; return FALSE; } if (isNeg) intVal = -intVal; retval->intVal = intVal; retval->type = VALUE_NUMBER; break; default: *retval = badValue; return FALSE; } return TRUE; } /* * Clear any condition that may be set. * This loses memory but that is ok because this is only done on startup. */ void ClearCondition(void) { condFlag = FALSE; } /* * Parse the specified condition expression into an expression tree. * The expression uses the C syntax mostly, and where left halfs of * comparisons statements are column names and right halfs are numeric * or implicit string constants. Column names by themselves act as nonzero * or NULL. Example: "percent-cpu || state == 'R'" */ BOOL ParseCondition(const char * str) { TREE treeData; if (!ParseTree(&treeData, str, 0)) return FALSE; condTree = treeData; condFlag = TRUE; return TRUE; } /* * Parse an expression into the specified tree. * This initialises the fields in the tree structure. * The depth shows the recursion depth of expression macros. */ BOOL ParseTree(TREE * tree, const char * str, int depth) { int len; badValue.type = VALUE_BAD; badValue.strVal = ""; badValue.intVal = 0; badValue.column = NULL; len = strlen(str); tree->expr = malloc(len + 1); tree->modExpr = malloc(len + 1);; if ((tree->expr == NULL) || (tree->modExpr == NULL)) { fprintf(stderr, "Cannot allocate condition expression\n"); return FALSE; } memcpy(tree->expr, str, len + 1); memcpy(tree->modExpr, str, len + 1); tree->cp = tree->expr; tree->failed = FALSE; tree->root = NULL; tree->token = TOKEN_BAD; tree->rescanToken = FALSE; tree->tokenStr = ""; tree->tokenInt = 0; tree->depth = depth; /* * This is the actual parse of the string into nodes. */ tree->root = ParseAlternation(tree); if (ParseToken(tree) != TOKEN_EOF) { if (!tree->failed) fprintf(stderr, "Bad expression\n"); tree->failed = TRUE; } if (tree->failed) return FALSE; return TRUE; } /* * Parse a conditional result expression. * alternation = oror ? oror : oror. */ static NODE * ParseAlternation(TREE * tree) { NODE * top; NODE * right; NODE * altRight; TOKEN token; top = ParseOrOr(tree); token = ParseToken(tree); if (token != TOKEN_QUESTIONMARK) { tree->rescanToken = TRUE; return top; } right = ParseOrOr(tree); token = ParseToken(tree); if (token != TOKEN_COLON) { tree->rescanToken = TRUE; return BadNode(tree, "Missing colon in conditional"); } altRight = ParseOrOr(tree); top = NewNode(tree, OP_OROR, top, right); top->child[2] = altRight; return top; } /* * Parse a list of OROR expressions. */ static NODE * ParseOrOr(TREE * tree) { NODE * top; NODE * right; TOKEN token; top = ParseAndAnd(tree); while (TRUE) { token = ParseToken(tree); if (token != TOKEN_OROR) { tree->rescanToken = TRUE; return top; } right = ParseAndAnd(tree); top = NewNode(tree, OP_OROR, top, right); } } /* * Parse a list of ANDAND expressions. */ static NODE * ParseAndAnd(TREE * tree) { NODE * top; NODE * right; TOKEN token; top = ParseRelation(tree); while (TRUE) { token = ParseToken(tree); if (token != TOKEN_ANDAND) { tree->rescanToken = TRUE; return top; } right = ParseRelation(tree); top = NewNode(tree, OP_ANDAND, top, right); } } /* * Parse a comparison relation between two values. */ static NODE * ParseRelation(TREE * tree) { NODE * top; NODE * right; TOKEN token; OP op; top = ParseSum(tree); token = ParseToken(tree); switch (token) { case TOKEN_EQUAL: op = OP_EQUAL; break; case TOKEN_NOTEQUAL: op = OP_NOTEQUAL; break; case TOKEN_LESS: op = OP_LESS; break; case TOKEN_LESSEQUAL: op = OP_LESSEQUAL; break; case TOKEN_GREATER: op = OP_GREATER; break; case TOKEN_GREATEREQUAL: op = OP_GREATEREQUAL; break; default: tree->rescanToken = TRUE; return top; } right = ParseSum(tree); return NewNode(tree, op, top, right); } /* * Parse the sum of products. */ static NODE * ParseSum(TREE * tree) { NODE * top; NODE * right; TOKEN token; OP op; top = ParseProduct(tree); /* * Now keep collecting values to be added or subtracted. */ while (TRUE) { token = ParseToken(tree); switch (token) { case TOKEN_PLUS: op = OP_ADD; break; case TOKEN_MINUS: op = OP_SUBTRACT; break; default: tree->rescanToken = TRUE; return top; } right = ParseProduct(tree); top = NewNode(tree, op, top, right); } } /* * Parse the products of inclusive or. */ static NODE * ParseProduct(TREE * tree) { NODE * top; NODE * right; TOKEN token; OP op; top = ParseOr(tree); /* * Now keep collecting values to be multiplied, divided, or moduloed. */ while (TRUE) { token = ParseToken(tree); switch (token) { case TOKEN_MULTIPLY: op = OP_MULTIPLY; break; case TOKEN_DIVIDE: op = OP_DIVIDE; break; case TOKEN_MODULO: op = OP_MODULO; break; default: tree->rescanToken = TRUE; return top; } right = ParseOr(tree); top = NewNode(tree, op, top, right); } } /* * Parse the OR of AND values. */ static NODE * ParseOr(TREE * tree) { NODE * top; TOKEN token; top = ParseAnd(tree); /* * Keep collecting values to be OR'd together. */ while (TRUE) { token = ParseToken(tree); if (token != TOKEN_OR) { tree->rescanToken = TRUE; return top; } top = NewNode(tree, OP_OR, top, ParseAnd(tree)); } } /* * Parse the AND or XOR of shift values. */ static NODE * ParseAnd(TREE * tree) { NODE * top; NODE * right; TOKEN token; top = ParseShift(tree); /* * Keep collecting values to be AND'd or XOR'd together. */ while (TRUE) { token = ParseToken(tree); switch (token) { case TOKEN_AND: right = ParseShift(tree); top = NewNode(tree, OP_AND, top, right); break; case TOKEN_XOR: right = ParseShift(tree); top = NewNode(tree, OP_XOR, top, right); break; default: tree->rescanToken = TRUE; return top; } } } /* * Parse the shift of unary operators. */ static NODE * ParseShift(TREE * tree) { NODE * top; TOKEN token; OP op; top = ParseUnary(tree); token = ParseToken(tree); switch (token) { case TOKEN_LEFTSHIFT: op = OP_LEFTSHIFT; break; case TOKEN_RIGHTSHIFT: op = OP_RIGHTSHIFT; break; default: tree->rescanToken = TRUE; return top; } return NewNode(tree, OP_LEFTSHIFT, top, ParseUnary(tree)); } /* * Parse a unary operator on a term (MINUS, NOT, COMPLEMENT). */ static NODE * ParseUnary(TREE * tree) { switch (ParseToken(tree)) { case TOKEN_MINUS: return NewNode(tree, OP_NEGATE, ParseUnary(tree), NULL); case TOKEN_NOT: return NewNode(tree, OP_NOT, ParseUnary(tree), NULL); case TOKEN_COMPLEMENT: return NewNode(tree, OP_COMPLEMENT, ParseUnary(tree), NULL); default: tree->rescanToken = TRUE; return ParseTerm(tree); } } /* * Parse a single term. */ static NODE * ParseTerm(TREE * tree) { TOKEN token; NODE * node; token = ParseToken(tree); switch (token) { case TOKEN_SYMBOL: return ParseSymbol(tree, tree->tokenStr); case TOKEN_STRING: node = NewNode(tree, OP_STRING, NULL, NULL); node->strVal = tree->tokenStr; return node; case TOKEN_NUMBER: node = NewNode(tree, OP_NUMBER, NULL, NULL); node->intVal = tree->tokenInt; return node; case TOKEN_LEFTPAREN: node = ParseAlternation(tree); token = ParseToken(tree); if (token == TOKEN_RIGHTPAREN) return node; if (token == TOKEN_EOF) return BadNode(tree, "Bad expression"); return BadNode(tree, "Bad expression"); case TOKEN_RIGHTPAREN: return BadNode(tree, "Unbalanced parenthesis"); default: return BadNode(tree, "Missing term"); } } /* * Parse a symbol name. * This can be a column name, a macro name, or a function name. * A column name can optionally be followed by qualifiers. */ static NODE * ParseSymbol(TREE * tree, char * str) { TOKEN token; NODE * node; COLUMN *column; OP op; token = ParseToken(tree); /* * See if the symbol name is followed by a parenthesis. * If so, then this is a function call. */ if (token == TOKEN_LEFTPAREN) { tree->rescanToken = TRUE; return ParseFunction(tree, str); } /* * See if the symbol name is upper case. * If so, then it is a macro that needs expanding. */ if (isUpper(*str)) { tree->rescanToken = TRUE; return ParseMacro(tree, str); } /* * The symbol must now be a column name. * See if the column is known. */ column = FindColumn(str); if (column == NULL) { if (!tree->failed) { fprintf(stderr, "Unknown column \"%s\"\n", str); tree->failed = TRUE; } return BadNode(tree, ""); } /* * If the column name is not followed by a period, * then it means return the base value for the column. */ if (token != TOKEN_PERIOD) { tree->rescanToken = TRUE; node = NewNode(tree, OP_COLUMN_BASE, NULL, NULL); node->column = column; return node; } /* * It was followed by a period. * Then following the period must be a symbol which is a * known qualifier for the column. */ token = ParseToken(tree); if (token != TOKEN_SYMBOL) return BadNode(tree, "Missing column qualifier"); if (strcmp(tree->tokenStr, "base") == 0) op = OP_COLUMN_BASE; else if (strcmp(tree->tokenStr, "show") == 0) op = OP_COLUMN_SHOW; else if (strcmp(tree->tokenStr, "test") == 0) op = OP_COLUMN_TEST; else { return BadNode(tree, "Illegal column qualifier"); } /* * Return the appropriate type of column reference. */ node = NewNode(tree, op, NULL, NULL); node->column = column; return node; } /* * Parse a function call. * Currently, there can only be 3 or less arguments for a function. */ static NODE * ParseFunction(TREE * tree, char * str) { NODE * node; int func; int argCount; int i; func = 0; /* * Look for the known function names. */ if (strcmp(str, "match") == 0) func = FUNC_MATCH; if (strcmp(str, "strlen") == 0) func = FUNC_STRLEN; if (strcmp(str, "min") == 0) func = FUNC_MIN; if (strcmp(str, "max") == 0) func = FUNC_MAX; if (strcmp(str, "abs") == 0) func = FUNC_ABS; if (strcmp(str, "str") == 0) func = FUNC_STR; if (strcmp(str, "my") == 0) func = FUNC_MY; if (strcmp(str, "cmp") == 0) func = FUNC_CMP; /* * If not found, complain. */ if (func == 0) { fprintf(stderr, "Unknown function \"%s\"\n", str); tree->failed = TRUE; return BadNode(tree, ""); } if (ParseToken(tree) != TOKEN_LEFTPAREN) return BadNode(tree, "Bad function call format\n"); node = NewNode(tree, OP_FUNCTION, NULL, NULL); node->intVal = func; /* * Collect the arguments and attach them to the function node. */ argCount = FUNCTION_ARGS(func); if (argCount > CHILDS) return BadNode(tree, "Too many function arguments"); for (i = 0; i < argCount; i++) { if ((i > 0) && (ParseToken(tree) != TOKEN_COMMA)) { tree->rescanToken = TRUE; return BadNode(tree, "Not enough arguments for function"); } node->child[i] = ParseAlternation(tree); } /* * Do special handling for the magic "my" function. * Its argument MUST be a column name (with optional qualifier). */ if (func == FUNC_MY) { switch (node->child[0]->op) { case OP_COLUMN_BASE: case OP_COLUMN_SHOW: case OP_COLUMN_TEST: break; default: return BadNode(tree, "Function \"my\" only uses columns"); } } /* * Look for the end of the function call. */ switch (ParseToken(tree)) { case TOKEN_COMMA: return BadNode(tree, "Too many function arguments"); case TOKEN_RIGHTPAREN: return node; default: return BadNode(tree, "Missing right parenthesis in function call"); } } /* * Parse a macro name. * This just parses and evaluates the macro expansion. */ static NODE * ParseMacro(TREE * tree, char * str) { TREE subTree; ARGS args; if (tree->depth >= MAX_EXPR_DEPTH) return BadNode(tree, "Too many levels of expression macros"); if (!ExpandMacro(MACRO_TYPE_EXPRESSION, str, &args)) return BadNode(tree, "Macro expansion failed"); if (args.count != 1) return BadNode(tree, "Expression macro isn't one string"); /* * Parse the expression. If it fails, an error message has * already been given so we don't need to add another one. */ if (!ParseTree(&subTree, args.table[0], tree->depth + 1)) { tree->failed = TRUE; return BadNode(tree, ""); } return subTree.root; } /* * Return the next token from the expression, taking into account * the rescanning of the current token. */ static TOKEN ParseToken(TREE * tree) { if (!tree->rescanToken) tree->token = ParseNewToken(tree); tree->rescanToken = FALSE; return tree->token; } /* * Parse a new token always, ignoring rescanning. */ static TOKEN ParseNewToken(TREE * tree) { int ch; while (isBlank(*tree->cp)) tree->cp++; tree->tokenStr = &tree->modExpr[tree->cp - tree->expr]; tree->tokenInt = 0; /* * Switch on the type of character. */ ch = *tree->cp++; switch (ch) { case '!': if (*tree->cp != '=') return TOKEN_NOT; tree->cp++; return TOKEN_NOTEQUAL; case '.': return TOKEN_PERIOD; case ',': return TOKEN_COMMA; case '~': return TOKEN_COMPLEMENT; case '^': return TOKEN_XOR; case '(': return TOKEN_LEFTPAREN; case ')': return TOKEN_RIGHTPAREN; case '&': if (*tree->cp != '&') return TOKEN_AND; tree->cp++; return TOKEN_ANDAND; case '|': if (*tree->cp != '|') return TOKEN_OR; tree->cp++; return TOKEN_OROR; case '+': return TOKEN_PLUS; case '-': return TOKEN_MINUS; case '*': return TOKEN_MULTIPLY; case '/': return TOKEN_DIVIDE; case '%': return TOKEN_MODULO; case '?': return TOKEN_QUESTIONMARK; case ':': return TOKEN_COLON; case '<': if (*tree->cp == '<') { tree->cp++; return TOKEN_LEFTSHIFT; } if (*tree->cp != '=') return TOKEN_LESS; tree->cp++; return TOKEN_LESSEQUAL; case '>': if (*tree->cp == '>') { tree->cp++; return TOKEN_RIGHTSHIFT; } if (*tree->cp != '=') return TOKEN_GREATER; tree->cp++; return TOKEN_GREATEREQUAL; case '=': if (*tree->cp == '=') tree->cp++; return TOKEN_EQUAL; case '\0': tree->cp--; return TOKEN_EOF; case '\'': case '"': tree->tokenStr++; while (*tree->cp != ch) { if (*tree->cp) { tree->cp++; continue; } return BadToken(tree, "Unterminated string"); } tree->modExpr[tree->cp - tree->expr] = '\0'; tree->cp++; return TOKEN_STRING; default: break; } /* * Check for a numeric value. */ if (isDigit(ch)) { tree->cp--; return ParseNumber(tree); } /* * We must now have a symbol name. */ if (!isBegSymbol(ch)) return BadToken(tree, "Illegal token"); /* * Yes, it is a symbol, so see how large it is and null terminate it. */ while (isSymbol(ch)) ch = *tree->cp++; tree->cp--; tree->modExpr[tree->cp - tree->expr] = '\0'; return TOKEN_SYMBOL; } /* * Parse a numeric value and return it (without any sign). * By default the number is decimal. With a leading 0 it is octal, * while with a leading 0x it is hex. Illegal numbers result in an * error message and a TOKEN_BAD return value. */ static TOKEN ParseNumber(TREE * tree) { long value; int ch; value = 0; ch = *tree->cp++; if (!isDigit(ch)) { tree->cp--; return BadToken(tree, "Number expected"); } /* * If the number doesn't begin with a zero, then it is decimal. */ if (ch != '0') { while (isDigit(ch)) { value = value * 10 + ch - '0'; ch = *tree->cp++; } tree->cp--; if (isSymbol(ch)) return BadToken(tree, "Bad decimal number"); tree->tokenInt = value; return TOKEN_NUMBER; } /* * The number is octal or hex. * If the next character is not an 'x', then it is octal. */ ch = *tree->cp++; if ((ch != 'x') && (ch != 'X')) { while (isOctal(ch)) { value = value * 8 + ch - '0'; ch = *tree->cp++; } tree->cp--; if (isSymbol(ch)) return BadToken(tree, "Bad octal number"); tree->tokenInt = value; return TOKEN_NUMBER; } /* * The number must now be hex. */ ch = *tree->cp++; while (isHex(ch)) { value = value * 16; if (isDigit(ch)) value += (ch - '0'); else if (isLower(ch)) value += (ch - 'a' + 10); else value += (ch - 'A' + 10); ch = *tree->cp++; } tree->cp--; if (isSymbol(ch)) return BadToken(tree, "Bad hex number"); tree->tokenInt = value; return TOKEN_NUMBER; } /* * Return a TOKEN_BAD token, mark the expression tree as having failed, * and give the associated error message if this is the first error. */ static TOKEN BadToken(TREE * tree, char * msg) { if (!tree->failed) { fprintf(stderr, "%s\n", msg); tree->failed = TRUE; } return TOKEN_BAD; } /* * Allocate a new for the tree, with the specified opcode and the specified * left and right children. */ NODE * NewNode(TREE * tree, OP op, NODE * left, NODE * right) { NODE * node; node = (NODE *) malloc(sizeof(NODE)); if (node == NULL) return BadNode(tree, "Cannot allocate memory"); if (op == OP_NONE) tree->failed = TRUE; node->op = op; node->child[0] = left; node->child[1] = right; node->child[2] = NULL; node->column = NULL; node->strVal = ""; node->intVal = 0; return node; } /* * Allocate a bad node, and give the specified error message * if there has not been an error yet. This marks the expression * tree as having failed. This routine can never fail. */ static NODE * BadNode(TREE * tree, char * message) { if (!tree->failed) { fprintf(stderr, "%s\n", message); tree->failed = TRUE; } badNode.op = OP_NONE; badNode.strVal = ""; return &badNode; } /* END CODE */ ips-4.0/proc.c0000644000175000017500000005506211364734440012223 0ustar dbelldbell/* * Configurable ps-like program. * Process information utility routines. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include "ips.h" /* * Static variables. */ static long elapsedMilliseconds; /* time between CPU percentage samples */ static struct timeval currentTimeval; /* current accurate time */ static struct timeval baseTimeval; /* accurate beginning time of current sample */ static struct timeval cpuSampleTimes[PERCENT_SAMPLES]; /* times of cpu samples */ /* * Static procedures. */ static void ScanDeadProcesses(BOOL isThread); static BOOL IsProcessRemovable(PROC * proc); static void FreeProcess(PROC * proc); static void RemoveOwnerThread(PROC * proc); static BOOL AppendState(PROC * proc, short * countTable, int state); /* * Perform the initial process scan if required, and sleep after it. * This is required if some columns are selected which require more than * one sample to calculate the value (e.g., cpu percentage), or if the * user wanted to only show active processes. */ void InitialProcessScan(void) { if (initSleepTime <= 0) return; if (activeOnly || useInitSleep) { ScanProcesses(); sleep(initSleepTime); } } /* * Find the process structure with the specified pid and tid. * A main process has a tid value of NO_TID_ID. * If the process is new, then it is malloc'd and flagged as such. * A new process structure has mostly rubbish values. * The process structure is linked into the process list. * This deliberately does not find dead processes since a pid * could be immediately reused but we still want to see the * old process data for a while. */ PROC * FindProcess(pid_t pid, pthread_t tid) { PROC * proc; int index; /* * See if the process is already in the process list. * If so, return the found structure. */ for (proc = processList; proc; proc = proc->next) { if ((proc->pid == pid) && (proc->tid == tid) && /* (proc->deathTime == 0) */ 1) { return proc; } } /* * Nope, so allocate a new structure either from the free * list or else using malloc. */ proc = freeProcessList; if (proc) { freeProcessList = proc->next; } else { proc = AllocMemory(sizeof(PROC)); procAllocCount++; } /* * Initialise some of its columns. */ proc->next = NULL; proc->nextThread = NULL; proc->owner = NULL; proc->pid = pid; proc->tid = tid; proc->isThread = (tid != NO_THREAD_ID); proc->isNew = TRUE; proc->isValid = FALSE; proc->isActive = FALSE; proc->isShown = FALSE; proc->hasCommand = FALSE; proc->isChanged = TRUE; proc->isAncient = ancientFlag; proc->state = ' '; proc->states[0] = '\0'; proc->deathTime = 0; proc->startTimeTicks = 0; proc->startTimeClock = 0; proc->firstCpuTime = 0; proc->lastSavedTime = 0; proc->lastActiveTime = 0; proc->lastSyncTime = 0; proc->liveCounter = 0; proc->percentCpu = 0; proc->percentMemory = 0; proc->threadCount = 0; proc->runOrder = 0; proc->openFiles = 0; proc->endCode = 0; proc->oldSystemRunTime = 0; proc->oldUserRunTime = 0; proc->policy = 0; proc->realTimePriority = 0; proc->commandLength = 0; proc->environmentLength = 0; proc->rootPathLength = 0; proc->cwdPathLength = 0; proc->execPathLength = 0; proc->command = proc->commandBuffer; proc->environment = emptyString; proc->rootPath = emptyString; proc->cwdPath = emptyString; proc->execPath = emptyString; proc->stdioPaths[0] = emptyString; proc->stdioPaths[1] = emptyString; proc->stdioPaths[2] = emptyString; proc->program[0] = '\0'; proc->waitChanSymbol[0] = '\0'; /* * Initialize all the cpu samples to zero. */ for (index = 0; index < PERCENT_SAMPLES; index++) { proc->cpuTable[index] = 0; } /* * Add it to the process list. */ proc->next = processList; processList = proc; /* * If this is a thread process then link it into the thread * list of its main process. */ if (tid != NO_THREAD_ID) { PROC * mainProc = FindProcess(pid, NO_THREAD_ID); proc->owner = mainProc; proc->nextThread = mainProc->nextThread; mainProc->nextThread = proc; } return proc; } /* * Remove all dead processes from the process list that are older than * the preserve death time. Such a delay gives the user a chance to * see transient processes which only appear for one process scan. * This is done with two passes through the process list. * First threads are removed, and then main processes are removed. */ void RemoveDeadProcesses(void) { ScanDeadProcesses(TRUE); ScanDeadProcesses(FALSE); } /* * Scan the process list for either main processes or thread processes * and check them for being dead. If they are dead long enough then * they are removed. */ static void ScanDeadProcesses(BOOL isThread) { PROC * proc; PROC * prevProc; PROC * nextProc; prevProc = NULL; for (proc = processList; proc; proc = nextProc) { /* * Save the next process locally since if this process is * freed its next pointer is modified. */ nextProc = proc->next; /* * If this process is not the specified type then ignore it. */ if (proc->isThread != isThread) { prevProc = proc; continue; } /* * Check to see if the process is dead and removable. * If not then leave it in the process list. */ if (!IsProcessRemovable(proc)) { prevProc = proc; continue; } /* * The process is completely finished. * Remove it from the process list by linking the previous * process to the next one and leaving prevproc alone. */ if (prevProc) prevProc->next = nextProc; else processList = nextProc; /* * If this is a thread process then remove it from its * owner's thread list. */ RemoveOwnerThread(proc); /* * Clear out the process structure and move it to the * free list for reuse. */ FreeProcess(proc); } } /* * Check whether the process can be removed. * This is true if it is known to be dead, and if it has been * dead long enough. */ static BOOL IsProcessRemovable(PROC * proc) { /* * If the process is still alive then it isn't removable. */ if (proc->liveCounter == liveCounter) return FALSE; /* * The process is dead. * Clear some of its state to look good in status displays. * It's unclear how much of this should be cleaned out... */ proc->state = ' '; proc->oldState = ' '; proc->states[0] = '\0'; proc->oldUserRunTime = proc->userRunTime; proc->oldSystemRunTime = proc->systemRunTime; proc->percentCpu = 0; proc->percentMemory = 0; proc->pagesSwapped = 0; proc->virtualSize = 0; proc->rss = 0; /* * Update the cpu percentage used for the process since it * is still useful to see that change for a dead process. */ CalculateCpuPercentage(proc); /* * If the process has just been noticed as being dead * then remember that time, and also the fact that it was * last active then (since it had to run to disappear). */ if (proc->deathTime == 0) { proc->deathTime = currentTime; proc->lastActiveTime = currentTime; proc->runOrder = liveCounter; } /* * If the process is dead recently enough, then leave it * alone for a while so that the user can still see it. */ if ((deathTime > 0) && (currentTime <= proc->deathTime + deathTime)) { return FALSE; } /* * If this is a main process which still has some thread * processes then leave it (but this should not happen!). */ if (!proc->isThread && (proc->nextThread != 0)) return FALSE; /* * The process can be removed. */ return TRUE; } /* * Remove the specified thread (if applicable) from its owner thread list. */ static void RemoveOwnerThread(PROC * proc) { PROC * testProc; /* * If this isn't a thread then do nothing. */ if (!proc->isThread || (proc->owner == 0)) return; /* * Remove this process from the owner's thread list. */ testProc = proc->owner->nextThread; if (testProc == proc) { proc->owner->nextThread = proc->nextThread; return; } while (testProc) { if (testProc->nextThread == proc) { testProc->nextThread = proc->nextThread; return; } testProc = testProc->nextThread; } } /* * Free any storage associated with a process structure and move it * onto the process free list. Any unlinking of the structure from * other processes or the process list has to be done first. */ static void FreeProcess(PROC * proc) { /* * Clear a bit of state just in case. */ proc->next = 0; proc->owner = 0; proc->nextThread = 0; proc->isValid = FALSE; proc->pid = 0; proc->tid = 0; proc->state = ' '; proc->states[0] = '\0'; /* * Free any dynamic storage used by the process. */ if (proc->command != proc->commandBuffer) { free(proc->command); proc->command = proc->commandBuffer; } proc->commandLength = 0; FreeSharedString(proc->environment); proc->environment = emptyString; proc->environmentLength = 0; FreeSharedString(proc->cwdPath); proc->cwdPath = emptyString; proc->cwdPathLength = 0; FreeSharedString(proc->rootPath); proc->rootPath = emptyString; proc->rootPathLength = 0; FreeSharedString(proc->execPath); proc->execPath = emptyString; proc->execPathLength = 0; FreeSharedString(proc->stdioPaths[0]); FreeSharedString(proc->stdioPaths[1]); FreeSharedString(proc->stdioPaths[2]); proc->stdioPaths[0] = emptyString; proc->stdioPaths[1] = emptyString; proc->stdioPaths[2] = emptyString; /* * Add the process structure to the free process list. */ proc->next = freeProcessList; freeProcessList = proc; } /* * Calculate how long it has been since the process has been active. * Part of this calculation compares previously gathered state with * the newest state. In this way, even transiently running processes * can still be detected as not being idle. */ void CheckActiveProcess(PROC * proc) { BOOL isActive; /* * The process is definitely active if it is currently runnable * or is in a short term wait like disk I/O. */ isActive = ((proc->state == 'R') || (proc->state == 'D')); if (isActive) { proc->lastActiveTime = currentTime; proc->runOrder = liveCounter; } /* * Recalculate the CPU and memory percentages. */ CalculateCpuPercentage(proc); if (totalMemoryClicks > 0) { proc->percentMemory = (proc->rss * MEMORY_SCALE) / totalMemoryClicks; } proc->isChanged = FALSE; /* * If some of its state has changed since the last check, * then it is also considered active. Save the new values * of that state for future checks. */ if (proc->isNew || (proc->userRunTime != proc->oldUserRunTime) || (proc->systemRunTime != proc->oldSystemRunTime) || (proc->state != proc->oldState) || (proc->flags != proc->oldFlags) || (proc->minorFaults != proc->oldMinorFaults) || (proc->majorFaults != proc->oldMajorFaults) || (proc->startTimeTicks != proc->oldStartTimeTicks) || (proc->endCode != proc->oldEndCode) || (proc->esp != proc->oldEsp) || (proc->eip != proc->oldEip) || (proc->waitChan != proc->oldWaitChan)) { proc->isChanged = TRUE; proc->isNew = FALSE; proc->isAncient = ancientFlag; proc->lastActiveTime = currentTime; proc->lastSavedTime = currentTime; proc->runOrder = liveCounter; proc->oldState = proc->state; proc->oldFlags = proc->flags; proc->oldMinorFaults = proc->minorFaults; proc->oldMajorFaults = proc->majorFaults; proc->oldUserRunTime = proc->userRunTime; proc->oldSystemRunTime = proc->systemRunTime; proc->oldStartTimeTicks = proc->startTimeTicks; proc->oldEndCode = proc->endCode; proc->oldEsp = proc->esp; proc->oldEip = proc->eip; proc->oldWaitChan = proc->waitChan; } /* * If the state changed recently, then the process is still active. * Don't do this check for ancient processes that were there * before we knew about their idleness. */ if ((!proc->isAncient) && (currentTime <= (proc->lastSavedTime + activeTime))) { isActive = TRUE; } if (isActive) proc->isAncient = FALSE; proc->isActive = isActive; } /* * Calculate the CPU percentage of a process. This uses the sample times * table and the cpu runtime sample table of the process. This should be * called soon after UpdateTimes has been called and soon after the new * runtime of the process has been found. */ void CalculateCpuPercentage(PROC * proc) { long oldCpuTime; long ticksUsed; double percentage; /* * Update the cpu time of the process for the current sample. */ proc->cpuTable[newCpuIndex] = proc->userRunTime + proc->systemRunTime; /* * If we don't have any samples yet then the percentage is zero. */ proc->percentCpu = 0; if (elapsedMilliseconds <= 0) return; /* * Calculate the ticks used by the process between the old and the * new samples. If we don't have the runtime for the beginning * sample time then use the process's first seen runtime. */ oldCpuTime = proc->cpuTable[oldCpuIndex]; if (oldCpuTime < proc->firstCpuTime) oldCpuTime = proc->firstCpuTime; ticksUsed = proc->cpuTable[newCpuIndex] - oldCpuTime; if (ticksUsed <= 0) return; /* * Calculate the percentange of the CPU used from the ticks used and * and the elapsed time of the sample. Do this using floating point * since otherwise it can overflow. */ percentage = ticksUsed; percentage *= 1000; percentage *= CPU_SCALE; percentage /= ticksPerSecond; percentage /= elapsedMilliseconds; proc->percentCpu = (int) percentage; } /* * Update the current time and determine whether or not it is time for a * new CPU sample to be taken for the processes. This provides a rolling * CPU percentage feature based on the last few seconds which is much * better than an "instantaneous" CPU percentage. */ void UpdateTimes(void) { /* * Increment the live counter and get the current time. */ liveCounter++; GetTimeOfDay(¤tTimeval); currentTime = currentTimeval.tv_sec; /* * If this is the first call then initialize the times. */ if (baseTimeval.tv_sec == 0) { baseTimeval = currentTimeval; cpuSampleTimes[0] = currentTimeval; cpuSampleTimes[1] = currentTimeval; oldCpuIndex = 0; newCpuIndex = 1; } /* * See how much time has elapsed since the start of the current * CPU sample interval. */ elapsedMilliseconds = ElapsedMilliSeconds(&baseTimeval, ¤tTimeval); /* * If enough time has gone by then start a new sample. * (But don't do this if we would catch up to the old index.) */ if (elapsedMilliseconds * PERCENT_RESOLUTION >= 1000) { int tempIndex = (newCpuIndex + 1) % PERCENT_SAMPLES; if (tempIndex != oldCpuIndex) { newCpuIndex = tempIndex; /* * Store the beginning time of the sample. */ baseTimeval = currentTimeval; } } /* * Update the time of the current sample. */ cpuSampleTimes[newCpuIndex] = currentTimeval; /* * Advance the old CPU index value if we can so that the * elapsed interval is only as large as desired. */ while (oldCpuIndex != newCpuIndex) { int tempIndex = (oldCpuIndex + 1) % PERCENT_SAMPLES; if (tempIndex == newCpuIndex) break; elapsedMilliseconds = ElapsedMilliSeconds( &cpuSampleTimes[tempIndex], ¤tTimeval); if (elapsedMilliseconds < percentSeconds * 1000) break; oldCpuIndex = tempIndex; } /* * Save the elapsed milliseconds between the old sample time * and the current time. This is the interval used for the * CPU percentage calculations. */ elapsedMilliseconds = ElapsedMilliSeconds( &cpuSampleTimes[oldCpuIndex], ¤tTimeval); } /* * Update the total number of processes and threads which are being shown. * This should only be called after the status of the processes has been * completed for a cycle, so that dead processes are removed and the isShown * flags for the processes are accurate. * Note: The total number of processes and threads is NOT updated here * since we don't necessarily scan all of the processes and so the list * of PROC structures doesn't have all of the needed information. */ void UpdateProcessCounts(void) { const PROC * proc; procShowCount = 0; threadShowCount = 0; /* * Loop over all of the processes (and maybe the threads). */ for (proc = processList; proc; proc = proc->next) { /* * If this is a thread then increment the thread count. */ if (proc->isThread) { if (proc->isShown) threadShowCount++; continue; } /* * This is a main process structure. * Increment the process count. */ if (proc->isShown) procShowCount++; /* * If the thread information is not being collected * or if there is only one thread for the process * then we must increment the thread count specially. * (Only the main thread of the process is seen.) */ if ((proc->threadCount <= 1) || (!useThreads && !showThreads)) { if (proc->isShown) threadShowCount++; } } } /* * Store a command line string into the process structure. * The len value is the length of the string without the * terminating null character. */ void SetCommandLine(PROC * proc, const char * str, int len) { /* * See if the old and new commands are empty. */ if ((len == 0) && (proc->commandLength == 0)) return; /* * See if the command line is the same as last time. * If so, then we are done. */ if ((len == proc->commandLength) && (str[0] == proc->command[0]) && (str[len - 1] == proc->command[len - 1]) && (memcmp(str, proc->command, len) == 0)) { return; } /* * Free any old command line buffer that was there, and point * back to the space already allocated in the proc structure. */ if (proc->command != proc->commandBuffer) free(proc->command); proc->command = proc->commandBuffer; /* * If the command line is too large for the proc buffer, * then allocate a new one. (The buffer has a couple of * extra bytes for the terminating null.) */ if (len > BUF_COMMAND_LEN) proc->command = AllocMemory(len + 1); /* * Copy the command line into the command buffer and set its length. */ memcpy(proc->command, str, len); proc->command[len] = '\0'; proc->commandLength = len; } /* * Set a shared string value with its length into the specified locations * (generally for one of the values in a PROC structure). The new string is * compared with the existing one, and if it differs, the previous string * value is freed and the new one is stored. Returns TRUE on success or * FALSE on an error with an empty string stored. */ BOOL SetSharedString(char ** saveStr, int * saveLen, const char * str, int len) { /* * Get convenient references to the old strings. */ char * oldStr = *saveStr; int oldLen = *saveLen; /* * If the old and new strings are both empty then there is * nothing to do. */ if ((oldLen == 0) && (len == 0)) return TRUE; /* * Compare the new string against what is already there. * If they are the same, then we don't need to do anything. */ if ((oldLen == len) && (oldStr[0] == str[0]) && (oldStr[len - 1] == str[len - 1]) && (memcmp(oldStr, str, len) == 0)) { return TRUE; } /* * The value is being changed. * Free the old one and try to allocate the new one. */ FreeSharedString(oldStr); *saveStr = AllocateSharedString(str, len); *saveLen = len; if (*saveStr != NULL) return TRUE; /* * The allocation failed. * Set an empty value and return failure. */ *saveStr = emptyString; *saveLen = 0; return FALSE; } /* * Get the run order of the process, taking into account its threads. */ ULONG GetRunOrder(const PROC * proc) { ULONG runOrder = proc->runOrder; if (!useThreads && !showThreads) return runOrder; if (proc->isThread || (proc->threadCount == 1)) return runOrder; for (proc = proc->nextThread; proc; proc = proc->nextThread) { if (runOrder < proc->runOrder) runOrder = proc->runOrder; } return runOrder; } /* * Get the last active time of the process, taking into account its threads. */ time_t GetLastActiveTime(const PROC * proc) { time_t lastActiveTime = proc->lastActiveTime; if (!useThreads && !showThreads) return lastActiveTime; if (proc->isThread || (proc->threadCount == 1)) return lastActiveTime; for (proc = proc->nextThread; proc; proc = proc->nextThread) { if (lastActiveTime < proc->lastActiveTime) lastActiveTime = proc->lastActiveTime; } return lastActiveTime; } /* * Get whether the process is active, taking into account its threads. */ BOOL GetIsActive(const PROC * proc) { if (proc->isActive) return TRUE; if (!useThreads && !showThreads) return FALSE; if (proc->isThread || (proc->threadCount == 1)) return FALSE; for (proc = proc->nextThread; proc; proc = proc->nextThread) { if (proc->isActive) return TRUE; } return FALSE; } /* * Get the state character of the process, taking into account its threads. * We have a hierarchy of states. */ int GetState(const PROC * proc) { int state = proc->state; if (!useThreads && !showThreads) return state; if (proc->isThread || (proc->threadCount == 1)) return state; for (proc = proc->nextThread; proc; proc = proc->nextThread) { state = PickBestState(state, proc->state); } return state; } /* * Build and save a string describing the list of thread states for the process. * The single character states can be followed by a numeric count as in "R4DS3". */ void BuildStates(PROC * proc) { int state; PROC * threadProc; short countTable[128]; /* * Get the single process state and use that if there are no * threads or we are a thread. */ proc->states[0] = proc->state; proc->states[1] = '\0'; if (!useThreads && !showThreads) return; if (proc->isThread || (proc->threadCount == 1)) return; /* * Calculate a table of counts for the threads indexed by each of * their state letters. */ memset(countTable, 0, 128 * sizeof(short)); for (threadProc = proc->nextThread; threadProc; threadProc = threadProc->nextThread) { countTable[threadProc->state & 0x7f]++; } /* * Format and append the state count values in a nice order. */ proc->states[0] = '\0'; AppendState(proc, countTable, 'R'); AppendState(proc, countTable, 'D'); AppendState(proc, countTable, 'S'); AppendState(proc, countTable, 'T'); /* * Append any other leftover states. */ for (state = ' '; state < 128; state++) AppendState(proc, countTable, state); } /* * Format and append a state with its count value and clear the value. * Returns TRUE if the state could be stored. */ static BOOL AppendState(PROC * proc, short * countTable, int state) { int count; int len; char buf[20]; count = countTable[state]; if (count == 0) return TRUE; countTable[state] = 0; len = strlen(proc->states); if (count == 1) { if (len >= MAX_STATES_LEN) return FALSE; proc->states[len++] = state; proc->states[len] = '\0'; return TRUE; } sprintf(buf, "%c%d", state, count); if (len + strlen(buf) >= MAX_STATES_LEN) return FALSE; strcpy(&proc->states[len], buf); return TRUE; } /* END CODE */ ips-4.0/x11display.c0000644000175000017500000006136211360562476013263 0ustar dbelldbell/* * Configurable ps-like program. * Display device which uses raw X11 calls to show text in a window. * The compilation of this display device is optional based on the * definition of the X11 symbol in the Makefile. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #ifdef X11 #include #include #include #include #include #include "ips.h" /* * Limits on the number of rows and columns for sanity. */ #define MIN_ROWS 3 #define MIN_COLS 20 #define MAX_ROWS 500 #define MAX_COLS 2000 /* * Extra reallocation slop size for lines which are being resized. */ #define COLUMN_REALLOC_SLOP_SIZE 20 /* * Storage of a single cell of the text. * This consists of a character and a color id. */ typedef short CELL; /* * Macros to manipulate the cell components */ #define GETCHAR(cell) ((cell) >> 8) #define GETCOLOR(cell) ((cell) & 0xff) #define MAKECELL(ch, color) (((ch) << 8) | (color)) /* * Storage for a line of text. * Note: This is a varying size structure. */ typedef struct LINE LINE; struct LINE { int beginUpdate; /* beginning column needing update */ int endUpdate; /* ending column (plus one) needing update */ int clearCount; /* columns needing clearing after used */ int usedCount; /* number of columns currently in use */ int alloc; /* number of columns allocated in data */ CELL data[1]; /* text for line (MUST BE LAST) */ }; /* * Allocation size for a LINE to hold a certain number of columns. */ #define LINE_SIZE(columns) (sizeof(LINE) + ((columns) * sizeof(CELL))) /* * Pixels for offsetting text from edges. */ #define TEXT_OFF 4 /* * The input buffer size. */ #define INPUT_BUFFER_SIZE 128 static BOOL X11Open(DISPLAY *); static BOOL X11DefineColor(DISPLAY *, int, const char *, const char *, int); static void X11CreateWindow(DISPLAY *); static void X11Close(DISPLAY *); static void X11SetColor(DISPLAY *, int); static void X11Refresh(DISPLAY *); static void X11BeginPage(DISPLAY *); static void X11PutChar(DISPLAY *, int); static void X11PutString(DISPLAY *, const char *); static void X11PutBuffer(DISPLAY *, const char *, int); static void X11EndPage(DISPLAY *); static BOOL X11EventWait(DISPLAY *, int); static BOOL X11InputReady(DISPLAY *); static int X11ReadChar(DISPLAY *); static void X11RingBell(DISPLAY *); static int X11GetRows(DISPLAY *); static int X11GetCols(DISPLAY *); static BOOL X11DoesScroll(DISPLAY *); static DISPLAY x11Display = { X11Open, X11DefineColor, X11CreateWindow, X11Close, X11SetColor, X11Refresh, X11BeginPage, X11PutChar, X11PutString, X11PutBuffer, X11EndPage, X11EventWait, X11InputReady, X11ReadChar, X11RingBell, X11GetRows, X11GetCols, X11DoesScroll }; /* * Local variables. */ static Display * display; /* display for X */ static int screen; /* screen number */ static Window rootWid; /* top level window id */ static Window mainWid; /* main window id */ static GC textGC; /* graphics context for text */ static GC clearGC; /* graphics context for clearing areas */ static XFontStruct * font; /* font for text */ static Colormap colorMap; /* default color map */ static long black; /* black pixel value */ static long white; /* white pixel value */ static long foreground; /* default foreground color */ static long background; /* default background color */ static int charWidth; /* width of characters */ static int charHeight; /* height of characters */ static int charAscent; /* ascent of characters */ static int mainWidth; /* width of main window */ static int mainHeight; /* height of main window */ static int textRow; /* row number for new text */ static int textCol; /* column number for new text */ static int rows; /* current number of rows of text */ static int cols; /* current number of columns of text */ static int inputCount; /* characters stored in input buffer */ static BOOL isMapped; /* main window is mapped */ static BOOL isRedrawNeeded; /* main window needs redrawing */ static LINE * lines[MAX_ROWS]; /* lines of text */ static char inputBuffer[INPUT_BUFFER_SIZE]; /* input buffer */ /* * Color information. * The color tables are indexed by the color id. */ static int currentColorId; static long currentForegroundPixel; static long currentBackgroundPixel; static int currentColorFlags; static long foregroundColorTable[MAX_COLORS]; static long backgroundColorTable[MAX_COLORS]; static int colorFlagsTable[MAX_COLORS]; /* * Local procedures. */ static void DoEvent(XEvent *); static void DoKeyPress(XKeyEvent *); static void DoConfigure(XConfigureEvent *); static void StoreCharacter(int row, int col, int ch); static void StoreEndOfLine(int row, int col); static void SetColor(int colorId); static void UpdateWindow(void); static void UpdateRow(int row); static void DrawText(int textX, int textY, const char * buffer, int count); static void ClearWindow(void); static LINE * GetLine(int row); /* * Return the instance of the X11 display device. */ DISPLAY * GetX11Display(void) { return &x11Display; } /* * Open the display device. * This doesn't yet create the window. */ static BOOL X11Open(DISPLAY * ipsDisplay) { XColor cellColor; XColor nameColor; int colorId; /* * Open the display. */ display = XOpenDisplay(displayName); if (display == NULL) return FALSE; screen = DefaultScreen(display); rootWid = RootWindow(display, screen); /* * Try to load the font to be used. */ font = XLoadQueryFont(display, fontName); /* * If the font isn't found then try the default one. */ if ((font == NULL) && (strcmp(fontName, DEFAULT_FONT) != 0)) font = XLoadQueryFont(display, DEFAULT_FONT); if (font == NULL) { XCloseDisplay(display); display = NULL; return FALSE; } /* * Save the font attributes. * This assumes that the font is mono-spaced. */ charAscent = font->ascent; charHeight = charAscent + font->descent; charWidth = font->max_bounds.width; /* * Allocate colors. */ black = BlackPixel(display, screen); white = WhitePixel(display, screen); colorMap = DefaultColormap(display, screen); foreground = black; background = white; if (XAllocNamedColor(display, colorMap, foregroundName, &cellColor, &nameColor)) { foreground = cellColor.pixel; } if (XAllocNamedColor(display, colorMap, backgroundName, &cellColor, &nameColor)) { background = cellColor.pixel; } /* * Set up the default color table information. */ currentColorId = DEFAULT_COLOR_ID; currentForegroundPixel = foreground; currentBackgroundPixel = background; currentColorFlags = COLOR_FLAG_NONE; for (colorId = 0; colorId < MAX_COLORS; colorId++) { foregroundColorTable[colorId] = foreground; backgroundColorTable[colorId] = background; colorFlagsTable[colorId] = COLOR_FLAG_NONE; } /* * Set up the graphics contexts. */ textGC = XCreateGC(display, rootWid, 0, NULL); XSetForeground(display, textGC, foreground); XSetBackground(display, textGC, background); XSetFont(display, textGC, font->fid); clearGC = XCreateGC(display, rootWid, 0, NULL); XSetForeground(display, clearGC, background); /* * Synchronize so we know we are ok. */ XSync(display, False); /* * Ok, return success. */ return TRUE; } /* * Create the output window. */ static void X11CreateWindow(DISPLAY * ipsDisplay) { XSizeHints sizeHints; XWMHints wmHints; XEvent event; int displayWidth; int displayHeight; int parsedFlags; int parsedX; int parsedY; unsigned int tryCols; unsigned int tryRows; /* * Parse the geometry string to get the preferred window size. * Then constrain the result to reasonable values. */ parsedFlags = XParseGeometry(geometry, &parsedX, &parsedY, &tryCols, &tryRows); if ((parsedFlags & WidthValue) == 0) tryCols = DEFAULT_GEOMETRY_COLS; if ((parsedFlags & HeightValue) == 0) tryRows = DEFAULT_GEOMETRY_ROWS; if (tryCols < MIN_COLS) tryCols = MIN_COLS; if (tryCols > MAX_COLS) tryCols = MAX_COLS; if (tryRows < MIN_ROWS) tryRows = MIN_ROWS; if (tryRows > MAX_ROWS) tryRows = MAX_ROWS; /* * Calculate a preferred window size based on holding text holding * the desired number of rows and columns. */ displayWidth = DisplayWidth(display, screen); displayHeight = DisplayHeight(display, screen); mainWidth = charWidth * tryCols + TEXT_OFF * 2; mainHeight = charHeight * tryRows + TEXT_OFF * 2; if (mainWidth > displayWidth) mainWidth = displayWidth; if (mainHeight > displayHeight) mainHeight = displayHeight; sizeHints.flags = USSize | PMinSize | PResizeInc | PBaseSize; sizeHints.width = mainWidth; sizeHints.height = mainHeight; sizeHints.width_inc = charWidth; sizeHints.height_inc = charHeight; sizeHints.min_width = TEXT_OFF * 2; sizeHints.min_height = TEXT_OFF * 2; sizeHints.base_width = TEXT_OFF * 2; sizeHints.base_height = TEXT_OFF * 2; /* * If a position was specified then set it. * Note: This doesn't seem to work right but I don't have enough * documentation to understand it right now. */ if (((parsedFlags & XValue) != 0) && ((parsedFlags & YValue) != 0)) { sizeHints.flags |= USPosition; sizeHints.x = parsedX; sizeHints.y = parsedY; } mainWid = XCreateSimpleWindow(display, rootWid, 0, 0, mainWidth, mainHeight, 0, 0, background); XSelectInput(display, mainWid, KeyPressMask | ExposureMask | StructureNotifyMask); XSetStandardProperties(display, mainWid, "ips", "ips", None, (char **) "", 0, &sizeHints); wmHints.flags = InputHint; wmHints.input = True; XSetWMHints(display, mainWid, &wmHints); /* * Map the window and wait for it to appear. */ XMapWindow(display, mainWid); isMapped = FALSE; while (!isMapped) { XNextEvent(display, &event); DoEvent(&event); } } /* * Close the display device. */ static void X11Close(DISPLAY * ipsDisplay) { if (display) XCloseDisplay(display); display = NULL; } /* * Define a foreground and background color pair to be associated * with the specified color id, along with flags which modify the * look of the characters. Empty names indicate that the default * colors are to be used. Returns TRUE if the definition was * successful. */ static BOOL X11DefineColor(DISPLAY * ipsDisplay, int colorId, const char * foregroundName, const char * backgroundName, int colorFlags) { long foregroundPixel = foreground; long backgroundPixel = background; XColor cellColor; XColor nameColor; if ((colorId < 0) || (colorId >= MAX_COLORS)) return FALSE; /* * Validate that the flags are known. */ if (colorFlags & ~(COLOR_FLAG_UNDERLINE|COLOR_FLAG_BOLD)) return FALSE; /* * Allocate the foreground and background colors if they * are not empty strings. */ if (*foregroundName) { if (!XAllocNamedColor(display, colorMap, foregroundName, &cellColor, &nameColor)) { return FALSE; } foregroundPixel = cellColor.pixel; } if (*backgroundName) { if (!XAllocNamedColor(display, colorMap, backgroundName, &cellColor, &nameColor)) { return FALSE; } backgroundPixel = cellColor.pixel; } /* * Store the colors in the table and return success. */ foregroundColorTable[colorId] = foregroundPixel; backgroundColorTable[colorId] = backgroundPixel; colorFlagsTable[colorId] = colorFlags; return TRUE; } /* * Set the color id to be used for new output in the window. */ static void X11SetColor(DISPLAY * ipsDisplay, int colorId) { if ((colorId < 0) || (colorId > MAX_COLORS)) colorId = DEFAULT_COLOR_ID; currentColorId = colorId; } static void X11Refresh(DISPLAY * ipsDisplay) { isRedrawNeeded = TRUE; } static void X11BeginPage(DISPLAY * ipsDisplay) { /* * Clear the window if we need to redraw it. */ if (isRedrawNeeded) ClearWindow(); /* * Set our writing position to the top left. */ textRow = 0; textCol = 0; } /* * Write one character to the screen handling newlines and tabs. * This does not yet do any actual X11 calls, only the LINE structures * are manipulated. */ static void X11PutChar(DISPLAY * ipsDisplay, int ch) { int spaceCount; /* * Switch on the character to handle it. */ switch (ch) { case '\0': break; case '\n': StoreEndOfLine(textRow, textCol); textCol = 0; textRow++; break; case '\t': spaceCount = 8 - (textCol + 1) % 8; while (spaceCount-- > 0) X11PutChar(ipsDisplay, ' '); break; default: StoreCharacter(textRow, textCol, ch); textCol++; break; } } static void X11PutString(DISPLAY * ipsDisplay, const char * str) { while (*str) X11PutChar(ipsDisplay, *str++); } static void X11PutBuffer(DISPLAY * ipsDisplay, const char * str, int len) { while (len-- > 0) X11PutChar(ipsDisplay, *str++); } /* * End the page by writing the changed text into the window. */ static void X11EndPage(DISPLAY * ipsDisplay) { /* * Clear all of the rest of the window. */ while (textRow <= rows) { StoreEndOfLine(textRow, textCol); textCol = 0; textRow++; } /* * Now update the window and flush it. */ UpdateWindow(); XFlush(display); } /* * Handle events while waiting for the specified amount of time. * Returns early if there are input characters to be processed. * Returns TRUE if the window is exposed or resized and needs redrawing. */ static BOOL X11EventWait(DISPLAY * ipsDisplay, int milliSeconds) { XEvent event; struct timeval timeOut; fd_set readFds; int fd; /* * First handle any already queued events. */ while (XEventsQueued(display, QueuedAfterFlush) > 0) { XNextEvent(display, &event); DoEvent(&event); } /* * If there is no delay then do nothing. */ if (milliSeconds <= 0) return isRedrawNeeded; /* * Wait for some more events to arrive or until the timeout expires. */ fd = ConnectionNumber(display); FD_ZERO(&readFds); FD_SET(fd, &readFds); timeOut.tv_sec = milliSeconds / 1000; timeOut.tv_usec = (milliSeconds % 1000) * 1000; (void) select(fd + 1, &readFds, NULL, NULL, &timeOut); /* * Now handle any events that arrived during our wait. */ while (XEventsQueued(display, QueuedAfterFlush) > 0) { XNextEvent(display, &event); DoEvent(&event); } /* * Return TRUE if the window needs redrawing. */ return isRedrawNeeded; } /* * See if keyboard input is ready to read from the window. * This does not block. */ static BOOL X11InputReady(DISPLAY * ipsDisplay) { XEvent event; if (inputCount > 0) return TRUE; while (TRUE) { if (XEventsQueued(display, QueuedAfterFlush) <= 0) return FALSE; XPeekEvent(display, &event); if (event.type == KeyPress) return TRUE; XNextEvent(display, &event); DoEvent(&event); } return FALSE; } /* * Read the next character from the window. * If no characters are available this will block. */ static int X11ReadChar(DISPLAY * ipsDisplay) { XEvent event; int ch; /* * Loop reading events until we have an input character and * until there are no more events to process. */ while ((inputCount == 0) || (XEventsQueued(display, QueuedAfterFlush) > 0)) { XNextEvent(display, &event); DoEvent(&event); } /* * There is now at least one character to read, so get it. */ ch = inputBuffer[0]; inputCount--; if (inputCount > 0) memmove(inputBuffer, inputBuffer + 1, inputCount); return ch; } static int X11GetRows(DISPLAY * ipsDisplay) { return rows; } static int X11GetCols(DISPLAY * ipsDisplay) { return cols; } static void X11RingBell(DISPLAY * ipsDisplay) { XBell(display, 0); XFlush(display); } static BOOL X11DoesScroll(DISPLAY * ipsDisplay) { return FALSE; } /* * Handle the specified event. */ static void DoEvent(XEvent * event) { switch (event->type) { case Expose: isMapped = TRUE; isRedrawNeeded = TRUE; break; case MapNotify: isMapped = TRUE; break; case UnmapNotify: isMapped = FALSE; break; case KeyPress: DoKeyPress(&event->xkey); break; case ConfigureNotify: DoConfigure(&event->xconfigure); break; } } /* * Here for a keypress event. * We just store the character into an internal buffer until it can be * collected by the caller. */ static void DoKeyPress(XKeyEvent * kp) { KeySym keySym; int len; char buf[12]; /* * Look up the key symbol. */ len = XLookupString(kp, buf, sizeof(buf), &keySym, NULL); /* * Check for a normal key. */ if ((len != 1) || IsModifierKey(keySym)) return; /* * Try to store the character into our buffer. */ if (inputCount < sizeof(inputBuffer)) inputBuffer[inputCount++] = buf[0]; else XBell(display, 0); } /* * Here for a configuration change to our window (e.g., resize). * We just recalculate the number of rows and columns. * If their value changes, we set the redraw flag. */ static void DoConfigure(XConfigureEvent * cp) { int newRows; int newCols; mainHeight = cp->height; mainWidth = cp->width; newRows = (mainHeight - TEXT_OFF * 2) / charHeight; newCols = (mainWidth - TEXT_OFF * 2) / charWidth; if (newRows > MAX_ROWS) newRows = MAX_ROWS; if (newCols > MAX_COLS) newCols = MAX_COLS; if ((rows != newRows) || (cols != newCols)) { rows = newRows; cols = newCols; isRedrawNeeded = TRUE; } } /* * Store a character at the indicated position of the window. * This only manipulates the LINE structures so no X11 calls are done yet. * No control character handling is performed here. */ static void StoreCharacter(int row, int col, int ch) { LINE * line; CELL cell; /* * Make sure the position is within the window. * If not, then ignore it. */ if ((row < 0) || (row >= rows) || (col < 0) || (col >= cols)) return; /* * Get the LINE for this row which is large enough for the column. */ line = GetLine(row); /* * Build the cell value from the character and the current color. */ cell = MAKECELL(ch, currentColorId); /* * If the used part of the line is being extended than handle that. */ if (col >= line->usedCount) { line->usedCount = col + 1; if (line->endUpdate == 0) line->beginUpdate = col; line->endUpdate = col + 1; line->data[col] = cell; return; } /* * If a old column's character is being changed then handle that. */ if (line->data[col] != cell) { if (line->endUpdate == 0) line->beginUpdate = col; if (col >= line->endUpdate) line->endUpdate = col + 1; line->data[col] = cell; } } /* * Store an end of line for the specified row. * The specified column number is the first unused column of the line. * This basically just marks the line as needing clearing. */ static void StoreEndOfLine(int row, int col) { LINE * line; /* * Make sure the position is within the window. * If not, then ignore it. */ if ((row < 0) || (row >= rows) || (col < 0) || (col >= cols)) return; /* * Store end of lines in the rest of the row. */ while (col < cols) StoreCharacter(row, col++, ' '); /* * Get the LINE for this row which is large enough for the column. */ line = GetLine(row); #if 0 /* * If the unused column value is less then before then mark * the fact that the end of line needs clearing and set the * new used value to the smaller value. */ if (col < line->usedCount) { line->clearCount = line->usedCount - col; line->usedCount = col; } #endif } /* * Update all of the rows in the window. */ static void UpdateWindow(void) { int row; for (row = 0; row < rows; row++) UpdateRow(row); } /* * Update the specified row by writing its changed text to the window. */ static void UpdateRow(int row) { LINE * line; const CELL * cell; int drawCount; int textX; int textY; int clearX; int clearY; int colorId; int runCount; char buffer[512]; line = GetLine(row); /* * Get the number of characters to be updated. */ drawCount = line->endUpdate - line->beginUpdate; /* * Calculate the window position of the change. */ textX = TEXT_OFF + charWidth * line->beginUpdate; textY = TEXT_OFF + charAscent + charHeight * row; cell = &line->data[line->beginUpdate]; /* * Loop drawing the characters until they are all done. */ while (drawCount > 0) { /* * Get the color of the first cell of the run * and make that color effective. */ colorId = GETCOLOR(*cell); SetColor(colorId); /* * Count the number of characters in the run having * the same color and copy the characters themselves * into a local buffer while they fit. */ runCount = 0; while ((runCount < drawCount) && (runCount < sizeof(buffer)) && (GETCOLOR(*cell) == colorId)) { buffer[runCount] = GETCHAR(*cell); cell++; runCount++; } /* * Draw the text at the specified location. */ DrawText(textX, textY, buffer, runCount); /* * Adjust the values for the characters used. */ textX += (charWidth * runCount); drawCount -= runCount; } /* * If the end of the line needs clearing then do so. */ if (line->clearCount > 0) { clearX = TEXT_OFF + charWidth * line->usedCount; clearY = TEXT_OFF + charHeight * row; XClearArea(display, mainWid, clearX, clearY, charWidth * line->clearCount, charHeight, False); } /* * Update the LINE values. */ line->beginUpdate = 0; line->endUpdate = 0; line->clearCount = 0; } /* * Draw some text at the specified location. */ static void DrawText(int textX, int textY, const char * buffer, int count) { /* * Clear or fill the area where the new text goes. */ if (currentBackgroundPixel == background) { XClearArea(display, mainWid, textX, textY - charAscent, charWidth * count, charHeight, False); } else { XFillRectangle(display, mainWid, clearGC, textX, textY - charAscent, charWidth * count, charHeight); } /* * Draw the new text. */ XDrawString(display, mainWid, textGC, textX, textY, buffer, count); /* * If the text is bold then draw it again shifted by a pixel. * This is a bit of a hack but is easier than using multiple fonts. */ if (currentColorFlags & COLOR_FLAG_BOLD) { XDrawString(display, mainWid, textGC, textX + 1, textY, buffer, count); } /* * If the text is being underlined then do that. */ if (currentColorFlags & COLOR_FLAG_UNDERLINE) { XDrawLine(display, mainWid, textGC, textX, textY, textX + charWidth * count - 1, textY); } } /* * Set the color being used for drawing text. */ void SetColor(int colorId) { long foregroundPixel; long backgroundPixel; /* * Get the foreground and background pixel values * for the color id. */ if ((colorId < 0) || (colorId >= MAX_COLORS)) colorId = DEFAULT_COLOR_ID; foregroundPixel = foregroundColorTable[colorId]; backgroundPixel = backgroundColorTable[colorId]; /* * Set the colors in the graphics contexts if necessary * and save the new values. */ if (foregroundPixel != currentForegroundPixel) XSetForeground(display, textGC, foregroundPixel); if (backgroundPixel != currentBackgroundPixel) { XSetBackground(display, textGC, backgroundPixel); XSetForeground(display, clearGC, backgroundPixel); } /* * Save the values for checking elsewhere. */ currentForegroundPixel = foregroundPixel; currentBackgroundPixel = backgroundPixel; currentColorFlags = colorFlagsTable[colorId]; } /* * Clear the window and reinitialise the LINE structures to be empty. * This is called after exposure or configuration events when we are * beginning a new page of data. */ static void ClearWindow(void) { LINE * line; int row; /* * Remove all of the information from the rows. */ for (row = 0; row < rows; row++) { line = GetLine(row); line->beginUpdate = 0; line->endUpdate = 0; line->usedCount = 0; line->clearCount = 0; } /* * Clear the window for real. */ XClearWindow(display, mainWid); /* * We now don't need to redraw anymore. */ isRedrawNeeded = FALSE; } /* * Get the LINE structure for the specified row. * This allocates or resizes the structure if required to hold the * current number of columns in the window. */ static LINE * GetLine(int row) { LINE * line; /* * Make sure the row number is valid. */ if ((row < 0) || (row >= rows)) { fprintf(stderr, "Illegal row number\n"); exit(1); } /* * Get the indicated line structure. */ line = lines[row]; /* * If the line structure is NULL then allocate it large enough * to hold the current number of columns in the window. */ if (line == NULL) { line = AllocMemory(LINE_SIZE(cols)); lines[row] = line; line->beginUpdate = 0; line->endUpdate = 0; line->clearCount = 0; line->usedCount = 0; line->alloc = cols; return line; } /* * If the line is large enough for the current number of columns * then return it. */ if (line->alloc >= cols) return line; /* * The line structure is too small so we need to reallocate it. * This only happens if the window has been resized, so in this * case we allocate a larger than needed line to try to prevent * some further reallocations if the resizing is continued. */ line = (LINE *) ReallocMemory(line, LINE_SIZE(cols + COLUMN_REALLOC_SLOP_SIZE)); lines[row] = line; line->alloc = cols + COLUMN_REALLOC_SLOP_SIZE; return line; } #endif /* END CODE */ ips-4.0/main.c0000644000175000017500000002457411363011557012204 0ustar dbelldbell/* * Configurable ps-like program. * Main program. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include #include #include "ips.h" /* * List of columns being shown. */ int showCount; COLUMN *showList[MAX_COLUMNS]; /* * Other global variables. */ ULONG startUptime; /* uptime jiffies at start */ time_t startTime; /* clock time at start */ time_t currentTime; /* current clock time */ long totalMemoryClicks; /* amount of total memory */ long ticksPerSecond; /* number of clock ticks per second */ long pageSize; /* number of bytes in a page */ ULONG liveCounter; /* counter for live procs */ int newCpuIndex; /* new CPU sample index */ int oldCpuIndex; /* old CPU sample index */ BOOL ancientFlag; /* seeing pre-existing procs */ BOOL showThreads; /* show threads */ BOOL noCopy; /* don't copy data for threads */ BOOL noSelf; /* don't show myself */ BOOL noRoot; /* don't show root procs */ BOOL isInfoShown; /* show info line at top */ BOOL noHeader; /* don't show column header */ BOOL myProcs; /* only show my procs */ BOOL activeOnly; /* only show active procs */ BOOL clearScreen; /* clear screen each loop */ BOOL isLooping; /* loop showing status */ BOOL isRunning; /* we still want to run */ BOOL isFrozen; /* data collection is frozen */ BOOL isUpdateForced; /* update once even if frozen */ BOOL isRefreshNeeded; /* need to refresh display */ BOOL useCurses; /* use curses display */ BOOL useX11; /* use X11 display */ BOOL isVertical; /* isVertical output format */ BOOL isTopMode; /* top option was used */ BOOL isTopAuto; /* autosize height for top */ BOOL useOpenFiles; /* using open file info */ BOOL useCurrentDirectory; /* using current dir info */ BOOL useRootDirectory; /* using root dir info */ BOOL useExecInode; /* using executable info */ BOOL useDeviceNames; /* using device name info */ BOOL useUserNames; /* using user name info */ BOOL useGroupNames; /* using group name info */ BOOL useInitSleep; /* using initial sleep */ BOOL useCommand; /* using command line info */ BOOL useSelf; /* using my own proc info */ BOOL useEnvironment; /* using environment info */ BOOL useWaitChan; /* using wait channel symbol */ BOOL useThreads; /* use thread data */ BOOL useStdioTable[3]; /* using various stdio info */ pid_t myPid; /* my pid */ uid_t myUid; /* my real user id */ gid_t myGid; /* my real group id */ dev_t nullDevice; /* device of /dev/null */ ino_t nullInode; /* inode of /dev/null */ int procAllocCount; /* allocated proc structures */ int deathTime; /* seconds for dead processes */ int activeTime; /* seconds for active procs */ int pidCount; /* pids in pidList */ int userCount; /* users in userList */ int groupCount; /* groups in groupList */ int programCount; /* programs in programList */ int outputWidth; /* width of output */ int outputHeight; /* height of output */ int separation; /* blanks between columns */ int sleepTimeMs; /* milliseconds between loops */ int syncTime; /* seconds between syncs */ int initSleepTime; /* seconds for initial sleep */ int topCount; /* number of procs for top */ int percentSeconds; /* seconds for cpu percentages */ int scrollSeconds; /* seconds between scrolling */ int overlapLines; /* lines of overlap */ int skipCount; /* lines to skip in display */ int procShowCount; /* processes wanting showing */ int procTotalCount; /* count of all processes */ int threadShowCount; /* threads wanting showing */ int threadTotalCount; /* count of all threads */ int infoColorId; /* color id for info line */ int headerColorId; /* color id for header line */ DISPLAY * display; /* the output display device */ PROC * processList; /* list of existent procs */ PROC * freeProcessList; /* free proc structure list */ char * geometry; /* window geometry string */ char * fontName; /* font name */ char * foregroundName; /* foreground color name */ char * backgroundName; /* background color name */ char * displayName; /* display name */ const char * displayType = DISPLAY_TYPE_TTY; /* display type */ char emptyString[4]; /* empty string */ char rootString[4] = "/"; /* root path string */ pid_t pidList[MAX_PIDS]; /* pids to be shown */ uid_t userList[MAX_USERS]; /* user ids to be shown */ gid_t groupList[MAX_GROUPS]; /* group ids to be shown */ char programList[MAX_PROGRAMS][MAX_PROGRAM_LEN + 2]; /* * Static procedures. */ static void GetTerminalSize(void); static void SetUseFlags(void); static void VerifyDescriptors(void); static void HandleSigPipe(int); int main(int argc, char ** argv) { ARGS * ap; ARGS args; /* * Make sure someone hasn't closed off our standard file * descriptors so that we don't accidentally overwrite * something with our output. */ VerifyDescriptors(); ap = &args; ap->table = ++argv; ap->count = --argc; myPid = getpid(); myUid = getuid(); myGid = getgid(); DpySetDisplay(NULL); InitializeColors(); DefaultAllOptions(); /* * Parse the system definition file. */ if (!ParseSystemInitFile()) return 1; /* * Check specially for the -noinit option before reading the user's * option file. This option MUST be immediately after the program * name. This check is a bit of a hack. */ if ((ap->count > 0) && (strcmp(ap->table[0], "-noinit") == 0)) { ap->table++; ap->count--; } else if (!ParseUserInitFile()) return 1; /* * Look up and execute the initial macros if they exist. */ if (MacroExists(MACRO_TYPE_OPTION, SYSTEM_INIT_MACRO) && !ExpandOptionName(SYSTEM_INIT_MACRO, 0)) { fprintf(stderr, "Problem expanding system initial macro \"%s\"\n", SYSTEM_INIT_MACRO); return 1; } if (MacroExists(MACRO_TYPE_OPTION, USER_INIT_MACRO) && !ExpandOptionName(USER_INIT_MACRO, 0)) { fprintf(stderr, "Problem expanding system initial macro \"%s\"\n", USER_INIT_MACRO); return 1; } /* * Parse the command line options possibly expanding them. * Indicate that we are level zero so that bare macro names on the * command line will be recognized even if they are lower case. */ if (!ParseOptions(ap, 0)) return 1; /* * Set the flags for what items we need collecting based on * the columns that have been referenced. */ SetUseFlags(); /* * Initialize for collecting of process data. */ if (!InitializeProcessData()) return 1; /* * Catch SIGPIPE so we can be clean if a window is closed. */ signal(SIGPIPE, HandleSigPipe); /* * Initialize for displaying of process status. */ if (!InitializeDisplay()) return 1; /* * If we require a time interval before displaying results * (such as for calculating percentage cpu), then collect * the initial process status and sleep a while. */ InitialProcessScan(); /* * Here is the main loop. It terminates after one iteration * if we just want a snapshot. */ isRunning = TRUE; while (isRunning) { /* * Release any string storage used during the last loop. */ FreeTempStrings(); /* * Collect the new process status if we aren't frozen * unless the update forced flag was also set. */ if (!isFrozen || isUpdateForced) ScanProcesses(); isUpdateForced = FALSE; /* * Recalculate the window size information. */ GetTerminalSize(); /* * Show the selected processes. */ ShowSelectedProcesses(); /* * If we don't want to loop, then we are done. */ if (!isLooping) break; /* * Sleep while handling commands if any. * If we are frozen then sleep a long time (i.e, 10 minutes). */ WaitForCommands(isFrozen ? 60*10*1000 : sleepTimeMs); } /* * Close the display and return success. */ DpyClose(); return 0; } /* * Set the use variables according to the items required by the columns * that have been referenced. These variables are used to avoid * collecting expensive-to-obtain data when it is not required. */ static void SetUseFlags(void) { int i; USEFLAG flags; flags = USE_NONE; /* * Scan the columns that are being shown. */ for (i = 0; i < showCount; i++) flags |= showList[i]->useFlag; /* * Add in the columns that are being sorted by. */ flags |= GetSortingUseFlags(); /* * Add in the columns referenced by conditions. */ flags |= GetConditionUseFlags(); /* * Add in the columns referenced by coloring conditions. */ flags |= GetRowColorUseFlags(); /* * Now set the boolean variables according to the flags. */ if (flags & USE_INIT) useInitSleep = TRUE; if (flags & USE_USER_NAME) useUserNames = TRUE; if (flags & USE_GROUP_NAME) useGroupNames = TRUE; if (flags & USE_DEV_NAME) useDeviceNames = TRUE; if (flags & USE_OPEN_FILE) useOpenFiles = TRUE; if (flags & USE_CURR_DIR) useCurrentDirectory = TRUE; if (flags & USE_ROOT_DIR) useRootDirectory = TRUE; if (flags & USE_EXEC_INODE) useExecInode = TRUE; if (flags & USE_COMMAND) useCommand = TRUE; if (flags & USE_SELF) useSelf = TRUE; if (flags & USE_ENVIRON) useEnvironment = TRUE; if (flags & USE_WCHAN) useWaitChan = TRUE; if (flags & USE_THREADS) useThreads = TRUE; if (flags & USE_STDIN) useStdioTable[0] = TRUE; if (flags & USE_STDOUT) useStdioTable[1] = TRUE; if (flags & USE_STDERR) useStdioTable[2] = TRUE; } /* * Routine called to get the new terminal size from the display device. */ static void GetTerminalSize(void) { outputWidth = DpyGetCols(); outputHeight = DpyGetRows(); if (outputWidth <= 0) outputWidth = 1; if (outputHeight <= 0) outputHeight = 1; /* * If we are automatically doing the top few processes * then set the top count to the number of lines. */ if (isTopAuto) { topCount = outputHeight; if (DpyDoesScroll()) topCount--; if (!noHeader) topCount--; if (isInfoShown) topCount--; if (topCount <= 0) topCount = 1; } } /* * Make sure that the standard file descriptors are opened. * If not, then stop right now without doing anything. * This check is required so that we can't be tricked into * writing error output into opened files. */ static void VerifyDescriptors(void) { struct stat statBuf; if ((fstat(STDIN_FILENO, &statBuf) < 0) || (fstat(STDOUT_FILENO, &statBuf) < 0) || (fstat(STDERR_FILENO, &statBuf) < 0)) { _exit(99); } } /* * Routine to catch SIGPIPE so we can exit cleanly. */ static void HandleSigPipe(int arg) { _exit(2); } /* END CODE */ ips-4.0/color.c0000644000175000017500000001375211360555447012402 0ustar dbelldbell/* * Configurable ps-like program. * Color support routines. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include #include #include "ips.h" #include "expr.h" /* * A pair of color names specifying a foreground and background and * an associated set of flags which modify the appearance of the text. * If either string is empty then that means use the default * foreground or background color. */ typedef struct { const char * foreground; const char * background; int flags; } ColorData; /* * The definition of a color to be applied to rows which meet a * specified condition. */ typedef struct { int colorId; /* color id for row */ TREE tree; /* condition required for coloring */ } RowColor; /* * The table of color data structures. * The table is indexed by the color id, with the first entry being * that of the default color id. */ static int colorCount; static ColorData colorTable[MAX_COLORS]; /* * The table of row coloring conditions. */ static int rowColorCount; static RowColor rowColorTable[MAX_ROW_COLORS]; /* * Initialize the color table. * This defines the DEFAULT_COLOR_ID entry which has the default * foreground and background colors and no color flags. */ void InitializeColors(void) { colorTable[0].foreground = ""; colorTable[0].background = ""; colorTable[0].flags = COLOR_FLAG_NONE; colorCount = 1; } /* * Allocate a color id for the specified pair of colors and flags * (while returning the same id if the same values are specified again). * The string contains a pair of color names separated with a slash. * Either name can be empty or the special "default" value to indicate * the default foreground or background color. If there is no slash * then only the foreground color is specified. If a second slash is * given then a set of color flag characters are parsed. Returns the * color id on success or BAD_COLOR_ID on an failure. */ int AllocateColor(const char * color) { char * dupColor; char * slash; const char * foreground; const char * background; const char * flagsString; ColorData * pair; int colorFlags; int colorId; /* * Copy the name and split it name into its foreground, * background, and flag parts which are separated by slashes. */ dupColor = strdup(color); if (dupColor == 0) return BAD_COLOR_ID; foreground = dupColor; background = ""; flagsString = ""; slash = strchr(dupColor, '/'); if (slash) { *slash++ = '\0'; background = slash; } if (slash) slash = strchr(slash, '/'); if (slash) { *slash++ = '\0'; flagsString = slash; } /* * Parse the flag characters into bits. */ colorFlags = COLOR_FLAG_NONE; while (*flagsString) { switch (*flagsString++) { case 'u': colorFlags |= COLOR_FLAG_UNDERLINE; break; case 'b': colorFlags |= COLOR_FLAG_BOLD; break; default: return BAD_COLOR_ID; } } /* * Check for the default name and change it to blank. */ if (strcmp(foreground, DEFAULT_COLOR_NAME) == 0) foreground = ""; if (strcmp(background, DEFAULT_COLOR_NAME) == 0) background = ""; /* * Search the color table for an existing entry and use that. */ for (colorId = 0; colorId < colorCount; colorId++) { pair = &colorTable[colorId]; if ((strcmp(pair->foreground, foreground) == 0) && (strcmp(pair->background, background) == 0) && (pair->flags == colorFlags)) { return colorId; } } /* * The color pair is new. * Allocate a new entry and return its color id. */ if (colorCount >= MAX_COLORS) { free(dupColor); return BAD_COLOR_ID; } pair = &colorTable[colorCount]; pair->foreground = foreground; pair->background = background; pair->flags = colorFlags; return colorCount++; } /* * Define all of the colors that have been allocated. * This is called within the opening code of the display. */ BOOL DefineColors(void) { int colorId; const ColorData * colorData; for (colorId = 0; colorId < colorCount; colorId++) { colorData = &colorTable[colorId]; if (!DpyDefineColor(colorId, colorData->foreground, colorData->background, colorData->flags)) { return FALSE; } } return TRUE; } /* * Clear the row color conditions. * This can lose memory but that happens only on startup. */ void ClearRowColorConditions(void) { rowColorCount = 0; } /* * Allocate and parse a new row coloring entry for the specified condition. * The color is a foreground/background/flags value separated by an optional slash. * Returns TRUE on success. */ BOOL ParseRowColorCondition(const char * color, const char * condition) { RowColor * rowColor; if (rowColorCount >= MAX_ROW_COLORS) return FALSE; rowColor = &rowColorTable[rowColorCount]; rowColor->colorId = AllocateColor(color); if (rowColor->colorId == BAD_COLOR_ID) return FALSE; if (!ParseTree(&rowColor->tree, condition, 0)) return FALSE; rowColorCount++; return TRUE; } /* * Return the use flags for the row color conditions. */ USEFLAG GetRowColorUseFlags(void) { USEFLAG useFlags = USE_NONE; int index; for (index = 0; index < rowColorCount; index++) { useFlags |= GetNodeUseFlags(rowColorTable[index].tree.root); } return useFlags; } /* * Evaluate and return the color id to be used for a process row. * The default color is returned if there are no conditions. */ int EvaluateRowColor(const PROC * proc) { int colorId = DEFAULT_COLOR_ID; int index; BOOL isWanted; RowColor * rowColor; VALUE value; for (index = 0; index < rowColorCount; index++) { isWanted = FALSE; rowColor = &rowColorTable[index]; rowColor->tree.proc = proc; value = EvaluateNode(&rowColor->tree, rowColor->tree.root); if ((value.type == VALUE_NUMBER) || (value.type == VALUE_BOOLEAN)) { isWanted = (value.intVal != 0); } else if (value.type == VALUE_STRING) { isWanted = (*value.strVal != '\0'); } if (isWanted) colorId = rowColor->colorId; } return colorId; } /* END CODE */ ips-4.0/utils.c0000644000175000017500000004724111360562661012420 0ustar dbelldbell/* * Configurable ps-like program. * Useful utility routines. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include #include #include #include #include "ips.h" /* * Definitions for collecting and looking up user names, group names, * and device names. */ #define DEVICE_DIR "/dev" /* path of device directory */ #define DEVICE_NULL "/dev/null" /* the null device name */ #define MAX_DEV_PATH_LEN 512 /* maximum size of device path */ #define MAX_NAME_LEN 16 /* length of user or group name */ #define MAX_DEV_LEN 32 /* max length of saved device name */ #define MAX_DEV_DEPTH 3 /* max depth of /dev name collection */ #define USERNAME_ALLOC 1000 /* reallocation size for user names */ #define GROUPNAME_ALLOC 1000 /* reallocation size for group names */ #define DEVNAME_ALLOC 1000 /* reallocation size for device names */ /* * Structure holding login or group names corresponding to numeric ids. */ typedef struct { union { uid_t uid; gid_t gid; } id; /* user or group id */ char name[MAX_NAME_LEN + 2]; /* user or group name */ } NAME; /* * Structure to hold information about devices. * This is only used for character devices. */ typedef struct { dev_t id; /* device id */ dev_t dev; /* dev device is on */ ino_t inode; /* inode device is on */ char name[MAX_DEV_LEN + 2]; /* name of device */ } DEVICE; /* * Table of user names. */ static int userNameCount; static int userNameAvail; static NAME * userNameTable; /* * Table of group names. */ static int groupNameCount; static int groupNameAvail; static NAME * groupNameTable; /* * Table of device names. */ static BOOL devNameCollected; static int devNameCount; static int devNameAvail; static DEVICE * devNameTable; /* * Definitions for allocation of temporary string values. * These buffers are linked together, and freed all at once. */ #define TEMPSTR_ALLOC_SIZE (1024 * 16) #define TEMPSTR_MAX_SIZE 512 typedef struct TEMPSTR TEMPSTR; struct TEMPSTR { TEMPSTR * next; char buf[TEMPSTR_ALLOC_SIZE]; }; /* * Amount of space left in the most recently allocated buffer. */ static int tempStrAvail; /* * Linked list of string buffers. * Only the first one in the list has space for new allocation. */ static TEMPSTR * tempStrList; /* * Definitions for shared hashed strings which are individually allocated and * freed. These strings are used for holding environment variable strings * since they are very large and many processes would share the same strings. * They are linked into hash tables for quick lookup. */ #define SHARED_HASH_SIZE 101 #define SHARED_MAGIC 749712759 typedef struct SHARED_STR SHARED_STR; struct SHARED_STR { long magic; /* magic number */ int tableIndex; /* index into table of the entry */ int count; /* reference counter */ int len; /* length of string */ SHARED_STR * next; /* next string in list */ char buf[4]; /* string buffer (variable sized) */ }; static SHARED_STR * sharedTable[SHARED_HASH_SIZE]; /* * The offset into the entry of the string buffer. */ #define SHARED_BUF_OFFSET ((int) (((SHARED_STR *) 0)->buf)) /* * Local routines. */ static void DeviceNameRecursion(char *, int, int); /* * Allocate a shared string holding the specified text of the given length. * If the string is already in the shared string table, then it is found and * its use count is simply incremented. Otherwise a new string is inserted. * The terminating NULL character is not included in the supplied length. * Returns NULL if the string could not be allocated. */ char * AllocateSharedString(const char * str, int len) { int begCh; int midCh; int endCh; int tableIndex; SHARED_STR * entry; if (len < 0) return NULL; if (len == 0) return emptyString; if ((len == 1) && (*str == '/')) return rootString; /* * Get the first, middle, and last characters for comparison. */ begCh = str[0]; midCh = str[len / 2]; endCh = str[len - 1]; /* * Hash the characters along with the string length to select * an offset into the hash table to use for this string. */ tableIndex = len + (begCh << 16) + (midCh << 20) + (endCh << 24); tableIndex = ((unsigned int) tableIndex) % SHARED_HASH_SIZE; /* * Search the selected string list in the table for the string. * Test the string length and the first, middle, and last characters. * If they all match then compare the whole string explicitly. */ for (entry = sharedTable[tableIndex]; entry; entry = entry->next) { if ((entry->len != len) || (entry->buf[0] != begCh)) continue; if (entry->buf[len / 2] != midCh) continue; if (entry->buf[len - 1] != endCh) continue; if (memcmp(entry->buf, str, len) != 0) continue; /* * The string was found. * Increment the use counter and return it. */ entry->count++; return entry->buf; } /* * The string is not in the table, so we have to allocate a new one * and link it in at the front of the appropriate shared table list. * Note: the buffer in the structure has room for the terminating NULL. */ entry = (SHARED_STR *) malloc(sizeof(SHARED_STR) + len); if (entry == NULL) return NULL; entry->next = sharedTable[tableIndex]; sharedTable[tableIndex] = entry; entry->magic = SHARED_MAGIC; entry->tableIndex = tableIndex; entry->count = 1; entry->len = len; memcpy(entry->buf, str, len); entry->buf[len] = '\0'; return entry->buf; } /* * Free a string that had been allocated as a shared string. * This just decrements the usage count, and if it goes to zero, * then frees the string. */ void FreeSharedString(char * str) { SHARED_STR * entry; SHARED_STR * prev; int tableIndex; if ((str == NULL) || (str == emptyString) || (str == rootString)) return; /* * Back up to the entry header of the string and make sure that it * looks reasonable. */ entry = (SHARED_STR *) (str - SHARED_BUF_OFFSET); tableIndex = entry->tableIndex; if ((entry->magic != SHARED_MAGIC) || (tableIndex < 0) || (tableIndex >= SHARED_HASH_SIZE) || (entry->len < 0)) { fprintf(stderr, "Freeing bad shared string header\n"); exit(1); } /* * Decrement the use counter, and if it is still positive, * then the string is still in use. */ entry->count--; if (entry->count > 0) return; /* * The string needs freeing. * See if it is the first string in the hash table. * If so, then removing it is easy. */ prev = sharedTable[tableIndex]; if (prev == entry) { sharedTable[tableIndex] = entry->next; free((char *) entry); return; } /* * We have to search the list for it. * It is a fatal error if the string is not found. */ while (prev) { if (prev->next != entry) { prev = prev->next; continue; } prev->next = entry->next; free((char *) entry); return; } fprintf(stderr, "Freeing unknown shared string\n"); exit(1); } /* * Allocate a string which can later be freed along with all other such * allocated strings. The string cannot be individually freed. * To avoid wastage, there is a maximum size that can be allocated. * Prints an error message and exits on failure. */ char * AllocTempString(int len) { TEMPSTR * head; char * cp; if ((len <= 0) || (len > TEMPSTR_MAX_SIZE)) { fprintf(stderr, "Allocating bad length %d\n", len); exit(1); } if (len > tempStrAvail) { head = (TEMPSTR *) AllocMemory(sizeof(TEMPSTR)); head->next = tempStrList; tempStrList = head; tempStrAvail = TEMPSTR_ALLOC_SIZE; } cp = &tempStrList->buf[TEMPSTR_ALLOC_SIZE - tempStrAvail]; tempStrAvail -= len; return cp; } /* * Copy a null-terminated string into a temporary string. * The new string cannot be individually freed. * Prints an error message and exits on failure. */ char * CopyTempString(const char * oldcp) { char * cp; int len; len = strlen(oldcp) + 1; cp = AllocTempString(len); memcpy(cp, oldcp, len); return cp; } /* * Free all temporary strings. */ void FreeTempStrings(void) { TEMPSTR * head; while (tempStrList) { head = tempStrList; tempStrList = head->next; free((char *) head); } tempStrAvail = 0; } /* * Allocate a buffer using malloc while complaining and exiting if the * allocation fails. The array should eventually be freed using free(3). */ void * AllocMemory(int len) { void * buffer; buffer = (void *) malloc(len); if (buffer == NULL) { fprintf(stderr, "Failed to allocate %d bytes\n", len); exit(1); } return buffer; } /* * Reallocate a buffer using realloc while complaining and exiting if the * allocation fails. The array should eventually be freed using free(3). */ void * ReallocMemory(void * oldBuffer, int len) { char * buffer; buffer = (void *) realloc(oldBuffer, len); if (buffer == NULL) { fprintf(stderr, "Failed to reallocate %d bytes\n", len); exit(1); } return buffer; } /* * Copy a null-terminated string value into a newly allocated string. * This does not return if the allocation fails. */ char * CopyString(const char * str) { char * newStr; int len; len = strlen(str) + 1; newStr = (char *) AllocMemory(len); memcpy(newStr, str, len); return newStr; } /* * Replace a string value by freeing it and replacing it with a * newly allocated value or NULL. */ void ReplaceString(char ** variable, const char * newValue) { free(*variable); *variable = NULL; if (newValue) *variable = CopyString(newValue); } /* * Replace null characters in the specified memory buffer with spaces * and replace other unprintable characters with question marks. */ void MakePrintable(char * cp, int len) { int ch; while (len-- > 0) { ch = *cp; if (ch == '\0') *cp = ' '; else if ((ch < ' ') || (ch >= 0x7f)) *cp = '?'; cp++; } } /* * Routine to see if a text string is matched by a wildcard pattern. * Returns TRUE if the text is matched, or FALSE if it is not matched * or if the pattern is invalid. * * matches zero or more characters * ? matches a single character * [abc] matches 'a', 'b' or 'c' * \c quotes character c * Adapted from code written by Ingo Wilken. */ BOOL PatternMatch(const char * text, const char * pattern) { const char * retryPat; const char * retryTxt; int ch; BOOL isFound; retryPat = NULL; retryTxt = NULL; while (*text || *pattern) { ch = *pattern++; switch (ch) { case '*': retryPat = pattern; retryTxt = text; break; case '[': isFound = FALSE; while ((ch = *pattern++) != ']') { if (ch == '\\') ch = *pattern++; if (ch == '\0') return FALSE; if (*text == ch) isFound = TRUE; } if (!isFound) { pattern = retryPat; text = ++retryTxt; } /* * Fall into next case */ case '?': if (*text++ == '\0') return FALSE; break; case '\\': ch = *pattern++; if (ch == '\0') return FALSE; /* * Fall into next case */ default: if (*text == ch) { if (*text) text++; break; } if (*text) { pattern = retryPat; text = ++retryTxt; break; } return FALSE; } if (pattern == NULL) return FALSE; } return TRUE; } /* * Collect all of the user names in the system. * This is only done once per run. */ void CollectUserNames(void) { const struct passwd * pwd; NAME * name; if (userNameCount > 0) return; while ((pwd = getpwent()) != NULL) { if (userNameCount >= userNameAvail) { userNameAvail += USERNAME_ALLOC; userNameTable = (NAME *) realloc(userNameTable, (userNameAvail * sizeof(NAME))); if (userNameTable == NULL) { fprintf(stderr, "Cannot allocate memory\n"); exit(1); } } name = &userNameTable[userNameCount++]; name->id.uid = pwd->pw_uid; strncpy(name->name, pwd->pw_name, MAX_NAME_LEN); name->name[MAX_NAME_LEN] = '\0'; } endpwent(); } /* * Find the user name for the specified user id. * Returns NULL if the user name is not known. */ const char * FindUserName(uid_t uid) { const NAME * user; int count; user = userNameTable; count = userNameCount; while (count-- > 0) { if (user->id.uid == uid) return user->name; user++; } return NULL; } /* * Find the user id associated with a user name. * Returns BAD_UID if the user name is not known. */ uid_t FindUserId(const char * name) { const NAME * user; int count; user = userNameTable; count = userNameCount; while (count-- > 0) { if (strcmp(user->name, name) == 0) return user->id.uid; user++; } return BAD_UID; } /* * Collect all of the group names in the system. * This is only done once per run. */ void CollectGroupNames(void) { const struct group * grp; NAME * name; if (groupNameCount > 0) return; while ((grp = getgrent()) != NULL) { if (groupNameCount >= groupNameAvail) { groupNameAvail += GROUPNAME_ALLOC; groupNameTable = (NAME *) realloc(groupNameTable, (groupNameAvail * sizeof(NAME))); if (groupNameTable == NULL) { fprintf(stderr, "Cannot allocate memory\n"); exit(1); } } name = &groupNameTable[groupNameCount++]; name->id.gid = grp->gr_gid; strncpy(name->name, grp->gr_name, MAX_NAME_LEN); name->name[MAX_NAME_LEN] = '\0'; } endgrent(); } /* * Find the group name for the specified group id. * Returns NULL if the group name is not known. */ const char * FindGroupName(gid_t gid) { const NAME * group; int count; group = groupNameTable; count = groupNameCount; while (count-- > 0) { if (group->id.gid == gid) return group->name; group++; } return NULL; } /* * Find the group id associated with a group name. * Returns BAD_GID if the group name is not known. */ gid_t FindGroupId(const char * name) { const NAME * group; int count; group = groupNameTable; count = groupNameCount; while (count-- > 0) { if (strcmp(group->name, name) == 0) return group->id.gid; group++; } return BAD_GID; } /* * Collect all device names that might be terminals or other * character devices. This is done only once per run. */ void CollectDeviceNames(void) { char pathBuffer[MAX_DEV_PATH_LEN + 2]; if (devNameCollected) return; strcpy(pathBuffer, DEVICE_DIR); DeviceNameRecursion(pathBuffer, strlen(pathBuffer), MAX_DEV_DEPTH); devNameCollected = TRUE; } /* * Recursive routine to scan the /dev directory structure collecting * useful device information. */ static void DeviceNameRecursion(char * pathBuffer, int usedLength, int maxDepth) { DIR * dir; const struct dirent * dp; DEVICE * device; int len; struct stat statBuf; if (maxDepth <= 0) return; /* * Open the directory and scan its entries. */ dir = opendir(pathBuffer); if (dir == NULL) return; while ((dp = readdir(dir)) != NULL) { /* * Make sure that the buffer is large enough for the * full new path. */ len = strlen(dp->d_name); if (usedLength + len >= MAX_DEV_PATH_LEN) break; /* * Ignore the . and .. entries. */ if ((len == 1) && (dp->d_name[0] == '.')) continue; if ((len == 2) && (dp->d_name[0] == '.') && (dp->d_name[1] == '.')) { continue; } /* * Build the full path and get its status. */ pathBuffer[usedLength] = '/'; strcpy(pathBuffer + usedLength + 1, dp->d_name); if (lstat(pathBuffer, &statBuf) < 0) continue; /* * If this is another directory level then walk down it * too if the depth isn't exceeded. */ if (S_ISDIR(statBuf.st_mode)) { DeviceNameRecursion(pathBuffer, usedLength + len + 1, maxDepth - 1); continue; } /* * If this isn't a character device then ignore it * since it can't be a terminal. */ if (!S_ISCHR(statBuf.st_mode)) continue; /* * Look for /dev/null for other uses. */ if (strcmp(pathBuffer, DEVICE_NULL) == 0) { nullDevice = statBuf.st_dev; nullInode = statBuf.st_ino; } /* * We want to store the device name. * Reallocate the table if necessary. */ if (devNameCount >= devNameAvail) { devNameAvail += DEVNAME_ALLOC; devNameTable = (DEVICE *) realloc(devNameTable, (devNameAvail * sizeof(DEVICE))); if (devNameTable == NULL) { fprintf(stderr, "Cannot allocate memory\n"); exit(1); } } /* * Store the device information, including its path * beyond the /dev prefix. */ device = &devNameTable[devNameCount++]; device->id = statBuf.st_rdev; device->dev = statBuf.st_dev; device->inode = statBuf.st_ino; strncpy(device->name, pathBuffer + sizeof(DEVICE_DIR), MAX_DEV_LEN); device->name[MAX_DEV_LEN] = '\0'; } closedir(dir); } /* * Find the device name for the specified device id. * Returns NULL if the device name is not known. */ const char * FindDeviceName(dev_t devid) { const DEVICE * device; int count; if (devid <= 0) return "-"; device = devNameTable; count = devNameCount; while (count-- > 0) { if (device->id == devid) return device->name; device++; } return NULL; } /* * Find the device name for a device and inode pair. * These arguments are for the inode which contains the device. */ const char * FindDeviceFromInode(dev_t dev, ino_t inode) { const DEVICE * device; int count; device = devNameTable; count = devNameCount; while (count-- > 0) { if ((device->dev == dev) && (device->inode == inode)) return device->name; device++; } return NULL; } /* * Parse a decimal number from a string and return it's value. * The supplied string pointer is updated past the number read. * Leading spaces or tabs are ignored. * An invalid character stops the parse. */ long GetDecimalNumber(const char ** cpp) { long value; BOOL isNeg; const char * cp; cp = *cpp; isNeg = FALSE; while (isBlank(*cp)) cp++; if (*cp == '-') { isNeg = TRUE; cp++; } value = 0; while (isDigit(*cp)) value = value * 10 + *cp++ - '0'; if (isNeg) value = -value; *cpp = cp; return value; } /* * Parse a floating point number from a string and return it's value. * The supplied string pointer is updated past the number read. * Leading spaces or tabs are ignored. * An invalid character stops the parse. */ double GetFloatingNumber(const char ** cpp) { double value; double scale; BOOL isNeg; const char * cp; cp = *cpp; isNeg = FALSE; while (isBlank(*cp)) cp++; if (*cp == '-') { isNeg = TRUE; cp++; } value = 0.0; scale = 1.0; while (isDigit(*cp)) value = value * 10 + *cp++ - '0'; if (*cp == '.') cp++; while (isDigit(*cp)) { value = value * 10.0 + *cp++ - '0'; scale *= 10.0; } value /= scale; if (isNeg) value = -value; *cpp = cp; return value; } /* * Calculate the elapsed time in milliseconds between two timevals. * The new timeval can be NULL in which case the current time is used. * If the time appears to go backwards or if the old time is zero * then a value of 0 is returned. */ long ElapsedMilliSeconds(const struct timeval * oldTime, const struct timeval * newTime) { long elapsedSeconds; long elapsedMicroSeconds; struct timeval currentTime; /* * Assume no elapsed time if the old time is zero. */ if (oldTime->tv_sec == 0) return 0; /* * If no new time is given then get it now. */ if (newTime == NULL) { GetTimeOfDay(¤tTime); newTime = ¤tTime; } elapsedSeconds = newTime->tv_sec - oldTime->tv_sec; elapsedMicroSeconds = newTime->tv_usec - oldTime->tv_usec; if (elapsedMicroSeconds < 0) { elapsedMicroSeconds += 1000000; elapsedSeconds--; } if (elapsedSeconds < 0) return 0; return (elapsedSeconds * 1000) + (elapsedMicroSeconds / 1000); } /* * Get the current time of day, checking whether it fails. */ void GetTimeOfDay(struct timeval * retTimeVal) { if (gettimeofday(retTimeVal, NULL) == 0) return; fprintf(stderr, "Cannot get time of day\n"); exit(1); } /* END CODE */ ips-4.0/expr.h0000644000175000017500000001060111360272601012221 0ustar dbelldbell/* * Configurable ps-like program. * Definitions for expression parsing. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #ifndef EXPR_H #define EXPR_H #include "ips.h" #define isLower(ch) (((ch) >= 'a') && ((ch) <= 'z')) #define isUpper(ch) (((ch) >= 'A') && ((ch) <= 'Z')) #define isOctal(ch) (((ch) >= '0') && ((ch) <= '7')) #define isHex(ch) ((((ch) >= 'a') && ((ch) <= 'f')) || \ (((ch) >= 'A') && ((ch) <= 'F')) || isDigit(ch)) #define isBegSymbol(ch) (isLower(ch) || isUpper(ch)) #define isSymbol(ch) (isBegSymbol(ch) || isDigit(ch) || ((ch) == '_')) /* * The maximum number of children for a node. * This limits functions to this number of arguments. */ #define CHILDS 3 /* * Function ids for use in expressions. * Encoded in the id is the number of arguments. */ #define FUNCTION_BUILD(op, args) (((op) * 10) + (args)) #define FUNCTION_OP(func) ((func) / 10) #define FUNCTION_ARGS(func) ((func) % 10) /* * Opcodes for expressions. */ typedef int OP; #define OP_NONE ((OP) 0) #define OP_NOT ((OP) 1) #define OP_ANDAND ((OP) 2) #define OP_OROR ((OP) 3) #define OP_COLUMN_BASE ((OP) 4) #define OP_COLUMN_SHOW ((OP) 5) #define OP_COLUMN_TEST ((OP) 6) #define OP_EQUAL ((OP) 7) #define OP_NOTEQUAL ((OP) 8) #define OP_LESS ((OP) 9) #define OP_LESSEQUAL ((OP) 10) #define OP_GREATER ((OP) 11) #define OP_GREATEREQUAL ((OP) 12) #define OP_ADD ((OP) 13) #define OP_SUBTRACT ((OP) 14) #define OP_MULTIPLY ((OP) 15) #define OP_DIVIDE ((OP) 16) #define OP_MODULO ((OP) 17) #define OP_NEGATE ((OP) 18) #define OP_AND ((OP) 19) #define OP_OR ((OP) 20) #define OP_COMPLEMENT ((OP) 21) #define OP_ALTERNATION ((OP) 22) #define OP_STRING ((OP) 23) #define OP_NUMBER ((OP) 24) #define OP_LEFTSHIFT ((OP) 25) #define OP_RIGHTSHIFT ((OP) 26) #define OP_XOR ((OP) 27) #define OP_FUNCTION ((OP) 28) /* * Different token types. */ typedef int TOKEN; #define TOKEN_BAD ((TOKEN) 0) #define TOKEN_EOF ((TOKEN) 1) #define TOKEN_SYMBOL ((TOKEN) 2) #define TOKEN_NUMBER ((TOKEN) 3) #define TOKEN_COMMA ((TOKEN) 4) #define TOKEN_PERIOD ((TOKEN) 5) #define TOKEN_LEFTPAREN ((TOKEN) 6) #define TOKEN_RIGHTPAREN ((TOKEN) 7) #define TOKEN_NOT ((TOKEN) 8) #define TOKEN_COMPLEMENT ((TOKEN) 9) #define TOKEN_ANDAND ((TOKEN) 10) #define TOKEN_OROR ((TOKEN) 11) #define TOKEN_EQUAL ((TOKEN) 12) #define TOKEN_NOTEQUAL ((TOKEN) 13) #define TOKEN_LESS ((TOKEN) 14) #define TOKEN_LESSEQUAL ((TOKEN) 15) #define TOKEN_GREATER ((TOKEN) 16) #define TOKEN_GREATEREQUAL ((TOKEN) 17) #define TOKEN_STRING ((TOKEN) 18) #define TOKEN_PLUS ((TOKEN) 19) #define TOKEN_MINUS ((TOKEN) 20) #define TOKEN_MULTIPLY ((TOKEN) 21) #define TOKEN_DIVIDE ((TOKEN) 22) #define TOKEN_MODULO ((TOKEN) 23) #define TOKEN_AND ((TOKEN) 24) #define TOKEN_OR ((TOKEN) 25) #define TOKEN_XOR ((TOKEN) 26) #define TOKEN_QUESTIONMARK ((TOKEN) 27) #define TOKEN_COLON ((TOKEN) 28) #define TOKEN_LEFTSHIFT ((TOKEN) 29) #define TOKEN_RIGHTSHIFT ((TOKEN) 30) /* * A node of the expression tree. * For non-leaf nodes, the node contains pointers to subnodes which are * evaluated first, and then operated on to construct this node's value. * For leaf nodes, the node contains the value to be returned. * This value can be a number, string, or column. */ typedef struct NODE NODE; struct NODE { OP op; /* operation for this node */ NODE * child[CHILDS]; /* children of this node */ COLUMN *column; /* named column */ char * strVal; /* string value */ long intVal; /* integer value */ }; /* * All the data associated with an expression tree. */ typedef struct { char * expr; /* expression string being parsed */ char * modExpr; /* modifyable copy of expression */ char * cp; /* current position being parsed */ TOKEN token; /* current token */ char * tokenStr; /* string value of current token */ long tokenInt; /* integer value of current token */ BOOL rescanToken; /* reread token next call */ NODE * root; /* root of expression tree */ const PROC * proc; /* process to evaluate for */ int depth; /* depth of sub-expressions */ BOOL failed; /* there is an error in the parse */ } TREE; /* * External procedures. */ extern BOOL ParseTree(TREE * tree, const char * str, int depth); extern VALUE EvaluateNode(TREE * tree, const NODE * node); extern USEFLAG GetNodeUseFlags(const NODE * node); #endif /* END CODE */ ips-4.0/Makefile0000644000175000017500000000113411364750220012535 0ustar dbelldbell# # Makefile for IPS. # # Normally, IPS will be able to display to the dumb tty, the terminal # using curses, or an X11 window. If you do not want the X11 display # capability, then remove the X-related options from the build lines. # OBJS = columns.o cond.o file.o linux.o macro.o main.o options.o \ proc.o show.o sort.o utils.o display.o ttydisplay.o \ cursesdisplay.o x11display.o color.o commands.o CFLAGS = -Wall -Wmissing-prototypes -O3 -DX11 ips: $(OBJS) Makefile $(CC) -o ips $(OBJS) -lcurses -L/usr/X11R6/lib -lX11 clean: rm -f ips $(OBJS) $(OBJS): ips.h cond.o: expr.h sort.o: expr.h ips-4.0/display.c0000644000175000017500000000501011356313061012702 0ustar dbelldbell/* * Configurable ps-like program. * High-level display device routines. * These global routines call the currently selected display routines. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include "ips.h" /* * The current display device. */ static DISPLAY * display; /* * Set the display type to use for later dpy calls. * A NULL or illegal type sets the display type to a dumb terminal. * Returns TRUE if the display type was NULL or was known. */ BOOL DpySetDisplay(const char * type) { if ((type == NULL) || (strcmp(type, DISPLAY_TYPE_TTY) == 0)) display = GetTtyDisplay(); else if (strcmp(type, DISPLAY_TYPE_CURSES) == 0) display = GetCursesDisplay(); #ifdef X11 else if (strcmp(type, DISPLAY_TYPE_X11) == 0) display = GetX11Display(); #endif else { display = GetTtyDisplay(); return FALSE; } return TRUE; } BOOL DpyOpen(void) { return display->open(display); } BOOL DpyDefineColor(int colorId, const char * foreground, const char * background, int flags) { return display->defineColor(display, colorId, foreground, background, flags); } void DpyCreateWindow() { display->createWindow(display); } void DpyClose(void) { display->close(display); } void DpySetColor(int colorId) { display->setColor(display, colorId); } void DpyRefresh(void) { display->refresh(display); } void DpyBeginPage(void) { display->beginPage(display); } void DpyChar(int ch) { display->putChar(display, ch); } void DpyString(const char * str) { display->putString(display, str); } void DpyBuffer(const char * buffer, int length) { display->putBuffer(display, buffer, length); } void DpyEndPage(void) { display->endPage(display); } /* * Handle any events from the display device while waiting for the * specified number of milliSeconds. This call returns early if an * input character is available for reading or if a refresh is required. * Returns TRUE if a refresh is required due to a window resize. */ BOOL DpyEventWait(int milliSeconds) { return display->eventWait(display, milliSeconds); } BOOL DpyInputReady(void) { return display->inputReady(display); } int DpyReadChar(void) { return display->readChar(display); } void DpyRingBell(void) { display->ringBell(display); } int DpyGetRows(void) { return display->getRows(display); } int DpyGetCols(void) { return display->getCols(display); } BOOL DpyDoesScroll(void) { return display->doesScroll(display); } /* END CODE */ ips-4.0/options.c0000644000175000017500000011024611363015354012742 0ustar dbelldbell/* * Configurable ps-like program. * Command line option parsing. * * Copyright (c) 2010 David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. */ #include "ips.h" /* * Definitions for version printout. */ #define VERSION "4.0" #define AUTHOR_NAME "David I. Bell" #define AUTHOR_EMAIL "dbell@canb.auug.org.au" /* * Command line options. */ #define OPT_ILLEGAL 0 #define OPT_SET_COLUMNS 1 #define OPT_ADD_COLUMNS 2 #define OPT_REMOVE_COLUMNS 3 #define OPT_SEPARATION 4 #define OPT_WIDTH 5 #define OPT_SLEEP_TIME 6 #define OPT_NO_SELF 7 #define OPT_MY_PROCS 9 #define OPT_ACTIVE_ONLY 10 #define OPT_NO_ROOT 11 #define OPT_CLEAR_SCREEN 12 #define OPT_READ_FILE 13 #define OPT_NORMAL_SORT 14 #define OPT_REVERSE_SORT 15 #define OPT_NORMAL_SORT_EXPR 16 #define OPT_NO_SORT 17 #define OPT_LOOP 18 #define OPT_INIT_TIME 19 #define OPT_CONDITION 20 #define OPT_TOP_COUNT 21 #define OPT_NO_HEADER 22 #define OPT_PIDS 23 #define OPT_USERS 24 #define OPT_HELP 25 #define OPT_LIST_COLUMNS 26 #define OPT_END 27 #define OPT_NO_INIT 28 #define OPT_ACTIVE_TIME 29 #define OPT_LIST_MACROS 30 #define OPT_VERSION 31 #define OPT_DEFAULT 32 #define OPT_SYNC_TIME 33 #define OPT_GROUPS 34 #define OPT_VERTICAL 35 #define OPT_REVERSE_SORT_EXPR 36 #define OPT_COLUMN_WIDTH 37 #define OPT_PROGRAMS 38 #define OPT_CURSES 39 #define OPT_X11 40 #define OPT_ONCE 41 #define OPT_DISPLAY 42 #define OPT_FONT 43 #define OPT_FOREGROUND 44 #define OPT_BACKGROUND 45 #define OPT_SCROLL_TIME 46 #define OPT_OVERLAP_LINES 47 #define OPT_INFO 48 #define OPT_DEATH_TIME 49 #define OPT_GEOMETRY 50 #define OPT_SHOW_THREADS 51 #define OPT_USE_THREADS 52 #define OPT_NO_COPY 53 #define OPT_INFO_COLOR 54 #define OPT_HEADER_COLOR 55 #define OPT_ROW_COLOR 56 #define OPT_PERCENT_SECONDS 57 #define OPT_LAST_OPTION 57 /* * Table of mappings between option names and option ids. */ typedef struct { char * name; /* option name */ int id; /* option id */ } OPTION; static OPTION option_table[OPT_LAST_OPTION + 2] = { {"col", OPT_SET_COLUMNS}, {"addcol", OPT_ADD_COLUMNS}, {"remcol", OPT_REMOVE_COLUMNS}, {"sep", OPT_SEPARATION}, {"width", OPT_WIDTH}, {"sleep", OPT_SLEEP_TIME}, {"noself", OPT_NO_SELF}, {"my", OPT_MY_PROCS}, {"active", OPT_ACTIVE_ONLY}, {"noroot", OPT_NO_ROOT}, {"clear", OPT_CLEAR_SCREEN}, {"read", OPT_READ_FILE}, {"sort", OPT_NORMAL_SORT}, {"revsort", OPT_REVERSE_SORT}, {"sortexpr", OPT_NORMAL_SORT_EXPR}, {"revsortexpr", OPT_REVERSE_SORT_EXPR}, {"nosort", OPT_NO_SORT}, {"loop", OPT_LOOP}, {"once", OPT_ONCE}, {"curses", OPT_CURSES}, {"x11", OPT_X11}, {"vert", OPT_VERTICAL}, {"initsleep", OPT_INIT_TIME}, {"cond", OPT_CONDITION}, {"top", OPT_TOP_COUNT}, {"noheader", OPT_NO_HEADER}, {"pid", OPT_PIDS}, {"user", OPT_USERS}, {"group", OPT_GROUPS}, {"program", OPT_PROGRAMS}, {"h", OPT_HELP}, {"help", OPT_HELP}, {"?", OPT_HELP}, {"listcolumns", OPT_LIST_COLUMNS}, {"end", OPT_END}, {"noinit", OPT_NO_INIT}, {"activetime", OPT_ACTIVE_TIME}, {"deathtime", OPT_DEATH_TIME}, {"listmacros", OPT_LIST_MACROS}, {"version", OPT_VERSION}, {"default", OPT_DEFAULT}, {"synctime", OPT_SYNC_TIME}, {"colwidth", OPT_COLUMN_WIDTH}, {"display", OPT_DISPLAY}, {"font", OPT_FONT}, {"foreground", OPT_FOREGROUND}, {"background", OPT_BACKGROUND}, {"scroll", OPT_SCROLL_TIME}, {"overlap", OPT_OVERLAP_LINES}, {"info", OPT_INFO}, {"geometry", OPT_GEOMETRY}, {"showthreads", OPT_SHOW_THREADS}, {"usethreads", OPT_USE_THREADS}, {"nocopy", OPT_NO_COPY}, {"infocolor", OPT_INFO_COLOR}, {"headercolor", OPT_HEADER_COLOR}, {"rowcolor", OPT_ROW_COLOR}, {"percentseconds", OPT_PERCENT_SECONDS}, {NULL, OPT_ILLEGAL} }; /* * Static routines. */ static BOOL ReadFile(ARGS *); static BOOL SetSleepTime(ARGS *); static BOOL SetActiveTime(ARGS *); static BOOL SetDeathTime(ARGS *); static BOOL SetSyncTime(ARGS *); static BOOL SetTopCount(ARGS *); static BOOL SetInitTime(ARGS *); static BOOL SetCondition(ARGS *); static BOOL SetSeparation(ARGS *); static BOOL SetWidth(ARGS *); static BOOL SetColumns(ARGS *); static BOOL SetColumnWidth(ARGS *); static BOOL SetPids(ARGS *); static BOOL SetUsers(ARGS *); static BOOL SetGroups(ARGS *); static BOOL SetPrograms(ARGS *); static BOOL SetFont(ARGS *); static BOOL SetGeometry(ARGS *); static BOOL SetForeground(ARGS *); static BOOL SetBackground(ARGS *); static BOOL SetDisplay(ARGS *); static BOOL SetScrollTime(ARGS *); static BOOL SetOverlapLines(ARGS *); static BOOL SetInfoColor(ARGS *); static BOOL SetHeaderColor(ARGS *); static BOOL SetDefault(ARGS *); static BOOL AddColumns(ARGS *); static BOOL RemoveColumns(ARGS *); static BOOL SetRowColorCondition(ARGS * ap); static BOOL SetPercentSeconds(ARGS *); static void DefaultOneOption(int); static void PrintUsage(void); static void PrintVersion(void); /* * Parse the command line options. * The program name argument must have already been removed. * Returns TRUE if they were successfully parsed. */ BOOL ParseOptions(ARGS * ap, int depth) { const OPTION * option; const char * str; BOOL status; BOOL isSuccess; isSuccess = TRUE; while (ap->count-- > 0) { str = *ap->table++; /* * If this is not an option, then it must be the name * of an option declaration, so expand that name and * collect those options. */ if (*str != '-') { if (!ExpandOptionName(str, depth)) return FALSE; continue; } /* * It is a normal option, so look it up and handle it. */ str++; option = option_table; while (option->name && (strcmp(option->name, str) != 0)) option++; status = TRUE; switch (option->id) { case OPT_SET_COLUMNS: status = SetColumns(ap); break; case OPT_ADD_COLUMNS: status = AddColumns(ap); break; case OPT_REMOVE_COLUMNS: status = RemoveColumns(ap); break; case OPT_SEPARATION: status = SetSeparation(ap); break; case OPT_WIDTH: status = SetWidth(ap); break; case OPT_SLEEP_TIME: status = SetSleepTime(ap); break; case OPT_ACTIVE_TIME: status = SetActiveTime(ap); break; case OPT_DEATH_TIME: status = SetDeathTime(ap); break; case OPT_SYNC_TIME: status = SetSyncTime(ap); break; case OPT_COLUMN_WIDTH: status = SetColumnWidth(ap); break; case OPT_TOP_COUNT: status = SetTopCount(ap); break; case OPT_INIT_TIME: status = SetInitTime(ap); break; case OPT_CONDITION: status = SetCondition(ap); break; case OPT_NO_SELF: noSelf = TRUE; break; case OPT_NO_ROOT: noRoot = TRUE; break; case OPT_NO_HEADER: noHeader = TRUE; break; case OPT_INFO: isInfoShown = TRUE; break; case OPT_SHOW_THREADS: showThreads = TRUE; break; case OPT_USE_THREADS: useThreads = TRUE; break; case OPT_NO_COPY: noCopy = TRUE; case OPT_MY_PROCS: myProcs = TRUE; break; case OPT_ACTIVE_ONLY: activeOnly = TRUE; break; case OPT_CLEAR_SCREEN: clearScreen = TRUE; break; case OPT_READ_FILE: status = ReadFile(ap); break; case OPT_NORMAL_SORT: status = AppendColumnSort(ap, FALSE); break; case OPT_REVERSE_SORT: status = AppendColumnSort(ap, TRUE); break; case OPT_NORMAL_SORT_EXPR: status = AppendExpressionSort(ap, FALSE); break; case OPT_REVERSE_SORT_EXPR: status = AppendExpressionSort(ap, TRUE); break; case OPT_NO_SORT: ClearSorting(); break; case OPT_DEFAULT: status = SetDefault(ap); break; case OPT_ONCE: isLooping = FALSE; displayType = DISPLAY_TYPE_TTY; break; case OPT_LOOP: isLooping = TRUE; displayType = DISPLAY_TYPE_TTY; break; case OPT_CURSES: isLooping = TRUE; displayType = DISPLAY_TYPE_CURSES; break; case OPT_X11: isLooping = TRUE; displayType = DISPLAY_TYPE_X11; break; case OPT_VERTICAL: isVertical = TRUE; break; case OPT_PIDS: status = SetPids(ap); break; case OPT_USERS: status = SetUsers(ap); break; case OPT_GROUPS: status = SetGroups(ap); break; case OPT_PROGRAMS: status = SetPrograms(ap); break; case OPT_GEOMETRY: status = SetGeometry(ap); break; case OPT_FONT: status = SetFont(ap); break; case OPT_FOREGROUND: status = SetForeground(ap); break; case OPT_BACKGROUND: status = SetBackground(ap); break; case OPT_DISPLAY: status = SetDisplay(ap); break; case OPT_PERCENT_SECONDS: status = SetPercentSeconds(ap); break; case OPT_SCROLL_TIME: status = SetScrollTime(ap); break; case OPT_OVERLAP_LINES: status = SetOverlapLines(ap); break; case OPT_INFO_COLOR: status = SetInfoColor(ap); break; case OPT_HEADER_COLOR: status = SetHeaderColor(ap); break; case OPT_ROW_COLOR: status = SetRowColorCondition(ap); break; case OPT_HELP: PrintUsage(); break; case OPT_LIST_COLUMNS: ListColumns(); exit(0); case OPT_LIST_MACROS: ListMacros(); exit(0); case OPT_VERSION: PrintVersion(); exit(0); case OPT_END: break; case OPT_NO_INIT: fprintf(stderr, "The -noinit option can only be used as the first command line argument\n"); status = FALSE; break; default: fprintf(stderr, "Unknown option -%s\n", str); status = FALSE; break; } if (!status) isSuccess = FALSE; } return isSuccess; } /* * Expand an option definition. * Such a definition has to be complete within the expansion. * For the top level expansions only, we uppercase words for convenience. */ BOOL ExpandOptionName(const char * name, int depth) { ARGS args; char upName[MAX_MACRO_LEN + 2]; if (depth > MAX_OPTION_DEPTH) { fprintf(stderr, "Too many levels of option macros\n"); return FALSE; } if (strlen(name) > MAX_MACRO_LEN) { fprintf(stderr, "Macro name \"%c%s\" is too long\n", *name + 'A' - 'a', name + 1); return FALSE; } /* * If we are on the top level command line, then uppercase the * macro name for convenience. */ if ((depth == 0) && isLower(*name)) { strcpy(upName, name); upName[0] += 'A' - 'a'; name = upName; } if (isLower(*name)) { fprintf(stderr, "Option macro \"%s\" must be upper case\n", name); return FALSE; } if (!isMacro(*name)) { fprintf(stderr, "String value \"%s\" is not a macro name\n", name); return FALSE; } if (!ExpandMacro(MACRO_TYPE_OPTION, name, &args)) return FALSE; return ParseOptions(&args, depth + 1); } /* * Expand command line arguments for an option by looking for macro names * and expanding the macros recursively. The expanded arguments are placed * into the specified table. Returns the number of arguments resulting * from the expansions, or -1 on an error. */ int ExpandArguments(ARGS * ap, char ** table, int tableLen) { char * name; int count; int subCount; ARGS args; count = 0; while ((ap->count > 0) && (**ap->table != '-')) { name = *ap->table++; ap->count--; /* * Make sure there is room left in the table. */ if (tableLen <= 0) { fprintf(stderr, "Too many arguments\n"); return -1; } /* * If the argument is not a macro name, then just put it * into the returned argument table */ if (!isMacro(*name)) { *table++ = name; tableLen--; count++; continue; } /* * The argument is a macro name, so expand it into more * arguments. */ if (!ExpandMacro(MACRO_TYPE_COLUMN, name, &args)) return -1; /* * Add the result of the expansion into the table at this * point, possibly doing a recursive expansion. */ subCount = ExpandArguments(&args, table, tableLen); if (subCount < 0) return -1; /* * Increment past the number of entries that the expansion * added to the table. */ table += subCount; tableLen -= subCount; count += subCount; } return count; } /* * Set the separation between columns. */ static BOOL SetSeparation(ARGS * ap) { const const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing separation argument\n"); return FALSE; } ap->count--; str = *ap->table++; separation = GetDecimalNumber(&str); if (*str || (separation < 0) || (separation > MAX_SEPARATION)) { fprintf(stderr, "Bad separation argument\n"); return FALSE; } return TRUE; } /* * Read the specified file containing macro definitions. * The file must exist. */ static BOOL ReadFile(ARGS * ap) { if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing file argument\n"); return FALSE; } ap->count--; return ParseFile(*ap->table++, FALSE); } /* * Set the default value associated with one or more options. * If no option names are given, then all defaults are set. */ static BOOL SetDefault(ARGS * ap) { OPTION *option; char * name; /* * If no option name was given, then default everything. */ if ((ap->count <= 0) || (**ap->table == '-')) { DefaultAllOptions(); return TRUE; } /* * There was at least one option given, so default them. */ while ((ap->count > 0) && (**ap->table != '-')) { ap->count--; name = *ap->table++; option = option_table; while (option->name && (strcmp(option->name, name) != 0)) option++; if (option->name == NULL) { fprintf(stderr, "Undefined option name \"%s\"\n", name); return FALSE; } DefaultOneOption(option->id); } return TRUE; } /* * Routine to default all option values. * This is called at program startup to initialize parameters, * and also when the user requests it. */ void DefaultAllOptions(void) { int id; for (id = 0; id <= OPT_LAST_OPTION; id++) DefaultOneOption(id); } /* * Routine to default the specified option id. * This is only meaningful for certain options, but calling this for the * remaining options isn't a problem. This routine never reports an error. */ static void DefaultOneOption(int id) { switch (id) { case OPT_SET_COLUMNS: case OPT_ADD_COLUMNS: case OPT_REMOVE_COLUMNS: DefaultColumns(); break; case OPT_SEPARATION: separation = 2; break; case OPT_WIDTH: outputWidth = DEFAULT_WIDTH; break; case OPT_SLEEP_TIME: sleepTimeMs = DEFAULT_SLEEP_SEC * 1000; break; case OPT_INFO: isInfoShown = FALSE; break; case OPT_NO_SELF: noSelf = FALSE; break; case OPT_MY_PROCS: myProcs = FALSE; break; case OPT_SHOW_THREADS: showThreads = FALSE; break; case OPT_USE_THREADS: useThreads = FALSE; break; case OPT_NO_COPY: noCopy = FALSE; case OPT_ACTIVE_ONLY: activeOnly = FALSE; break; case OPT_NO_ROOT: noRoot = FALSE; break; case OPT_CLEAR_SCREEN: clearScreen = FALSE; break; case OPT_NORMAL_SORT: case OPT_REVERSE_SORT: case OPT_NORMAL_SORT_EXPR: case OPT_REVERSE_SORT_EXPR: case OPT_NO_SORT: ClearSorting(); break; case OPT_LOOP: isLooping = FALSE; break; case OPT_VERTICAL: isVertical = FALSE; break; case OPT_INIT_TIME: initSleepTime = DEFAULT_INIT_SEC; break; case OPT_CONDITION: ClearCondition(); break; case OPT_TOP_COUNT: topCount = 0; break; case OPT_NO_HEADER: noHeader = FALSE; break; case OPT_PIDS: pidCount = 0; break; case OPT_USERS: userCount = 0; break; case OPT_GROUPS: groupCount = 0; break; case OPT_PROGRAMS: programCount = 0; break; case OPT_ACTIVE_TIME: activeTime = DEFAULT_ACTIVE_SEC; break; case OPT_DEATH_TIME: deathTime = DEFAULT_DEATH_SEC; break; case OPT_SYNC_TIME: syncTime = DEFAULT_SYNC_SEC; break; case OPT_COLUMN_WIDTH: DefaultColumnWidths(); break; case OPT_GEOMETRY: ReplaceString(&geometry, DEFAULT_GEOMETRY); break; case OPT_FONT: ReplaceString(&fontName, DEFAULT_FONT); break; case OPT_FOREGROUND: ReplaceString(&foregroundName, DEFAULT_FOREGROUND); break; case OPT_BACKGROUND: ReplaceString(&backgroundName, DEFAULT_BACKGROUND); break; case OPT_DISPLAY: ReplaceString(&displayName, NULL); break; case OPT_PERCENT_SECONDS: percentSeconds = PERCENT_DEFAULT_SECONDS; break; case OPT_SCROLL_TIME: scrollSeconds = DEFAULT_SCROLL_SEC; break; case OPT_OVERLAP_LINES: overlapLines = DEFAULT_OVERLAP_LINES; break; case OPT_INFO_COLOR: infoColorId = DEFAULT_COLOR_ID; break; case OPT_HEADER_COLOR: headerColorId = DEFAULT_COLOR_ID; break; case OPT_ROW_COLOR: ClearRowColorConditions(); break; default: break; } } /* * Set the condition for displaying of processes. */ static BOOL SetCondition(ARGS * ap) { const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing condition expression\n"); return FALSE; } ap->count--; str = *ap->table++; return ParseCondition(str); } /* * Set the sleep time between updates. * This value can be a floating point number. */ static BOOL SetSleepTime(ARGS * ap) { const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing sleep time argument\n"); return FALSE; } ap->count--; str = *ap->table++; sleepTimeMs = (int) (GetFloatingNumber(&str) * 1000.0); if (*str || (sleepTimeMs < 0)) { fprintf(stderr, "Bad sleep time argument\n"); return FALSE; } return TRUE; } /* * Set the number of seconds to keep an active process in the display. */ static BOOL SetActiveTime(ARGS * ap) { const const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing active time argument\n"); return FALSE; } ap->count--; str = *ap->table++; activeTime = GetDecimalNumber(&str); if (*str || (activeTime < 0)) { fprintf(stderr, "Bad active time argument\n"); return FALSE; } return TRUE; } /* * Set the number of seconds to keep a dead process in the display. */ static BOOL SetDeathTime(ARGS * ap) { const const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing death time argument\n"); return FALSE; } ap->count--; str = *ap->table++; deathTime = GetDecimalNumber(&str); if (*str || (deathTime < 0)) { fprintf(stderr, "Bad death time argument\n"); return FALSE; } return TRUE; } /* * Set the number of seconds to force synchronization with process status. */ static BOOL SetSyncTime(ARGS * ap) { const const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing sync time argument\n"); return FALSE; } ap->count--; str = *ap->table++; syncTime = GetDecimalNumber(&str); if (*str || (syncTime < 0)) { fprintf(stderr, "Bad sync time argument\n"); return FALSE; } return TRUE; } /* * Set the number of processes to show from the top. * The count can be omitted to indicate that we calculate it * from the terminal height. */ static BOOL SetTopCount(ARGS * ap) { const char * str; isTopMode = TRUE; if ((ap->count <= 0) || (**ap->table == '-')) { topCount = outputHeight - 1; if (!noHeader) topCount--; if (isInfoShown) topCount--; if (topCount <= 0) topCount = 1; isTopAuto = TRUE; return TRUE; } ap->count--; str = *ap->table++; topCount = GetDecimalNumber(&str); if (*str || (topCount < 0)) { fprintf(stderr, "Bad top argument\n"); return FALSE; } isTopAuto = FALSE; return TRUE; } /* * Set the list of pids to be examined. */ static BOOL SetPids(ARGS * ap) { const char * str; pid_t pid; int i; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing pid argument\n"); return FALSE; } while ((ap->count > 0) && (**ap->table != '-')) { ap->count--; str = *ap->table++; pid = (pid_t) GetDecimalNumber(&str); if (*str || (((long) pid) <= 0)) { fprintf(stderr, "Bad pid argument\n"); return FALSE; } for (i = 0; i < pidCount; i++) { if (pidList[i] == pid) break; } if (i < pidCount) continue; if (pidCount >= MAX_PIDS) { fprintf(stderr, "Too many pids specified\n"); return FALSE; } pidList[pidCount++] = pid; } return TRUE; } /* * Set the list of users to be examined. */ static BOOL SetUsers(ARGS * ap) { const char * str; uid_t uid; int i; CollectUserNames(); if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing user argument\n"); return FALSE; } while ((ap->count > 0) && (**ap->table != '-')) { ap->count--; str = *ap->table++; if (isDigit(*str)) { uid = (uid_t) GetDecimalNumber(&str); if (*str) { fprintf(stderr, "Bad uid argument\n"); return FALSE; } } else { uid = FindUserId(str); if (uid == BAD_UID) { fprintf(stderr, "Unknown user \"%s\"\n", str); return FALSE; } } for (i = 0; i < userCount; i++) { if (userList[i] == uid) break; } if (i < userCount) continue; if (userCount >= MAX_USERS) { fprintf(stderr, "Too many users specified\n"); return FALSE; } userList[userCount++] = uid; } return TRUE; } /* * Set the list of groups to be examined. */ static BOOL SetGroups(ARGS * ap) { const char * str; gid_t gid; int i; CollectGroupNames(); if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing group argument\n"); return FALSE; } while ((ap->count > 0) && (**ap->table != '-')) { ap->count--; str = *ap->table++; if (isDigit(*str)) { gid = (gid_t) GetDecimalNumber(&str); if (*str) { fprintf(stderr, "Bad gid argument\n"); return FALSE; } } else { gid = FindGroupId(str); if (gid == BAD_GID) { fprintf(stderr, "Unknown group \"%s\"\n", str); return FALSE; } } for (i = 0; i < groupCount; i++) { if (groupList[i] == gid) break; } if (i < groupCount) continue; if (groupCount >= MAX_GROUPS) { fprintf(stderr, "Too many groups specified\n"); return FALSE; } groupList[groupCount++] = gid; } return TRUE; } /* * Set the list of programs to be examined. */ static BOOL SetPrograms(ARGS * ap) { char * str; int i; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing program argument\n"); return FALSE; } while ((ap->count > 0) && (**ap->table != '-')) { ap->count--; str = *ap->table++; /* * If the name is too long, then silently ignore it * since it cannot match. */ if (strlen(str) > MAX_PROGRAM_LEN) continue; for (i = 0; i < programCount; i++) { if (strcmp(str, programList[i]) == 0) break; } if (i < programCount) continue; if (programCount >= MAX_PROGRAMS) { fprintf(stderr, "Too many programss specified\n"); return FALSE; } strcpy(programList[programCount++], str); } return TRUE; } /* * Set the sleep time before the initial update. */ static BOOL SetInitTime(ARGS * ap) { const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing init time argument\n"); return FALSE; } ap->count--; str = *ap->table++; initSleepTime = GetDecimalNumber(&str); if (*str || (initSleepTime < 0)) { fprintf(stderr, "Bad init time argument\n"); return FALSE; } return TRUE; } /* * Set the display width. */ static BOOL SetWidth(ARGS * ap) { const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing width argument\n"); return FALSE; } ap->count--; str = *ap->table++; outputWidth = GetDecimalNumber(&str); if (*str || (outputWidth <= 0)) { fprintf(stderr, "Bad width argument\n"); return FALSE; } if (outputWidth > MAX_WIDTH) outputWidth = MAX_WIDTH; return TRUE; } /* * Set the width of the specified column to the indicated value, * or else back to its default value if no value was given. */ static BOOL SetColumnWidth(ARGS * ap) { COLUMN * column; const char * name; const char * str; int width; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing Column name\n"); return FALSE; } ap->count--; name = *ap->table++; /* * Find the column from the name. */ column = FindColumn(name); if (column == NULL) { fprintf(stderr, "Bad column name %s\n", name); return FALSE; } /* * If no additional argument was given, * then set the column's width back to its initial value. */ if ((ap->count <= 0) || (**ap->table == '-')) { column->width = column->initWidth; return TRUE; } /* * There was a width specified, so parse it and set the * column width to that value. */ ap->count--; str = *ap->table++; width = GetDecimalNumber(&str); if (*str || (width <= 0)) { fprintf(stderr, "Bad column width argument\n"); return FALSE; } if (width > MAX_WIDTH) width = MAX_WIDTH; column->width = width; return TRUE; } /* * Set the columns to display according to the indicated names. */ static BOOL SetColumns(ARGS * ap) { showCount = 0; return AddColumns(ap); } /* * Add the columns to display according to the indicated names. * The columns are added to the end of the list of columns to display, * or else at the column number specified before the column name. * This removes any previous usage of the column. */ static BOOL AddColumns(ARGS * ap) { const char * name; COLUMN * column; int count; int i; int j; int pos; char * table[MAX_WORDS]; count = ExpandArguments(ap, table, MAX_WORDS); if (count < 0) return FALSE; /* * Default the insert position to be after all existing columns. */ pos = showCount; /* * Go through the column names one by one, inserting them into * the show list. */ for (i = 0; i < count; i++) { name = table[i]; /* * If the column name is actually a number, then the * argument is the column number to insert columns at. * Subtract one to convert it to an array offset. */ if (isDigit(*name)) { pos = 0; while (isDigit(*name)) pos = pos * 10 + *name++ - '0'; if (*name || (pos < 0)) { fprintf(stderr, "Bad column position argument\n"); return FALSE; } pos--; continue; } /* * This is actually a column name, so look it up. */ column = FindColumn(name); if (column == NULL) { fprintf(stderr, "Bad column name %s\n", name); return FALSE; } /* * Remove the column if it was already in the column list. */ for (j = 0; j < showCount; j++) { if (column == showList[j]) { while (j < showCount) { showList[j] = showList[j + 1]; j++; } showCount--; } } /* * Now insert the column at the specified column position. * Do this by moving all columns at the position up one, * and putting the new column at the proper location. * Make sure the position is valid beforehand, and increment * the position afterward so that the next column to be * inserted goes in after this one. */ if (pos < 0) pos = 0; if (pos > showCount) pos = showCount; for (j = showCount; j > pos; j--) showList[j] = showList[j - 1]; showList[pos++] = column; showList[++showCount] = NULL; } return TRUE; } /* * Remove the indicated columns from the display. */ static BOOL RemoveColumns(ARGS * ap) { const char * name; COLUMN * column; int count; int i; int j; char * table[MAX_WORDS]; count = ExpandArguments(ap, table, MAX_WORDS); if (count < 0) return FALSE; for (i = 0; i < count; i++) { name = table[i]; column = FindColumn(name); if (column == NULL) { fprintf(stderr, "Bad column name %s\n", name); return FALSE; } for (j = 0; j < showCount; j++) { if (column == showList[j]) { while (j < showCount) { showList[j] = showList[j + 1]; j++; } showCount--; } } } return TRUE; } /* * Set the font name. */ static BOOL SetFont(ARGS * ap) { char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing font argument\n"); return FALSE; } ap->count--; str = *ap->table++; ReplaceString(&fontName, str); return TRUE; } /* * Set geometry of the window. */ static BOOL SetGeometry(ARGS * ap) { char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing geometry argument\n"); return FALSE; } ap->count--; str = *ap->table++; ReplaceString(&geometry, str); return TRUE; } /* * Set the foreground color. */ static BOOL SetForeground(ARGS * ap) { char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing foreground argument\n"); return FALSE; } ap->count--; str = *ap->table++; ReplaceString(&foregroundName, str); return TRUE; } /* * Set the background color. */ static BOOL SetBackground(ARGS * ap) { char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing background argument\n"); return FALSE; } ap->count--; str = *ap->table++; ReplaceString(&backgroundName, str); return TRUE; } /* * Set the display name for X11. */ static BOOL SetDisplay(ARGS * ap) { char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing display argument\n"); return FALSE; } ap->count--; str = *ap->table++; ReplaceString(&displayName, str); return TRUE; } /* * Set the seconds for cpu percentage calculations. */ static BOOL SetPercentSeconds(ARGS * ap) { const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing cpu percentage argument\n"); return FALSE; } ap->count--; str = *ap->table++; percentSeconds = GetDecimalNumber(&str); if (*str || (percentSeconds < 0) || (percentSeconds > PERCENT_MAX_SECONDS)) { fprintf(stderr, "Bad cpu percentage argument\n"); return FALSE; } return TRUE; } /* * Set the scolling time. */ static BOOL SetScrollTime(ARGS * ap) { const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing scroll time argument\n"); return FALSE; } ap->count--; str = *ap->table++; scrollSeconds = GetDecimalNumber(&str); if (*str || (scrollSeconds < 0)) { fprintf(stderr, "Bad scroll time argument\n"); return FALSE; } return TRUE; } /* * Set the lines of overlap. */ static BOOL SetOverlapLines(ARGS * ap) { const char * str; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing overlap argument\n"); return FALSE; } ap->count--; str = *ap->table++; overlapLines = GetDecimalNumber(&str); if (*str || (overlapLines < 0)) { fprintf(stderr, "Bad overlap argument\n"); return FALSE; } return TRUE; } /* * Set the color of the information line. */ static BOOL SetInfoColor(ARGS * ap) { const char * color; int colorId; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing information color argument\n"); return FALSE; } ap->count--; color = *ap->table++; colorId = AllocateColor(color); if (colorId == BAD_COLOR_ID) { fprintf(stderr, "Cannot allocate information color\n"); return FALSE; } infoColorId = colorId; return TRUE; } /* * Set the color of the header line. */ static BOOL SetHeaderColor(ARGS * ap) { const char * color; int colorId; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing header color argument\n"); return FALSE; } ap->count--; color = *ap->table++; colorId = AllocateColor(color); if (colorId == BAD_COLOR_ID) { fprintf(stderr, "Cannot allocate header color\n"); return FALSE; } headerColorId = colorId; return TRUE; } /* * Set a condition for coloring or rows. */ static BOOL SetRowColorCondition(ARGS * ap) { char * color; char * condition; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing color\n"); return FALSE; } ap->count--; color = *ap->table++; if ((ap->count <= 0) || (**ap->table == '-')) { fprintf(stderr, "Missing condition\n"); return FALSE; } ap->count--; condition = *ap->table++; return ParseRowColorCondition(color, condition); } /* * Usage text */ static const char * const usage_text[] = { "Usage: ips ", "", " specify how to display the output:", " -col ... Set columns as the display list", " -addcol ... Add columns to display list", " -remcol ... Remove columns from display list", " -sort ... Add columns to sorting list as normal sort", " -revsort ... Add columns to sorting list as reverse sort", " -sortexpr Add expression to sorting list as normal sort", " -revsortexpr Add expression to sorting list as reverse sort", " -nosort Clear the sorting list", " -sep Set the separation between columns", " -width Set the total width of output for all columns", " -colwidth [width] Set (or default) the width for one column", " -noheader Suppress display of header line", " -vert Output status in vertical format (multi-line)", " -clear Clear screen before displaying status in dumb display", " -info Show information line when in loop mode", " -scroll Set time between scrolling display when looping", " -overlap Set number of overlapped lines when scrolling", " -once Display status once only in dumb display (default)", " -loop Display status repeatedly in dumb display", " -curses Display status repeatedly in tty using curses", " -x11 Display status repeatedly in new X11 window", " -display Set display name for X11 (default NULL)", " -geometry Set window geometry for X11 (default \"150x50\")", " -font Set font name for X11 (default \"fixed\")", " -foreground Set foreground color for X11 (default \"black\")", " -background Set background color for X11 (default \"white\")", " -headercolor Set colors for header line", " -infocolor Set colors for info line", " -rowcolor Set colors for rows satisfying condition", "", " restrict which processes can be displayed:", " -noself Do not show the ips process itself", " -noroot Do not show processes owned by root", " -my Show processes with my own user id", " -pid ... Show specified process ids", " -user ... Show specified user names or ids", " -group ... Show specified group names or ids", " -program ... Show specified program names", " -active Show active processes", " -activetime Set number of seconds processes remain active", " -deathtime Set number of seconds to show dead processes", " -top [count] Show number of processes from the top or what fits", " -cond Show processes for which the expression is nonzero", " -showthreads Show threads instead of just processes", "", " are:", " -sleep Set the sleep interval between loops", " -initsleep Set the initial sleep time for active checks", " -synctime Synchronize with some status at least this often", " -usethreads Use thread information for process status", " -percentseconds Set seconds for cpu percentages (0 to 20)", " -read Read definitions from the specified filename", " -noinit Don't read \"~/.ipsrc\" (must be first option)", " -end Used to end argument lists if needed", " -help Display this message and exit", " -listcolumns Display the list of allowable columns and exit", " -listmacros Display the list of defined macros and exit", " -version Display version information and exit", " -default [name] ... Set specified options back to their default values", "", " are a list of macros (defined in the initialization files)", "which are to be expanded into options. The first character of each macro", "name is converted to upper case for convenience.", NULL }; static void PrintUsage(void) { const char * const * text; for (text = usage_text; *text; text++) fprintf(stderr, "%s\n", *text); exit(1); } static void PrintVersion(void) { printf("Version: %s\n", VERSION); printf("Built on: %s\n", __DATE__); printf("Author: %s\n", AUTHOR_NAME); printf("Email: %s\n", AUTHOR_EMAIL); } /* END CODE */