xref: /illumos-gate/usr/src/lib/libproc/common/proc_arg.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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 
43 static int
44 open_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 
79 static int
80 open_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  */
118 static int
119 open_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 
130 static void *
131 proc_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 
243 struct ps_prochandle *
244 proc_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 
250 struct ps_prochandle *
251 proc_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 
256 pid_t
257 proc_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 
270 pid_t
271 proc_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  */
289 void
290 proc_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 
305 static int
306 proc_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  */
354 int
355 proc_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 
390 int
391 proc_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  */
426 int
427 proc_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