xref: /illumos-gate/usr/src/cmd/coreadm/coreadm.c (revision 9a016c63ca347047a236dff12f0da83aac8981d1)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <fcntl.h>
31 #include <ctype.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <limits.h>
37 #include <libintl.h>
38 #include <locale.h>
39 #include <sys/stat.h>
40 #include <sys/corectl.h>
41 #include <libproc.h>
42 
43 #define	E_SUCCESS	0		/* Exit status for success */
44 #define	E_ERROR		1		/* Exit status for error */
45 #define	E_USAGE		2		/* Exit status for usage error */
46 
47 static	const	char	PATH_CONFIG[] = "/etc/coreadm.conf";
48 #define	CF_OWNER	0				/* Uid 0 (root) */
49 #define	CF_GROUP	1				/* Gid 1 (other) */
50 #define	CF_PERM	(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)	/* Mode 0644 */
51 
52 static	char		*command;
53 static	char		*glob_pattern;
54 static	size_t		glob_size;
55 static	core_content_t	glob_content = CC_CONTENT_INVALID;
56 static	char		*init_pattern;
57 static	size_t		init_size;
58 static	core_content_t	init_content = CC_CONTENT_INVALID;
59 static	char		*proc_pattern;
60 static	size_t		proc_size;
61 static	core_content_t	proc_content = CC_CONTENT_INVALID;
62 static	int		enable;
63 static	int		disable;
64 
65 static	int		report_settings(void);
66 static	int		do_processes(int, char **);
67 static	int		do_modify(void);
68 static	int		do_update(void);
69 static	int		write_config(int);
70 
71 static void
72 usage(void)
73 {
74 	(void) fprintf(stderr, gettext(
75 "usage:\n"));
76 	(void) fprintf(stderr, gettext(
77 "    %s [ -g pattern ] [ -i pattern ] [ -G content ] [ -I content ]\n"),
78 		command);
79 	(void) fprintf(stderr, gettext(
80 "            [ -e {global | process | global-setid | proc-setid | log} ]\n"));
81 	(void) fprintf(stderr, gettext(
82 "            [ -d {global | process | global-setid | proc-setid | log} ]\n"));
83 	(void) fprintf(stderr, gettext(
84 "    %s [ -p pattern ] [ -P content ] [ pid ... ]\n"), command);
85 	(void) fprintf(stderr, gettext(
86 "    %s -u\n"), command);
87 	exit(E_USAGE);
88 }
89 
90 static int
91 perm(void)
92 {
93 	(void) fprintf(stderr, gettext("%s: insufficient privileges to "
94 	    "exercise the -[GIgiedu] options\n"), command);
95 	return (E_USAGE);
96 }
97 
98 int
99 main(int argc, char **argv)
100 {
101 	int flag;
102 	int opt;
103 	int modify;
104 	int update = 0;
105 	int error = 0;
106 	int npids;
107 	char **pidlist;
108 
109 	char curpid[11];
110 	char *curpid_ptr = &curpid[0];
111 
112 	(void) setlocale(LC_ALL, "");
113 	(void) textdomain(TEXT_DOMAIN);
114 
115 	/* command name (e.g., "coreadm") */
116 	if ((command = strrchr(argv[0], '/')) != NULL)
117 		command++;
118 	else
119 		command = argv[0];
120 
121 	while ((opt = getopt(argc, argv, "g:G:i:I:p:P:e:d:u?")) != EOF) {
122 		switch (opt) {
123 		case 'g':
124 			glob_pattern = optarg;
125 			glob_size = strlen(glob_pattern) + 1;
126 			break;
127 		case 'i':
128 			init_pattern = optarg;
129 			init_size = strlen(init_pattern) + 1;
130 			break;
131 		case 'p':
132 			proc_pattern = optarg;
133 			proc_size = strlen(proc_pattern) + 1;
134 			break;
135 		case 'G':
136 			if (proc_str2content(optarg, &glob_content) != 0) {
137 				(void) fprintf(stderr, gettext("invalid "
138 				    "content string '%s'\n"), optarg);
139 				error = 1;
140 			}
141 			break;
142 		case 'I':
143 			if (proc_str2content(optarg, &init_content) != 0) {
144 				(void) fprintf(stderr, gettext("invalid "
145 				    "content string '%s'\n"), optarg);
146 				error = 1;
147 			}
148 			break;
149 		case 'P':
150 			if (proc_str2content(optarg, &proc_content) != 0) {
151 				(void) fprintf(stderr, gettext("invalid "
152 				    "content string '%s'\n"), optarg);
153 				error = 1;
154 			}
155 			break;
156 		case 'e':
157 		case 'd':
158 			if (strcmp(optarg, "global") == 0)
159 				flag = CC_GLOBAL_PATH;
160 			else if (strcmp(optarg, "process") == 0)
161 				flag = CC_PROCESS_PATH;
162 			else if (strcmp(optarg, "global-setid") == 0)
163 				flag = CC_GLOBAL_SETID;
164 			else if (strcmp(optarg, "proc-setid") == 0)
165 				flag = CC_PROCESS_SETID;
166 			else if (strcmp(optarg, "log") == 0)
167 				flag = CC_GLOBAL_LOG;
168 			else {
169 				flag = 0;
170 				error = 1;
171 			}
172 			if (opt == 'e') {
173 				enable |= flag;
174 				disable &= ~flag;
175 			} else {
176 				disable |= flag;
177 				enable &= ~flag;
178 			}
179 			break;
180 		case 'u':
181 			update = 1;
182 			break;
183 		case '?':
184 		default:
185 			error = 1;
186 			break;
187 		}
188 	}
189 
190 	npids = argc - optind;
191 	pidlist = argv + optind;
192 
193 	if (error)
194 		usage();
195 
196 	/*
197 	 * If 'modify' is true, we must modify the system settings
198 	 * and update the configuration file with the new parameters.
199 	 */
200 	modify = glob_pattern != NULL || glob_content != CC_CONTENT_INVALID ||
201 		init_pattern != NULL || init_content != CC_CONTENT_INVALID ||
202 		(enable | disable) != 0;
203 
204 	if (update && (modify || proc_pattern != NULL ||
205 	    proc_content != CC_CONTENT_INVALID || npids != 0)) {
206 		(void) fprintf(stderr,
207 		    gettext(
208 		    "%s: the -u option must stand alone\n"),
209 		    command);
210 		usage();
211 	}
212 	if (modify &&
213 	    (proc_pattern != NULL || proc_content != CC_CONTENT_INVALID)) {
214 		(void) fprintf(stderr,
215 		    gettext(
216 		    "%s: -[GIgied] and -[Pp] options are mutually exclusive\n"),
217 		    command);
218 		usage();
219 	}
220 	if (modify && npids != 0) {
221 		(void) fprintf(stderr,
222 		    gettext(
223 		    "%s: -[GIgied] options cannot have a process-id list\n"),
224 		    command);
225 		usage();
226 	}
227 	if ((proc_pattern != NULL || proc_content != CC_CONTENT_INVALID) &&
228 	    npids == 0) {
229 		(void) sprintf(curpid, "%u", (uint_t)getppid());
230 		npids = 1;
231 		pidlist = &curpid_ptr;
232 	}
233 
234 	if (update)
235 		return (do_update());
236 	if (modify)
237 		return (do_modify());
238 	if (npids != 0)
239 		return (do_processes(npids, pidlist));
240 
241 	return (report_settings());
242 }
243 
244 static int
245 report_settings(void)
246 {
247 	int options;
248 	char global_path[PATH_MAX];
249 	char init_path[PATH_MAX];
250 	core_content_t gcontent, icontent;
251 	char content_str[80];
252 
253 	if ((options = core_get_options()) == -1) {
254 		perror("core_get_options()");
255 		return (E_ERROR);
256 	}
257 	if (core_get_global_path(global_path, sizeof (global_path)) != 0) {
258 		perror("core_get_global_path()");
259 		return (E_ERROR);
260 	}
261 	if (core_get_default_path(init_path, sizeof (init_path)) != 0) {
262 		perror("core_get_default_path()");
263 		return (E_ERROR);
264 	}
265 	if (core_get_global_content(&gcontent) != 0) {
266 		perror("core_get_global_content()");
267 		return (E_ERROR);
268 	}
269 	if (core_get_default_content(&icontent) != 0) {
270 		perror("core_get_default_content()");
271 		return (E_ERROR);
272 	}
273 	(void) printf(gettext("     global core file pattern: %s\n"),
274 	    global_path);
275 	(void) proc_content2str(gcontent, content_str, sizeof (content_str));
276 	(void) printf(gettext("     global core file content: %s\n"),
277 	    content_str);
278 	(void) printf(gettext("       init core file pattern: %s\n"),
279 	    init_path);
280 	(void) proc_content2str(icontent, content_str, sizeof (content_str));
281 	(void) printf(gettext("       init core file content: %s\n"),
282 	    content_str);
283 	(void) printf(gettext("            global core dumps: %s\n"),
284 	    (options & CC_GLOBAL_PATH)? "enabled" : "disabled");
285 	(void) printf(gettext("       per-process core dumps: %s\n"),
286 	    (options & CC_PROCESS_PATH)? "enabled" : "disabled");
287 	(void) printf(gettext("      global setid core dumps: %s\n"),
288 	    (options & CC_GLOBAL_SETID)? "enabled" : "disabled");
289 	(void) printf(gettext(" per-process setid core dumps: %s\n"),
290 	    (options & CC_PROCESS_SETID)? "enabled" : "disabled");
291 	(void) printf(gettext("     global core dump logging: %s\n"),
292 	    (options & CC_GLOBAL_LOG)? "enabled" : "disabled");
293 	return (E_SUCCESS);
294 }
295 
296 static int
297 do_processes(int npids, char **pidlist)
298 {
299 	char process_path[PATH_MAX];
300 	core_content_t content;
301 	pid_t pid;
302 	char *next;
303 	int rc = E_SUCCESS;
304 	char content_str[80];
305 
306 	if (proc_pattern == NULL && proc_content == CC_CONTENT_INVALID) {
307 		while (npids-- > 0) {
308 			pid = strtol(*pidlist, &next, 10);
309 			if (*next != '\0' || !isdigit(**pidlist)) {
310 				(void) fprintf(stderr,
311 				    gettext("%s: invalid process-id\n"),
312 				    *pidlist);
313 				rc = E_USAGE;
314 			} else if (core_get_process_path(process_path,
315 			    sizeof (process_path), pid) != 0 ||
316 			    core_get_process_content(&content, pid) != 0) {
317 				perror(*pidlist);
318 				rc = E_USAGE;
319 			} else {
320 				(void) proc_content2str(content, content_str,
321 				    sizeof (content_str));
322 				(void) printf(gettext("%s:\t%s\t%s\n"),
323 				    *pidlist, process_path, content_str);
324 			}
325 			pidlist++;
326 		}
327 	} else {
328 		while (npids-- > 0) {
329 			pid = strtol(*pidlist, &next, 10);
330 			if (*next != '\0') {
331 				(void) fprintf(stderr,
332 				    gettext("%s: invalid process-id\n"),
333 				    *pidlist);
334 				rc = E_USAGE;
335 			} else {
336 				if (proc_pattern != NULL &&
337 				    core_set_process_path(proc_pattern,
338 				    proc_size, pid) != 0) {
339 					perror(*pidlist);
340 					rc = E_USAGE;
341 				}
342 
343 				if (proc_content != CC_CONTENT_INVALID &&
344 				    core_set_process_content(
345 				    &proc_content, pid) != 0) {
346 					perror(*pidlist);
347 					rc = E_USAGE;
348 				}
349 			}
350 			pidlist++;
351 		}
352 	}
353 
354 	return (rc);
355 }
356 
357 static int
358 do_modify(void)
359 {
360 	int options;
361 
362 	if ((options = core_get_options()) == -1) {
363 		perror("core_get_options()");
364 		return (E_ERROR);
365 	}
366 	options |= enable;
367 	options &= ~disable;
368 	if (core_set_options(options) != 0) {
369 		if (errno == EPERM)
370 			return (perm());
371 		perror("core_set_options()");
372 		return (E_ERROR);
373 	}
374 	if (glob_pattern != NULL &&
375 	    core_set_global_path(glob_pattern, glob_size) != 0) {
376 		if (errno == EPERM)
377 			return (perm());
378 		perror("core_set_global_path()");
379 		return (E_ERROR);
380 	}
381 	if (glob_content != CC_CONTENT_INVALID &&
382 	    core_set_global_content(&glob_content) != 0) {
383 		if (errno == EPERM)
384 			return (perm());
385 		perror("core_set_global_content()");
386 		return (E_ERROR);
387 	}
388 	if (init_pattern != NULL &&
389 	    core_set_default_path(init_pattern, init_size) != 0) {
390 		if (errno == EPERM)
391 			return (perm());
392 		perror("core_set_default_path()");
393 		return (E_ERROR);
394 	}
395 	if (init_content != CC_CONTENT_INVALID &&
396 	    core_set_default_content(&init_content) != 0) {
397 		if (errno == EPERM)
398 			return (perm());
399 		perror("core_set_default_content()");
400 		return (E_ERROR);
401 	}
402 	return (write_config(0));
403 }
404 
405 /*
406  * BUFSIZE must be large enough to contain the longest path plus some more.
407  */
408 #define	BUFSIZE	(PATH_MAX + 80)
409 
410 static int
411 yes(char *name, char *value, int line)
412 {
413 	if (strcmp(value, "yes") == 0)
414 		return (1);
415 	if (strcmp(value, "no") == 0)
416 		return (0);
417 	(void) fprintf(stderr,
418 		gettext(
419 		"\"%s\", line %d: warning: value must be yes or no: %s=%s\n"),
420 		PATH_CONFIG, line, name, value);
421 	return (0);
422 }
423 
424 static int
425 do_update(void)
426 {
427 	FILE *fp;
428 	int line;
429 	int options;
430 	char gpattern[PATH_MAX];
431 	char ipattern[PATH_MAX];
432 	core_content_t gcontent, icontent;
433 	char buf[BUFSIZE];
434 	char name[BUFSIZE], value[BUFSIZE];
435 	int n;
436 	int len;
437 
438 	/* defaults */
439 	options = CC_PROCESS_PATH;
440 	gpattern[0] = '\0';
441 	(void) strcpy(ipattern, "core");
442 	gcontent = icontent = CC_CONTENT_DEFAULT;
443 
444 	if ((fp = fopen(PATH_CONFIG, "r")) == NULL) {
445 		/*
446 		 * No config file, just accept the current settings.
447 		 */
448 		return (write_config(1));
449 	}
450 
451 	for (line = 1; fgets(buf, sizeof (buf), fp) != NULL; line++) {
452 		/*
453 		 * Skip comment lines and empty lines.
454 		 */
455 		if (buf[0] == '#' || buf[0] == '\n')
456 			continue;
457 		/*
458 		 * Look for "name=value", with optional whitespace on either
459 		 * side, terminated by a newline, and consuming the whole line.
460 		 */
461 		/* LINTED - unbounded string specifier */
462 		n = sscanf(buf, " %[^=]=%s \n%n", name, value, &len);
463 		if (n >= 1 && name[0] != '\0' &&
464 		    (n == 1 || len == strlen(buf))) {
465 			if (n == 1)
466 				value[0] = '\0';
467 			if (strcmp(name, "COREADM_GLOB_PATTERN") == 0) {
468 				(void) strcpy(gpattern, value);
469 				continue;
470 			}
471 			if (strcmp(name, "COREADM_GLOB_CONTENT") == 0) {
472 				(void) proc_str2content(value, &gcontent);
473 				continue;
474 			}
475 			if (strcmp(name, "COREADM_INIT_PATTERN") == 0) {
476 				(void) strcpy(ipattern, value);
477 				continue;
478 			}
479 			if (strcmp(name, "COREADM_INIT_CONTENT") == 0) {
480 				(void) proc_str2content(value, &icontent);
481 				continue;
482 			}
483 			if (strcmp(name, "COREADM_GLOB_ENABLED") == 0) {
484 				if (yes(name, value, line))
485 					options |= CC_GLOBAL_PATH;
486 				continue;
487 			}
488 			if (strcmp(name, "COREADM_PROC_ENABLED") == 0) {
489 				if (yes(name, value, line))
490 					options |= CC_PROCESS_PATH;
491 				else
492 					options &= ~CC_PROCESS_PATH;
493 				continue;
494 			}
495 			if (strcmp(name, "COREADM_GLOB_SETID_ENABLED") == 0) {
496 				if (yes(name, value, line))
497 					options |= CC_GLOBAL_SETID;
498 				continue;
499 			}
500 			if (strcmp(name, "COREADM_PROC_SETID_ENABLED") == 0) {
501 				if (yes(name, value, line))
502 					options |= CC_PROCESS_SETID;
503 				continue;
504 			}
505 			if (strcmp(name, "COREADM_GLOB_LOG_ENABLED") == 0) {
506 				if (yes(name, value, line))
507 					options |= CC_GLOBAL_LOG;
508 				continue;
509 			}
510 			(void) fprintf(stderr,
511 				gettext(
512 			"\"%s\", line %d: warning: invalid token: %s\n"),
513 				PATH_CONFIG, line, name);
514 		} else {
515 			(void) fprintf(stderr,
516 				gettext("\"%s\", line %d: syntax error\n"),
517 				PATH_CONFIG, line);
518 		}
519 	}
520 	(void) fclose(fp);
521 	if (core_set_options(options) != 0) {
522 		if (errno == EPERM)
523 			return (perm());
524 		perror("core_set_options()");
525 		return (E_ERROR);
526 	}
527 	if (core_set_global_path(gpattern, strlen(gpattern) + 1) != 0) {
528 		if (errno == EPERM)
529 			return (perm());
530 		perror("core_set_global_path()");
531 		return (E_ERROR);
532 	}
533 	if (core_set_default_path(ipattern, strlen(ipattern) + 1) != 0) {
534 		if (errno == EPERM)
535 			return (perm());
536 		perror("core_set_default_path()");
537 		return (E_ERROR);
538 	}
539 	if (core_set_global_content(&gcontent) != 0) {
540 		if (errno == EPERM)
541 			return (perm());
542 		perror("core_set_global_content()");
543 		return (E_ERROR);
544 	}
545 	if (core_set_default_content(&icontent) != 0) {
546 		if (errno == EPERM)
547 			return (perm());
548 		perror("core_set_default_content()");
549 		return (E_ERROR);
550 	}
551 	return (write_config(1));
552 }
553 
554 static int
555 write_config(int justtry)
556 {
557 	int fd;
558 	FILE *fp;
559 	int options;
560 	char global_path[PATH_MAX];
561 	char init_path[PATH_MAX];
562 	core_content_t gcontent, icontent;
563 	char content_str[PRCONTENTBUFSZ];
564 
565 	if ((fd = open(PATH_CONFIG, O_WRONLY | O_CREAT | O_TRUNC,
566 	    CF_PERM)) == -1) {
567 		/*
568 		 * If we're updating the kernel settings from the contents
569 		 * of the config file, it's not essential that we rewrite
570 		 * that file.
571 		 */
572 		if (justtry)
573 			return (E_SUCCESS);
574 
575 		if (errno == EACCES) {
576 			(void) fprintf(stderr, gettext("%s: insufficient "
577 			    "privileges to update %s\n"), command, PATH_CONFIG);
578 			return (E_SUCCESS);
579 		}
580 
581 		(void) fprintf(stderr, gettext("failed to open %s: %s\n"),
582 		    PATH_CONFIG, strerror(errno));
583 		return (E_ERROR);
584 	}
585 	if ((options = core_get_options()) == -1) {
586 		perror("core_get_options()");
587 		goto err;
588 	}
589 	if (core_get_global_path(global_path, sizeof (global_path)) != 0) {
590 		perror("core_get_global_path()");
591 		goto err;
592 	}
593 	if (core_get_default_path(init_path, sizeof (init_path)) != 0) {
594 		perror("core_get_default_path()");
595 		goto err;
596 	}
597 	if (core_get_global_content(&gcontent) != 0) {
598 		perror("core_get_global_content()");
599 		goto err;
600 	}
601 	if (core_get_default_content(&icontent) != 0) {
602 		perror("core_get_default_content()");
603 		goto err;
604 	}
605 	if ((fp = fdopen(fd, "w")) == NULL) {
606 		(void) fprintf(stderr,
607 		    gettext("failed to open stream for %s: %s\n"),
608 		    PATH_CONFIG, strerror(errno));
609 		goto err;
610 	}
611 	(void) fputs(
612 		"#\n"
613 		"# coreadm.conf\n"
614 		"#\n"
615 		"# Parameters for system core file configuration.\n"
616 		"# Do NOT edit this file by hand -- use coreadm(1) instead.\n"
617 		"#\n",
618 		fp);
619 
620 	(void) fprintf(fp, "COREADM_GLOB_PATTERN=%s\n", global_path);
621 	(void) proc_content2str(gcontent, content_str, sizeof (content_str));
622 	(void) fprintf(fp, "COREADM_GLOB_CONTENT=%s\n", content_str);
623 	(void) fprintf(fp, "COREADM_INIT_PATTERN=%s\n", init_path);
624 	(void) proc_content2str(icontent, content_str, sizeof (content_str));
625 	(void) fprintf(fp, "COREADM_INIT_CONTENT=%s\n", content_str);
626 
627 	(void) fprintf(fp, "COREADM_GLOB_ENABLED=%s\n",
628 		(options & CC_GLOBAL_PATH)? "yes" : "no");
629 	(void) fprintf(fp, "COREADM_PROC_ENABLED=%s\n",
630 		(options & CC_PROCESS_PATH)? "yes" : "no");
631 	(void) fprintf(fp, "COREADM_GLOB_SETID_ENABLED=%s\n",
632 		(options & CC_GLOBAL_SETID)? "yes" : "no");
633 	(void) fprintf(fp, "COREADM_PROC_SETID_ENABLED=%s\n",
634 		(options & CC_PROCESS_SETID)? "yes" : "no");
635 	(void) fprintf(fp, "COREADM_GLOB_LOG_ENABLED=%s\n",
636 		(options & CC_GLOBAL_LOG)? "yes" : "no");
637 
638 	(void) fflush(fp);
639 	(void) fsync(fd);
640 	(void) fchmod(fd, CF_PERM);
641 	(void) fchown(fd, CF_OWNER, CF_GROUP);
642 	(void) fclose(fp);
643 
644 	return (0);
645 
646 err:
647 	(void) close(fd);
648 	return (E_ERROR);
649 }
650