/* $NetBSD: procfs_ops.c,v 1.4 1999/03/28 00:46:47 bgrayson 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 #include #include #include #include #include #include #include /* 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_proc *)); struct kinfo_proc * procfs_getprocs __P((int, int, int *)); 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, kp) const char *path; struct kinfo_proc *kp; { 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/"path"/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. */ /*warn("Open failed for %s", fullpath);*/ close(fd); 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); close(fd); if (nbytes <= 0) { /* Don't print warning, as the process may have * died since our scan of the directory entries. */ /*warn("Read failed for %s", fullpath);*/ return -1; /* Process may no longer exist. */ } /* Terminate the buffer. */ buff[nbytes] = '\0'; sscanf(buff, "%s %d %d %d %d %d,%d %s %ld,%ld %ld,%ld %ld,%ld %s %d", kp->kp_proc.p_comm, &kp->kp_proc.p_pid, &kp->kp_eproc.e_ppid, &kp->kp_eproc.e_pgid, &kp->kp_eproc.e_sid, &devmajor, &devminor, flagstr, &starttime.tv_sec, &starttime.tv_usec, &usertime.tv_sec, &usertime.tv_usec, &systime.tv_sec, &systime.tv_usec, kp->kp_eproc.e_wmesg, &kp->kp_eproc.e_ucred.cr_uid); kp->kp_proc.p_wmesg = kp->kp_eproc.e_wmesg; kp->kp_proc.p_wchan = (void*)1; /* Set it to _something_. */ kp->kp_eproc.e_tdev = makedev(devmajor, devminor); /* Put both user and sys time into rtime field. */ kp->kp_proc.p_rtime.tv_sec = usertime.tv_sec + systime.tv_sec; kp->kp_proc.p_rtime.tv_usec = usertime.tv_usec + systime.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")) kp->kp_proc.p_flag |= P_CONTROLT; return 0; } struct kinfo_proc * procfs_getprocs(op, arg, cnt) int op, arg; int *cnt; { struct stat statbuf; int procdirfd, nbytes, knum=0, maxknum=0; char *direntbuff; struct kinfo_proc *kp; 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. */ kp = (struct kinfo_proc *) malloc(sizeof(struct kinfo_proc)*maxknum); memset(kp, 0, sizeof(struct kinfo_proc)*maxknum); /* 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; id_reclen; if (!strcmp(dp->d_name, ".")) continue; if (!strcmp(dp->d_name, "..")) continue; if (!strcmp(dp->d_name, "curproc")) continue; if (parsekinfo(dp->d_name, &kp[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 (kp[knum].kp_proc.p_pid == arg) knum++; break; case KERN_PROC_PGRP: if (kp[knum].kp_eproc.e_pgid == arg) knum++; break; case KERN_PROC_SESSION: if (kp[knum].kp_eproc.e_sid == arg) knum++; break; case KERN_PROC_TTY: if (kp[knum].kp_eproc.e_tdev == arg) knum++; break; case KERN_PROC_UID: if (kp[knum].kp_eproc.e_ucred.cr_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); return kp; }