diff options
author | Martin Baulig <martin@src.gnome.org> | 1998-07-18 16:15:56 +0000 |
---|---|---|
committer | Martin Baulig <martin@src.gnome.org> | 1998-07-18 16:15:56 +0000 |
commit | 5e9d34c91a58103012dbc41bd3e2165f76ee99f0 (patch) | |
tree | 746aa07e44fb1aec0f5406638bb24986547f38bf | |
parent | 0e086aef2513d7263ba21f6835f74945fa9915e2 (diff) | |
download | libgtop-5e9d34c91a58103012dbc41bd3e2165f76ee99f0.tar.gz |
Initial revision
-rw-r--r-- | libproc/Makefile | 111 | ||||
-rw-r--r-- | libproc/alloc.c | 49 | ||||
-rw-r--r-- | libproc/compare.c | 307 | ||||
-rw-r--r-- | libproc/devname.c | 269 | ||||
-rw-r--r-- | libproc/devname.h | 10 | ||||
-rw-r--r-- | libproc/ksym.c | 252 | ||||
-rw-r--r-- | libproc/output.c | 52 | ||||
-rw-r--r-- | libproc/ps.h | 30 | ||||
-rw-r--r-- | libproc/psdata.h | 103 | ||||
-rw-r--r-- | libproc/pwcache.c | 37 | ||||
-rw-r--r-- | libproc/readproc.c | 395 | ||||
-rw-r--r-- | libproc/readproc.h | 168 | ||||
-rw-r--r-- | libproc/signals.c | 65 | ||||
-rw-r--r-- | libproc/signals.h | 12 | ||||
-rw-r--r-- | libproc/signames.h | 32 | ||||
-rw-r--r-- | libproc/status.c | 20 | ||||
-rw-r--r-- | libproc/sysinfo.c | 176 | ||||
-rw-r--r-- | libproc/sysinfo.h | 17 | ||||
-rw-r--r-- | libproc/tree.h | 14 | ||||
-rw-r--r-- | libproc/version.c | 39 | ||||
-rw-r--r-- | libproc/version.h | 24 | ||||
-rw-r--r-- | libproc/whattime.c | 89 | ||||
-rw-r--r-- | libproc/whattime.h | 9 |
23 files changed, 2280 insertions, 0 deletions
diff --git a/libproc/Makefile b/libproc/Makefile new file mode 100644 index 00000000..6d72070b --- /dev/null +++ b/libproc/Makefile @@ -0,0 +1,111 @@ +# Auto-adaptive C library Makefile adapted for libproc, Chuck Blake. +# Assumptions are basically that all the .c files in the CWD are modules +# for the library and that all .h files are the interface to the library. + +# PROJECT SPECIFIC MACROS +NAME = proc + +# INSTALLATION OPTIONS +TOPDIR = /usr +HDRDIR = $(TOPDIR)/include/$(NAME)# where to put .h files +LIBDIR = $(TOPDIR)/lib# where to put library files +SHLIBDIR = /lib# where to put shared library files +HDROWN = -o root -g root # owner of header files +LIBOWN = -o root -g root # owner of library files +INSTALL = install + +# COMPILATION OPTIONS +CC = gcc -O2 -D_GNU_SOURCE #-ggdb # easy to command-line override +CFLAGS = -I.. -Wall + +# ----------------------------------------------------------------# +# The rest is the auto-magic section -- highly GNU make dependent # +# You should never need to edit this. # +# ----------------------------------------------------------------# + +VC_SUF = ,v +VC_PFX = RCS/ +RCSFILES = $(patsubst $(VC_PFX)%$(VC_SUF),%,$(wildcard $(VC_PFX)*$(VC_SUF))) + +# We take the union of RCS files and other files in CWD so that new files do +# not need to alter this makefile. 'sort' removes duplicates. This allows the +# convenience of compiling and testing new files before the initial check-in. + +SRC = $(sort $(wildcard *.c) $(filter %.c,$(RCSFILES))) +HDR = $(sort $(wildcard *.h) $(filter %.h,$(RCSFILES))) + +OBJ = $(SRC:.c=.o) +SONAME = lib$(NAME).so.$(LIBVERSION) + +ifeq ($(SHARED),1) +CFLAGS += -fpic +all: lib$(NAME).a $(SONAME) +else +all: lib$(NAME).a +endif + +lib$(NAME).a: $(OBJ) + $(AR) rcs $@ $^ + +$(SONAME): $(OBJ) + gcc -Wl,-shared -Wl,-soname,$(SONAME) -o $@ $^ -lc + ln -sf $(SONAME) lib$(NAME).so + +# AUTOMATIC DEPENDENCY GENERATION -- GCC AND GNUMAKE DEPENDENT + +.depend: + $(strip $(CC) $(CFLAGS) -MM -MG $(SRC) > .depend) +include .depend + +# INSTALLATION + +install: all + if ! [ -d $(HDRDIR) ] ; then mkdir $(HDRDIR) ; fi + $(INSTALL) $(HDROWN) $(HDR) $(TOPDIR)/include/$(NAME) + $(INSTALL) $(LIBOWN) lib$(NAME).a $(LIBDIR) +ifeq ($(SHARED),1) + $(INSTALL) $(LIBOWN) $(SONAME) $(SHLIBDIR) + ln -sf $(SHLIBDIR)/$(SONAME) $(SHLIBDIR)/lib$(NAME).so + ldconfig +endif + +# VARIOUS SHORT CUT TARGETS +.PHONY: all install dep clean distclean checkout checkclean + +dep: .depend + +clean: + $(RM) lib$(NAME).* *.o + +distclean: clean + $(RM) .depend signames.h + +checkout: + $(CO) $(RCSFILES) + +checkclean: + $(RM) $(RCSFILES) + +# CUSTOM c -> o rule so that command-line has minimal whitespace + +%.o : %.c + $(strip $(CC) $(CFLAGS) -c $<) + +# PROJECT SPECIFIC DEPENDENCIES/BUILD RULES + + +version.o: version.c version.h +ifdef MINORVERSION + $(strip $(CC) $(CFLAGS) -DVERSION=\"$(VERSION)\" -DSUBVERSION=\"$(SUBVERSION)\" -DMINORVERSION=\"$(MINORVERSION)\" -c version.c) +else + $(strip $(CC) $(CFLAGS) -DVERSION=\"$(VERSION)\" -DSUBVERSION=\"$(SUBVERSION)\" -c version.c) +endif + +signals.o : signames.h + +signames.h ../proc/signames.h : /usr/include/signal.h + /lib/cpp -dM </usr/include/signal.h | \ + tr -s '\t ' ' ' | sort -n +2 | sed \ + 's:#define SIG\([A-Z]\+[0-9]*\) \([0-9]\+\) *\(\|/\*.*\)$$:{\ +\2,"\1" },:p;d' > signames.h + diff --git a/libproc/alloc.c b/libproc/alloc.c new file mode 100644 index 00000000..92fba2c5 --- /dev/null +++ b/libproc/alloc.c @@ -0,0 +1,49 @@ +/***********************************************************************\ +* Copyright (C) 1992 by Michael K. Johnson, johnsonm@sunsite.unc.edu * +* * +* This file is placed under the conditions of the GNU public * +* license, version 2, or any later version. See file COPYING * +* for information on distribution conditions. * +\***********************************************************************/ +#include <stdlib.h> +#include <stdio.h> + +void *xcalloc(void *pointer, int size) { + void * ret; + if (pointer) + free(pointer); + if (!(ret = calloc(1, size))) { + fprintf(stderr, "xcalloc: allocation error, size = %d\n", size); + exit(1); + } else { + return ret; + } +} + +void *xmalloc(unsigned int size) { + void *p; + + if (size == 0) + ++size; + p = malloc(size); + if (!p) { + fprintf(stderr, "xmalloc: malloc(%d) failed", size); + perror(NULL); + exit(1); + } + return(p); +} + +void *xrealloc(void *oldp, unsigned int size) { + void *p; + + if (size == 0) + ++size; + p = realloc(oldp, size); + if (!p) { + fprintf(stderr, "xrealloc: realloc(%d) failed", size); + perror(NULL); + exit(1); + } + return(p); +} diff --git a/libproc/compare.c b/libproc/compare.c new file mode 100644 index 00000000..cfbdebd6 --- /dev/null +++ b/libproc/compare.c @@ -0,0 +1,307 @@ +/* + * + * Copyright 1994 Charles Blake and Michael K. Johnson + * This file is a part of procps, which is distributable + * under the conditions of the GNU Public License. See the + * file COPYING for details. + * + */ + +#include <string.h> /* for strcmp */ +#include <stdio.h> /* for parse error output */ +#include "proc/readproc.h" /* for proc_t */ +#include "proc/tree.h" /* for struct tree_node */ + +/* + This module was written by Charles Blake for procps. + +mult_lvl_cmp: + slick general purpose multi-level compare function I invented. +sort_depth: + the number of levels of functions *to use*. This means many more levels + can be defined than mult_lvl_cmp tres out. If this is 1 then mult_lvl_cmp + is just a trivial wrapper around (*sort_function[0]). +sort_direction: + multiplicative factor for the output of cmp_whatever. + 1 ==> default order, -1 ==> reverse order, 0 ==> forced equality + The 0 bit is the neat part. Since a value of zero is the code for equality + multiplying the output of cmp_foo(a,b) forces a==b to be true. This is a + convenient way to turn sorting off in middle levels of a multi-level sort. + If time is a problem, reforming the whole sort_function array to not include + these unsorted middle levels will be faster since then cmp_foo won't even + be called. It might simplify some code depending upon how you organize it. +sort_function[]: + array of function pointers that points to our family of comparison functions + (I have named them cmp_* but mult_lvl_cmp doesn't care what they're named). + This may be declared and initialized like so: + int (*sort_function[])(void* a, void* b)={&cmp_foo, &cmp_bar, &cmp_hiho}; + You could also use my command line '-O' parser below. + +Note that we only descend levels until the order is determined. If we descend +all levels, that means that the items are equal at all levels, so we return 0. +Otherwise we return whatever the level's cmp_foo function would have returned. +This allows whatever default behavior you want for cmp_foo. sort_direction[] +reverses this default behavior, but mult_lvl_cmp doesn't decide that ascending +or descending is the default. That is the job of your cmp_foo's. +*/ + +/* the only reason these are global is because qsort(3) likes it that way. + It's also a little more efficient if mult_lvl_cmp() is called many times. +*/ + +typedef int (*cmp_t)(void*,void*); /* for function pointer casts */ + +int sort_depth = 0; +int sort_direction[10]; /* storage for 10 levels, but 4 would be plenty!*/ +int (*sort_function[10])(void* a, void* b); + +int mult_lvl_cmp(void* a, void* b) { + int i, cmp_val; + for(i = 0; i < sort_depth; i++) { + cmp_val = sort_direction[i] * (*sort_function[i])(a,b); + if (cmp_val != 0) + return cmp_val; + } + return 0; +} + +int node_mult_lvl_cmp(void* a, void* b) { + int i, cmp_val; + for(i = 0; i < sort_depth; i++) { + cmp_val = sort_direction[i] * (*sort_function[i])(&(((struct tree_node *)a)->proc),&(((struct tree_node *)b)->proc)); + if (cmp_val != 0) + return cmp_val; + } + return 0; +} + +/* qsort(3) compliant comparison functions for all members of the ps_proc + structure (in the same order in which they appear in the proc_t declaration) + return is {-1,0,1} as {a<b, a==b, a>b} + default ordering is ascending for all members. (flip 1,-1 to reverse) +*/ +/* pre-processor macros to cut down on source size (and typing!) + Note the use of the string concatenation operator ## +*/ +#define CMP_STR(NAME) \ +int cmp_ ## NAME(proc_t** P, proc_t** Q) { \ + return strcmp((*P)-> ## NAME, (*Q)-> ## NAME); \ +} + +#define CMP_INT(NAME) \ +int cmp_ ## NAME (proc_t** P, proc_t** Q) { \ + if ((*P)-> ## NAME < (*Q)-> ## NAME) return -1; \ + if ((*P)-> ## NAME > (*Q)-> ## NAME) return 1; \ + return 0; \ +} + +/* Define the (46!) cmp_ functions with the above macros for every element + of proc_t. If the binary gets too big, we could nuke inessentials. +*/ + +/* CMP_STR(cmdline) */ +CMP_STR(user) +CMP_STR(cmd) +/* CMP_INT(state) */ +/* CMP_STR(ttyc) */ +CMP_INT(uid) +CMP_INT(pid) +CMP_INT(ppid) +CMP_INT(pgrp) +CMP_INT(session) +CMP_INT(tty) +CMP_INT(tpgid) +CMP_INT(utime) +CMP_INT(stime) +CMP_INT(cutime) +CMP_INT(cstime) +/* CMP_INT(priority) */ +CMP_INT(nice) +CMP_INT(start_time) +/* CMP_INT(signal) */ +/* CMP_INT(blocked) */ +/* CMP_INT(sigignore) */ +/* CMP_INT(sigcatch) */ +CMP_INT(flags) +CMP_INT(min_flt) +CMP_INT(cmin_flt) +CMP_INT(maj_flt) +CMP_INT(cmaj_flt) +/* CMP_INT(timeout) */ +CMP_INT(vsize) +CMP_INT(rss) +/* CMP_INT(rss_rlim) */ +/* CMP_INT(start_code) */ +/* CMP_INT(end_code) */ +/* CMP_INT(start_stack) */ +/* CMP_INT(kstk_esp) */ +/* CMP_INT(kstk_eip) */ +/* CMP_INT(wchan) */ +CMP_INT(pcpu) +CMP_INT(size) +CMP_INT(resident) +CMP_INT(share) +/* CMP_INT(trs) */ +/* CMP_INT(lrs) */ +/* CMP_INT(drs) */ +/* CMP_INT(dt) */ + +/* define user interface to sort keys. Fairly self-explanatory. */ + +struct cmp_fun_struct { + char letter; /* single option-letter for key */ + char name[15]; /* long option name for key */ + int (*fun)(proc_t**, proc_t**); /* pointer to cmp_key */ +} cmp[] = { +/* { '?', "cmdline", &cmp_cmdline }, */ + { 'u', "user", &cmp_user }, + { 'c', "cmd", &cmp_cmd }, +/* { '?', "state", &cmp_state }, */ +/* { '?', "ttyc", &cmp_ttyc }, */ + { 'U', "uid", &cmp_uid }, + { 'p', "pid", &cmp_pid }, + { 'P', "ppid", &cmp_ppid }, + { 'g', "pgrp", &cmp_pgrp }, + { 'o', "session", &cmp_session }, + { 't', "tty", &cmp_tty }, + { 'G', "tpgid", &cmp_tpgid }, + { 'k', "utime", &cmp_utime }, + { 'K', "stime", &cmp_stime }, + { 'j', "cutime", &cmp_cutime }, + { 'J', "cstime", &cmp_cstime }, +/* { '?', "counter", &cmp_counter }, */ + { 'y', "priority", &cmp_nice }, + { 'T', "start_time", &cmp_start_time }, +/* { '?', "signal", &cmp_signal }, */ +/* { '?', "blocked", &cmp_blocked }, */ +/* { '?', "sigignore", &cmp_sigignore }, */ +/* { '?', "sigcatch", &cmp_sigcatch }, */ + { 'f', "flags", &cmp_flags }, + { 'm', "min_flt", &cmp_min_flt }, + { 'n', "cmin_flt", &cmp_cmin_flt }, + { 'M', "maj_flt", &cmp_maj_flt }, + { 'N', "cmaj_flt", &cmp_cmaj_flt }, +/* { 'C', "timeout", &cmp_timeout }, */ + { 'v', "vsize", &cmp_vsize }, + { 'r', "rss", &cmp_rss }, +/* { '?', "rss_rlim", &cmp_rss_rlim }, */ +/* { '?', "start_code", &cmp_start_code }, */ +/* { '?', "end_code", &cmp_end_code }, */ +/* { '?', "start_stack", &cmp_start_stack }, */ +/* { '?', "kstk_esp", &cmp_kstk_esp }, */ +/* { '?', "kstk_eip", &cmp_kstk_eip }, */ +/* { '?', "wchan", &cmp_wchan }, */ + { 'C', "pcpu", &cmp_pcpu }, + { 's', "size", &cmp_size }, + { 'R', "resident", &cmp_resident }, + { 'S', "share", &cmp_share }, +/* { '?', "trs", &cmp_trs }, */ +/* { '?', "lrs", &cmp_lrs }, */ +/* { '?', "drs", &cmp_drs }, */ +/* { '?', "dt", &cmp_dt }, */ + { '\0',"terminator", NULL } +}; + +void dump_keys(void) { + int i; + for(i=0; cmp[i].letter; i++) + fprintf(stderr, "%s-O%c , --sort:%-15.15s%s", + i%2?"":" ", + cmp[i].letter, cmp[i].name, + i%2?"\n":""); + if (i%2) + fprintf(stderr, "\n"); +} + +/* command line option parsing. Assign sort_{depth,direction[],function[]} + based upon a string of the form: + [+-]a[+-]b[+-]c... + with a,b,c,... being letter flags corresponding to a particular sort + key and the optional '-' specifying a reverse sort on that key. + doesn't + mean anything, but it keeps things looking balanced... +*/ +int parse_sort_opt(char* opt) { + int i, next_dir=1; + for(; *opt ; ++opt) { + if (*opt == '-' || *opt == '+') { + if (*opt == '-') + next_dir = -1; + opt++; + continue; + } + for (i = 0; cmp[i].letter; i++) + if (*opt == cmp[i].letter) + break; + if (!cmp[i].letter) { + fprintf(stderr, + "ps: no such sort key -- %c. Possibilities are:\n", *opt); + dump_keys(); + return -1; + } else { +#ifdef DEBUG + fprintf(stderr, + "sort level %d: key %s, direction % d\n", + sort_depth, cmp[i].name, next_dir); +#endif + sort_function[sort_depth] = (cmp_t)cmp[i].fun; + sort_direction[sort_depth++] = next_dir; + next_dir = 1; + } + } + return 0; +} + +int parse_long_sort(char* opt) { + char* comma; + int i, more_keys, next_dir=1; + do { + if (*opt == '-' || *opt == '+') { + if (*opt == '-') + next_dir = -1; + more_keys = 1; + opt++; + continue; + } + more_keys = ((comma=index(opt,',')) != NULL); + /* keys are ',' delimited */ + if (more_keys) + *comma='\0'; /* terminate for strcmp() */ + for(i = 0; cmp[i].letter; ++i) + if (strcmp(opt, cmp[i].name) == 0) + break; + if (!cmp[i].letter) { + fprintf(stderr, + "ps: no such sort key -- %s. Possibilities are:\n", opt); + dump_keys(); + return -1; + } else { +#ifdef DEBUG + fprintf(stderr, + "sort level %d: key %s, direction % d\n", + sort_depth, cmp[i].name, next_dir); +#endif + sort_function[sort_depth] = (cmp_t)cmp[i].fun; + sort_direction[sort_depth++] = next_dir; + next_dir = 1; + } + opt = comma + 1; /* do next loop on next key, if more keys, else done*/ + } while (more_keys); + return 0; +} + +void reset_sort_options (void) +{ + int i; + + sort_depth=0; + for (i=0;i<10;i++){ + sort_direction[i]=0; + sort_function[i]=(cmp_t)NULL; + } +} + +void register_sort_function (int dir, cmp_t func) +{ + sort_function[sort_depth] = func; + sort_direction[sort_depth++] = dir; +} diff --git a/libproc/devname.c b/libproc/devname.c new file mode 100644 index 00000000..f81af2dc --- /dev/null +++ b/libproc/devname.c @@ -0,0 +1,269 @@ +/* device name <-> number map system optimized for rapid, constant time lookup. + Copyright Charles Blake, 1996, see COPYING for details. +*/ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <dirent.h> +#include <unistd.h> +#include <fcntl.h> + +#define __KERNEL__ +#include <linux/kdev_t.h> +#undef __KERNEL__ + +#define DEVDIR "/dev" +#define DEVTAB "psdevtab" +static char *devtab_paths[] = { + "/etc/" DEVTAB, + "%s/." DEVTAB, + NULL +}; +#define DEVINITIALMODE 0664 +#define DEV_MAX_PATH (5+256) +#define DEV_NAME_MAX 8 + +static dev_t major[] = { 2, 3, 4, 5, 19, 20, 22, 23, 24, 25, 32, 33, + 46, 47, 48, 49 }; +#define Nminor 256 +#define Nmajor (sizeof(major)/sizeof(dev_t)) +#define Ndev (Nmajor*Nminor) +#define Ndevtab (Ndev*DEV_NAME_MAX) + +static char* devtab; /* the memory buffer holding all the name strings */ + +/* This macro evaluates to the address into the table of the string of + DEV_NAME_MAX chars for the device with major m, minor n. */ +#define TAB(m,n) (devtab + (m)*(Nminor*DEV_NAME_MAX) + (n)*DEV_NAME_MAX) + +static int devtab_initialized = 0; + +static char* name_to_path(char* name); /* forward declarations */ +static int init_devtab (void); + +/* Device Name -> Number Map + many-to-one: -1 on failed match. +*/ +dev_t name_to_dev(char* name) { + static struct stat sbuf; + return (stat(name_to_path(name), &sbuf) < 0) ? -1 : sbuf.st_rdev; +} + +/* find m in a[] assuming a is sorted into ascending order */ +/* in-line linear search placeholder until more majors warrant binary search */ +static __inline__ int lookup(dev_t m, dev_t* a, int n) { + int k; + for(k=0; k < n && a[k] != m; k++) + ; + return (k < n) ? k : -1; +} + +/* Device Number -> Name Map + one-to-many: first directory order match in DEVDIR, "" on failed match. +*/ +char* dev_to_name(dev_t num) { + static char rval[DEV_NAME_MAX+1]; + dev_t m = MAJOR(num), n = MINOR(num), tmp; + if (!devtab_initialized && !init_devtab()) + return ""; + if ((tmp = lookup(m, major, Nmajor)) == (dev_t)-1) + return ""; + strncpy(rval, TAB(tmp,n), DEV_NAME_MAX); + rval[DEV_NAME_MAX] = '\0'; + return rval; +} + +static int dev_to_devtab(int); + +static int init_devtab(void) { + static struct stat sbuf, lbuf; + static int fd; + char **fmt, path[64], *HOME = getenv("HOME") ? getenv("HOME") : ""; + for (fmt = devtab_paths; *fmt; fmt++) { + snprintf(path, sizeof path, *fmt, HOME); + lbuf.st_ino = 0; /* initialize for test later */ + if (lstat(path, &lbuf) >= 0 && S_ISLNK(lbuf.st_mode)) + /* don't follow symlinks */ + continue; + if ( (fd = open(path, O_RDONLY)) < 0 /* open DEVTAB file */ + || fstat(fd, &sbuf) < 0 /* fstat it */ + || (lbuf.st_ino && (sbuf.st_ino != lbuf.st_ino)) /* race */ + || sbuf.st_nlink > 1 /* hardlink attack */ + || sbuf.st_size != Ndevtab /* make sure it's the right size */ + || (devtab = mmap(0, Ndevtab, PROT_READ, MAP_SHARED, fd, 0)) == (caddr_t) -1 + || close(fd) == -1) + { /* could not open for read, attempt to fix/create */ + int oumsk = umask(0); + if (devtab) + munmap(devtab, Ndevtab); + if (((fd = open(path, O_RDWR|O_TRUNC|O_CREAT, DEVINITIALMODE)) == -1 && + (unlink(path), fd = open(path, O_RDWR|O_TRUNC|O_CREAT, DEVINITIALMODE)) == -1) + || !dev_to_devtab(fd)) { + close(fd); /* either both opens failed or the constructor failed */ + unlink(path); /* in case we created but could not fill a file */ + umask(oumsk); + continue; + } else { + devtab_initialized = 1; + close(fd); + umask(oumsk); + return 1; + } + } + else + return devtab_initialized = 1; + } + return devtab_initialized; +} + +/* stat every file in DEVDIR saving its basename in devtab[] if it has + a MAJOR(st_rdev) in our list of majors. return 0 on error otherwise 1. */ +static int dev_to_devtab(int fd) { + static struct stat sbuf; + int i; + dev_t m; + struct dirent* ent; + DIR* dev; + + if (!(dev = opendir(DEVDIR))) { + fprintf(stderr, "%s: %s\nCannot generate device number -> name mapping.\n", + DEVDIR, sys_errlist[errno]); + return 0; + } + if (!(devtab = malloc(Ndevtab))) { + fprintf(stderr, "%s: could not allocate memory\n", sys_errlist[errno]); + return 0; + } + memset((void*)devtab, 0, Ndevtab); + while ((ent = readdir(dev))) { /* loop over all dirents in DEVDIR */ + if (lstat(name_to_path(ent->d_name), &sbuf) < 0 + || !S_ISCHR(sbuf.st_mode)) /* only look for char special devs */ + continue; /* due to overloading of majors */ + m = MAJOR(sbuf.st_rdev); /* copy name to appropriate spot */ + if ((i = lookup(m, major, Nmajor)) != -1) + strncpy(TAB(i,MINOR(sbuf.st_rdev)), ent->d_name, DEV_NAME_MAX); + } + closedir(dev); + if (write(fd, devtab, Ndevtab) != Ndevtab) /* probably no disk space */ + return 0; + return 1; +} + +static char path[DEV_MAX_PATH]; + +static char* name_to_path(char* name) { + static char* Path; + if (!Path) { + strcpy(path, DEVDIR); /* copy DEVDIR */ + path[sizeof(DEVDIR) - 1] = '/'; /* change NUL to '/' */ + Path = path + sizeof(DEVDIR); /* put Path at start of basename */ + } + strncpy(Path, name, DEV_MAX_PATH - sizeof(DEVDIR)); + return path; +} + +#ifdef TEST_DEVNAME +int main(int argc, char** argv) { /* usage: cmd [<major> <minor>|<name>] */ + dev_t tmp; + if (argc < 2) { + printf("%s: [ maj min... | name ... ]\n", argv[0]); + return 0; + } + if (argv[1][0] >= '0' && argv[1][0] <= '9') + for(argv++ ; argv[0] && argv[1] ; argv+=2) + printf("%s\n", dev_to_name(MKDEV( atoi(argv[0]), atoi(argv[1]) ))); + else + for(argv++ ; *argv ; argv++) { + tmp = name_to_dev(*argv); + printf("%d, %d\n", MAJOR(tmp), MINOR(tmp)); + } + return 0; +} +#endif +/* +Using this program on over 700 files in /dev to perform number->name resolution +took well under 300 microsecs per device number pair on a Pentium 90. It is +somewhat tough to time because once the 3 pages have been mapped in, the time is +almost zero. For things like top, this method may even be faster in the long +run. Those interested can gprof it for me. This system has the virtue of being +nearly perfectly adaptable to individual systems, self updating when /dev +changes and pretty darn fast when it hasn't. It will be slow for users without +perms to change the psdevtab file, though. So this is what I decided was +reasonable. If the process does not have perms to create or update +/etc/psdevtab and it is out of date, we try /tmp/psdevtab. If /tmp/psdevtab is +either out of date or unreadable (malicious user creates it and chmods it), +$HOME/.psdevtab is used. This secondarily allows for per-user naming of ttys, +but is really so that at most one user sees only a single delay per /dev +modification. + +To do the timings I did something like this with zsh: + a=(`ls -l *(%^@/) | awk '{print $5 $6}' | sed 's/,/ /'`); + time ./test $a + +Finally, for lack of a better file for these to be in, I have collected the +old algorithmic device number <-> device name mappings. + Let m = major device number and n = minor device number satisfy: + devno = m<<8 + n , m = devno>>8 , n = devno && 0x00FF, and let + char L[32]="pqrstuvwxyzABCDEFGHIJKLMNOPQRSTU", H[16]="01234567890abcdef"; + DEVICE NUMBERS SPECIAL FILE NAMES + OLD SYSTEM (64 pseudoterminal devices): + m=4: + n=0..63: tty + itoa_dec(n+1) + n=128..191: pty + L[(n-128)/16] + H[(n-128)%16] + n=192..255: tty + L[(n-192)/16] + H[(n-192)%16] + NEW SYSTEM (256/512 pseudoterminal devices): + m=2, n: pty + L[n/16] + H[n%16] + m=3, n: tty + L[n/16] + H[n%16] + m=4, n: tty + itoa_dec(n+1) + m=49, n: pty + L[16+n/16] + H[n%16] + m=50, n: tty + L[16+n/16] + H[n%16] + (THE SAME IN EITHER SYSTEM) + CALL-UNIX AND CONTROLLING TERMINAL DEVICES + m=5: + n=0: tty + n=64..128: cua + {'0' + (n-64)} + CYCLADES MULTIPORT: + m=19, n: ttyC + itoa_hex(n) + m=20, n: cub + itoa_hex(n) */ + +/* Re-implementation of old interface with the new generic functions. */ + +/* This does exactly the same thing as name_to_dev only now a full "ttyXX" + specification will work as well. +*/ +int tty_to_dev(char *tty) { + static char pref_name_1[32] = "tty", *pnam1 = pref_name_1 + 3, + pref_name_2[32] = "cu", *pnam2 = pref_name_2 + 2; + dev_t num; + if ((num = name_to_dev(tty)) != (dev_t) -1) /* try tty straight up */ + return num; + strncpy(pnam1, tty, 32 - 3); /* try with "tty" prepended */ + if ((num = name_to_dev(pref_name_1)) != (dev_t) -1) + return num; + strncpy(pnam2, tty, 32 - 2); /* try with "cu" prepended */ + if ((num = name_to_dev(pref_name_2)) != (dev_t) -1) + return num; + return -1; /* no match */ +} + +/* new abstraction that can maybe be generalized a little better. */ +char* abbrev_of_tty(char *tty) { + static char temp[32]; /* return buf: good only until next call */ + char *pos = strpbrk(tty, "yu"); /* end of (presumed) prefices: tty*, cu* */ + temp[0] = 0; + if (tty && tty[0] && pos && pos[0] && pos[1]) + sprintf(temp, "%*.*s", 3, 3, pos + 1); + else + strncpy(temp, " ? ", 31); + return temp; +} + +/* Do in-place modification of the 4-buffer `tty' based upon `dev' */ +void dev_to_tty(char *tty, int dev) { + char* new = abbrev_of_tty(dev_to_name(dev)); + strncpy(tty, new, 4); +} diff --git a/libproc/devname.h b/libproc/devname.h new file mode 100644 index 00000000..e1371f45 --- /dev/null +++ b/libproc/devname.h @@ -0,0 +1,10 @@ + +#include <sys/types.h> + +dev_t name_to_dev(char* name); +char* dev_to_name(dev_t num); + +dev_t tty_to_dev(char *tty); +void dev_to_tty(char *tty, int dev); + +char* abbrev_of_tty(char *tty); diff --git a/libproc/ksym.c b/libproc/ksym.c new file mode 100644 index 00000000..b3ae3284 --- /dev/null +++ b/libproc/ksym.c @@ -0,0 +1,252 @@ +/* kernel address -> symbol with next lower address. Charles Blake, 1996. + * Written to obviate the need for psdatabase initialization based upon kernel + * binary formats, etc. + * + * The basic algorithm is an approximate (intervals split vaguely 50-50) binary + * search taking advantage of the fact the System.map is already sorted in + * ascending order by the kernel makefile. It needs to assume an average symbol + * record length to avoid scanning the entire symbol table, but in practice the + * search time does not seem to be especially sensitive to this choice. + * + * The search could be an exact binary search if the lines of System.map were + * padded with blanks to the right. awk '{printf "%8s%2s %-21.21s\n",$1,$2,$3}' + * would do the trick for this but either makes the file large or truncates + * symbols. The approximate method seems to be plenty fast enough, costing + * only about as much as one extra fstat() or so per process. + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/utsname.h> +#include "proc/psdata.h" +#include "proc/ps.h" +#include "proc/version.h" + +#define MAX_ADDR_SZ 32 +static char *sysmap, *sysmap_last, sysmap_fmt[10]; +static int sysmap_len, sysmap_mean = 32, sysmap_addrsz; + +/* scan backward in a string no further than address beg looking for c */ +static char *strchrrev(char *a, char *beg, char c) { + if (a) + while (--a > beg && *a != c) ; + return a; +} + +/* return ptr to the beg of approximately the i-th record */ +static char *addr_str(int i) { + char *guess = sysmap + sysmap_mean * i; + if (!i) return sysmap; + if (guess - sysmap > sysmap_len - 2) guess = sysmap + sysmap_len - 2; + for ( ; *guess != '\n' && guess > sysmap; guess--) + ; + return guess + 1; + +} + +/* return ptr to symbol string (\n terminated) given beg of record ptr */ +static char *sym_put(char *buf, int len, char *addrptr) { + char *s; + while (*addrptr++ != ' ') ; + while (*addrptr++ != ' ') ; + strncpy(buf, addrptr, len); + for (s = buf; s < buf + len; s++) + if (*s == '\n') + *s = '\0'; + buf[len - 1] = '\0'; + return buf; +} + +/* Try to open and mmap a single symbol table file and initialize globals */ +int sysmap_mmap(char *path) { + int fd; + struct stat sbuf; + char *p; + if (sysmap) /* do nothing if already mapped */ + return 1; + if ((fd = open(path, O_RDONLY)) < 0 + || fstat(fd, &sbuf) < 0 + || (sysmap = mmap(0, sbuf.st_size, + PROT_READ, MAP_SHARED, + fd, 0)) == (caddr_t) -1) + { + close(fd); + sysmap = NULL; + return 0; + } + sysmap_len = sbuf.st_size; + sysmap_last = strchrrev(sysmap + sysmap_len - 2, sysmap, '\n') + 1; + + /* Now check first line of sysmap for hex numbers in first column. Note: + 0x/0X prefixes are disallowed, but easily addable. Capitalization is + irrelevant because strncasecmp(3) is used below instead of strncmp. */ + for (p = sysmap; *p != ' ' + && ((*p >= '0' && *p <= '9') || + (*p >= 'A' && *p <= 'F') || + (*p >= 'a' && *p <= 'f')) + && p < sysmap + MAX_ADDR_SZ; + p++) /* no-op */ ; + if (*p != ' ') { /* uh-oh: cannot understand format */ + fprintf(stderr, "warning: %s not parseable as a System.map.\n", path); + munmap(sysmap, sysmap_len); + sysmap = NULL; + close(fd); + return 0; + } + sysmap_addrsz = p - sysmap; + snprintf(sysmap_fmt, sizeof sysmap_fmt, "%%0%dlx", sysmap_addrsz); + close(fd); + return 1; +} + +/* kernel address -> name resolver. + returned value is only good until the next call to the function. + */ +char *sysmap_symbol(unsigned long address) { + static char rval[128], *pc, addr[MAX_ADDR_SZ]; + int i, p, n = sysmap_len / (double)sysmap_mean; + + sprintf(addr, sysmap_fmt, address); + p = 0; pc = sysmap; + while (n) { + i = p + (n >> 1); + if (strncasecmp(addr, pc = addr_str(i), sysmap_addrsz) > 0) + p = i + 1; + n >>= 1; + } + if (pc == sysmap_last) /* scan forward but not past end */ + return sym_put(rval, sizeof rval, pc); + while (strncasecmp(addr, pc, sysmap_addrsz) > 0) + pc = strchr(pc, '\n') + 1; + if (pc == sysmap) /* scan backward but not past beg */ + return sym_put(rval, sizeof rval, pc); + while (strncasecmp(addr, pc, sysmap_addrsz) < 0) + pc = strchrrev(pc - 1, sysmap, '\n') + 1; + return sym_put(rval, sizeof rval, pc); +} + +/* extern struct nlist *namelist; */ +struct tbl_s vars, fncs; +struct psdb_hdr db_hdr; +int psdb = -1; + +int open_psdb(void) { + static char *sysmap_paths[] = { + "/boot/System.map-%s", + "/boot/System.map", + "/lib/modules/%s/System.map", + NULL + }; + static char *psdb_paths[] = { + "/etc/psdatabase", + "/boot/psdatabase-%s", + "/boot/psdatabase", + "/lib/modules/%s/psdatabase", + NULL + }; + char **fmt, *env, path[64]; + struct utsname uts; + uname(&uts); + if ((env = getenv("PS_SYSMAP")) && sysmap_mmap(env)) + return 0; + for (fmt = sysmap_paths; *fmt; fmt++) { + snprintf(path, sizeof path, *fmt, uts.release); + if (sysmap_mmap(path)) + return 0; + } + for (fmt = psdb_paths; *fmt; fmt++) { + snprintf(path, sizeof path, *fmt, uts.release); + if ((psdb = open(path, O_RDONLY)) != -1 && + read(psdb, (char*)&db_hdr, sizeof db_hdr) == sizeof db_hdr && + strncmp(db_hdr.magic, procps_version, sizeof(db_hdr.magic)) == 0) + /* && version_cmp(kernel,psdatabase) */ + return 0; + if (psdb != -1) + fprintf(stderr, + "psdatabase has magic no. %*s instead of %*s\n", + (int) sizeof db_hdr.magic, db_hdr.magic, + (int) sizeof db_hdr.magic, procps_version); + close(psdb); + } + return -1; +} + +void close_psdb(void) { + if (sysmap) + munmap(sysmap, sysmap_len); + else if (psdb != -1) + close(psdb); + psdb = -1; + sysmap = NULL; +} + +int read_tbl(struct dbtbl_s *dbtbl, struct tbl_s *tbl) { + lseek(psdb, dbtbl->off, SEEK_SET); + tbl->tbl = (struct sym_s *) xmalloc(dbtbl->size); + if (read(psdb, (char *) tbl->tbl, dbtbl->size) != dbtbl->size) { + perror(PSDATABASE); + exit(1); + } + tbl->nsym = dbtbl->nsym; + tbl->strings = (char *) (tbl->tbl + tbl->nsym); + return 0; +} + +char * find_func(unsigned long address) { + int n; + struct sym_s *p; + char *s; + + if (sysmap) + return sysmap_symbol(address); + if (psdb == -1) + return "(no psdb)"; + if (fncs.tbl == NULL) + read_tbl(&db_hdr.fncs, &fncs); + p = fncs.tbl; + n = fncs.nsym; + while (n) { + int i = n / 2; + if (p[i].addr < address) { + p = &p[i+1]; + if (p->addr > address) { + --p; + break; + } + --n; + } + n /= 2; + } + s = p->name + fncs.strings; + return *s=='_' ? s+1 : s; +} + +char * wchan(unsigned long address) { + static char zero = 0; + char *p; + + if (address) { + p = find_func(address); + if (strncmp(p, "sys_", 4) == 0) + p += 4; + while (*p == '_' && *p) + ++p; + } else /* 0 address means not in kernel space */ + p = &zero; + return p; +} + +#ifdef SYSMAP_TEST +int main(int ac, char** av) { + if (ac < 3) {printf("%s System.map lines hexaddr ...\n",av[0]); return 1;} + if (!sysmap_mmap(av[1])) return 1; + if ((sysmap_mean = atoi(av[2])) <= 0) return 1; + for (av += 3; *av; av++) + printf("%s %s\n", *av, sysmap_symbol(strtoul(*av, NULL, 16))); + return 0; +} +#endif diff --git a/libproc/output.c b/libproc/output.c new file mode 100644 index 00000000..66e4e407 --- /dev/null +++ b/libproc/output.c @@ -0,0 +1,52 @@ +/* + Some output conversion routines for libproc + Copyright (C) 1996, Charles Blake. See COPYING for details. +*/ +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +/* output a string, converting unprintables to octal as we go, and stopping after + processing max chars of output (accounting for expansion due to octal rep). +*/ +unsigned print_str(FILE* file, char *s, unsigned max) { + int i; + for (i=0; s[i] && i < max; i++) + if (isprint(s[i]) || s[i] == ' ') + fputc(s[i], file); + else { + if (max - i > 3) { + fprintf(file, "\\%03o", s[i]); + i += 3; /* 4 printed, but i counts one */ + } else + return max - i; + } + return max - i; +} + +/* output an argv style NULL-terminated string list, converting unprintables + to octal as we go, separating items of the list by 'sep' and stopping after + processing max chars of output (accounting for expansion due to octal rep). +*/ +unsigned print_strlist(FILE* file, char **strs, char* sep, unsigned max) { + int i, n, seplen = strlen(sep); + for (n=0; *strs && n < max; strs++) { + for (i=0; strs[0][i] && n+i < max; i++) + if (isprint(strs[0][i]) || strs[0][i] == ' ') + fputc(strs[0][i], file); + else { + if (max-(n+i) > 3) { + fprintf(file, "\\%03o", strs[0][i]); + n += 3; /* 4 printed, but i counts one */ + } else + return max - n; + } + n += i; + if (n + seplen < max) { + fputs(sep, file); + n += seplen; + } else + return max - n; + } + return max - n; +} diff --git a/libproc/ps.h b/libproc/ps.h new file mode 100644 index 00000000..aa065cbf --- /dev/null +++ b/libproc/ps.h @@ -0,0 +1,30 @@ +/* The shadow of the original with only common prototypes now. */ +#include <stdio.h> +#include <sys/types.h> + +/* get definition of HZ */ +#include <asm/param.h> + +/* get page info */ +#include <asm/page.h> + +char *wchan(unsigned long); +char *find_func(unsigned long address); +void *xcalloc(void *pointer, int size); +void *xmalloc(unsigned int size); +void *xrealloc(void *oldp, unsigned int size); + +int mult_lvl_cmp(void* a, void* b); +int node_mult_lvl_cmp(void* a, void* b); +void dump_keys(void); + +char *user_from_uid(int uid); + +int open_sysmap(void); +int open_psdb(void); +void close_psdb(void); +void make_fnctbl(void); + +unsigned print_str (FILE* file, char *s, unsigned max); +unsigned print_strlist(FILE* file, char **strs, char* sep, unsigned max); +unsigned snprint_strlist(char *buf, int max, char **strs, char *sep); diff --git a/libproc/psdata.h b/libproc/psdata.h new file mode 100644 index 00000000..a6c480e8 --- /dev/null +++ b/libproc/psdata.h @@ -0,0 +1,103 @@ +/* + * psdata.h + * + * Jeffrey A. Uphoff <juphoff@nrao.edu>, 1995, 1996. + * Michael K. Johnson. + * Bruno Lankester. + * (And others I'm sure...) + * + */ + +/* + * Capabilities are for reading system images and producing maps for + * WCHAN output. + * + * AOUT_CAPABLE and ELF_CAPABLE may have 32-bit word size limitations + * and have only been tested by the maintainer on Intel systems. They + * are retained in the source tree in case they are useful; they are + * intended to be generally deprecated. + * + * BFD_CAPABLE should work on any system with BFD. + * + * Set the capabilities in the top-level Makefile. + */ + +#if defined(ELF_CAPABLE) +# define ELF_OBJECT 1 +# define ELF_FUNC 2 +#endif + +#include <sys/types.h> +#include <linux/utsname.h> + +#define PSDATABASE "/etc/psdatabase" + +struct dbtbl_s { + off_t off; /* offset in psdatabase */ + int nsym; /* # symbols */ + int size; /* size of array + strings */ +}; + +/* + * header of psdatabase + */ +struct psdb_hdr { + /* Current procps package version goes here. kmemps doesn't like this. */ + char magic[32]; + /* + * These are not functional--they only reside in the database for + * informational purposes (i.e. if you want to look at the raw + * database and see what kernel it's for). + */ + char uts_release[__NEW_UTS_LEN]; + char uts_version[__NEW_UTS_LEN]; + /* + * Again, this is not functional, it's just there for information: it + * shows the path to the uncompressed kernel image that was used to + * generate this database. + */ + char sys_path[128]; + /* List of all functions. */ + struct dbtbl_s fncs; + /* + * This is currently only used to look up system_utsname while + * psupdate is building the database--it really should be phased out! + */ + /* List of all bss and data symbols. */ + struct dbtbl_s vars; + /* + * The list of tty names that kmemps likes/uses in no longer present + * in the procps psdatabase--it was never being built by procps' + * psupdate anyway, so I removed the entry from the database header. + */ +}; + +struct sym_s { + unsigned long addr; /* core address in kernel */ + int name; /* offset from strings ptr */ +}; + +struct tbl_s { + struct sym_s *tbl; + int nsym; + char *strings; /* ptr to start of strings */ +}; + +extern struct psdb_hdr db_hdr; +extern struct tbl_s fncs, vars; + +int read_tbl (struct dbtbl_s *, struct tbl_s *); +void *xmalloc (unsigned int); +void *xrealloc (void *, unsigned int); + +#define MLSEEK(FD, WHERE, WHENCE, ERROR)\ +if (lseek ((FD), (WHERE), (WHENCE)) == -1) {\ + perror ((ERROR));\ + exit (errno);\ +} + +#define MREAD(FD, WHAT, SIZE, ERROR)\ +if (read ((FD), (WHAT), (SIZE)) != (SIZE)) {\ + perror ((ERROR));\ + exit (errno);\ +} diff --git a/libproc/pwcache.c b/libproc/pwcache.c new file mode 100644 index 00000000..2d50054c --- /dev/null +++ b/libproc/pwcache.c @@ -0,0 +1,37 @@ +#include <stdio.h> +#include <stdlib.h> +#include <pwd.h> +#include "proc/ps.h" + +#define HASHSIZE 16 /* power of 2 */ +#define HASH(x) ((x) & (HASHSIZE - 1)) + + +static struct pwbuf { + int uid; + char name[12]; + struct pwbuf *next; +} *pwhash[HASHSIZE]; + +char *user_from_uid(int uid) +{ + struct pwbuf **p; + struct passwd *pw; + + p = &pwhash[HASH(uid)]; + while (*p) { + if ((*p)->uid == uid) + return((*p)->name); + p = &(*p)->next; + } + *p = (struct pwbuf *) xmalloc(sizeof(struct pwbuf)); + (*p)->uid = uid; + if ((pw = getpwuid(uid)) == NULL) + sprintf((*p)->name, "#%d", uid); + else + sprintf((*p)->name, "%-.8s", pw->pw_name); + (*p)->next = NULL; + return((*p)->name); +} + +void bad_user_access_length() { } diff --git a/libproc/readproc.c b/libproc/readproc.c new file mode 100644 index 00000000..31836b0c --- /dev/null +++ b/libproc/readproc.c @@ -0,0 +1,395 @@ +/* + * New Interface to Process Table -- PROCTAB Stream (a la Directory streams) + * Copyright(C) 1996. Charles L. Blake. + */ +#include "proc/version.h" +#include "proc/readproc.h" +#include "proc/devname.h" +#include "proc/ps.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/dir.h> +#include <sys/types.h> +#include <sys/stat.h> + +#define Do(x) (flags & PROC_ ## x) /* convenient shorthand */ + +/* initiate a process table scan + */ +PROCTAB* openproc(int flags, ...) { + va_list ap; + PROCTAB* PT = xmalloc(sizeof(PROCTAB)); + + if (!Do(PID) && !(PT->procfs = opendir("/proc"))) + return NULL; + PT->flags = flags; + va_start(ap, flags); /* Init args list */ + if (Do(PID)) + PT->pids = va_arg(ap, pid_t*); + else if (Do(TTY)) + PT->ttys = va_arg(ap, dev_t*); + else if (Do(UID)) { + PT->uids = va_arg(ap, uid_t*); + PT->nuid = va_arg(ap, int); + } else if (Do(STAT)) + PT->stats = va_arg(ap, char*); + va_end(ap); /* Clean up args list */ + if (Do(ANYTTY) && Do(TTY)) + PT->flags = PT->flags & ~PROC_TTY; /* turn off TTY flag */ + return PT; +} + +/* terminate a process table scan + */ +void closeproc(PROCTAB* PT) { + if (PT->procfs) closedir(PT->procfs); + if (PT) free(PT); +} + +/* deallocate the space allocated by readproc if the passed rbuf was NULL + */ +void freeproc(proc_t* p) { + if (!p) /* in case p is NULL */ + return; + /* ptrs are after strings to avoid copying memory when building them. */ + /* so free is called on the address of the address of strvec[0]. */ + if (p->cmdline) + free((void*)*p->cmdline); + if (p->environ) + free((void*)*p->environ); + free(p); +} + +/* stat2proc() makes sure it can handle arbitrary executable file basenames + for `cmd', i.e. those with embedded whitespace or embedded ')'s. Such names + confuse %s (see scanf(3)), so the string is split and %39c is used instead. + (except for embedded ')' "(%[^)]c)" would work. +*/ +void stat2proc(char* S, proc_t* P) { + char* tmp = strrchr(S, ')'); /* split into "PID (cmd" and "<rest>" */ + *tmp = '\0'; /* replace trailing ')' with NUL */ + /* parse these two strings separately, skipping the leading "(". */ + memset(P->cmd, 0, sizeof P->cmd); /* clear even though *P xcalloc'd ?! */ + sscanf(S, "%d (%39c", &P->pid, P->cmd); + sscanf(tmp + 2, /* skip space after ')' too */ + "%c %d %d %d %d %d %lu %lu %lu %lu %lu %ld %ld %ld %ld %d " + "%d %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %LX %LX %LX %LX %lu", + &P->state, &P->ppid, &P->pgrp, &P->session, &P->tty, &P->tpgid, + &P->flags, &P->min_flt, &P->cmin_flt, &P->maj_flt, &P->cmaj_flt, + &P->utime, &P->stime, &P->cutime, &P->cstime, &P->priority, &P->nice, + &P->timeout, &P->it_real_value, &P->start_time, &P->vsize, &P->rss, + &P->rss_rlim, &P->start_code, &P->end_code, &P->start_stack, + &P->kstk_esp, &P->kstk_eip, &P->signal, &P->blocked, &P->sigignore, + &P->sigcatch, &P->wchan); + if (P->tty == 0) + P->tty = -1; /* the old notty val, update elsewhere bef. moving to 0 */ + if (linux_version_code < LINUX_VERSION(1,3,39)) { + P->priority = 2*15 - P->priority; /* map old meanings to new */ + P->nice = 15 - P->nice; + } + if (linux_version_code < LINUX_VERSION(1,1,30) && P->tty != -1) + P->tty = 4*0x100 + P->tty; /* when tty wasn't full devno */ +} + +void statm2proc(char* s, proc_t* P) { + sscanf(s, "%ld %ld %ld %ld %ld %ld %ld", + &P->size, &P->resident, &P->share, + &P->trs, &P->lrs, &P->drs, &P->dt); +} + +void nulls2sep(char* str, int len, char sep) { + int i; + for (i = 0; i < len; i++) + if (str[i] == 0) + str[i] = sep; +} + +int file2str(char *directory, char *what, char *ret, int cap) { + static char filename[80]; + int fd, num_read; + + sprintf(filename, "%s/%s", directory, what); + if ( (fd = open(filename, O_RDONLY, 0)) == -1 ) return -1; + if ( (num_read = read(fd, ret, cap - 1)) <= 0 ) return -1; + ret[num_read] = 0; + close(fd); + return num_read; +} + +char** file2strvec(char* directory, char* what) { + char buf[2048]; /* read buf bytes at a time */ + char *p, *rbuf = 0, *endbuf, **q, **ret; + int fd, tot = 0, n, c, end_of_file = 0; + int align; + + sprintf(buf, "%s/%s", directory, what); + if ( (fd = open(buf, O_RDONLY, 0) ) == -1 ) return NULL; + + /* read whole file into a memory buffer, allocating as we go */ + while ((n = read(fd, buf, sizeof buf - 1)) > 0) { + if (n < sizeof buf - 1) + end_of_file = 1; + if (n == 0 && rbuf == 0) + return NULL; /* process died between our open and read */ + if (n < 0) { + if (rbuf) + free(rbuf); + return NULL; /* read error */ + } + if (end_of_file && buf[n-1]) /* last read char not null */ + buf[n++] = '\0'; /* so append null-terminator */ + rbuf = xrealloc(rbuf, tot + n); /* allocate more memory */ + memcpy(rbuf + tot, buf, n); /* copy buffer into it */ + tot += n; /* increment total byte ctr */ + if (end_of_file) + break; + } + close(fd); + if (n <= 0 && !end_of_file) { + if (rbuf) free(rbuf); + return NULL; /* read error */ + } + endbuf = rbuf + tot; /* count space for pointers */ + align = (sizeof(char*)-1) - ((tot + sizeof(char*)-1) & (sizeof(char*)-1)); + for (c = 0, p = rbuf; p < endbuf; p++) + if (!*p) + c += sizeof(char*); + c += sizeof(char*); /* one extra for NULL term */ + + rbuf = xrealloc(rbuf, tot + c + align); /* make room for ptrs AT END */ + endbuf = rbuf + tot; /* addr just past data buf */ + q = ret = (char**) (endbuf+align); /* ==> free(*ret) to dealloc */ + *q++ = p = rbuf; /* point ptrs to the strings */ + endbuf--; /* do not traverse final NUL */ + while (++p < endbuf) + if (!*p) /* NUL char implies that */ + *q++ = p+1; /* next string -> next char */ + + *q = 0; /* null ptr list terminator */ + return ret; +} + + +/* These are some nice GNU C expression subscope "inline" functions. + The can be used with arbitrary types and evaluate their arguments + exactly once. +*/ + +/* Test if item X of type T is present in the 0 terminated list L */ +# define XinL(T, X, L) ( { \ + T x = (X), *l = (L); \ + while (*l && *l != x) l++; \ + *l == x; \ + } ) + +/* Test if item X of type T is present in the list L of length N */ +# define XinLN(T, X, L, N) ( { \ + T x = (X), *l = (L); \ + int i = 0, n = (N); \ + while (i < n && l[i] != x) i++; \ + i < n && l[i] == x; \ + } ) + +/* readproc: return a pointer to a proc_t filled with requested info about the + * next process available matching the restriction set. If no more such + * processes are available, return a null pointer (boolean false). Use the + * passed buffer instead of allocating space if it is non-NULL. */ + +/* This is optimized so that if a PID list is given, only those files are + * searched for in /proc. If other lists are given in addition to the PID list, + * the same logic can follow through as for the no-PID list case. This is + * fairly complex, but it does try to not to do any unnecessary work. + * Unfortunately, the reverse filtering option in which any PID *except* the + * ones listed is pursued. + */ +#define flags (PT->flags) +proc_t* readproc(PROCTAB* PT, proc_t* rbuf) { + static struct direct *ent; /* dirent handle */ + static struct stat sb; /* stat buffer */ + static char path[32], sbuf[256]; /* bufs for stat,statm */ + int allocated = 0, matched = 0; /* flags */ + proc_t *p = NULL; + + /* loop until a proc matching restrictions is found or no more processes */ + /* I know this could be a while loop -- this way is easier to indent ;-) */ +next_proc: /* get next PID for consideration */ + if (Do(PID)) { + if (!*PT->pids) /* set to next item in pids */ + return NULL; + sprintf(path, "/proc/%d", *(PT->pids)++); + matched = 1; + } else { /* get next numeric /proc ent */ + while ((ent = readdir(PT->procfs)) && + (*ent->d_name < '0' || *ent->d_name > '9')) + ; + if (!ent || !ent->d_name) + return NULL; + sprintf(path, "/proc/%s", ent->d_name); + } + if (stat(path, &sb) == -1) /* no such dirent (anymore) */ + goto next_proc; + if (Do(UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid)) + goto next_proc; /* not one of the requested uids */ + + if (!allocated) { /* assign mem for return buf */ + p = rbuf ? rbuf : xcalloc(p, sizeof *p); /* passed buf or alloced mem */ + allocated = 1; /* remember space is set up */ + } + p->uid = sb.st_uid; /* need a way to get real uid */ + + if ((file2str(path, "stat", sbuf, sizeof sbuf)) == -1) + goto next_proc; /* error reading /proc/#/stat */ + stat2proc(sbuf, p); /* parse /proc/#/stat */ + + if (!matched && Do(TTY) && !XinL(dev_t, p->tty, PT->ttys)) + goto next_proc; /* not one of the requested ttys */ + + if (!matched && Do(ANYTTY) && p->tty == -1) + goto next_proc; /* no controlling terminal */ + + if (!matched && Do(STAT) && !strchr(PT->stats,p->state)) + goto next_proc; /* not one of the requested states */ + + if (Do(FILLMEM)) { /* read, parse /proc/#/statm */ + if ((file2str(path, "statm", sbuf, sizeof sbuf)) != -1 ) + statm2proc(sbuf, p); /* ignore statm errors here */ + } /* statm fields just zero */ + + /* some number->text resolving which is time consuming */ + if (Do(FILLTTY)) + dev_to_tty(p->ttyc, p->tty); + if (Do(FILLUSR)) + strncpy(p->user, user_from_uid(p->uid), sizeof p->user); + + if (Do(FILLCMD)) /* read+parse /proc/#/cmdline */ + p->cmdline = file2strvec(path, "cmdline"); + if (Do(FILLENV)) /* read+parse /proc/#/environ */ + p->environ = file2strvec(path, "environ"); + + if (p->state == 'Z') /* fixup cmd for zombies */ + strncat(p->cmd," <zombie>", sizeof p->cmd); + + return p; +} +#undef flags + +/* Convenient wrapper around openproc and readproc to slurp in the whole process + * tree subset satisfying the constraints of flags and the optional PID list. + * Free allocated memory with freeproctree(). The tree structure is a classic + * left-list children + right-list siblings. The algorithm is a two-pass of the + * process table. Since most process trees will have children with strictly + * increasing PIDs, most of the structure will be picked up in the first pass. + * The second loop then cleans up any nodes which turn out to have preceeded + * their parent in /proc order. + */ + +/* Traverse tree 't' breadth-first looking for a process with pid p */ +proc_t* LookupPID(proc_t* t, pid_t p) { + proc_t* tmp = NULL; + if (!t) + return NULL; + if (t->pid == p) /* look here/terminate recursion */ + return t; + if ((tmp = LookupPID(t->l, p))) /* recurse over children */ + return tmp; + for (; t; t=t->r) /* recurse over siblings */ + if ((tmp = LookupPID(tmp, p))) + return tmp; + return NULL; +} + +proc_t* readproctree(int flags, ...) { + static proc_t tree; + PROCTAB* PT = NULL; + proc_t *node, *tmp=NULL, *tmp2=NULL; + va_list ap; + + /* pass through apropriate arguments to openproc */ + va_start(ap, flags); + if (Do(UID)) { + /* temporary variables to ensure that va_arg() instances + * are called in the right order + */ + uid_t* u; + int i; + + u = va_arg(ap, uid_t*); + i = va_arg(ap, int); + PT = openproc(flags, u, i); + } + else if (Do(PID) || Do(TTY) || Do(STAT)) + PT = openproc(flags, va_arg(ap, void*)); + else + PT = openproc(flags); + va_end(ap); + + /* first pass: build tree, putting orphans on the first level */ + tree.l = tree.r = NULL; + while ((node = readproc(PT,0))) + if ((tmp = LookupPID(&tree, node->ppid))) { + node->r = tmp->l->r; /* node --> left list of parent */ + tmp->l->r = node; + } else { + node->r = tree.r; /* node --> right list of 'tree' */ + tree.r = node; + } + /* second pass: scan tree for PPIDs of level-1 nodes moving links as necessary */ + for (node = &tree; node; node = node->r) + if ((tmp = LookupPID(&tree, node->r->ppid))) { + tmp2 = node->r; /* unlink from right list of 'tree' */ + node->r = node->r->r; + tmp2->r = tmp->l->r; /* insert as child of found node */ + tmp->l->r = node; + } + closeproc(PT); + return &tree; +} + +/* Convenient wrapper around openproc and readproc to slurp in the whole process + * table subset satisfying the constraints of flags and the optional PID list. + * Free allocated memory with freeproctab(). Access via tab[N]->member. The + * pointer list is NULL terminated. + */ +proc_t** readproctab(int flags, ...) { + PROCTAB* PT = NULL; + proc_t** tab = NULL; + int n = 0; + va_list ap; + + va_start(ap, flags); /* pass through args to openproc */ + if (Do(UID)) { + /* temporary variables to ensure that va_arg() instances + * are called in the right order + */ + uid_t* u; + int i; + + u = va_arg(ap, uid_t*); + i = va_arg(ap, int); + PT = openproc(flags, u, i); + } + else if (Do(PID) || Do(TTY) || Do(STAT)) + PT = openproc(flags, va_arg(ap, void*)); /* assume ptr sizes same */ + else + PT = openproc(flags); + va_end(ap); + do { /* read table: */ + tab = xrealloc(tab, (n+1)*sizeof(proc_t*));/* realloc as we go, using */ + tab[n] = readproc(PT, NULL); /* final null to terminate */ + } while (tab[n++]); /* stop when NULL reached */ + closeproc(PT); + return tab; +} + +/* deallocate a table of pointers to proc structures + */ +void freeproctab(proc_t** tab) { + proc_t** p; + for(p = tab; *p; p++) + freeproc(*p); + free(tab); +} diff --git a/libproc/readproc.h b/libproc/readproc.h new file mode 100644 index 00000000..b93f0198 --- /dev/null +++ b/libproc/readproc.h @@ -0,0 +1,168 @@ +/* + * New Interface to Process Table -- PROCTAB Stream (a la Directory streams) + * Copyright(C) 1996. Charles L. Blake. + */ + +/* Basic data structure which holds all information we can get about a process. + * (unless otherwise specified, fields are read from /proc/#/stat) + */ +typedef struct proc_s { + char + user[10], /* user name corresponding to owner of process */ + cmd[40], /* basename of executable file in call to exec(2) */ + state, /* single-char code for process state (S=sleeping) */ + ttyc[5], /* string representation of controlling tty device */ + **environ, /* environment string vector (/proc/#/environ) */ + **cmdline; /* command line string vector (/proc/#/cmdline) */ + int + uid, /* user id */ + pid, /* process id */ + ppid, /* pid of parent process */ + pgrp, /* process group id */ + session, /* session id */ + tty, /* full device number of controlling terminal */ + tpgid, /* terminal process group id */ + priority, /* kernel scheduling priority */ + nice; /* standard unix nice level of process */ + long long + signal, /* mask of pending signals */ + blocked, /* mask of blocked signals */ + sigignore, /* mask of ignored signals */ + sigcatch; /* mask of caught signals */ + long + start_time, /* start time of process -- seconds since 1-1-70 */ + utime, /* user-mode CPU time accumulated by process */ + stime, /* kernel-mode CPU time accumulated by process */ + cutime, /* cumulative utime of process and reaped children */ + cstime, /* cumulative stime of process and reaped children */ + /* the next 7 members come from /proc/#/statm */ + size, /* total # of pages of memory */ + resident, /* number of resident set (non-swapped) pages (4k) */ + share, /* number of pages of shared (mmap'd) memory */ + trs, /* text resident set size */ + lrs, /* shared-lib resident set size */ + drs, /* data resident set size */ + dt; /* dirty pages */ + unsigned + pcpu; /* %CPU usage (is not filled in by readproc!!!) */ + unsigned long + vsize, /* number of pages of virtual memory ... */ + rss, /* resident set size from /proc/#/stat */ + rss_rlim, /* resident set size ... ? */ + timeout, /* ? */ + it_real_value, /* ? */ + flags, /* kernel flags for the process */ + min_flt, /* number of minor page faults since process start */ + maj_flt, /* number of major page faults since process start */ + cmin_flt, /* cumulative min_flt of process and child processes */ + cmaj_flt, /* cumulative maj_flt of process and child processes */ + start_code, /* address of beginning of code segment */ + end_code, /* address of end of code segment */ + start_stack, /* address of the bottom of stack for the process */ + kstk_esp, /* kernel stack pointer */ + kstk_eip, /* kernel stack pointer */ + wchan; /* address of kernel wait channel proc is sleeping in */ + struct proc_s *l, /* ptrs for building arbitrary linked structs */ + *r; /* (i.e. singly/doubly-linked lists and trees */ +} proc_t; + +/* PROCTAB: data structure holding the persistent information readproc needs + * from openproc(). The setup is intentionally similar to the dirent interface + * and other system table interfaces (utmp+wtmp come to mind). + */ +#include <sys/types.h> +#include <dirent.h> +#include <unistd.h> +typedef struct { + DIR* procfs; + int flags; + pid_t* pids; /* pids of the procs */ + dev_t* ttys; /* devnos of the cttys */ + uid_t* uids; /* uids of procs */ + int nuid; /* cannot really sentinel-terminate unsigned short[] */ + char* stats; /* status chars (actually output into /proc//stat) */ +} PROCTAB; + +/* initialize a PROCTAB structure holding needed call-to-call persistent data + */ +PROCTAB* openproc(int flags, ... /* pid_t*|uid_t*|dev_t*|char* [, int n] */ ); + +/* Convenient wrapper around openproc and readproc to slurp in the whole process + * table subset satisfying the constraints of flags and the optional PID list. + * Free allocated memory with freeproctab(). Access via tab[N]->member. The + * pointer list is NULL terminated. + */ +proc_t** readproctab(int flags, ... /* same as openproc */ ); + +/* Convenient wrapper around openproc and readproc to slurp in the whole process + * tree subset satisfying the constraints of flags and the optional PID list. + */ +proc_t* readproctree(int flags, ... /* same as openproc */ ); + +/* clean-up open files, etc from the openproc() + */ +void closeproc(PROCTAB* PT); + +/* retrieve the next process matching the criteria set by the openproc() + */ +proc_t* readproc(PROCTAB* PT, proc_t* return_buf); + +/* deallocate space allocated by readproc + */ +void freeproc(proc_t* p); + +/* deallocate space allocated by readproctab + */ +void freeproctab(proc_t** p); + +/* openproc/readproctab: + * + * Return PROCTAB* / *proc_t[] or NULL on error ((probably) "/proc" cannot be + * opened.) By default readproc will consider all processes as valid to parse + * and return, but not actually fill in the cmdline, environ, and /proc/#/statm + * derived memory fields. + * + * `flags' (a bitwise-or of PROC_* below) modifies the default behavior. The + * "fill" options will cause more of the proc_t to be filled in. The "filter" + * options all use the second argument as the pointer to a list of objects: + * process status', process id's, user id's, and tty device numbers. The third + * argument is the length of the list (currently only used for lists of user + * id's since unsigned short[] supports no convenient termination sentinel.) + */ +#define PROC_FILLMEM 0x1 /* read statm into the appropriate proc_t entries */ +#define PROC_FILLCMD 0x2 /* alloc and fill in `cmdline' part of proc_t */ +#define PROC_FILLENV 0x4 /* alloc and fill in `environ' part of proc_t */ +#define PROC_FILLTTY 0x8 /* resolve device number -> tty name via psdevtab */ +#define PROC_FILLUSR 0x10 /* resolve user id number -> user name via passwd */ + /* consider only processes with one of the passed: */ +#define PROC_PID 0x100 /* process id numbers ( 0 terminated) */ +#define PROC_TTY 0x200 /* ctty device nos. ( 0 terminated) */ +#define PROC_UID 0x400 /* user id numbers ( length needed ) */ +#define PROC_STAT 0x800 /* status fields ('\0' terminated) */ +#define PROC_ANYTTY 0x1000 /* proc must have a controlling terminal */ + +/* utility functions which may be of general interest: */ + +/* slurp /proc/DIR/FILE into a single large string into the passed + buffer. return the number of bytes actually used. used for stat,statm + */ +int file2str(char *dir, char *file, char *buf, int buf_size); + +/* convert a file of null terminated strings into an argv-style string vector + * which may be de-allocated with a single free() on a dereference of the return + * value, e.g. free(*ret). used for cmdline, environ. + */ +char** file2strvec(char* directory, char* what); + +/* parse /proc/#/stat entries in string s into a proc_t + */ +void stat2proc(char* S, proc_t*); + +/* parse /proc/#/statm entries in string s into a proc_t + */ +void statm2proc(char* s, proc_t*); + +/* convert a memory buffer with nulls into a single string, + replacing the nulls with sep. No longer used. + */ +void nulls2sep(char* str, int len, char sep); diff --git a/libproc/signals.c b/libproc/signals.c new file mode 100644 index 00000000..5a1e8aa0 --- /dev/null +++ b/libproc/signals.c @@ -0,0 +1,65 @@ +/* signals.c - signal name handling */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include "proc/signals.h" + + +typedef struct { + int number; + char *name; +} SIGNAME; + + +static SIGNAME signals[] = { +#include "signames.h" /* should be in same dir as this file */ + { 0,NULL }}; + + +void list_signals(void) +{ + SIGNAME *walk; + int col; + + col = 0; + for (walk = signals; walk->name; walk++) { + if (col+strlen(walk->name)+1 > 80) { + putchar('\n'); + col = 0; + } + printf("%s%s",col ? " " : "",walk->name); + col += strlen(walk->name)+1; + } + putchar('\n'); +} + + +int get_signal(char *name,char *cmd) +{ + SIGNAME *walk; + + if (isdigit(*name)) + return atoi(name); + for (walk = signals; walk->name; walk++) + if (!strcmp(walk->name,name)) break; + if (walk->name) return walk->number; + fprintf(stderr,"%s: unknown signal; %s -l lists signals.\n",name,cmd); + exit(1); +} + +/* get_signal2 is by Michael Shields. 1994/04/25. */ +int get_signal2(char *name) +{ + SIGNAME *walk; + + if (!name) + return(-1); + if (isdigit(*name)) + return atoi(name); + for (walk = signals; walk->name; walk++) + if (!strcmp(walk->name,name)) + return(walk->number); + return(-1); +} diff --git a/libproc/signals.h b/libproc/signals.h new file mode 100644 index 00000000..c793fae0 --- /dev/null +++ b/libproc/signals.h @@ -0,0 +1,12 @@ +/* signals.h - signal name handling */ + +void list_signals(void); + +/* Lists all known signal names on standard output. */ + +int get_signal(char *name,char *cmd); +int get_signal2(char *name); + +/* Returns the signal number of NAME. If no such signal exists, an error + message is displayed and the program is terminated. CMD is the name of the + application. */ diff --git a/libproc/signames.h b/libproc/signames.h new file mode 100644 index 00000000..edcd6a38 --- /dev/null +++ b/libproc/signames.h @@ -0,0 +1,32 @@ +{ 1,"HUP" }, +{ 2,"INT" }, +{ 3,"QUIT" }, +{ 4,"ILL" }, +{ 5,"TRAP" }, +{ 6,"ABRT" }, +{ 6,"IOT" }, +{ 7,"BUS" }, +{ 8,"FPE" }, +{ 9,"KILL" }, +{ 10,"USR1" }, +{ 11,"SEGV" }, +{ 12,"USR2" }, +{ 13,"PIPE" }, +{ 14,"ALRM" }, +{ 15,"TERM" }, +{ 16,"STKFLT" }, +{ 17,"CHLD" }, +{ 18,"CONT" }, +{ 19,"STOP" }, +{ 20,"TSTP" }, +{ 21,"TTIN" }, +{ 22,"TTOU" }, +{ 23,"URG" }, +{ 24,"XCPU" }, +{ 25,"XFSZ" }, +{ 26,"VTALRM" }, +{ 27,"PROF" }, +{ 28,"WINCH" }, +{ 29,"IO" }, +{ 30,"PWR" }, +{ 31,"UNUSED" }, diff --git a/libproc/status.c b/libproc/status.c new file mode 100644 index 00000000..92758196 --- /dev/null +++ b/libproc/status.c @@ -0,0 +1,20 @@ +#include "proc/ps.h" +#include "proc/readproc.h" + +char * status(proc_t* task) { + static char buf[4] = " "; + + buf[0] = task->state; + if (task->rss == 0 && task->state != 'Z') + buf[1] = 'W'; + else + buf[1] = ' '; + if (task->nice < 0) + buf[2] = '<'; + else if (task->nice > 0) + buf[2] = 'N'; + else + buf[2] = ' '; + + return(buf); +} diff --git a/libproc/sysinfo.c b/libproc/sysinfo.c new file mode 100644 index 00000000..8a5e1a7f --- /dev/null +++ b/libproc/sysinfo.c @@ -0,0 +1,176 @@ +/* File for parsing top-level /proc entities. */ +#include "proc/sysinfo.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include <unistd.h> +#include <fcntl.h> +#include "proc/version.h" + +#define BAD_OPEN_MESSAGE \ +"Error: /proc must be mounted\n" \ +" To mount /proc at boot you need an /etc/fstab line like:\n" \ +" /proc /proc proc defaults\n" \ +" In the meantime, mount /proc /proc -t proc\n" + +#define UPTIME_FILE "/proc/uptime" +#define LOADAVG_FILE "/proc/loadavg" +#define MEMINFO_FILE "/proc/meminfo" + +static char buf[1024]; + +/* This macro opens FILE only if necessary and seeks to 0 so that successive + calls to the functions are more efficient. It also reads the current + contents of the file into the global buf. +*/ +#define FILE_TO_BUF(FILE) { \ + static int n, fd = -1; \ + if (fd == -1 && (fd = open(FILE, O_RDONLY)) == -1) { \ + fprintf(stderr, BAD_OPEN_MESSAGE); \ + close(fd); \ + return 0; \ + } \ + lseek(fd, 0L, SEEK_SET); \ + if ((n = read(fd, buf, sizeof buf - 1)) < 0) { \ + perror(FILE); \ + close(fd); \ + fd = -1; \ + return 0; \ + } \ + buf[n] = '\0'; \ +} + +#define SET_IF_DESIRED(x,y) if (x) *(x) = (y) /* evals 'x' twice */ + +int uptime(double *uptime_secs, double *idle_secs) { + double up=0, idle=0; + + FILE_TO_BUF(UPTIME_FILE) + if (sscanf(buf, "%lf %lf", &up, &idle) < 2) { + fprintf(stderr, "bad data in " UPTIME_FILE "\n"); + return 0; + } + SET_IF_DESIRED(uptime_secs, up); + SET_IF_DESIRED(idle_secs, idle); + return up; /* assume never be zero seconds in practice */ +} + +int loadavg(double *av1, double *av5, double *av15) { + double avg_1=0, avg_5=0, avg_15=0; + + FILE_TO_BUF(LOADAVG_FILE) + if (sscanf(buf, "%lf %lf %lf", &avg_1, &avg_5, &avg_15) < 3) { + fprintf(stderr, "bad data in " LOADAVG_FILE "\n"); + exit(1); + } + SET_IF_DESIRED(av1, avg_1); + SET_IF_DESIRED(av5, avg_5); + SET_IF_DESIRED(av15, avg_15); + return 1; +} + +/* The following /proc/meminfo parsing routine assumes the following format: + [ <label> ... ] # header lines + [ <label> ] <num> [ <num> ... ] # table rows + [ repeats of above line ] + + Any lines with fewer <num>s than <label>s get trailing <num>s set to zero. + The return value is a NULL terminated unsigned** which is the table of + numbers without labels. Convenient enumeration constants for the major and + minor dimensions are available in the header file. Note that this version + requires that labels do not contain digits. It is readily extensible to + labels which do not *begin* with digits, though. +*/ + +#define MAX_ROW 3 /* these are a little liberal for flexibility */ +#define MAX_COL 7 + +unsigned** meminfo(void) { + static unsigned *row[MAX_ROW + 1]; /* row pointers */ + static unsigned num[MAX_ROW * MAX_COL]; /* number storage */ + char *p; + char fieldbuf[12]; /* bigger than any field name or size in kb */ + int i, j, k, l; + + set_linux_version(); + FILE_TO_BUF(MEMINFO_FILE) + if (!row[0]) /* init ptrs 1st time through */ + for (i=0; i < MAX_ROW; i++) /* std column major order: */ + row[i] = num + MAX_COL*i; /* A[i][j] = A + COLS*i + j */ + p = buf; + for (i=0; i < MAX_ROW; i++) /* zero unassigned fields */ + for (j=0; j < MAX_COL; j++) + row[i][j] = 0; + if (linux_version_code < LINUX_VERSION(2,0,0)) { + for (i=0; i < MAX_ROW && *p; i++) { /* loop over rows */ + while(*p && !isdigit(*p)) p++; /* skip chars until a digit */ + for (j=0; j < MAX_COL && *p; j++) { /* scanf column-by-column */ + l = sscanf(p, "%u%n", row[i] + j, &k); + p += k; /* step over used buffer */ + if (*p == '\n' || l < 1) /* end of line/buffer */ + break; + } + } + } + else { + while(*p) { + sscanf(p,"%11s%n",fieldbuf,&k); + if(!strcmp(fieldbuf,"MemTotal:")) { + p+=k; + sscanf(p," %d",&(row[meminfo_main][meminfo_total])); + row[meminfo_main][meminfo_total]<<=10; + while(*p++ != '\n'); + } + else if(!strcmp(fieldbuf,"MemFree:")) { + p+=k; + sscanf(p," %d",&(row[meminfo_main][meminfo_free])); + row[meminfo_main][meminfo_free]<<=10; + while(*p++ != '\n'); + } + else if(!strcmp(fieldbuf,"MemShared:")) { + p+=k; + sscanf(p," %d",&(row[meminfo_main][meminfo_shared])); + row[meminfo_main][meminfo_shared]<<=10; + while(*p++ != '\n'); + } + else if(!strcmp(fieldbuf,"Buffers:")) { + p+=k; + sscanf(p," %d",&(row[meminfo_main][meminfo_buffers])); + row[meminfo_main][meminfo_buffers]<<=10; + while(*p++ != '\n'); + } + else if(!strcmp(fieldbuf,"Cached:")) { + p+=k; + sscanf(p," %d",&(row[meminfo_main][meminfo_cached])); + row[meminfo_main][meminfo_cached]<<=10; + while(*p++ != '\n'); + } + else if(!strcmp(fieldbuf,"SwapTotal:")) { + p+=k; + sscanf(p," %d",&(row[meminfo_swap][meminfo_total])); + row[meminfo_swap][meminfo_total]<<=10; + while(*p++ != '\n'); + } + else if(!strcmp(fieldbuf,"SwapFree:")) { + p+=k; + sscanf(p," %d",&(row[meminfo_swap][meminfo_free])); + row[meminfo_swap][meminfo_free]<<=10; + while(*p++ != '\n'); + } + else + while(*p++ != '\n'); /* ignore lines we don't understand */ + } + row[meminfo_swap][meminfo_used]=row[meminfo_swap][meminfo_total]-row[meminfo_swap][meminfo_free]; + row[meminfo_main][meminfo_used]=row[meminfo_main][meminfo_total]-row[meminfo_main][meminfo_free]; + } + return row; /* NULL return ==> error */ +} + +/* shorthand for read_table("/proc/meminfo")[meminfo_main][meminfo_total] */ +unsigned read_total_main(void) { + unsigned** mem; + return (mem = meminfo()) ? mem[meminfo_main][meminfo_total] : -1; +} diff --git a/libproc/sysinfo.h b/libproc/sysinfo.h new file mode 100644 index 00000000..d78770fd --- /dev/null +++ b/libproc/sysinfo.h @@ -0,0 +1,17 @@ +#ifndef SYSINFO_H +#define SYSINFO_H + +int loadavg(double *av1, double *av5, double *av15); +int uptime (double *uptime_secs, double *idle_secs); +unsigned** meminfo(void); + +enum meminfo_row { meminfo_main = 0, + meminfo_swap }; + +enum meminfo_col { meminfo_total = 0, meminfo_used, meminfo_free, + meminfo_shared, meminfo_buffers, meminfo_cached +}; + +unsigned read_total_main(void); + +#endif /* SYSINFO_H */ diff --git a/libproc/tree.h b/libproc/tree.h new file mode 100644 index 00000000..b370732e --- /dev/null +++ b/libproc/tree.h @@ -0,0 +1,14 @@ +struct tree_node { + proc_t *proc; + pid_t pid; + pid_t ppid; + char *line; + char *cmd; + char **cmdline; + char **environ; + int children; + int maxchildren; + int *child; + int have_parent; +}; + diff --git a/libproc/version.c b/libproc/version.c new file mode 100644 index 00000000..e8f152de --- /dev/null +++ b/libproc/version.c @@ -0,0 +1,39 @@ +/* Suite version information for procps utilities + * Copyright (c) 1995 Martin Schulze <joey@infodrom.north.de> + * Ammended by cblake to only export the function symbol. + */ +#include <stdio.h> + +#ifdef MINORVERSION +char procps_version[] = "procps version " VERSION "." SUBVERSION "." MINORVERSION; +#else +char procps_version[] = "procps version " VERSION "." SUBVERSION; +#endif + +void display_version(void) { + fprintf(stdout, "%s\n", procps_version); +} + +/* Linux kernel version information for procps utilities + * Copyright (c) 1996 Charles Blake <cblake@bbn.com> + */ +#include <sys/utsname.h> + +#define LINUX_VERSION(x,y,z) (0x10000*(x) + 0x100*(y) + z) + +int linux_version_code = 0; + +void set_linux_version(void) { + static struct utsname uts; + int x = 0, y = 0, z = 0; /* cleared in case sscanf() < 3 */ + + if (linux_version_code) return; + if (uname(&uts) == -1) /* failure most likely implies impending death */ + exit(1); + if (sscanf(uts.release, "%d.%d.%d", &x, &y, &z) < 3) + fprintf(stderr, /* *very* unlikely to happen by accident */ + "Non-standard uts for running kernel:\n" + "release %s=%d.%d.%d gives version code %d\n", + uts.release, x, y, z, LINUX_VERSION(x,y,z)); + linux_version_code = LINUX_VERSION(x, y, z); +} diff --git a/libproc/version.h b/libproc/version.h new file mode 100644 index 00000000..0d388e99 --- /dev/null +++ b/libproc/version.h @@ -0,0 +1,24 @@ +#ifndef PROC_VERSION_H +#define PROC_VERSION_H + +/* Suite version information for procps utilities + * Copyright (c) 1995 Martin Schulze <joey@infodrom.north.de> + * Linux kernel version information for procps utilities + * Copyright (c) 1996 Charles Blake <cblake@bbn.com> + */ + +extern void display_version(void); /* display suite version */ +extern char procps_version[]; /* global buf for suite version */ + +extern int linux_version_code; /* runtime version of LINUX_VERSION_CODE + in /usr/include/linux/version.h */ +extern void set_linux_version(void); /* set linux_version_code */ + + +/* Convenience macros for composing/decomposing version codes */ +#define LINUX_VERSION(x,y,z) (0x10000*(x) + 0x100*(y) + z) +#define LINUX_VERSION_MAJOR(x) ((x) & 0xFF0000) /* Dare we hope for a */ +#define LINUX_VERSION_MINOR(x) ((x) & 0x00FF00) /* Linux 256.0.0? ;-) */ +#define LINUX_VERSION_PATCH(x) ((x) & 0x0000FF) + +#endif /* PROC_VERSION_H */ diff --git a/libproc/whattime.c b/libproc/whattime.c new file mode 100644 index 00000000..031ce0e5 --- /dev/null +++ b/libproc/whattime.c @@ -0,0 +1,89 @@ +/* This is a trivial uptime program. I hereby release this program + * into the public domain. I disclaim any responsibility for this + * program --- use it at your own risk. (as if there were any.. ;-) + * -michaelkjohnson (johnsonm@sunsite.unc.edu) + * + * Modified by Larry Greenfield to give a more traditional output, + * count users, etc. (greenfie@gauss.rutgers.edu) + * + * Modified by mkj again to fix a few tiny buglies. + * + * Modified by J. Cowley to add printing the uptime message to a + * string (for top) and to optimize file handling. 19 Mar 1993. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> +#include <utmp.h> +#include <sys/ioctl.h> +#include "proc/whattime.h" +#include "proc/sysinfo.h" + +static char buf[128]; +double av[3]; + +char *sprint_uptime(void) { + struct utmp *utmpstruct; + int upminutes, uphours, updays; + int pos; + struct tm *realtime; + time_t realseconds; + int numuser; + double uptime_secs, idle_secs; + +/* first get the current time */ + + time(&realseconds); + realtime = localtime(&realseconds); + pos = sprintf(buf, " %2d:%02d%s ", + realtime->tm_hour%12 ? realtime->tm_hour%12 : 12, + realtime->tm_min, realtime->tm_hour > 11 ? "pm" : "am"); + +/* read and calculate the amount of uptime */ + + uptime(&uptime_secs, &idle_secs); + + updays = (int) uptime_secs / (60*60*24); + strcat (buf, "up "); + pos += 3; + if (updays) + pos += sprintf(buf + pos, "%d day%s, ", updays, (updays != 1) ? "s" : ""); + upminutes = (int) uptime_secs / 60; + uphours = upminutes / 60; + uphours = uphours % 24; + upminutes = upminutes % 60; + if(uphours) + pos += sprintf(buf + pos, "%2d:%02d, ", uphours, upminutes); + else + pos += sprintf(buf + pos, "%d min, ", upminutes); + +/* count the number of users */ + + numuser = 0; + setutent(); + while ((utmpstruct = getutent())) { + if ((utmpstruct->ut_type == USER_PROCESS) && + (utmpstruct->ut_name[0] != '\0')) + numuser++; + } + endutent(); + + pos += sprintf(buf + pos, "%2d user%s, ", numuser, numuser == 1 ? "" : "s"); + + loadavg(&av[0], &av[1], &av[2]); + + pos += sprintf(buf + pos, " load average: %.2f, %.2f, %.2f", + av[0], av[1], av[2]); + + return buf; +} + +void print_uptime(void) +{ + printf("%s\n", sprint_uptime()); +} diff --git a/libproc/whattime.h b/libproc/whattime.h new file mode 100644 index 00000000..46a1d821 --- /dev/null +++ b/libproc/whattime.h @@ -0,0 +1,9 @@ +/* whattime.h --- see whattime.c for explanation */ + +#ifndef __WHATTIME_H +#define __WHATTIME_H + +void print_uptime(void); +char *sprint_uptime(void); + +#endif |