diff options
Diffstat (limited to 'dialects/linux/dproc.c')
-rw-r--r-- | dialects/linux/dproc.c | 1563 |
1 files changed, 1563 insertions, 0 deletions
diff --git a/dialects/linux/dproc.c b/dialects/linux/dproc.c new file mode 100644 index 0000000..c3045a2 --- /dev/null +++ b/dialects/linux/dproc.c @@ -0,0 +1,1563 @@ +/* + * dproc.c - Linux process access functions for /proc-based lsof + */ + + +/* + * Copyright 1997 Purdue Research Foundation, West Lafayette, Indiana + * 47907. All rights reserved. + * + * Written by Victor A. Abell + * + * This software is not subject to any license of the American Telephone + * and Telegraph Company or the Regents of the University of California. + * + * Permission is granted to anyone to use this software for any purpose on + * any computer system, and to alter it and redistribute it freely, subject + * to the following restrictions: + * + * 1. Neither the authors nor Purdue University are responsible for any + * consequences of the use of this software. + * + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Credit to the authors and Purdue + * University must appear in documentation and sources. + * + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 4. This notice may not be removed or altered. + */ + +#ifndef lint +static char copyright[] = +"@(#) Copyright 1997 Purdue Research Foundation.\nAll rights reserved.\n"; +static char *rcsid = "$Id: dproc.c,v 1.27 2013/01/02 17:02:36 abe Exp $"; +#endif + +#include "lsof.h" + + +/* + * Local definitions + */ + +#define FDINFO_FLAGS 1 /* fdinfo flags available */ +#define FDINFO_POS 2 /* fdinfo position available */ +#define FDINFO_ALL (FDINFO_FLAGS | FDINFO_POS) +#define LSTAT_TEST_FILE "/" +#define LSTAT_TEST_SEEK 1 + +#if !defined(ULLONG_MAX) +#define ULLONG_MAX 18446744073709551615ULL +#endif /* !defined(ULLONG_MAX) */ + + +/* + * Local structures + */ + +struct l_fdinfo { + int flags; /* flags: line value */ + off_t pos; /* pos: line value */ +}; + + +/* + * Local variables + */ + +static short Cckreg; /* conditional status of regular file + * checking: + * 0 = unconditionally check + * 1 = conditionally check */ +static short Ckscko; /* socket file only checking status: + * 0 = none + * 1 = check only socket files */ + + +/* + * Local function prototypes + */ + +_PROTOTYPE(static int get_fdinfo,(char *p, struct l_fdinfo *fi)); +_PROTOTYPE(static int getlinksrc,(char *ln, char *src, int srcl, char **rest)); +_PROTOTYPE(static int isefsys,(char *path, char *type, int l, + efsys_list_t **rep, struct lfile **lfr)); +_PROTOTYPE(static int nm2id,(char *nm, int *id, int *idl)); +_PROTOTYPE(static int read_id_stat,(int ty, char *p, int id, char **cmd, + int *ppid, int *pgid)); +_PROTOTYPE(static void process_proc_map,(char *p, struct stat *s, int ss)); +_PROTOTYPE(static int process_id,(char *idp, int idpl, char *cmd, UID_ARG uid, + int pid, int ppid, int pgid, int tid)); +_PROTOTYPE(static int statEx,(char *p, struct stat *s, int *ss)); + + +#if defined(HASSELINUX) +_PROTOTYPE(static int cmp_cntx_eq,(char *pcntx, char *ucntx)); + + +#include <fnmatch.h> + + +/* + * cmp_cntx_eq -- compare program and user security contexts + */ + +static int +cmp_cntx_eq(pcntx, ucntx) + char *pcntx; /* program context */ + char *ucntx; /* user supplied context */ +{ + return !fnmatch(ucntx, pcntx, 0); +} + + +/* + * enter_cntx_arg() - enter name ecurity context argument + */ + +int +enter_cntx_arg(cntx) + char *cntx; /* context */ +{ + cntxlist_t *cntxp; +/* + * Search the argument list for a duplicate. + */ + for (cntxp = CntxArg; cntxp; cntxp = cntxp->next) { + if (!strcmp(cntxp->cntx, cntx)) { + if (!Fwarn) { + (void) fprintf(stderr, "%s: duplicate context: %s\n", + Pn, cntx); + } + return(1); + } + } +/* + * Create and link a new context argument list entry. + */ + if (!(cntxp = (cntxlist_t *)malloc((MALLOC_S)sizeof(cntxlist_t)))) { + (void) fprintf(stderr, "%s: no space for context: %s\n", Pn, cntx); + Exit(1); + } + cntxp->f = 0; + cntxp->cntx = cntx; + cntxp->next = CntxArg; + CntxArg = cntxp; + return(0); +} +#endif /* defined(HASSELINUX) */ + + +/* + * gather_proc_info() -- gather process information + */ + +void +gather_proc_info() +{ + char *cmd, *tcmd; + struct dirent *dp; + unsigned char ht, pidts; + int n, nl, pgid, pid, ppid, rv, tid, tpgid, tppid, tx; + static char *path = (char *)NULL; + static int pathl = 0; + static char *pidpath = (char *)NULL; + static MALLOC_S pidpathl = 0; + static MALLOC_S pidx = 0; + static DIR *ps = (DIR *)NULL; + struct stat sb; + static char *taskpath = (char *)NULL; + static int taskpathl = 0; + static char *tidpath = (char *)NULL; + static int tidpathl = 0; + DIR *ts; + UID_ARG uid; + +/* + * Do one-time setup. + */ + if (!pidpath) { + pidx = strlen(PROCFS) + 1; + pidpathl = pidx + 64 + 1; /* 64 is growth room */ + if (!(pidpath = (char *)malloc(pidpathl))) { + (void) fprintf(stderr, + "%s: can't allocate %d bytes for \"%s/\"<pid>\n", + Pn, (int)pidpathl, PROCFS); + Exit(1); + } + (void) snpf(pidpath, pidpathl, "%s/", PROCFS); + } +/* + * Get lock and net information. + */ + (void) make_proc_path(pidpath, pidx, &path, &pathl, "locks"); + (void) get_locks(path); + (void) make_proc_path(pidpath, pidx, &path, &pathl, "net/"); + (void) set_net_paths(path, strlen(path)); +/* + * If only socket files have been selected, or socket files have been selected + * ANDed with other selection options, enable the skipping of regular files. + * + * If socket files and some process options have been selected, enable + * conditional skipping of regular file; i.e., regular files will be skipped + * unless they belong to a process selected by one of the specified options. + */ + if (Selflags & SELNW) { + + /* + * Some network files selection options have been specified. + */ + if (Fand || !(Selflags & ~SELNW)) { + + /* + * Selection ANDing or only network file options have been + * specified, so set unconditional skipping of regular files + * and socket file only checking. + */ + Cckreg = 0; + Ckscko = 1; + } else { + + /* + * If ORed file selection options have been specified, or no ORed + * process selection options have been specified, enable + * unconditional file checking and clear socket file only checking. + * + * If only ORed process selection options have been specified, + * enable conditional file skipping and socket file only checking. + */ + if ((Selflags & SELFILE) || !(Selflags & SELPROC)) + Cckreg = Ckscko = 0; + else + Cckreg = Ckscko = 1; + } + } else { + + /* + * No network file selection options were specified. Enable + * unconditional file checking and clear socket file only checking. + */ + Cckreg = Ckscko = 0; + } +/* + * Read /proc, looking for PID directories. Open each one and + * gather its process and file information. + */ + if (!ps) { + if (!(ps = opendir(PROCFS))) { + (void) fprintf(stderr, "%s: can't open %s\n", Pn, PROCFS); + Exit(1); + } + } else + (void) rewinddir(ps); + while ((dp = readdir(ps))) { + if (nm2id(dp->d_name, &pid, &n)) + continue; + /* + * Build path to PID's directory. + */ + if ((pidx + n + 1 + 1) > pidpathl) { + pidpathl = pidx + n + 1 + 1 + 64; + if (!(pidpath = (char *)realloc((MALLOC_P *)pidpath, pidpathl))) + { + (void) fprintf(stderr, + "%s: can't allocate %d bytes for \"%s/%s/\"\n", + Pn, (int)pidpathl, PROCFS, dp->d_name); + Exit(1); + } + } + (void) snpf(pidpath + pidx, pidpathl - pidx, "%s/", dp->d_name); + n += (pidx + 1); + /* + * Process the PID's stat info. + */ + if (stat(pidpath, &sb)) + continue; + uid = (UID_ARG)sb.st_uid; + ht = pidts = 0; + +#if defined(HASTASKS) + /* + * If task reporting is selected, check the tasks of the process first, + * so that the "-p<PID> -aK" options work properly. + */ + if ((Selflags & SELTASK)) { + (void) make_proc_path(pidpath, n, &taskpath, &taskpathl, + "task"); + tx = n + 4; + if ((ts = opendir(taskpath))) { + + /* + * Process the PID's tasks. Record the open files of those + * whose TIDs do not match the PID and which are themselves + * not zombies. + */ + while ((dp = readdir(ts))) { + + /* + * Get the task ID. Skip the task if its ID matches the + * process PID. + */ + if (nm2id(dp->d_name, &tid, &nl)) + continue; + if (tid == pid) { + pidts = 1; + continue; + } + /* + * Form the path for the TID. + */ + if ((tx + 1 + nl + 1 + 4) > tidpathl) { + tidpathl = tx + 1 + n + 1 + 4 + 64; + if (tidpath) + tidpath = (char *)realloc((MALLOC_P *)tidpath, + tidpathl); + else + tidpath = (char *)malloc((MALLOC_S)tidpathl); + if (!tidpath) { + (void) fprintf(stderr, + "%s: can't allocate %d task bytes", Pn, + tidpathl); + (void) fprintf(stderr, " for \"%s/%s/stat\"\n", + taskpath, dp->d_name); + Exit(1); + } + } + (void) snpf(tidpath, tidpathl, "%s/%s/stat", taskpath, + dp->d_name); + /* + * Check the task state. + */ + rv = read_id_stat(1, tidpath, tid, &tcmd, &tppid, + &tpgid); + if ((rv < 0) || (rv == 1)) + continue; + /* + * Attempt to record the task. + */ + if (!process_id(tidpath, (tx + 1 + nl+ 1), tcmd, uid, + pid, tppid, tpgid, tid)) + { + ht = 1; + } + } + (void) closedir(ts); + } + } +#endif /* defined(HASTASKS) */ + + /* + * If the main process is a task and task selection has been specified + * along with option ANDing, enter the main process temporarily as a + * task, so that the "-aK" option set lists the main process along + * with its tasks. + */ + (void) make_proc_path(pidpath, n, &path, &pathl, "stat"); + if (((rv = read_id_stat(0, path, pid, &cmd, &ppid, &pgid)) >= 0) + && (rv != 1)) + { + tid = (Fand && ht && pidts && (Selflags & SELTASK)) ? pid : 0; + if ((!process_id(pidpath, n, cmd, uid, pid, ppid, pgid, tid)) + && tid) + { + Lp->tid = 0; + } + } + } +} + + +/* + * get_fdinfo() - get values from /proc/<PID>fdinfo/FD + */ + +static int +get_fdinfo(p, fi) + char *p; /* path to fdinfo file */ + struct l_fdinfo *fi; /* pointer to local fdinfo values + * return structure */ +{ + char buf[MAXPATHLEN + 1], *ep, **fp; + FILE *fs; + int rv = 0; + unsigned long ul; + unsigned long long ull; +/* + * Signal no values returned (0) if no fdinfo pointer was provided or if the + * fdinfo path can't be opened. + */ + if (!fi) + return(0); + if (!p || !*p || !(fs = fopen(p, "r"))) + return(0); +/* + * Read the fdinfo file. + */ + while (fgets(buf, sizeof(buf), fs)) { + if (get_fields(buf, (char *)NULL, &fp, (int *)NULL, 0) < 2) + continue; + if (!fp[0] || !*fp[0] || !fp[1] || !*fp[1]) + continue; + if (!strcmp(fp[0], "flags:")) { + + /* + * Process a "flags:" line. + */ + ep = (char *)NULL; + if ((ul = strtoul(fp[1], &ep, 0)) == ULONG_MAX + || !ep || *ep) + continue; + fi->flags = (unsigned int)ul; + if ((rv |= FDINFO_FLAGS) == FDINFO_ALL) + break; + } else if (!strcmp(fp[0], "pos:")) { + + /* + * Process a "pos:" line. + */ + ep = (char *)NULL; + if ((ull = strtoull(fp[1], &ep, 0)) == ULLONG_MAX + || !ep || *ep) + continue; + fi->pos = (off_t)ull; + if ((rv |= FDINFO_POS) == FDINFO_ALL) + break; + } + } + fclose(fs); +/* + * Signal via the return value what information was obtained. (0 == none) + */ + return(rv); +} + + +/* + * getlinksrc() - get the source path name for the /proc/<PID>/fd/<FD> link + */ + + +static int +getlinksrc(ln, src, srcl, rest) + char *ln; /* link path */ + char *src; /* link source path return address */ + int srcl; /* length of src[] */ + char **rest; /* pointer to what follows the ':' in + * the link source path (NULL if no + * return requested) */ +{ + char *cp; + int ll; + + if (rest) + *rest = (char *)NULL; + if ((ll = readlink(ln, src, srcl - 1)) < 1 + || ll >= srcl) + return(-1); + src[ll] = '\0'; + if (*src == '/') + return(ll); + if ((cp = strchr(src, ':'))) { + *cp = '\0'; + ll = strlen(src); + if (rest) + *rest = cp + 1; + } + return(ll); +} + + +/* + * initialize() - perform all initialization + */ + +void +initialize() +{ + int fd; + struct l_fdinfo fi; + char path[MAXPATHLEN]; + struct stat sb; +/* + * Test for -i and -X option conflict. + */ + if (Fxopt && (Fnet || Nwad)) { + (void) fprintf(stderr, "%s: -i is useless when -X is specified.\n", + Pn); + usage(1, 0, 0); + } +/* + * Open LSTAT_TEST_FILE and seek to byte LSTAT_TEST_SEEK, then lstat the + * /proc/<PID>/fd/<FD> for LSTAT_TEST_FILE to see what position is reported. + * If the result is LSTAT_TEST_SEEK, enable offset reporting. + * + * If the result isn't LSTAT_TEST_SEEK, next check the fdinfo file for the + * open LSTAT_TEST_FILE file descriptor. If it exists and contains a "pos:" + * value, and if the value is LSTAT_TEST_SEEK, enable offset reporting. + */ + if ((fd = open(LSTAT_TEST_FILE, O_RDONLY)) >= 0) { + if (lseek(fd, (off_t)LSTAT_TEST_SEEK, SEEK_SET) + == (off_t)LSTAT_TEST_SEEK) { + (void) snpf(path, sizeof(path), "%s/%d/fd/%d", PROCFS, Mypid, + fd); + if (!lstat(path, &sb)) { + if (sb.st_size == (off_t)LSTAT_TEST_SEEK) + OffType = 1; + } + } + if (!OffType) { + (void) snpf(path, sizeof(path), "%s/%d/fdinfo/%d", PROCFS, + Mypid, fd); + if (get_fdinfo(path, &fi) & FDINFO_POS) { + if (fi.pos == (off_t)LSTAT_TEST_SEEK) + OffType = 2; + } + } + (void) close(fd); + } + if (!OffType) { + if (Foffset && !Fwarn) + (void) fprintf(stderr, + "%s: WARNING: can't report offset; disregarding -o.\n", + Pn); + Foffset = 0; + Fsize = 1; + } + if (Fsv && (OffType != 2)) { + if (!Fwarn && FsvByf) + (void) fprintf(stderr, + "%s: WARNING: can't report file flags; disregarding +f.\n", + Pn); + Fsv = 0; + } +/* + * Make sure the local mount info table is loaded if doing anything other + * than just Internet lookups. (HasNFS is defined during the loading of the + * local mount table.) + */ + if (Selinet == 0) + (void) readmnt(); +} + + +/* + * make_proc_path() - make a path in a /proc directory + * + * entry: + * pp = pointer to /proc prefix + * lp = length of prefix + * np = pointer to malloc'd buffer to receive new file's path + * nl = length of new file path buffer + * sf = new path's suffix + * + * return: length of new path + * np = updated with new path + * nl = updated with new path length + */ + +int +make_proc_path(pp, pl, np, nl, sf) + char *pp; /* path prefix -- e.g., /proc/<pid>/ */ + int pl; /* strlen(pp) */ + char **np; /* malloc'd receiving buffer */ + int *nl; /* strlen(*np) */ + char *sf; /* suffix of new path */ +{ + char *cp; + MALLOC_S rl, sl; + + sl = strlen(sf); + if ((rl = pl + sl + 1) > *nl) { + if ((cp = *np)) + cp = (char *)realloc((MALLOC_P *)cp, rl); + else + cp = (char *)malloc(rl); + if (!cp) { + (void) fprintf(stderr, + "%s: can't allocate %d bytes for %s%s\n", + Pn, (int)rl, pp, sf); + Exit(1); + } + *nl = rl; + *np = cp; + } + (void) snpf(*np, *nl, "%s", pp); + (void) snpf(*np + pl, *nl - pl, "%s", sf); + return(rl - 1); +} + + +/* + * isefsys() -- is path on a file system exempted with -e + * + * Note: alloc_lfile() must have been called in advance. + */ + +static int +isefsys(path, type, l, rep, lfr) + char *path; /* path to file */ + char *type; /* unknown file type */ + int l; /* link request: 0 = report + * 1 = link */ + efsys_list_t **rep; /* returned Efsysl pointer, if not + * NULL */ + struct lfile **lfr; /* allocated struct lfile pointer */ +{ + efsys_list_t *ep; + int ds, len; + struct mounts *mp; + char nmabuf[MAXPATHLEN + 1]; + + len = (int) strlen(path); + for (ep = Efsysl; ep; ep = ep->next) { + + /* + * Look for a matching exempt file system path at the beginning of + * the file path. + */ + if (ep->pathl > len) + continue; + if (strncmp(ep->path, path, ep->pathl)) + continue; + /* + * If only reporting, return information as requested. + */ + if (!l) { + if (rep) + *rep = ep; + return(0); + } + /* + * Process an exempt file. + */ + ds = 0; + if ((mp = ep->mp)) { + if (mp->ds & SB_DEV) { + Lf->dev = mp->dev; + ds = Lf->dev_def = 1; + } + if (mp->ds & SB_RDEV) { + Lf->rdev = mp->rdev; + ds = Lf->rdev_def = 1; + } + } + if (!ds) + (void) enter_dev_ch("UNKNOWN"); + Lf->ntype = N_UNKN; + (void) snpf(Lf->type, sizeof(Lf->type), "%s", + (type ? type : "UNKN")); + (void) enter_nm(path); + (void) snpf(nmabuf, sizeof(nmabuf), "(%ce %s)", + ep->rdlnk ? '+' : '-', ep->path); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + if (Lf->sf) { + if (lfr) + *lfr = Lf; + link_lfile(); + } else if (lfr) + *lfr = (struct lfile *)NULL; + return(0); + } + return(1); +} + + +/* + * nm2id() - convert a name to an integer ID + */ + +static int +nm2id(nm, id, idl) + char *nm; /* pointer to name */ + int *id; /* pointer to ID receiver */ + int *idl; /* pointer to ID length receiver */ +{ + register int tid, tidl; + + for (*id = *idl = tid = tidl = 0; *nm; nm++) { + +#if defined(__STDC__) /* { */ + if (!isdigit((unsigned char)*nm)) +#else /* !defined(__STDC__) } { */ + if (!isascii(*nm) || !isdigit((unsigned char)*cp)) +#endif /* defined(__STDC__) } */ + + { + return(1); + } + tid = tid * 10 + (int)(*nm - '0'); + tidl++; + } + *id = tid; + *idl = tidl; + return(0); +} + + +/* + * open_proc_stream() -- open a /proc stream + */ + +FILE * +open_proc_stream(p, m, buf, sz, act) + char *p; /* pointer to path to open */ + char *m; /* pointer to mode -- e.g., "r" */ + char **buf; /* pointer tp setvbuf() address + * (NULL if none) */ + size_t *sz; /* setvbuf() size (0 if none or if + * getpagesize() desired */ + int act; /* fopen() failure action: + * 0 : return (FILE *)NULL + * <>0 : fprintf() an error message + * and Exit(1) + */ +{ + FILE *fs; /* opened stream */ + static size_t psz = (size_t)0; /* page size */ + size_t tsz; /* temporary size */ +/* + * Open the stream. + */ + if (!(fs = fopen(p, m))) { + if (!act) + return((FILE *)NULL); + (void) fprintf(stderr, "%s: can't fopen(%s, \"%s\"): %s\n", + Pn, p, m, strerror(errno)); + Exit(1); + } +/* + * Return the stream if no buffer change is required. + */ + if (!buf) + return(fs); +/* + * Determine the buffer size required. + */ + if (!(tsz = *sz)) { + if (!psz) + psz = getpagesize(); + tsz = psz; + } +/* + * Allocate a buffer for the stream, as required. + */ + if (!*buf) { + if (!(*buf = (char *)malloc((MALLOC_S)tsz))) { + (void) fprintf(stderr, + "%s: can't allocate %d bytes for %s stream buffer\n", + Pn, (int)tsz, p); + Exit(1); + } + *sz = tsz; + } +/* + * Assign the buffer to the stream. + */ + if (setvbuf(fs, *buf, _IOFBF, tsz)) { + (void) fprintf(stderr, "%s: setvbuf(%s)=%d failure: %s\n", + Pn, p, (int)tsz, strerror(errno)); + Exit(1); + } + return(fs); +} + + +/* + * process_id - process ID: PID or LWP + * + * return: 0 == ID processed + * 1 == ID not processed + */ + +static int +process_id(idp, idpl, cmd, uid, pid, ppid, pgid, tid) + char *idp; /* pointer to ID's path */ + int idpl; /* pointer to ID's path length */ + char *cmd; /* pointer to ID's command */ + UID_ARG uid; /* ID's UID */ + int pid; /* ID's PID */ + int ppid; /* parent PID */ + int pgid; /* parent GID */ + int tid; /* task ID, if non-zero */ +{ + int av; + static char *dpath = (char *)NULL; + static int dpathl = 0; + short efs, enls, enss, lnk, oty, pn, pss, sf; + int fd, i, ls, n, ss, sv; + struct l_fdinfo fi; + DIR *fdp; + struct dirent *fp; + static char *ipath = (char *)NULL; + static int ipathl = 0; + int j = 0; + struct lfile *lfr; + struct stat lsb, sb; + char nmabuf[MAXPATHLEN + 1], pbuf[MAXPATHLEN + 1]; + static char *path = (char *)NULL; + static int pathl = 0; + static char *pathi = (char *)NULL; + static int pathil = 0; + char *rest; + int txts = 0; + +#if defined(HASSELINUX) + cntxlist_t *cntxp; +#endif /* defined(HASSELINUX) */ + +/* + * See if process is excluded. + */ + if (is_proc_excl(pid, pgid, uid, &pss, &sf, tid) + || is_cmd_excl(cmd, &pss, &sf)) + return(1); + if (Cckreg) { + + /* + * If conditional checking of regular files is enabled, enable + * socket file only checking, based on the process' selection + * status. + */ + Ckscko = (sf & SELPROC) ? 0 : 1; + } + alloc_lproc(pid, pgid, ppid, uid, cmd, (int)pss, (int)sf); + Lp->tid = tid; + Plf = (struct lfile *)NULL; +/* + * Process the ID's current working directory info. + */ + if (!Ckscko) { + (void) make_proc_path(idp, idpl, &path, &pathl, "cwd"); + alloc_lfile(CWD, -1); + efs = 0; + if (getlinksrc(path, pbuf, sizeof(pbuf), (char **)NULL) < 1) { + if (!Fwarn) { + (void) memset((void *)&sb, 0, sizeof(sb)); + lnk = ss = 0; + (void) snpf(nmabuf, sizeof(nmabuf), "(readlink: %s)", + strerror(errno)); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + pn = 1; + } else + pn = 0; + } else { + lnk = pn = 1; + if (Efsysl && !isefsys(pbuf, "UNKNcwd", 1, NULL, &lfr)) { + efs = 1; + pn = 0; + } else { + ss = SB_ALL; + if (HasNFS) { + if ((sv = statsafely(path, &sb))) + sv = statEx(pbuf, &sb, &ss); + } else + sv = stat(path, &sb); + if (sv) { + ss = 0; + if (!Fwarn) { + (void) snpf(nmabuf, sizeof(nmabuf), "(stat: %s)", + strerror(errno)); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + } + } + } + } + if (pn) { + (void) process_proc_node(lnk ? pbuf : path, + path, &sb, ss, + (struct stat *)NULL, 0); + if (Lf->sf) + link_lfile(); + } + } +/* + * Process the ID's root directory info. + */ + if (!Ckscko) { + (void) make_proc_path(idp, idpl, &path, &pathl, "root"); + alloc_lfile(RTD, -1); + if (getlinksrc(path, pbuf, sizeof(pbuf), (char **)NULL) < 1) { + if (!Fwarn) { + (void) memset((void *)&sb, 0, sizeof(sb)); + lnk = ss = 0; + (void) snpf(nmabuf, sizeof(nmabuf), "(readlink: %s)", + strerror(errno)); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + pn = 1; + } else + pn = 0; + } else { + lnk = pn = 1; + if (Efsysl && !isefsys(pbuf, "UNKNrtd", 1, NULL, NULL)) + pn = 0; + else { + ss = SB_ALL; + if (HasNFS) { + if ((sv = statsafely(path, &sb))) + sv = statEx(pbuf, &sb, &ss); + } else + sv = stat(path, &sb); + if (sv) { + ss = 0; + if (!Fwarn) { + (void) snpf(nmabuf, sizeof(nmabuf), "(stat: %s)", + strerror(errno)); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + } + } + } + } + if (pn) { + (void) process_proc_node(lnk ? pbuf : path, + path, &sb, ss, + (struct stat *)NULL, 0); + if (Lf->sf) + link_lfile(); + } + } +/* + * Process the ID's execution info. + */ + if (!Ckscko) { + txts = 0; + (void) make_proc_path(idp, idpl, &path, &pathl, "exe"); + alloc_lfile("txt", -1); + if (getlinksrc(path, pbuf, sizeof(pbuf), (char **)NULL) < 1) { + (void) memset((void *)&sb, 0, sizeof(sb)); + lnk = ss = 0; + if (!Fwarn) { + if ((errno != ENOENT) || uid) { + (void) snpf(nmabuf, sizeof(nmabuf), "(readlink: %s)", + strerror(errno)); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + } + pn = 1; + } else + pn = 0; + } else { + lnk = pn = 1; + if (Efsysl && !isefsys(pbuf, "UNKNtxt", 1, NULL, NULL)) + pn = 0; + else { + ss = SB_ALL; + if (HasNFS) { + if ((sv = statsafely(path, &sb))) { + sv = statEx(pbuf, &sb, &ss); + if (!sv && (ss & SB_DEV) && (ss & SB_INO)) + txts = 1; + } + } else + sv = stat(path, &sb); + if (sv) { + ss = 0; + if (!Fwarn) { + (void) snpf(nmabuf, sizeof(nmabuf), "(stat: %s)", + strerror(errno)); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + } + } else + txts = 1; + } + } + if (pn) { + (void) process_proc_node(lnk ? pbuf : path, + path, &sb, ss, + (struct stat *)NULL, 0); + if (Lf->sf) + link_lfile(); + } + } +/* + * Process the ID's memory map info. + */ + if (!Ckscko) { + (void) make_proc_path(idp, idpl, &path, &pathl, "maps"); + (void) process_proc_map(path, txts ? &sb : (struct stat *)NULL, + txts ? ss : 0); + } + +#if defined(HASSELINUX) +/* + * Process the PID's SELinux context. + */ + if (Fcntx) { + + /* + * If the -Z (cntx) option was specified, match the valid contexts. + */ + errno = 0; + if (getpidcon(pid, &Lp->cntx) == -1) { + Lp->cntx = (char *)NULL; + if (!Fwarn) { + (void) snpf(nmabuf, sizeof(nmabuf), + "(getpidcon: %s)", strerror(errno)); + if (!(Lp->cntx = strdup(nmabuf))) { + (void) fprintf(stderr, + "%s: no context error space: PID %ld", + Pn, (long)Lp->pid); + Exit(1); + } + } + } else if (CntxArg) { + + /* + * See if context includes the process. + */ + for (cntxp = CntxArg; cntxp; cntxp = cntxp->next) { + if (cmp_cntx_eq(Lp->cntx, cntxp->cntx)) { + cntxp->f = 1; + Lp->pss |= PS_PRI; + Lp->sf |= SELCNTX; + break; + } + } + } + } +#endif /* defined(HASSELINUX) */ + +/* + * Process the ID's file descriptor directory. + */ + if ((i = make_proc_path(idp, idpl, &dpath, &dpathl, "fd/")) < 3) + return(0); + dpath[i - 1] = '\0'; + if ((OffType == 2) + && ((j = make_proc_path(idp, idpl, &ipath, &ipathl, "fdinfo/")) >= 7)) + oty = 1; + else + oty = 0; + if (!(fdp = opendir(dpath))) { + if (!Fwarn) { + (void) snpf(nmabuf, sizeof(nmabuf), "%s (opendir: %s)", + dpath, strerror(errno)); + alloc_lfile("NOFD", -1); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + link_lfile(); + } + return(0); + } + dpath[i - 1] = '/'; + while ((fp = readdir(fdp))) { + if (nm2id(fp->d_name, &fd, &n)) + continue; + (void) make_proc_path(dpath, i, &path, &pathl, fp->d_name); + (void) alloc_lfile((char *)NULL, fd); + if (getlinksrc(path, pbuf, sizeof(pbuf), &rest) < 1) { + (void) memset((void *)&sb, 0, sizeof(sb)); + lnk = ss = 0; + if (!Fwarn) { + (void) snpf(nmabuf, sizeof(nmabuf), "(readlink: %s)", + strerror(errno)); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + pn = 1; + } else + pn = 0; + } else { + lnk = 1; + if (Efsysl && !isefsys(pbuf, "UNKNfd", 1, NULL, &lfr)) { + efs = 1; + pn = 0; + } else { + if (HasNFS) { + if (lstatsafely(path, &lsb)) { + (void) statEx(pbuf, &lsb, &ls); + enls = errno; + } else { + enls = 0; + ls = SB_ALL; + } + if (statsafely(path, &sb)) { + (void) statEx(pbuf, &sb, &ss); + enss = errno; + } else { + enss = 0; + ss = SB_ALL; + } + } else { + ls = lstat(path, &lsb) ? 0 : SB_ALL; + enls = errno; + ss = stat(path, &sb) ? 0 : SB_ALL; + enss = errno; + } + if (!ls && !Fwarn) { + (void) snpf(nmabuf, sizeof(nmabuf), "lstat: %s)", + strerror(enls)); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + } + if (!ss && !Fwarn) { + (void) snpf(nmabuf, sizeof(nmabuf), "(stat: %s)", + strerror(enss)); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + } + if (Ckscko) { + if ((ss & SB_MODE) + && ((sb.st_mode & S_IFMT) == S_IFSOCK)) + { + pn = 1; + } else + pn = 0; + } else + pn = 1; + } + } + if (pn || (efs && lfr && oty)) { + if (oty) { + (void) make_proc_path(ipath, j, &pathi, &pathil, + fp->d_name); + if ((av = get_fdinfo(pathi, &fi)) & FDINFO_POS) { + if (efs) { + if (Foffset) { + lfr->off = (SZOFFTYPE)fi.pos; + lfr->off_def = 1; + } + } else { + ls |= SB_SIZE; + lsb.st_size = fi.pos; + } + } else + ls &= ~SB_SIZE; + +#if !defined(HASNOFSFLAGS) + if ((av & FDINFO_FLAGS) && (Fsv & FSV_FG)) { + if (efs) { + lfr->ffg = (long)fi.flags; + lfr->fsv |= FSV_FG; + } else { + Lf->ffg = (long)fi.flags; + Lf->fsv |= FSV_FG; + } + } +# endif /* !defined(HASNOFSFLAGS) */ + + } + if (pn) { + process_proc_node(lnk ? pbuf : path, path, &sb, ss, &lsb, + ls); + if ((Lf->ntype == N_ANON_INODE) && rest && *rest) + enter_nm(rest); + if (Lf->sf) + link_lfile(); + } + } + } + (void) closedir(fdp); + return(0); +} + + +/* + * process_proc_map() - process the memory map of a process + */ + +static void +process_proc_map(p, s, ss) + char *p; /* path to process maps file */ + struct stat *s; /* executing text file state buffer */ + int ss; /* *s status -- i.e., SB_* values */ +{ + char buf[MAXPATHLEN + 1], *ep, fmtbuf[32], **fp, nmabuf[MAXPATHLEN + 1]; + dev_t dev; + int ds, efs, en, i, mss, nf, sv; + int eb = 6; + INODETYPE inode; + MALLOC_S len; + long maj, min; + FILE *ms; + int ns = 0; + struct stat sb; + struct saved_map { + dev_t dev; + INODETYPE inode; + }; + static struct saved_map *sm = (struct saved_map *)NULL; + efsys_list_t *rep; + static int sma = 0; + static char *vbuf = (char *)NULL; + static size_t vsz = (size_t)0; +/* + * Open the /proc/<pid>/maps file, assign a page size buffer to its stream, + * and read it/ + */ + if (!(ms = open_proc_stream(p, "r", &vbuf, &vsz, 0))) + return; + while (fgets(buf, sizeof(buf), ms)) { + if ((nf = get_fields(buf, ":", &fp, &eb, 1)) < 7) + continue; /* not enough fields */ + if (!fp[6] || !*fp[6]) + continue; /* no path name */ + /* + * See if the path ends in " (deleted)". If it does, strip the + * " (deleted)" characters and remember that they were there. + */ + if (((ds = (int)strlen(fp[6])) > 10) + && !strcmp(fp[6] + ds - 10, " (deleted)")) + { + *(fp[6] + ds - 10) = '\0'; + } else + ds = 0; + /* + * Assemble the major and minor device numbers. + */ + ep = (char *)NULL; + if (!fp[3] || !*fp[3] + || (maj = strtol(fp[3], &ep, 16)) == LONG_MIN || maj == LONG_MAX + || !ep || *ep) + continue; + ep = (char *)NULL; + if (!fp[4] || !*fp[4] + || (min = strtol(fp[4], &ep, 16)) == LONG_MIN || min == LONG_MAX + || !ep || *ep) + continue; + /* + * Assemble the device and inode numbers. If they are both zero, skip + * the entry. + */ + dev = (dev_t)makedev((int)maj, (int)min); + if (!fp[5] || !*fp[5]) + continue; + ep = (char *)NULL; + if ((inode = strtoull(fp[5], &ep, 0)) == ULLONG_MAX + || !ep || *ep) + continue; + if (!dev && !inode) + continue; + /* + * See if the device + inode pair match that of the executable. + * If they do, skip this map entry. + */ + if (s && (ss & SB_DEV) && (ss & SB_INO) + && (dev == s->st_dev) && (inode == (INODETYPE)s->st_ino)) + continue; + /* + * See if this device + inode pair has already been processed as + * a map entry. + */ + for (i = 0; i < ns; i++) { + if (dev == sm[i].dev && inode == sm[i].inode) + break; + } + if (i < ns) + continue; + /* + * Record the processing of this map entry's device and inode pair. + */ + if (ns >= sma) { + sma += 10; + len = (MALLOC_S)(sma * sizeof(struct saved_map)); + if (sm) + sm = (struct saved_map *)realloc(sm, len); + else + sm = (struct saved_map *)malloc(len); + if (!sm) { + (void) fprintf(stderr, + "%s: can't allocate %d bytes for saved maps, PID %d\n", + Pn, (int)len, Lp->pid); + Exit(1); + } + } + sm[ns].dev = dev; + sm[ns++].inode = inode; + /* + * Allocate space for the mapped file, then get stat(2) information + * for it. Skip the stat(2) operation if this is on an exempt file + * system. + */ + alloc_lfile("mem", -1); + if (Efsysl && !isefsys(fp[6], (char *)NULL, 0, &rep, NULL)) + efs = sv = 1; + else + efs = 0; + if (!efs) { + if (HasNFS) + sv = statsafely(fp[6], &sb); + else + sv = stat(fp[6], &sb); + } + if (sv || efs) { + en = errno; + /* + * Applying stat(2) to the file was not possible (file is on an + * exempt file system) or stat(2) failed, so manufacture a partial + * stat(2) reply from the process' maps file entry. + * + * If the file has been deleted, reset its type to "DEL"; otherwise + * generate a stat() error name addition. + */ + (void) memset((void *)&sb, 0, sizeof(sb)); + sb.st_dev = dev; + sb.st_ino = (ino_t)inode; + sb.st_mode = S_IFREG; + mss = SB_DEV | SB_INO | SB_MODE; + if (ds) + alloc_lfile("DEL", -1); + else if (!efs && !Fwarn) { + (void) snpf(nmabuf, sizeof(nmabuf), "(stat: %s)", + strerror(en)); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + } + } else if ((sb.st_dev != dev) || ((INODETYPE)sb.st_ino != inode)) { + + /* + * The stat(2) device and inode numbers don't match those obtained + * from the process' maps file. + * + * If the file has been deleted, reset its type to "DEL"; otherwise + * generate inconsistency name additions. + * + * Manufacture a partial stat(2) reply from the maps file + * information. + */ + if (ds) + alloc_lfile("DEL", -1); + else if (!Fwarn) { + char *sep; + + if (sb.st_dev != dev) { + (void) snpf(nmabuf, sizeof(nmabuf), + "(path dev=%d,%d%s", + GET_MAJ_DEV(sb.st_dev), GET_MIN_DEV(sb.st_dev), + ((INODETYPE)sb.st_ino == inode) ? ")" : ","); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + sep = ""; + } else + sep = "(path "; + if ((INODETYPE)sb.st_ino != inode) { + (void) snpf(fmtbuf, sizeof(fmtbuf), "%%sinode=%s)", + InodeFmt_d); + (void) snpf(nmabuf, sizeof(nmabuf), fmtbuf, + sep, (INODETYPE)sb.st_ino); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + } + } + (void) memset((void *)&sb, 0, sizeof(sb)); + sb.st_dev = dev; + sb.st_ino = (ino_t)inode; + sb.st_mode = S_IFREG; + mss = SB_DEV | SB_INO | SB_MODE; + } else + mss = SB_ALL; + /* + * Record the file's information. + */ + if (!efs) + process_proc_node(fp[6], fp[6], &sb, mss, (struct stat *)NULL, + 0); + else { + + /* + * If this file is on an exempt file system, complete the lfile + * structure, but change its type and add the exemption note to + * the NAME column. + */ + Lf->dev = sb.st_dev; + Lf->inode = (ino_t)sb.st_ino; + Lf->dev_def = Lf->inp_ty = 1; + (void) enter_nm(fp[6]); + (void) snpf(Lf->type, sizeof(Lf->type), "%s", + (ds ? "UNKNdel" : "UNKNmem")); + (void) snpf(nmabuf, sizeof(nmabuf), "(%ce %s)", + rep->rdlnk ? '+' : '-', rep->path); + nmabuf[sizeof(nmabuf) - 1] = '\0'; + (void) add_nma(nmabuf, strlen(nmabuf)); + } + if (Lf->sf) + link_lfile(); + } + (void) fclose(ms); +} + + +/* + * read_id_stat() - read ID (PID or LWP ID) status + * + * return: -1 == ID is unavailable + * 0 == ID OK + * 1 == ID is a zombie + * 2 == ID is a thread + */ + +static int +read_id_stat(ty, p, id, cmd, ppid, pgid) + int ty; /* type: 0 == PID, 1 == LWP */ + char *p; /* path to status file */ + int id; /* ID: PID or LWP */ + char **cmd; /* malloc'd command name */ + int *ppid; /* returned parent PID for PID type */ + int *pgid; /* returned process group ID for PID + * type */ +{ + char buf[MAXPATHLEN], *cp, *cp1, **fp; + static char *cbf = (char *)NULL; + static MALLOC_S cbfa = 0; + FILE *fs; + MALLOC_S len; + int nf; + static char *vbuf = (char *)NULL; + static size_t vsz = (size_t)0; +/* + * Open the stat file path, assign a page size buffer to its stream, + * and read the file's first line. + */ + if (!(fs = open_proc_stream(p, "r", &vbuf, &vsz, 0))) + return(-1); + cp = fgets(buf, sizeof(buf), fs); + (void) fclose(fs); + if (!cp) + return(-1); +/* + * Separate the line into fields on white space separators. Expect five fields + * for a PID type and three for an LWP type. + */ + if ((nf = get_fields(buf, (char *)NULL, &fp, (int *)NULL, 0)) + < (ty ? 5 : 3)) + { + return(-1); + } +/* + * Convert the first field to an integer; its conversion must match the + * ID argument. + */ + if (!fp[0] || (atoi(fp[0]) != id)) + return(-1); +/* + * Get the command name from the second field. Strip a starting '(' and + * an ending ')'. Allocate space to hold the result and return the space + * pointer. + */ + if (!(cp = fp[1])) + return(-1); + if (cp && *cp == '(') + cp++; + if ((cp1 = strrchr(cp, ')'))) + *cp1 = '\0'; + if ((len = strlen(cp) + 1) > cbfa) { + cbfa = len; + if (cbf) + cbf = (char *)realloc((MALLOC_P *)cbf, cbfa); + else + cbf = (char *)malloc(cbfa); + if (!cbf) { + (void) fprintf(stderr, + "%s: can't allocate %d bytes for command \"%s\"\n", + Pn, (int)cbfa, cp); + Exit(1); + } + } + (void) snpf(cbf, len, "%s", cp); + *cmd = cbf; +/* + * Convert and return parent process (fourth field) and process group (fifth + * field) IDs. + */ + if (fp[3] && *fp[3]) + *ppid = atoi(fp[3]); + else + return(-1); + if (fp[4] && *fp[4]) + *pgid = atoi(fp[4]); + else + return(-1); +/* + * Check the state in the third field. If it is 'Z', return that indication. + */ + if (fp[2] && !strcmp(fp[2], "Z")) + return(1); + else if (fp[2] && !strcmp(fp[2], "T")) + return(2); + return(0); +} + + +/* + * statEx() - extended stat() to get device numbers when a "safe" stat has + * failed and the system has an NFS mount + * + * Note: this function was suggested by Paul Szabo as a way to get device + * numbers for NFS files when an NFS mount point has the root_squash + * option set. In that case, even if lsof is setuid(root), the identity + * of its requests to stat() NFS files lose root permission and may fail. + * + * This function should be used only when links have been successfully + * resolved in the /proc path by getlinksrc(). + */ + +static int +statEx(p, s, ss) + char *p; /* file path */ + struct stat *s; /* stat() result -- NULL if none + * wanted */ + int *ss; /* stat() status -- SB_* values */ +{ + static size_t ca = 0; + static char *cb = NULL; + char *cp; + int ensv = ENOENT; + struct stat sb; + int st = 0; + size_t sz; +/* + * Make a copy of the path. + */ + sz = strlen(p); + if ((sz + 1) > ca) { + if (cb) + cb = (char *)realloc((MALLOC_P *)cb, sz + 1); + else + cb = (char *)malloc(sz + 1); + if (!cb) { + (void) fprintf(stderr, + "%s: PID %ld: no statEx path space: %s\n", + Pn, (long)Lp->pid, p); + Exit(1); + } + ca = sz + 1; + } + (void) strcpy(cb, p); +/* + * Trim trailing leaves from the end of the path one at a time and do a safe + * stat() on each trimmed result. Stop when a safe stat() succeeds or doesn't + * fail because of EACCES or EPERM. + */ + for (cp = strrchr(cb, '/'); cp && (cp != cb);) { + *cp = '\0'; + if (!statsafely(cb, &sb)) { + st = 1; + break; + } + ensv = errno; + if ((ensv != EACCES) && (ensv != EPERM)) + break; + cp = strrchr(cb, '/'); + } +/* + * If a stat() on a trimmed result succeeded, form partial results containing + * only the device and raw device numbers. + */ + memset((void *)s, 0, sizeof(struct stat)); + if (st) { + errno = 0; + s->st_dev = sb.st_dev; + s->st_rdev = sb.st_rdev; + *ss = SB_DEV | SB_RDEV; + return(0); + } + errno = ensv; + *ss = 0; + return(1); +} |