xref: /illumos-gate/usr/src/lib/libproc/common/proc_arg.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
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 = alloca(strlen(arg) + 1);
184 			(void) strcpy(core, arg);
185 			if ((Pr = Pfgrab_core(fd, path == NULL ?
186 			    dirname(core) : path, perr)) != NULL) {
187 				if (psp) {
188 					(void) memcpy(psp, Ppsinfo(Pr),
189 					    sizeof (psinfo_t));
190 					Prelease(Pr, 0);
191 					return (psp);
192 				} else {
193 					return (Pr);
194 				}
195 			}
196 		}
197 
198 		*slash = '/';
199 	}
200 
201 	if ((oflag & PR_ARG_PIDS) && (fd = open_psinfo(arg, perr)) != -1) {
202 		if (read(fd, &psinfo, sizeof (psinfo_t)) == sizeof (psinfo_t)) {
203 			(void) close(fd);
204 			if (psp) {
205 				*psp = psinfo;
206 				return (psp);
207 			} else {
208 				return (Pgrab(psinfo.pr_pid, gflag, perr));
209 			}
210 		}
211 		/*
212 		 * If the read failed, the process may have gone away;
213 		 * we continue checking for core files or fail with G_NOPROC
214 		 */
215 		(void) close(fd);
216 	}
217 
218 	if ((oflag & PR_ARG_CORES) && (fd = open_core(arg, perr)) != -1) {
219 		core = alloca(strlen(arg) + 1);
220 		(void) strcpy(core, arg);
221 		if ((Pr = Pfgrab_core(fd, path == NULL ? dirname(core) : path,
222 		    perr)) != NULL) {
223 			if (psp) {
224 				(void) memcpy(psp, Ppsinfo(Pr),
225 				    sizeof (psinfo_t));
226 				Prelease(Pr, 0);
227 				return (psp);
228 			} else {
229 				return (Pr);
230 			}
231 		}
232 	}
233 
234 	/*
235 	 * We were unable to open the corefile.  If we have no meaningful
236 	 * information, report the (ambiguous) error from open_error().
237 	 */
238 
239 	if (*perr == 0)
240 		*perr = open_error(oflag);
241 
242 	return (NULL);
243 }
244 
245 struct ps_prochandle *
246 proc_arg_xgrab(const char *arg, const char *path, int oflag, int gflag,
247     int *perr, const char **lwps)
248 {
249 	return (proc_grab_common(arg, path, oflag, gflag, perr, lwps, NULL));
250 }
251 
252 struct ps_prochandle *
253 proc_arg_grab(const char *arg, int oflag, int gflag, int *perr)
254 {
255 	return (proc_grab_common(arg, NULL, oflag, gflag, perr, NULL, NULL));
256 }
257 
258 pid_t
259 proc_arg_psinfo(const char *arg, int oflag, psinfo_t *psp, int *perr)
260 {
261 	psinfo_t psinfo;
262 
263 	if (psp == NULL)
264 		psp = &psinfo;
265 
266 	if (proc_grab_common(arg, NULL, oflag, 0, perr, NULL, psp) == NULL)
267 		return (-1);
268 	else
269 		return (psp->pr_pid);
270 }
271 
272 pid_t
273 proc_arg_xpsinfo(const char *arg, int oflag, psinfo_t *psp, int *perr,
274     const char **lwps)
275 {
276 	psinfo_t psinfo;
277 
278 	if (psp == NULL)
279 		psp = &psinfo;
280 
281 	if (proc_grab_common(arg, NULL, oflag, 0, perr, lwps, psp) == NULL)
282 		return (-1);
283 	else
284 		return (psp->pr_pid);
285 }
286 
287 /*
288  * Convert psinfo_t.pr_psargs string into itself, replacing unprintable
289  * characters with space along the way.  Stop on a null character.
290  */
291 void
292 proc_unctrl_psinfo(psinfo_t *psp)
293 {
294 	char *s = &psp->pr_psargs[0];
295 	size_t n = PRARGSZ;
296 	int c;
297 
298 	while (n-- != 0 && (c = (*s & UCHAR_MAX)) != '\0') {
299 		if (!isprint(c))
300 			c = ' ';
301 		*s++ = (char)c;
302 	}
303 
304 	*s = '\0';
305 }
306 
307 static int
308 proc_lwp_get_range(char *range, id_t *low, id_t *high)
309 {
310 	if (*range == '-')
311 		*low = 0;
312 	else
313 		*low = (id_t)strtol(range, &range, 10);
314 
315 	if (*range == '\0' || *range == ',') {
316 		*high = *low;
317 		return (0);
318 	}
319 	if (*range != '-') {
320 		return (-1);
321 	}
322 	range++;
323 
324 	if (*range == '\0')
325 		*high = INT_MAX;
326 	else
327 		*high = (id_t)strtol(range, &range, 10);
328 
329 	if (*range != '\0' && *range != ',') {
330 		return (-1);
331 	}
332 
333 	if (*high < *low) {
334 		id_t tmp = *high;
335 		*high = *low;
336 		*low = tmp;
337 	}
338 
339 	return (0);
340 }
341 
342 /*
343  * Determine if the specified lwpid is in the given set of lwpids.
344  * The set can include multiple lwpid ranges separated by commas
345  * and has the following syntax:
346  *
347  * 	lwp_range[,lwp_range]*
348  *
349  * where lwp_range is specifed as:
350  *
351  * 	-n			lwpid <= n
352  * 	n-m			n <= lwpid <= m
353  * 	n-			lwpid >= n
354  * 	n			lwpid == n
355  */
356 int
357 proc_lwp_in_set(const char *set, lwpid_t lwpid)
358 {
359 	id_t low, high;
360 	id_t id = (id_t)lwpid;
361 	char *comma;
362 	char *range = (char *)set;
363 
364 	/*
365 	 * A NULL set indicates that all LWPs are valid.
366 	 */
367 	if (set == NULL)
368 		return (1);
369 
370 	while (range != NULL) {
371 		comma = strchr(range, ',');
372 		if (comma != NULL)
373 			*comma = '\0';
374 		if (proc_lwp_get_range(range, &low, &high) != 0) {
375 			if (comma != NULL)
376 				*comma = ',';
377 			return (0);
378 		}
379 		if (comma != NULL) {
380 			*comma = ',';
381 			range = comma + 1;
382 		} else {
383 			range = NULL;
384 		}
385 		if (id >= low && id <= high)
386 			return (1);
387 	}
388 
389 	return (0);
390 }
391 
392 int
393 proc_lwp_range_valid(const char *set)
394 {
395 	char *comma;
396 	char *range = (char *)set;
397 	id_t low, high;
398 	int ret;
399 
400 	if (range == NULL || *range == '\0' || *range == ',')
401 		return (-1);
402 
403 	while (range != NULL) {
404 		comma = strchr(range, ',');
405 		if (comma != NULL)
406 			*comma = '\0';
407 		if ((ret = proc_lwp_get_range(range, &low, &high)) != 0) {
408 			if (comma != NULL)
409 				*comma = ',';
410 			return (ret);
411 		}
412 		if (comma != NULL) {
413 			*comma = ',';
414 			range = comma + 1;
415 		} else {
416 			range = NULL;
417 		}
418 	}
419 
420 	return (0);
421 }
422 
423 /*
424  * Walk all processes or LWPs in /proc and call func() for each.
425  * Omit system processes (like process-IDs 0, 2, and 3).
426  * Stop calling func() if it returns non 0 value and return it.
427  */
428 int
429 proc_walk(proc_walk_f *func, void *arg, int flag)
430 {
431 	DIR *procdir;
432 	struct dirent *dirent;
433 	char *errptr;
434 	char pidstr[PATH_MAX];
435 	psinfo_t psinfo;
436 	lwpsinfo_t *lwpsinfo;
437 	prheader_t prheader;
438 	void *buf;
439 	char *ptr;
440 	int bufsz;
441 	id_t pid;
442 	int fd, i;
443 	int ret = 0;
444 
445 	if (flag != PR_WALK_PROC && flag != PR_WALK_LWP) {
446 		errno = EINVAL;
447 		return (-1);
448 	}
449 	if ((procdir = opendir(procfs_path)) == NULL)
450 		return (-1);
451 	while (dirent = readdir(procdir)) {
452 		if (dirent->d_name[0] == '.')	/* skip . and .. */
453 			continue;
454 		pid = (id_t)strtol(dirent->d_name, &errptr, 10);
455 		if (errptr != NULL && *errptr != '\0')
456 			continue;
457 		/* PR_WALK_PROC case */
458 		(void) snprintf(pidstr, sizeof (pidstr),
459 		    "%s/%ld/psinfo", procfs_path, pid);
460 		fd = open(pidstr, O_RDONLY);
461 		if (fd < 0)
462 			continue;
463 		if (read(fd, &psinfo, sizeof (psinfo)) != sizeof (psinfo) ||
464 		    (psinfo.pr_flag & SSYS)) {
465 			(void) close(fd);
466 			continue;
467 		}
468 		(void) close(fd);
469 		if (flag == PR_WALK_PROC) {
470 			if ((ret = func(&psinfo, &psinfo.pr_lwp, arg)) != 0)
471 				break;
472 			continue;
473 		}
474 		/* PR_WALK_LWP case */
475 		(void) snprintf(pidstr, sizeof (pidstr),
476 		    "%s/%ld/lpsinfo", procfs_path, pid);
477 		fd = open(pidstr, O_RDONLY);
478 		if (fd < 0)
479 			continue;
480 		if (read(fd, &prheader, sizeof (prheader)) !=
481 		    sizeof (prheader)) {
482 			(void) close(fd);
483 			continue;
484 		}
485 		bufsz = prheader.pr_nent * prheader.pr_entsize;
486 		if ((buf = malloc(bufsz)) == NULL) {
487 			(void) close(fd);
488 			ret = -1;
489 			break;
490 		}
491 		ptr = buf;
492 		if (pread(fd, buf, bufsz, sizeof (prheader)) != bufsz) {
493 			free(buf);
494 			(void) close(fd);
495 			continue;
496 		}
497 		(void) close(fd);
498 		for (i = 0; i < prheader.pr_nent;
499 		    i++, ptr += prheader.pr_entsize) {
500 			/*LINTED ALIGNMENT*/
501 			lwpsinfo = (lwpsinfo_t *)ptr;
502 			if ((ret = func(&psinfo, lwpsinfo, arg)) != 0) {
503 				free(buf);
504 				break;
505 			}
506 		}
507 		free(buf);
508 	}
509 	(void) closedir(procdir);
510 	return (ret);
511 }
512