/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "Pcontrol.h" static int open_psinfo(const char *arg, int *perr) { /* * Allocate enough space for procfs_path + arg + "/psinfo" */ char *path = alloca(strlen(arg) + strlen(procfs_path) + 9); struct stat64 st; int fd; if (strchr(arg, '/') == NULL) { (void) strcpy(path, procfs_path); (void) strcat(path, "/"); (void) strcat(path, arg); } else (void) strcpy(path, arg); (void) strcat(path, "/psinfo"); /* * Attempt to open the psinfo file, and return the fd if we can * confirm this is a regular file provided by /proc. */ if ((fd = open64(path, O_RDONLY)) >= 0) { if (fstat64(fd, &st) != 0 || !S_ISREG(st.st_mode) || strcmp(st.st_fstype, "proc") != 0) { (void) close(fd); fd = -1; } } else if (errno == EACCES || errno == EPERM) *perr = G_PERM; return (fd); } static int open_core(const char *arg, int *perr) { #ifdef _BIG_ENDIAN uchar_t order = ELFDATA2MSB; #else uchar_t order = ELFDATA2LSB; #endif GElf_Ehdr ehdr; int fd; int is_noelf = -1; /* * Attempt to open the core file, and return the fd if we can confirm * this is an ELF file of type ET_CORE. */ if ((fd = open64(arg, O_RDONLY)) >= 0) { if (read(fd, &ehdr, sizeof (ehdr)) != sizeof (ehdr)) { (void) close(fd); fd = -1; } else if ((is_noelf = memcmp(&ehdr.e_ident[EI_MAG0], ELFMAG, SELFMAG)) != 0 || ehdr.e_type != ET_CORE) { (void) close(fd); fd = -1; if (is_noelf == 0 && ehdr.e_ident[EI_DATA] != order) *perr = G_ISAINVAL; } } else if (errno == EACCES || errno == EPERM) *perr = G_PERM; return (fd); } /* * Make the error message precisely match the type of arguments the caller * wanted to process. This ensures that a tool which only accepts pids does * not produce an error message saying "no such process or core file 'foo'". */ static int open_error(int oflag) { if ((oflag & PR_ARG_ANY) == PR_ARG_PIDS) return (G_NOPROC); if ((oflag & PR_ARG_ANY) == PR_ARG_CORES) return (G_NOCORE); return (G_NOPROCORCORE); } static void * proc_grab_common(const char *arg, const char *path, int oflag, int gflag, int *perr, const char **lwps, psinfo_t *psp) { psinfo_t psinfo; char *core; int fd; char *slash; struct ps_prochandle *Pr; *perr = 0; if (lwps) *lwps = NULL; if (lwps != NULL && (slash = strrchr(arg, '/')) != NULL) { /* * Check to see if the user has supplied an lwp range. First, * try to grab it as a pid/lwp combo. */ *slash = '\0'; if ((oflag & PR_ARG_PIDS) && (fd = open_psinfo(arg, perr)) != -1) { if (read(fd, &psinfo, sizeof (psinfo_t)) == sizeof (psinfo_t)) { (void) close(fd); *lwps = slash + 1; *slash = '/'; if (proc_lwp_range_valid(*lwps) != 0) { *perr = G_BADLWPS; return (NULL); } if (psp) { *psp = psinfo; return (psp); } else { return (Pgrab(psinfo.pr_pid, gflag, perr)); } } (void) close(fd); } /* * Next, try grabbing it as a corefile. */ if ((oflag & PR_ARG_CORES) && (fd = open_core(arg, perr)) != -1) { *lwps = slash + 1; *slash = '/'; if (proc_lwp_range_valid(*lwps) != 0) { *perr = G_BADLWPS; return (NULL); } core = strdupa(arg); if ((Pr = Pfgrab_core(fd, path == NULL ? dirname(core) : path, perr)) != NULL) { if (psp) { (void) memcpy(psp, Ppsinfo(Pr), sizeof (psinfo_t)); Prelease(Pr, 0); return (psp); } else { return (Pr); } } } *slash = '/'; } if ((oflag & PR_ARG_PIDS) && (fd = open_psinfo(arg, perr)) != -1) { if (read(fd, &psinfo, sizeof (psinfo_t)) == sizeof (psinfo_t)) { (void) close(fd); if (psp) { *psp = psinfo; return (psp); } else { return (Pgrab(psinfo.pr_pid, gflag, perr)); } } /* * If the read failed, the process may have gone away; * we continue checking for core files or fail with G_NOPROC */ (void) close(fd); } if ((oflag & PR_ARG_CORES) && (fd = open_core(arg, perr)) != -1) { core = strdupa(arg); if ((Pr = Pfgrab_core(fd, path == NULL ? dirname(core) : path, perr)) != NULL) { if (psp) { (void) memcpy(psp, Ppsinfo(Pr), sizeof (psinfo_t)); Prelease(Pr, 0); return (psp); } else { return (Pr); } } } /* * We were unable to open the corefile. If we have no meaningful * information, report the (ambiguous) error from open_error(). */ if (*perr == 0) *perr = open_error(oflag); return (NULL); } struct ps_prochandle * proc_arg_xgrab(const char *arg, const char *path, int oflag, int gflag, int *perr, const char **lwps) { return (proc_grab_common(arg, path, oflag, gflag, perr, lwps, NULL)); } struct ps_prochandle * proc_arg_grab(const char *arg, int oflag, int gflag, int *perr) { return (proc_grab_common(arg, NULL, oflag, gflag, perr, NULL, NULL)); } pid_t proc_arg_psinfo(const char *arg, int oflag, psinfo_t *psp, int *perr) { psinfo_t psinfo; if (psp == NULL) psp = &psinfo; if (proc_grab_common(arg, NULL, oflag, 0, perr, NULL, psp) == NULL) return (-1); else return (psp->pr_pid); } pid_t proc_arg_xpsinfo(const char *arg, int oflag, psinfo_t *psp, int *perr, const char **lwps) { psinfo_t psinfo; if (psp == NULL) psp = &psinfo; if (proc_grab_common(arg, NULL, oflag, 0, perr, lwps, psp) == NULL) return (-1); else return (psp->pr_pid); } /* * Convert psinfo_t.pr_psargs string into itself, replacing unprintable * characters with space along the way. Stop on a null character. */ void proc_unctrl_psinfo(psinfo_t *psp) { char *s = &psp->pr_psargs[0]; size_t n = PRARGSZ; int c; while (n-- != 0 && (c = (*s & UCHAR_MAX)) != '\0') { if (!isprint(c)) c = ' '; *s++ = (char)c; } *s = '\0'; } static int proc_lwp_get_range(char *range, id_t *low, id_t *high) { if (*range == '-') *low = 0; else *low = (id_t)strtol(range, &range, 10); if (*range == '\0' || *range == ',') { *high = *low; return (0); } if (*range != '-') { return (-1); } range++; if (*range == '\0') *high = INT_MAX; else *high = (id_t)strtol(range, &range, 10); if (*range != '\0' && *range != ',') { return (-1); } if (*high < *low) { id_t tmp = *high; *high = *low; *low = tmp; } return (0); } /* * Determine if the specified lwpid is in the given set of lwpids. * The set can include multiple lwpid ranges separated by commas * and has the following syntax: * * lwp_range[,lwp_range]* * * where lwp_range is specifed as: * * -n lwpid <= n * n-m n <= lwpid <= m * n- lwpid >= n * n lwpid == n */ int proc_lwp_in_set(const char *set, lwpid_t lwpid) { id_t low, high; id_t id = (id_t)lwpid; char *comma; char *range = (char *)set; /* * A NULL set indicates that all LWPs are valid. */ if (set == NULL) return (1); while (range != NULL) { comma = strchr(range, ','); if (comma != NULL) *comma = '\0'; if (proc_lwp_get_range(range, &low, &high) != 0) { if (comma != NULL) *comma = ','; return (0); } if (comma != NULL) { *comma = ','; range = comma + 1; } else { range = NULL; } if (id >= low && id <= high) return (1); } return (0); } int proc_lwp_range_valid(const char *set) { char *comma; char *range = (char *)set; id_t low, high; int ret; if (range == NULL || *range == '\0' || *range == ',') return (-1); while (range != NULL) { comma = strchr(range, ','); if (comma != NULL) *comma = '\0'; if ((ret = proc_lwp_get_range(range, &low, &high)) != 0) { if (comma != NULL) *comma = ','; return (ret); } if (comma != NULL) { *comma = ','; range = comma + 1; } else { range = NULL; } } return (0); } /* * Walk all processes or LWPs in /proc and call func() for each. * Omit system processes (like process-IDs 0, 2, and 3). * Stop calling func() if it returns non 0 value and return it. */ int proc_walk(proc_walk_f *func, void *arg, int flag) { DIR *procdir; struct dirent *dirent; char *errptr; char pidstr[PATH_MAX]; psinfo_t psinfo; lwpsinfo_t *lwpsinfo; prheader_t prheader; void *buf; char *ptr; int bufsz; id_t pid; int fd, i; int ret = 0; boolean_t walk_sys = B_FALSE; if ((flag & PR_WALK_INCLUDE_SYS) != 0) walk_sys = B_TRUE; flag &= ~PR_WALK_INCLUDE_SYS; if (flag != PR_WALK_PROC && flag != PR_WALK_LWP) { errno = EINVAL; return (-1); } if ((procdir = opendir(procfs_path)) == NULL) return (-1); while (dirent = readdir(procdir)) { if (dirent->d_name[0] == '.') /* skip . and .. */ continue; pid = (id_t)strtol(dirent->d_name, &errptr, 10); if (errptr != NULL && *errptr != '\0') continue; /* PR_WALK_PROC case */ (void) snprintf(pidstr, sizeof (pidstr), "%s/%ld/psinfo", procfs_path, pid); fd = open(pidstr, O_RDONLY); if (fd < 0) continue; if (read(fd, &psinfo, sizeof (psinfo)) != sizeof (psinfo) || ((psinfo.pr_flag & SSYS) != 0 && !walk_sys)) { (void) close(fd); continue; } (void) close(fd); if (flag == PR_WALK_PROC) { if ((ret = func(&psinfo, &psinfo.pr_lwp, arg)) != 0) break; continue; } /* PR_WALK_LWP case */ (void) snprintf(pidstr, sizeof (pidstr), "%s/%ld/lpsinfo", procfs_path, pid); fd = open(pidstr, O_RDONLY); if (fd < 0) continue; if (read(fd, &prheader, sizeof (prheader)) != sizeof (prheader)) { (void) close(fd); continue; } bufsz = prheader.pr_nent * prheader.pr_entsize; if ((buf = malloc(bufsz)) == NULL) { (void) close(fd); ret = -1; break; } ptr = buf; if (pread(fd, buf, bufsz, sizeof (prheader)) != bufsz) { free(buf); (void) close(fd); continue; } (void) close(fd); for (i = 0; i < prheader.pr_nent; i++, ptr += prheader.pr_entsize) { /*LINTED ALIGNMENT*/ lwpsinfo = (lwpsinfo_t *)ptr; if ((ret = func(&psinfo, lwpsinfo, arg)) != 0) { free(buf); break; } } free(buf); } (void) closedir(procdir); return (ret); }