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 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <stdio.h>
27#include <fcntl.h>
28#include <ctype.h>
29#include <string.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <errno.h>
33#include <limits.h>
34#include <libintl.h>
35#include <locale.h>
36#include <sys/stat.h>
37#include <sys/corectl.h>
38#include <libproc.h>
39#include <libscf.h>
40#include <libscf_priv.h>
41#include <assert.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
47static	const	char	PATH_CONFIG[] = "/etc/coreadm.conf";
48static	const	char	PATH_CONFIG_OLD[] = "/etc/coreadm.conf.old";
49
50#define	COREADM_INST_NAME	"system/coreadm:default"
51#define	COREADM_INST_FMRI	\
52    SCF_FMRI_SVC_PREFIX SCF_FMRI_SERVICE_PREFIX COREADM_INST_NAME
53
54#define	CONFIG_PARAMS		"config_params"
55#define	GLOBAL_ENABLED		"global_enabled"
56#define	PROCESS_ENABLED		"process_enabled"
57#define	GLOBAL_SETID_ENABLED	"global_setid_enabled"
58#define	PROCESS_SETID_ENABLED	"process_setid_enabled"
59#define	GLOBAL_LOG_ENABLED	"global_log_enabled"
60#define	GLOBAL_PATTERN		"global_pattern"
61#define	GLOBAL_CONTENT		"global_content"
62#define	INIT_PATTERN		"init_pattern"
63#define	INIT_CONTENT		"init_content"
64
65static	char		*command;
66static	uint64_t	options;
67static	int		alloptions;
68static	char		*glob_pattern;
69static	char		gpattern[PATH_MAX];
70static	core_content_t	glob_content = CC_CONTENT_INVALID;
71static	char		*init_pattern;
72static	char		ipattern[PATH_MAX];
73static	core_content_t	init_content = CC_CONTENT_INVALID;
74static	char		*proc_pattern;
75static	size_t		proc_size;
76static	core_content_t	proc_content = CC_CONTENT_INVALID;
77
78static	int		report_settings(void);
79static	int		do_processes(int, char **);
80static	int		do_modify(boolean_t);
81static	int		do_update(void);
82static	int		do_legacy(void);
83
84static scf_propvec_t prop_gpattern = { GLOBAL_PATTERN, NULL, SCF_TYPE_ASTRING };
85static scf_propvec_t prop_gcontent = { GLOBAL_CONTENT, NULL, SCF_TYPE_ASTRING };
86static scf_propvec_t prop_ipattern = { INIT_PATTERN, NULL, SCF_TYPE_ASTRING };
87static scf_propvec_t prop_icontent = { INIT_CONTENT, NULL, SCF_TYPE_ASTRING };
88static scf_propvec_t prop_option[] = {
89    { GLOBAL_ENABLED, NULL, SCF_TYPE_BOOLEAN, NULL, CC_GLOBAL_PATH },
90    { PROCESS_ENABLED, NULL, SCF_TYPE_BOOLEAN, NULL, CC_PROCESS_PATH },
91    { GLOBAL_SETID_ENABLED, NULL, SCF_TYPE_BOOLEAN, NULL, CC_GLOBAL_SETID },
92    { PROCESS_SETID_ENABLED, NULL, SCF_TYPE_BOOLEAN, NULL, CC_PROCESS_SETID },
93    { GLOBAL_LOG_ENABLED, NULL, SCF_TYPE_BOOLEAN, NULL, CC_GLOBAL_LOG },
94    { NULL }
95};
96#define	MAX_PROPS	(4 + (sizeof (prop_option) / sizeof (scf_propvec_t)))
97
98static void
99usage(void)
100{
101	(void) fprintf(stderr, gettext(
102"usage:\n"));
103	(void) fprintf(stderr, gettext(
104"    %s [ -g pattern ] [ -i pattern ] [ -G content ] [ -I content ]\n"),
105	    command);
106	(void) fprintf(stderr, gettext(
107"            [ -e {global | process | global-setid | proc-setid | log} ]\n"));
108	(void) fprintf(stderr, gettext(
109"            [ -d {global | process | global-setid | proc-setid | log} ]\n"));
110	(void) fprintf(stderr, gettext(
111"    %s [ -p pattern ] [ -P content ] [ pid ... ]\n"), command);
112	exit(E_USAGE);
113}
114
115static int
116perm(void)
117{
118	(void) fprintf(stderr, gettext("%s: insufficient privileges to "
119	    "exercise the -[GIgied] options\n"), command);
120	return (E_USAGE);
121}
122
123static int
124parse_content(char *arg, core_content_t *content)
125{
126	if (proc_str2content(arg, content) == 0)
127		return (0);
128	(void) fprintf(stderr, gettext("%s: invalid content string '%s'\n"),
129	    command, arg);
130	return (1);
131}
132
133int
134main(int argc, char **argv)
135{
136	int flag;
137	int opt;
138	int modify;
139	int update = 0;
140	int legacy_update = 0;
141	int error = 0;
142	int npids;
143	char **pidlist;
144
145	char curpid[11];
146	char *curpid_ptr = &curpid[0];
147
148	(void) setlocale(LC_ALL, "");
149	(void) textdomain(TEXT_DOMAIN);
150
151	/* command name (e.g., "coreadm") */
152	if ((command = strrchr(argv[0], '/')) != NULL)
153		command++;
154	else
155		command = argv[0];
156
157	while ((opt = getopt(argc, argv, "g:G:i:I:p:P:e:d:uU?")) != EOF) {
158		switch (opt) {
159		case 'g':
160			glob_pattern = optarg;
161			break;
162		case 'i':
163			init_pattern = optarg;
164			break;
165		case 'p':
166			proc_pattern = optarg;
167			proc_size = strlen(proc_pattern) + 1;
168			break;
169		case 'G':
170			error |= parse_content(optarg, &glob_content);
171			break;
172		case 'I':
173			error |= parse_content(optarg, &init_content);
174			break;
175		case 'P':
176			error |= parse_content(optarg, &proc_content);
177			break;
178		case 'e':
179		case 'd':
180			if (strcmp(optarg, "global") == 0)
181				flag = CC_GLOBAL_PATH;
182			else if (strcmp(optarg, "process") == 0)
183				flag = CC_PROCESS_PATH;
184			else if (strcmp(optarg, "global-setid") == 0)
185				flag = CC_GLOBAL_SETID;
186			else if (strcmp(optarg, "proc-setid") == 0)
187				flag = CC_PROCESS_SETID;
188			else if (strcmp(optarg, "log") == 0)
189				flag = CC_GLOBAL_LOG;
190			else {
191				flag = 0;
192				error = 1;
193			}
194			if (opt == 'e')
195				options |= flag;
196			else
197				options &= ~flag;
198			alloptions |= flag;
199			break;
200		case 'U':
201			update = 1;
202			break;
203		case 'u':
204			legacy_update = 1;
205			break;
206		case '?':
207		default:
208			error = 1;
209			break;
210		}
211	}
212
213	npids = argc - optind;
214	pidlist = argv + optind;
215
216	if (error)
217		usage();
218
219	/*
220	 * If 'modify' is true, we must modify the system settings
221	 * and update the configuration file with the new parameters.
222	 */
223	modify = glob_pattern != NULL || glob_content != CC_CONTENT_INVALID ||
224	    init_pattern != NULL || init_content != CC_CONTENT_INVALID ||
225	    alloptions != 0;
226
227	if ((update || legacy_update) && (modify || proc_pattern != NULL ||
228	    proc_content != CC_CONTENT_INVALID || npids != 0)) {
229		(void) fprintf(stderr,
230		    gettext("%s: the -u option must stand alone\n"), command);
231		usage();
232	}
233	if (modify &&
234	    (proc_pattern != NULL || proc_content != CC_CONTENT_INVALID)) {
235		(void) fprintf(stderr, gettext(
236		    "%s: -[GIgied] and -[Pp] options are mutually exclusive\n"),
237		    command);
238		usage();
239	}
240	if (modify && npids != 0) {
241		(void) fprintf(stderr, gettext(
242		    "%s: -[GIgied] options cannot have a process-id list\n"),
243		    command);
244		usage();
245	}
246	if ((proc_pattern != NULL || proc_content != CC_CONTENT_INVALID) &&
247	    npids == 0) {
248		(void) sprintf(curpid, "%u", (uint_t)getppid());
249		npids = 1;
250		pidlist = &curpid_ptr;
251	}
252
253	if (legacy_update)
254		return (do_legacy());
255	if (update)
256		return (do_update());
257	if (modify)
258		return (do_modify(B_FALSE));
259	if (npids != 0)
260		return (do_processes(npids, pidlist));
261
262	return (report_settings());
263}
264
265static int
266report_settings(void)
267{
268	char content_str[PRCONTENTBUFSZ];
269
270	if ((options = core_get_options()) == -1) {
271		perror("core_get_options()");
272		return (E_ERROR);
273	}
274	if (core_get_global_path(gpattern, sizeof (gpattern)) != 0) {
275		perror("core_get_global_path()");
276		return (E_ERROR);
277	}
278	if (core_get_default_path(ipattern, sizeof (ipattern)) != 0) {
279		perror("core_get_default_path()");
280		return (E_ERROR);
281	}
282	if (core_get_global_content(&glob_content) != 0) {
283		perror("core_get_global_content()");
284		return (E_ERROR);
285	}
286	if (core_get_default_content(&init_content) != 0) {
287		perror("core_get_default_content()");
288		return (E_ERROR);
289	}
290
291	(void) printf(gettext("     global core file pattern: %s\n"),
292	    gpattern);
293	(void) proc_content2str(glob_content, content_str,
294	    sizeof (content_str));
295	(void) printf(gettext("     global core file content: %s\n"),
296	    content_str);
297	(void) printf(gettext("       init core file pattern: %s\n"),
298	    ipattern);
299	(void) proc_content2str(init_content, content_str,
300	    sizeof (content_str));
301	(void) printf(gettext("       init core file content: %s\n"),
302	    content_str);
303	(void) printf(gettext("            global core dumps: %s\n"),
304	    (options & CC_GLOBAL_PATH)? "enabled" : "disabled");
305	(void) printf(gettext("       per-process core dumps: %s\n"),
306	    (options & CC_PROCESS_PATH)? "enabled" : "disabled");
307	(void) printf(gettext("      global setid core dumps: %s\n"),
308	    (options & CC_GLOBAL_SETID)? "enabled" : "disabled");
309	(void) printf(gettext(" per-process setid core dumps: %s\n"),
310	    (options & CC_PROCESS_SETID)? "enabled" : "disabled");
311	(void) printf(gettext("     global core dump logging: %s\n"),
312	    (options & CC_GLOBAL_LOG)? "enabled" : "disabled");
313	return (E_SUCCESS);
314}
315
316static int
317do_processes(int npids, char **pidlist)
318{
319	char process_path[PATH_MAX];
320	core_content_t content;
321	pid_t pid;
322	char *next;
323	int rc = E_SUCCESS;
324	char content_str[PRCONTENTBUFSZ];
325
326	if (proc_pattern == NULL && proc_content == CC_CONTENT_INVALID) {
327		while (npids-- > 0) {
328			pid = strtol(*pidlist, &next, 10);
329			if (*next != '\0' || !isdigit(**pidlist)) {
330				(void) fprintf(stderr,
331				    gettext("%s: invalid process-id\n"),
332				    *pidlist);
333				rc = E_USAGE;
334			} else if (core_get_process_path(process_path,
335			    sizeof (process_path), pid) != 0 ||
336			    core_get_process_content(&content, pid) != 0) {
337				perror(*pidlist);
338				rc = E_USAGE;
339			} else {
340				(void) proc_content2str(content, content_str,
341				    sizeof (content_str));
342				(void) printf(gettext("%s:\t%s\t%s\n"),
343				    *pidlist, process_path, content_str);
344			}
345			pidlist++;
346		}
347	} else {
348		while (npids-- > 0) {
349			pid = strtol(*pidlist, &next, 10);
350			if (*next != '\0') {
351				(void) fprintf(stderr,
352				    gettext("%s: invalid process-id\n"),
353				    *pidlist);
354				rc = E_USAGE;
355			} else {
356				if (proc_pattern != NULL &&
357				    core_set_process_path(proc_pattern,
358				    proc_size, pid) != 0) {
359					perror(*pidlist);
360					rc = E_USAGE;
361				}
362
363				if (proc_content != CC_CONTENT_INVALID &&
364				    core_set_process_content(
365				    &proc_content, pid) != 0) {
366					perror(*pidlist);
367					rc = E_USAGE;
368				}
369			}
370			pidlist++;
371		}
372	}
373
374	return (rc);
375}
376
377static void
378addprop(scf_propvec_t *props, int size, int count, scf_propvec_t *pv, void *ptr)
379{
380	assert(count + 1 < size);
381	props[count] = *pv;
382	props[count].pv_ptr = ptr;
383}
384
385static boolean_t
386is_online(const char *fmri)
387{
388	char *state = smf_get_state(fmri);
389	boolean_t result = state != NULL &&
390	    strcmp(state, SCF_STATE_STRING_ONLINE) == 0;
391
392	free(state);
393	return (result);
394}
395
396/*
397 * The user has specified the -g, -G, -i, -I, -d, or -e options to
398 * modify the given configuration parameter. Perform the modification
399 * in the smf repository and then perform a smf_refresh_instance which
400 * will cause a coreadm -u to occur which will transfer ALL coreadm
401 * configuration information from the repository to the kernel.
402 */
403static int
404do_modify(boolean_t method)
405{
406	char gcontentstr[PRCONTENTBUFSZ];
407	char icontentstr[PRCONTENTBUFSZ];
408	scf_propvec_t *prop;
409	scf_propvec_t properties[MAX_PROPS + 1];
410	int count = 0;
411
412	if (!method && !is_online(COREADM_INST_FMRI)) {
413		(void) fprintf(stderr,
414		    gettext("%s: coreadm service not online\n"), command);
415		return (E_ERROR);
416	}
417
418	if (glob_pattern != NULL)
419		addprop(properties, MAX_PROPS, count++, &prop_gpattern,
420		    glob_pattern);
421
422	if (glob_content != CC_CONTENT_INVALID) {
423		(void) proc_content2str(glob_content, gcontentstr,
424		    sizeof (gcontentstr));
425		addprop(properties, MAX_PROPS, count++, &prop_gcontent,
426		    gcontentstr);
427	}
428
429	if (init_pattern != NULL)
430		addprop(properties, MAX_PROPS, count++, &prop_ipattern,
431		    init_pattern);
432
433	if (init_content != CC_CONTENT_INVALID) {
434		(void) proc_content2str(init_content, icontentstr,
435		    sizeof (icontentstr));
436		addprop(properties, MAX_PROPS, count++, &prop_icontent,
437		    icontentstr);
438	}
439
440	for (prop = prop_option; prop->pv_prop != NULL; prop++)
441		if ((alloptions & prop->pv_aux) != 0)
442			addprop(properties, MAX_PROPS, count++, prop, &options);
443
444	properties[count].pv_prop = NULL;
445
446	prop = NULL;
447	if (scf_write_propvec(COREADM_INST_FMRI, CONFIG_PARAMS, properties,
448	    &prop) == SCF_FAILED) {
449		if (prop != NULL) {
450			(void) fprintf(stderr, gettext(
451			    "%s: Unable to write property '%s': %s"), command,
452			    prop->pv_prop, scf_strerror(scf_error()));
453		} else {
454			(void) fprintf(stderr, gettext(
455			    "%s: Unable to write configuration: %s\n"),
456			    command, scf_strerror(scf_error()));
457		}
458		return (E_ERROR);
459	}
460
461	if (smf_refresh_instance(COREADM_INST_FMRI) != 0) {
462		(void) fprintf(stderr,
463		    gettext("%s: Unable to refresh %s: %s\n"
464		    "Configuration stored but not made active.\n"),
465		    command, COREADM_INST_FMRI, scf_strerror(scf_error()));
466		return (E_ERROR);
467	}
468
469	return (E_SUCCESS);
470}
471
472static const char *
473write_kernel(void)
474{
475	if (core_set_global_path(glob_pattern, strlen(glob_pattern) + 1) != 0)
476		return ("core_set_global_path()");
477
478	if (core_set_global_content(&glob_content) != 0)
479		return ("core_set_global_content()");
480
481	if (core_set_default_path(init_pattern, strlen(init_pattern) + 1) != 0)
482		return ("core_set_default_path()");
483
484	if (core_set_default_content(&init_content) != 0)
485		return ("core_set_init_content()");
486
487	if (core_set_options((int)options) != 0)
488		return ("core_set_options()");
489
490	return (NULL);
491}
492
493/*
494 * BUFSIZE must be large enough to contain the longest path plus some more.
495 */
496#define	BUFSIZE	(PATH_MAX + 80)
497
498static int
499yes(char *name, char *value, int line)
500{
501	if (strcmp(value, "yes") == 0)
502		return (1);
503	if (strcmp(value, "no") == 0)
504		return (0);
505	(void) fprintf(stderr, gettext(
506	    "\"%s\", line %d: warning: value must be yes or no: %s=%s\n"),
507	    PATH_CONFIG, line, name, value);
508	return (0);
509}
510
511static int
512read_legacy(void)
513{
514	FILE *fp;
515	int line;
516	char buf[BUFSIZE];
517	char name[BUFSIZE], value[BUFSIZE];
518	int n, len;
519
520	/* defaults */
521	alloptions = CC_OPTIONS;
522	options = CC_PROCESS_PATH;
523	gpattern[0] = '\0';
524	(void) strcpy(ipattern, "core");
525	glob_content = init_content = CC_CONTENT_DEFAULT;
526
527	glob_pattern = gpattern;
528	init_pattern = ipattern;
529
530	if ((fp = fopen(PATH_CONFIG, "r")) == NULL)
531		return (0);
532
533	for (line = 1; fgets(buf, sizeof (buf), fp) != NULL; line++) {
534		/*
535		 * Skip comment lines and empty lines.
536		 */
537		if (buf[0] == '#' || buf[0] == '\n')
538			continue;
539		/*
540		 * Look for "name=value", with optional whitespace on either
541		 * side, terminated by a newline, and consuming the whole line.
542		 */
543		/* LINTED - unbounded string specifier */
544		n = sscanf(buf, " %[^=]=%s \n%n", name, value, &len);
545		if (n >= 1 && name[0] != '\0' &&
546		    (n == 1 || len == strlen(buf))) {
547			if (n == 1)
548				value[0] = '\0';
549			if (strcmp(name, "COREADM_GLOB_PATTERN") == 0) {
550				(void) strcpy(gpattern, value);
551				continue;
552			}
553			if (strcmp(name, "COREADM_GLOB_CONTENT") == 0) {
554				(void) proc_str2content(value, &glob_content);
555				continue;
556			}
557			if (strcmp(name, "COREADM_INIT_PATTERN") == 0) {
558				(void) strcpy(ipattern, value);
559				continue;
560			}
561			if (strcmp(name, "COREADM_INIT_CONTENT") == 0) {
562				(void) proc_str2content(value, &init_content);
563				continue;
564			}
565			if (strcmp(name, "COREADM_GLOB_ENABLED") == 0) {
566				if (yes(name, value, line))
567					options |= CC_GLOBAL_PATH;
568				continue;
569			}
570			if (strcmp(name, "COREADM_PROC_ENABLED") == 0) {
571				if (yes(name, value, line))
572					options |= CC_PROCESS_PATH;
573				else
574					options &= ~CC_PROCESS_PATH;
575				continue;
576			}
577			if (strcmp(name, "COREADM_GLOB_SETID_ENABLED") == 0) {
578				if (yes(name, value, line))
579					options |= CC_GLOBAL_SETID;
580				continue;
581			}
582			if (strcmp(name, "COREADM_PROC_SETID_ENABLED") == 0) {
583				if (yes(name, value, line))
584					options |= CC_PROCESS_SETID;
585				continue;
586			}
587			if (strcmp(name, "COREADM_GLOB_LOG_ENABLED") == 0) {
588				if (yes(name, value, line))
589					options |= CC_GLOBAL_LOG;
590				continue;
591			}
592			(void) fprintf(stderr, gettext(
593			    "\"%s\", line %d: warning: invalid token: %s\n"),
594			    PATH_CONFIG, line, name);
595		} else {
596			(void) fprintf(stderr,
597			    gettext("\"%s\", line %d: syntax error\n"),
598			    PATH_CONFIG, line);
599		}
600	}
601	(void) fclose(fp);
602
603	return (1);
604}
605
606/*
607 * Loads and applies the coreadm configuration stored in the default
608 * coreadm instance.  As this option is (only) used from within an SMF
609 * service method, this function must return an SMF_EXIT_* exit status
610 * to its caller.
611 */
612static int
613do_update(void)
614{
615	char		*gcstr, *icstr;
616	scf_propvec_t	properties[MAX_PROPS + 1];
617	scf_propvec_t	*prop;
618	int		count = 0;
619	const char	*errstr;
620
621	if (read_legacy()) {
622		if ((errstr = write_kernel()) != NULL)
623			goto error;
624
625		if (do_modify(B_TRUE) != 0 ||
626		    rename(PATH_CONFIG, PATH_CONFIG_OLD) != 0) {
627			(void) fprintf(stderr, gettext(
628			    "%s: failed to import legacy configuration.\n"),
629			    command);
630			return (SMF_EXIT_ERR_FATAL);
631		}
632		return (SMF_EXIT_OK);
633	}
634
635	addprop(properties, MAX_PROPS, count++, &prop_gpattern, &glob_pattern);
636	addprop(properties, MAX_PROPS, count++, &prop_gcontent, &gcstr);
637	addprop(properties, MAX_PROPS, count++, &prop_ipattern, &init_pattern);
638	addprop(properties, MAX_PROPS, count++, &prop_icontent, &icstr);
639	for (prop = prop_option; prop->pv_prop != NULL; prop++)
640		addprop(properties, MAX_PROPS, count++, prop, &options);
641	properties[count].pv_prop = NULL;
642
643	alloptions = CC_OPTIONS;
644	if (scf_read_propvec(COREADM_INST_FMRI, CONFIG_PARAMS, B_TRUE,
645	    properties, &prop) == SCF_FAILED) {
646		if (prop != NULL) {
647			(void) fprintf(stderr, gettext(
648			    "%s: configuration property '%s' not found.\n"),
649			    command, prop->pv_prop);
650		} else {
651			(void) fprintf(stderr, gettext(
652			    "%s: unable to read configuration: %s\n"),
653			    command, scf_strerror(scf_error()));
654		}
655		return (SMF_EXIT_ERR_FATAL);
656	}
657
658	(void) proc_str2content(gcstr, &glob_content);
659	(void) proc_str2content(icstr, &init_content);
660
661	errstr = write_kernel();
662	scf_clean_propvec(properties);
663	if (errstr == NULL)
664		return (SMF_EXIT_OK);
665
666error:
667	if (errno == EPERM) {
668		(void) perm();
669		return (SMF_EXIT_ERR_PERM);
670	}
671	perror(errstr);
672	return (SMF_EXIT_ERR_FATAL);
673}
674
675static int do_legacy()
676{
677	const char *errstr;
678
679	if (read_legacy() && (errstr = write_kernel()) != NULL) {
680		if (errno == EPERM)
681			return (perm());
682		perror(errstr);
683		return (E_ERROR);
684	}
685
686	return (E_SUCCESS);
687}
688