1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2019 Joyent, Inc.
25 */
26
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <sys/proc.h>
30
31#include <libgen.h>
32#include <limits.h>
33#include <alloca.h>
34#include <unistd.h>
35#include <string.h>
36#include <fcntl.h>
37#include <ctype.h>
38#include <errno.h>
39#include <dirent.h>
40
41#include "Pcontrol.h"
42
43static int
44open_psinfo(const char *arg, int *perr)
45{
46	/*
47	 * Allocate enough space for procfs_path + arg + "/psinfo"
48	 */
49	char *path = alloca(strlen(arg) + strlen(procfs_path) + 9);
50
51	struct stat64 st;
52	int fd;
53
54	if (strchr(arg, '/') == NULL) {
55		(void) strcpy(path, procfs_path);
56		(void) strcat(path, "/");
57		(void) strcat(path, arg);
58	} else
59		(void) strcpy(path, arg);
60
61	(void) strcat(path, "/psinfo");
62
63	/*
64	 * Attempt to open the psinfo file, and return the fd if we can
65	 * confirm this is a regular file provided by /proc.
66	 */
67	if ((fd = open64(path, O_RDONLY)) >= 0) {
68		if (fstat64(fd, &st) != 0 || !S_ISREG(st.st_mode) ||
69		    strcmp(st.st_fstype, "proc") != 0) {
70			(void) close(fd);
71			fd = -1;
72		}
73	} else if (errno == EACCES || errno == EPERM)
74		*perr = G_PERM;
75
76	return (fd);
77}
78
79static int
80open_core(const char *arg, int *perr)
81{
82#ifdef _BIG_ENDIAN
83	uchar_t order = ELFDATA2MSB;
84#else
85	uchar_t order = ELFDATA2LSB;
86#endif
87	GElf_Ehdr ehdr;
88	int fd;
89	int is_noelf = -1;
90
91	/*
92	 * Attempt to open the core file, and return the fd if we can confirm
93	 * this is an ELF file of type ET_CORE.
94	 */
95	if ((fd = open64(arg, O_RDONLY)) >= 0) {
96		if (read(fd, &ehdr, sizeof (ehdr)) != sizeof (ehdr)) {
97			(void) close(fd);
98			fd = -1;
99		} else if ((is_noelf = memcmp(&ehdr.e_ident[EI_MAG0], ELFMAG,
100		    SELFMAG)) != 0 || ehdr.e_type != ET_CORE) {
101			(void) close(fd);
102			fd = -1;
103			if (is_noelf == 0 &&
104			    ehdr.e_ident[EI_DATA] != order)
105				*perr = G_ISAINVAL;
106		}
107	} else if (errno == EACCES || errno == EPERM)
108		*perr = G_PERM;
109
110	return (fd);
111}
112
113/*
114 * Make the error message precisely match the type of arguments the caller
115 * wanted to process.  This ensures that a tool which only accepts pids does
116 * not produce an error message saying "no such process or core file 'foo'".
117 */
118static int
119open_error(int oflag)
120{
121	if ((oflag & PR_ARG_ANY) == PR_ARG_PIDS)
122		return (G_NOPROC);
123
124	if ((oflag & PR_ARG_ANY) == PR_ARG_CORES)
125		return (G_NOCORE);
126
127	return (G_NOPROCORCORE);
128}
129
130static void *
131proc_grab_common(const char *arg, const char *path, int oflag, int gflag,
132    int *perr, const char **lwps, psinfo_t *psp)
133{
134	psinfo_t psinfo;
135	char *core;
136	int fd;
137	char *slash;
138	struct ps_prochandle *Pr;
139
140	*perr = 0;
141	if (lwps)
142		*lwps = NULL;
143
144	if (lwps != NULL && (slash = strrchr(arg, '/')) != NULL) {
145		/*
146		 * Check to see if the user has supplied an lwp range.  First,
147		 * try to grab it as a pid/lwp combo.
148		 */
149		*slash = '\0';
150		if ((oflag & PR_ARG_PIDS) &&
151		    (fd = open_psinfo(arg, perr)) != -1) {
152			if (read(fd, &psinfo,
153			    sizeof (psinfo_t)) == sizeof (psinfo_t)) {
154				(void) close(fd);
155				*lwps = slash + 1;
156				*slash = '/';
157				if (proc_lwp_range_valid(*lwps) != 0) {
158					*perr = G_BADLWPS;
159					return (NULL);
160				}
161				if (psp) {
162					*psp = psinfo;
163					return (psp);
164				} else  {
165					return (Pgrab(psinfo.pr_pid, gflag,
166					    perr));
167				}
168			}
169			(void) close(fd);
170		}
171
172		/*
173		 * Next, try grabbing it as a corefile.
174		 */
175		if ((oflag & PR_ARG_CORES) &&
176		    (fd = open_core(arg, perr)) != -1) {
177			*lwps = slash + 1;
178			*slash = '/';
179			if (proc_lwp_range_valid(*lwps) != 0) {
180				*perr = G_BADLWPS;
181				return (NULL);
182			}
183			core = strdupa(arg);
184			if ((Pr = Pfgrab_core(fd, path == NULL ?
185			    dirname(core) : path, perr)) != NULL) {
186				if (psp) {
187					(void) memcpy(psp, Ppsinfo(Pr),
188					    sizeof (psinfo_t));
189					Prelease(Pr, 0);
190					return (psp);
191				} else {
192					return (Pr);
193				}
194			}
195		}
196
197		*slash = '/';
198	}
199
200	if ((oflag & PR_ARG_PIDS) && (fd = open_psinfo(arg, perr)) != -1) {
201		if (read(fd, &psinfo, sizeof (psinfo_t)) == sizeof (psinfo_t)) {
202			(void) close(fd);
203			if (psp) {
204				*psp = psinfo;
205				return (psp);
206			} else {
207				return (Pgrab(psinfo.pr_pid, gflag, perr));
208			}
209		}
210		/*
211		 * If the read failed, the process may have gone away;
212		 * we continue checking for core files or fail with G_NOPROC
213		 */
214		(void) close(fd);
215	}
216
217	if ((oflag & PR_ARG_CORES) && (fd = open_core(arg, perr)) != -1) {
218		core = strdupa(arg);
219		if ((Pr = Pfgrab_core(fd, path == NULL ? dirname(core) : path,
220		    perr)) != NULL) {
221			if (psp) {
222				(void) memcpy(psp, Ppsinfo(Pr),
223				    sizeof (psinfo_t));
224				Prelease(Pr, 0);
225				return (psp);
226			} else {
227				return (Pr);
228			}
229		}
230	}
231
232	/*
233	 * We were unable to open the corefile.  If we have no meaningful
234	 * information, report the (ambiguous) error from open_error().
235	 */
236
237	if (*perr == 0)
238		*perr = open_error(oflag);
239
240	return (NULL);
241}
242
243struct ps_prochandle *
244proc_arg_xgrab(const char *arg, const char *path, int oflag, int gflag,
245    int *perr, const char **lwps)
246{
247	return (proc_grab_common(arg, path, oflag, gflag, perr, lwps, NULL));
248}
249
250struct ps_prochandle *
251proc_arg_grab(const char *arg, int oflag, int gflag, int *perr)
252{
253	return (proc_grab_common(arg, NULL, oflag, gflag, perr, NULL, NULL));
254}
255
256pid_t
257proc_arg_psinfo(const char *arg, int oflag, psinfo_t *psp, int *perr)
258{
259	psinfo_t psinfo;
260
261	if (psp == NULL)
262		psp = &psinfo;
263
264	if (proc_grab_common(arg, NULL, oflag, 0, perr, NULL, psp) == NULL)
265		return (-1);
266	else
267		return (psp->pr_pid);
268}
269
270pid_t
271proc_arg_xpsinfo(const char *arg, int oflag, psinfo_t *psp, int *perr,
272    const char **lwps)
273{
274	psinfo_t psinfo;
275
276	if (psp == NULL)
277		psp = &psinfo;
278
279	if (proc_grab_common(arg, NULL, oflag, 0, perr, lwps, psp) == NULL)
280		return (-1);
281	else
282		return (psp->pr_pid);
283}
284
285/*
286 * Convert psinfo_t.pr_psargs string into itself, replacing unprintable
287 * characters with space along the way.  Stop on a null character.
288 */
289void
290proc_unctrl_psinfo(psinfo_t *psp)
291{
292	char *s = &psp->pr_psargs[0];
293	size_t n = PRARGSZ;
294	int c;
295
296	while (n-- != 0 && (c = (*s & UCHAR_MAX)) != '\0') {
297		if (!isprint(c))
298			c = ' ';
299		*s++ = (char)c;
300	}
301
302	*s = '\0';
303}
304
305static int
306proc_lwp_get_range(char *range, id_t *low, id_t *high)
307{
308	if (*range == '-')
309		*low = 0;
310	else
311		*low = (id_t)strtol(range, &range, 10);
312
313	if (*range == '\0' || *range == ',') {
314		*high = *low;
315		return (0);
316	}
317	if (*range != '-') {
318		return (-1);
319	}
320	range++;
321
322	if (*range == '\0')
323		*high = INT_MAX;
324	else
325		*high = (id_t)strtol(range, &range, 10);
326
327	if (*range != '\0' && *range != ',') {
328		return (-1);
329	}
330
331	if (*high < *low) {
332		id_t tmp = *high;
333		*high = *low;
334		*low = tmp;
335	}
336
337	return (0);
338}
339
340/*
341 * Determine if the specified lwpid is in the given set of lwpids.
342 * The set can include multiple lwpid ranges separated by commas
343 * and has the following syntax:
344 *
345 *	lwp_range[,lwp_range]*
346 *
347 * where lwp_range is specifed as:
348 *
349 *	-n			lwpid <= n
350 *	n-m			n <= lwpid <= m
351 *	n-			lwpid >= n
352 *	n			lwpid == n
353 */
354int
355proc_lwp_in_set(const char *set, lwpid_t lwpid)
356{
357	id_t low, high;
358	id_t id = (id_t)lwpid;
359	char *comma;
360	char *range = (char *)set;
361
362	/*
363	 * A NULL set indicates that all LWPs are valid.
364	 */
365	if (set == NULL)
366		return (1);
367
368	while (range != NULL) {
369		comma = strchr(range, ',');
370		if (comma != NULL)
371			*comma = '\0';
372		if (proc_lwp_get_range(range, &low, &high) != 0) {
373			if (comma != NULL)
374				*comma = ',';
375			return (0);
376		}
377		if (comma != NULL) {
378			*comma = ',';
379			range = comma + 1;
380		} else {
381			range = NULL;
382		}
383		if (id >= low && id <= high)
384			return (1);
385	}
386
387	return (0);
388}
389
390int
391proc_lwp_range_valid(const char *set)
392{
393	char *comma;
394	char *range = (char *)set;
395	id_t low, high;
396	int ret;
397
398	if (range == NULL || *range == '\0' || *range == ',')
399		return (-1);
400
401	while (range != NULL) {
402		comma = strchr(range, ',');
403		if (comma != NULL)
404			*comma = '\0';
405		if ((ret = proc_lwp_get_range(range, &low, &high)) != 0) {
406			if (comma != NULL)
407				*comma = ',';
408			return (ret);
409		}
410		if (comma != NULL) {
411			*comma = ',';
412			range = comma + 1;
413		} else {
414			range = NULL;
415		}
416	}
417
418	return (0);
419}
420
421/*
422 * Walk all processes or LWPs in /proc and call func() for each.
423 * Omit system processes (like process-IDs 0, 2, and 3).
424 * Stop calling func() if it returns non 0 value and return it.
425 */
426int
427proc_walk(proc_walk_f *func, void *arg, int flag)
428{
429	DIR *procdir;
430	struct dirent *dirent;
431	char *errptr;
432	char pidstr[PATH_MAX];
433	psinfo_t psinfo;
434	lwpsinfo_t *lwpsinfo;
435	prheader_t prheader;
436	void *buf;
437	char *ptr;
438	int bufsz;
439	id_t pid;
440	int fd, i;
441	int ret = 0;
442	boolean_t walk_sys = B_FALSE;
443
444	if ((flag & PR_WALK_INCLUDE_SYS) != 0)
445		walk_sys = B_TRUE;
446	flag &= ~PR_WALK_INCLUDE_SYS;
447
448	if (flag != PR_WALK_PROC && flag != PR_WALK_LWP) {
449		errno = EINVAL;
450		return (-1);
451	}
452	if ((procdir = opendir(procfs_path)) == NULL)
453		return (-1);
454	while (dirent = readdir(procdir)) {
455		if (dirent->d_name[0] == '.')	/* skip . and .. */
456			continue;
457		pid = (id_t)strtol(dirent->d_name, &errptr, 10);
458		if (errptr != NULL && *errptr != '\0')
459			continue;
460		/* PR_WALK_PROC case */
461		(void) snprintf(pidstr, sizeof (pidstr),
462		    "%s/%ld/psinfo", procfs_path, pid);
463		fd = open(pidstr, O_RDONLY);
464		if (fd < 0)
465			continue;
466		if (read(fd, &psinfo, sizeof (psinfo)) != sizeof (psinfo) ||
467		    ((psinfo.pr_flag & SSYS) != 0 && !walk_sys)) {
468			(void) close(fd);
469			continue;
470		}
471		(void) close(fd);
472		if (flag == PR_WALK_PROC) {
473			if ((ret = func(&psinfo, &psinfo.pr_lwp, arg)) != 0)
474				break;
475			continue;
476		}
477		/* PR_WALK_LWP case */
478		(void) snprintf(pidstr, sizeof (pidstr),
479		    "%s/%ld/lpsinfo", procfs_path, pid);
480		fd = open(pidstr, O_RDONLY);
481		if (fd < 0)
482			continue;
483		if (read(fd, &prheader, sizeof (prheader)) !=
484		    sizeof (prheader)) {
485			(void) close(fd);
486			continue;
487		}
488		bufsz = prheader.pr_nent * prheader.pr_entsize;
489		if ((buf = malloc(bufsz)) == NULL) {
490			(void) close(fd);
491			ret = -1;
492			break;
493		}
494		ptr = buf;
495		if (pread(fd, buf, bufsz, sizeof (prheader)) != bufsz) {
496			free(buf);
497			(void) close(fd);
498			continue;
499		}
500		(void) close(fd);
501		for (i = 0; i < prheader.pr_nent;
502		    i++, ptr += prheader.pr_entsize) {
503			/*LINTED ALIGNMENT*/
504			lwpsinfo = (lwpsinfo_t *)ptr;
505			if ((ret = func(&psinfo, lwpsinfo, arg)) != 0) {
506				free(buf);
507				break;
508			}
509		}
510		free(buf);
511	}
512	(void) closedir(procdir);
513	return (ret);
514}
515