blob: ed7b02dfca565a63e1633a4cfe493b7f521552af [file] [log] [blame]
/*
* 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 <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)
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/<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, 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/<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) {
(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);
}