/* $NetBSD: procfs_ops.c,v 1.9 2000/05/26 03:04:28 simonb Exp $ */ /* * Copyright (c) 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Brian Grayson (bgrayson@netbsd.org). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include /* for rusage */ #include #include #include #include #include #include #include #include #include "ps.h" /* Assume that no process status file will ever be larger than this. */ #define STATUS_SIZE 8192 /* * Handy macro for only printing a warning once. Notice that * one needs to use two sets of parentheses when invoking the * macro: WARNX_ONLY_ONCE(("mesgstr", arg1, arg2, ...)); */ #define WARNX_ONLY_ONCE(x) \ { \ static int firsttime = 1; \ if (firsttime) { \ firsttime = 0; \ warnx x ; \ } \ } static int verify_procfs_fd __P((int, const char *)); static int parsekinfo __P((const char *, struct kinfo_proc2 *)); static int verify_procfs_fd(fd, path) int fd; const char *path; { struct statfs procfsstat; /* * If the fstatfs fails, die immediately. Since we already have the * FD open, any error is probably one that can't be worked around. */ if (fstatfs(fd, &procfsstat)) err(1, "fstatfs on %s", path); /* Now verify that the open file is truly on a procfs filesystem. */ if (strcmp(procfsstat.f_fstypename, MOUNT_PROCFS)) { warnx("%s is on a `%s' filesystem, not `procfs'", path, procfsstat.f_fstypename); return -1; } return 0; } static int parsekinfo(path, ki) const char *path; struct kinfo_proc2 *ki; { char fullpath[MAXPATHLEN]; int dirfd, fd, nbytes, devmajor, devminor; struct timeval usertime, systime, starttime; char buff[STATUS_SIZE]; char flagstr[256]; /* * Verify that /proc/ is a procfs file (and that no one has * mounted anything on top of it). If we didn't do this, an intruder * could hide processes by simply mount_null'ing /tmp on top of the * /proc/ directory. (And we can't just print warnings if we * fail to open /proc//status, because the process may have died * since our getdents() call.) */ snprintf(fullpath, MAXPATHLEN, "/proc/%s", path); dirfd = open(fullpath, O_RDONLY, 0); if (verify_procfs_fd(dirfd, fullpath)) { close(dirfd); return -1; } /* Open /proc//status, and parse it into the kinfo_proc. */ snprintf(fullpath, MAXPATHLEN, "/proc/%s/status", path); fd = open(fullpath, O_RDONLY, 0); close(dirfd); if (fd == -1) { /* * Don't print warning, as the process may have died since our * scan of the directory entries. */ return -1; /* Process may no longer exist. */ } /* * Bail out for this process attempt if it isn't on a procfs. Some * intruder could have mounted something on top of portions of /proc. */ if (verify_procfs_fd(fd, fullpath)) { close(fd); return -1; } nbytes = read(fd, buff, STATUS_SIZE - 1); close(fd); if (nbytes <= 0) { /* * Don't print warning, as the process may have died since our * scan of the directory entries. */ return -1; /* Process may no longer exist. */ } /* Make sure the buffer is terminated. */ buff[nbytes] = '\0'; sscanf(buff, "%s %d %d %d %d %d,%d %s %ld,%ld %ld,%ld %ld,%ld %s %d", ki->p_comm, &ki->p_pid, &ki->p_ppid, &ki->p_tpgid, &ki->p_sid, &devmajor, &devminor, flagstr, &starttime.tv_sec, &starttime.tv_usec, &usertime.tv_sec, &usertime.tv_usec, &systime.tv_sec, &systime.tv_usec, ki->p_wmesg, &ki->p_uid); ki->p_wchan = 1; /* XXX Set it to _something_. */ ki->p_tdev = makedev(devmajor, devminor); /* Put both user and sys time into rtime field. */ ki->p_rtime_sec = usertime.tv_sec + systime.tv_sec; ki->p_rtime_usec = usertime.tv_usec + systime.tv_usec; /* if starttime.[u]sec is != -1, it's in-memory process */ if (starttime.tv_sec != -1 && starttime.tv_usec != -1) { ki->p_flag |= P_INMEM; ki->p_uvalid = 1; ki->p_ustart_sec = starttime.tv_sec; ki->p_ustart_usec = starttime.tv_usec; } /* * CPU time isn't shown unless the ki_u.u_valid flag is set. * Unfortunately, we don't have access to that here. */ /* Set the flag for whether or not there is a controlling terminal. */ if (strstr(flagstr, "ctty")) ki->p_flag |= P_CONTROLT; /* Set the flag for whether or not this process is session leader */ if (strstr(flagstr, "sldr")) ki->p_eflag |= EPROC_SLEADER; return 0; } struct kinfo_proc2 * getkinfo_procfs(op, arg, cnt) int op, arg; int *cnt; { struct stat statbuf; int procdirfd, nbytes, knum = 0, maxknum = 0; char *direntbuff; struct kinfo_proc2 *ki; int mib[4]; size_t len; struct statfs procfsstat; /* First, make sure that /proc is a procfs filesystem. */ if (statfs("/proc", &procfsstat)) { warn("statfs on /proc failed"); return 0; } if (strcmp(procfsstat.f_fstypename, MOUNT_PROCFS)) { warnx("/proc exists but does not have a procfs mounted on it."); return 0; } /* * Try to stat /proc/1/status. If we can't do that, then just return * right away. */ if (stat("/proc/1/status", &statbuf)) { warn("stat of /proc/1/status"); return 0; } /* Now, try to open /proc, and read in all process' information. */ procdirfd = open("/proc", O_RDONLY, 0); if (procdirfd == -1) { warn("open of /proc directory"); close(procdirfd); return 0; } if (verify_procfs_fd(procdirfd, "/proc")) { close(procdirfd); return 0; } direntbuff = malloc(statbuf.st_blksize); if (direntbuff == NULL) { warn("malloc() of %d bytes", statbuf.st_blksize); close(procdirfd); return 0; } /* * Use sysctl to find out the total number of processes. There's still * a race condition -- once we do the sysctl, someone could use a * sysctl to bump it, and fork off a lot of processes. So, to be * _really_ safe, let's allocate twice as much memory. */ mib[0] = CTL_KERN; mib[1] = KERN_MAXPROC; len = sizeof(maxknum); if (sysctl(mib, 2, &maxknum, &len, NULL, 0) == -1) err(1, "sysctl to fetch maxproc"); maxknum *= 2; /* Double it, to be really paranoid. */ ki = (struct kinfo_proc2 *) calloc(sizeof(struct kinfo_proc2)*maxknum, 1); /* Read in a batch of entries at a time. */ while ((knum < maxknum) && (nbytes = getdents(procdirfd, direntbuff, statbuf.st_blksize)) != 0) { int i; struct dirent *dp; for (i = 0; i < nbytes; /* nothing */ ) { dp = (struct dirent *) & direntbuff[i]; i += dp->d_reclen; if (strcmp(dp->d_name, ".") == 0) continue; if (strcmp(dp->d_name, "..") == 0) continue; if (strcmp(dp->d_name, "curproc") == 0) continue; if (strcmp(dp->d_name, "self") == 0) continue; if (parsekinfo(dp->d_name, &ki[knum]) != 0) continue; /* * Now check some of the flags. If the newest entry * doesn't match the flag settings, then don't bump * the pointer past it! */ switch (op) { case KERN_PROC_PID: if (ki[knum].p_pid == arg) knum++; break; case KERN_PROC_PGRP: if (ki[knum].p_tpgid == arg) knum++; break; case KERN_PROC_SESSION: if (ki[knum].p_sid == arg) knum++; break; case KERN_PROC_TTY: if (ki[knum].p_tdev == arg) knum++; break; case KERN_PROC_UID: if (ki[knum].p_uid == arg) knum++; break; case KERN_PROC_RUID: WARNX_ONLY_ONCE(("KERN_PROC_RUID flag " "not implemented. Returning " "info for all processes.")); knum++; break; case KERN_PROC_ALL: knum++; break; default: WARNX_ONLY_ONCE(("Bad switch case! " "Returning info for " "all processes.")); knum++; break; } if (knum > maxknum) { WARNX_ONLY_ONCE(("Warning: only reporting " "information for first %d " "processes!", maxknum)); break; } } } *cnt = knum; close(procdirfd); /* free unused memory */ if (knum < maxknum) ki = realloc(ki, sizeof(*ki) * knum); return ki; } /* * return process arguments, possibly ones used when exec()ing * the process; return the array as two element array, first is * argv[0], second element is all the other args separated by spaces */ char ** procfs_getargv(kp, nchr) const struct kinfo_proc2 *kp; int nchr; { char fullpath[MAXPATHLEN], *buf, *name, *args, **argv; int fd, num; ssize_t len; size_t idx; /* Open /proc//cmdline, and parse it into the argv array */ snprintf(fullpath, MAXPATHLEN, "/proc/%d/cmdline", kp->p_pid); fd = open(fullpath, O_RDONLY, 0); if (fd == -1 || verify_procfs_fd(fd, fullpath)) { /* * Don't print warning, as the process may have died since our * scan of the directory entries. */ return NULL; /* Process may no longer exist. */ } buf = (char *)malloc(nchr+1); len = read(fd, buf, nchr); close(fd); if (len == -1) { warnx("procfs_getargv"); return NULL; } num = 1; args = NULL; name = buf; /* substitute any \0's with space */ for(idx=0; idx < len; idx++) { if (buf[idx] == '\0') { if (!args) args = &buf[idx+1]; else buf[idx] = ' '; num++; } } buf[len] = '\0'; /* end the string */ /* if the name is the same as the p_comm, just enclosed * in parentheses, just return NULL - the code in command() * will DTRT */ if (num == 1 && name[0] == '(' && name[len-1] == ')' && strncmp(name+1, kp->p_comm, len-2) == 0) { return (NULL); } argv = (char **) malloc(3*sizeof(char *)); argv[0] = name; argv[1] = args; argv[2] = NULL; return argv; }