/* * 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.25 2011/09/07 19:07:45 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)); _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 /* * 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/\"\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 -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/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//fd/ link */ static int getlinksrc(ln, src, srcl) char *ln; /* link path */ char *src; /* link source path return address */ int srcl; /* length of src[] */ { char *cp; int ll; 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); } 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//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// */ 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, tsf; 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; 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)) < 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, &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)) < 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, &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)) < 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, &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)) < 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, &sb, ss, &lsb, ls); 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; struct mounts *mp; 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//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) { (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], &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); }