Home | History | Annotate | Download | only in coreadm
      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