Home | History | Annotate | Download | only in power
      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 2009 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 /*
     27  * "pmconfig" performs a mixture of Energy-Star configuration tasks
     28  * for both CheckPoint-Resume and Power-Management services.
     29  * Tasks include parsing a config file (usually "/etc/power.conf"),
     30  * updating CPR and PM config files, and setting various PM options
     31  * via ioctl requests.  From the mix, pmconfig should have a more
     32  * generalized name similar to "estarconfig".
     33  *
     34  * OPTIONS:
     35  * "-r"		reset CPR and PM options to default and exit.
     36  * "-f file"	specify an alternate config file; this is a
     37  *		private/non-advertised option used by "dtpower".
     38  */
     39 
     40 #include "pmconfig.h"
     41 #include <sys/wait.h>
     42 #include <signal.h>
     43 #include <stdarg.h>
     44 #include <locale.h>
     45 #include "powerd.h"
     46 
     47 
     48 #define	MCCPY_FIELD(dst, src, field) \
     49 	(void) memccpy(&dst.field, &src.field, 0, sizeof (dst.field) - 1)
     50 
     51 
     52 static char conf_header[] =
     53 "#\n"
     54 "# Copyright 1996-2002 Sun Microsystems, Inc.  All rights reserved.\n"
     55 "# Use is subject to license terms.\n"
     56 "#\n"
     57 "#pragma ident	\"@(#)power.conf	2.1	02/03/04 SMI\"\n"
     58 "#\n"
     59 "# Power Management Configuration File\n"
     60 "#\n\n";
     61 
     62 static char *prog;
     63 static char *cpr_conf = CPR_CONFIG;
     64 static char tmp_conf[] = "/etc/.tmp.conf.XXXXXX";
     65 static char orig_conf[] = "/etc/power.conf-Orig";
     66 static char default_conf[] = "/etc/power.conf";
     67 static char *power_conf = default_conf;
     68 static pid_t powerd_pid;
     69 static prmup_t *checkup;
     70 static int tmp_fd;
     71 
     72 char estar_vers = ESTAR_VNONE;
     73 int ua_err = 0;
     74 int debug = 0;
     75 
     76 static struct cprconfig disk_cc;
     77 struct cprconfig new_cc;
     78 struct stat def_info;
     79 static int fflag, rflag;
     80 int pm_fd;
     81 uid_t ruid;
     82 int def_src;
     83 /*
     84  * Until we get more graphics driver support, we only enable autopm,
     85  * S3 support and autoS3 by default on X86 systems that are on our whitelist.
     86  */
     87 int whitelist_only = 1;
     88 
     89 int verify = 0;
     90 
     91 
     92 static void
     93 cleanup(void)
     94 {
     95 	free(line_args);
     96 	if (access(tmp_conf, F_OK) == 0)
     97 		(void) unlink(tmp_conf);
     98 }
     99 
    100 
    101 /*
    102  * Multi-purpose message output routine; also exits when
    103  * (status == MEXIT), other status is non-fatal.
    104  * VARARGS2
    105  */
    106 void
    107 mesg(int code, char *fmt, ...)
    108 {
    109 	va_list vargs;
    110 
    111 	/*
    112 	 * debug is checked once here, avoiding N duplicate checks
    113 	 * before each MDEBUG caller and unnecessary text dupduplication.
    114 	 */
    115 	if (debug == 0) {
    116 		/*
    117 		 * If debug is not enabled, skip a debug message;
    118 		 * lead with the program name for an error message,
    119 		 * and follow with a filename and line number if an
    120 		 * error occurs while parsing a conf file.
    121 		 */
    122 		if (code == MDEBUG)
    123 			return;
    124 		(void) fprintf(stderr, "%s: ", prog);
    125 		if (lineno)
    126 			(void) fprintf(stderr,
    127 			    "\"%s\" line %d, ", power_conf, lineno);
    128 	}
    129 
    130 	va_start(vargs, fmt);
    131 	(void) vfprintf(stderr, gettext(fmt), vargs);
    132 	va_end(vargs);
    133 
    134 	if (code == MEXIT) {
    135 		cleanup();
    136 		exit(MEXIT);
    137 	}
    138 }
    139 
    140 
    141 static void
    142 usage(void)
    143 {
    144 	(void) fprintf(stderr, gettext("Usage: %s [-r]\n"), prog);
    145 	exit(1);
    146 }
    147 
    148 
    149 /*
    150  * Lookup estar version, check if uadmin() service is supported,
    151  * and read cpr_config info from disk.
    152  */
    153 static void
    154 get_cpr_info(void)
    155 {
    156 	ssize_t nread;
    157 	char *err_fmt;
    158 	int fd;
    159 
    160 #ifdef sparc
    161 	lookup_estar_vers();
    162 	if (estar_vers == ESTAR_V2)
    163 		new_cc.is_cpr_default = 1;
    164 	else if (estar_vers == ESTAR_V3)
    165 		new_cc.is_autopm_default = 1;
    166 
    167 	if (uadmin(A_FREEZE, AD_CHECK, 0) == 0)
    168 		new_cc.is_cpr_capable = 1;
    169 	else
    170 		ua_err = errno;
    171 
    172 	if ((fd = open("/dev/tod", O_RDONLY)) != -1) {
    173 		new_cc.is_autowakeup_capable = 1;
    174 		(void) close(fd);
    175 	}
    176 #endif /* sparc */
    177 
    178 	/*
    179 	 * Read in the cpr conf file.  If any open or read error occurs,
    180 	 * display an error message only for a non-root user.  The file
    181 	 * may not exist on a newly installed system.
    182 	 */
    183 	err_fmt = "%s %s; please rerun %s as root\n";
    184 	if ((fd = open(cpr_conf, O_RDONLY)) == -1) {
    185 		if (ruid)
    186 			mesg(MEXIT, err_fmt, gettext("cannot open"),
    187 			    cpr_conf, prog);
    188 	} else {
    189 		nread = read(fd, &disk_cc, sizeof (disk_cc));
    190 		(void) close(fd);
    191 		if (nread != (ssize_t)sizeof (disk_cc)) {
    192 			if (ruid)
    193 				mesg(MEXIT, err_fmt, cpr_conf,
    194 				    gettext("file corrupted"), prog);
    195 			else {
    196 				(void) unlink(cpr_conf);
    197 				bzero(&disk_cc, sizeof (disk_cc));
    198 			}
    199 		}
    200 	}
    201 }
    202 
    203 
    204 /*
    205  * Unconfigure and reset PM, device is left open for later use.
    206  */
    207 static void
    208 pm_rem_reset(void)
    209 {
    210 	char *err_fmt = NULL;
    211 
    212 	if ((pm_fd = open("/dev/pm", O_RDWR)) == -1)
    213 		err_fmt = "cannot open \"/dev/pm\": %s\n";
    214 	else if (ioctl(pm_fd, PM_RESET_PM, 0) == -1)
    215 		err_fmt = "cannot reset pm state: %s\n";
    216 	if (err_fmt)
    217 		mesg(MEXIT, err_fmt, strerror(errno));
    218 }
    219 
    220 
    221 static void
    222 get_powerd_pid(void)
    223 {
    224 	char pidstr[16];
    225 	int fd;
    226 
    227 	if ((fd = open(PIDPATH, O_RDONLY)) == -1)
    228 		return;
    229 	bzero(pidstr, sizeof (pidstr));
    230 	if (read(fd, pidstr, sizeof (pidstr)) > 0) {
    231 		powerd_pid = atoi(pidstr);
    232 		mesg(MDEBUG, "got powerd pid %ld\n", powerd_pid);
    233 	}
    234 	(void) close(fd);
    235 }
    236 
    237 
    238 /*
    239  * Write revised cprconfig struct to disk based on perms;
    240  * returns 1 if any error, otherwise 0.
    241  */
    242 static int
    243 update_cprconfig(void)
    244 {
    245 	struct cprconfig *wrt_cc = &new_cc;
    246 	char *err_fmt = NULL;
    247 	int fd;
    248 
    249 	if (rflag) {
    250 		/* For "pmconfig -r" case, copy select cpr-related fields. */
    251 		new_cc.cf_magic = disk_cc.cf_magic;
    252 		new_cc.cf_type = disk_cc.cf_type;
    253 		MCCPY_FIELD(new_cc, disk_cc, cf_path);
    254 		MCCPY_FIELD(new_cc, disk_cc, cf_fs);
    255 		MCCPY_FIELD(new_cc, disk_cc, cf_devfs);
    256 		MCCPY_FIELD(new_cc, disk_cc, cf_dev_prom);
    257 	}
    258 
    259 	if (!pm_status.perm) {
    260 		if (cpr_status.update == NOUP)
    261 			return (1);
    262 		/* save new struct data with old autopm setting */
    263 		MCCPY_FIELD(new_cc, disk_cc, apm_behavior);
    264 	} else if (!cpr_status.perm) {
    265 		if (pm_status.update == NOUP)
    266 			return (1);
    267 		/* save original struct with new autopm setting */
    268 		MCCPY_FIELD(disk_cc, new_cc, apm_behavior);
    269 		wrt_cc = &disk_cc;
    270 	} else if (cpr_status.update == NOUP || pm_status.update == NOUP)
    271 		return (1);
    272 
    273 	if ((fd = open(cpr_conf, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1)
    274 		err_fmt = "cannot open/create \"%s\", %s\n";
    275 	else if (write(fd, wrt_cc, sizeof (*wrt_cc)) != sizeof (*wrt_cc))
    276 		err_fmt = "error writing \"%s\", %s\n";
    277 	if (err_fmt)
    278 		mesg(MERR, err_fmt, cpr_conf, strerror(errno));
    279 	if (fd != -1)
    280 		(void) close(fd);
    281 	return (err_fmt != NULL);
    282 }
    283 
    284 
    285 /*
    286  * Signal old powerd when there's a valid pid, or start a new one;
    287  * returns 1 if any error, otherwise 0.
    288  */
    289 static int
    290 restart_powerd(void)
    291 {
    292 	char *powerd = "/usr/lib/power/powerd";
    293 	int status = 0;
    294 	pid_t pid, wp;
    295 
    296 	if (powerd_pid > 0) {
    297 		if (sigsend(P_PID, powerd_pid, SIGHUP) == 0)
    298 			return (0);
    299 		else if (errno != ESRCH) {
    300 			mesg(MERR, "cannot deliver hangup to powerd\n");
    301 			return (1);
    302 		}
    303 	}
    304 
    305 	if ((pid = fork()) == NOPID)
    306 		wp = -1;
    307 	else if (pid == P_MYPID) {
    308 		(void) setreuid(0, 0);
    309 		(void) setregid(0, 0);
    310 		(void) setgroups(0, NULL);
    311 		if (debug)
    312 			(void) execle(powerd, powerd, "-d", NULL, NULL);
    313 		else
    314 			(void) execle(powerd, powerd, NULL, NULL);
    315 		exit(1);
    316 	} else {
    317 		do {
    318 			wp = waitpid(pid, &status, 0);
    319 		} while (wp == -1 && errno == EINTR);
    320 	}
    321 
    322 	if (wp == -1)
    323 		mesg(MERR, "could not start %s\n", powerd);
    324 	return (wp == -1 || status != 0);
    325 }
    326 
    327 
    328 static void
    329 save_orig(void)
    330 {
    331 	static char *args[] = { "/usr/bin/cp", default_conf, orig_conf, NULL };
    332 	struct stat stbuf;
    333 	int pid;
    334 
    335 	if (stat(orig_conf, &stbuf) == 0 && stbuf.st_size)
    336 		return;
    337 	pid = fork();
    338 	if (pid == NOPID)
    339 		return;
    340 	else if (pid == P_MYPID) {
    341 		(void) execve(args[0], args, NULL);
    342 		exit(1);
    343 	} else
    344 		(void) waitpid(pid, NULL, 0);
    345 }
    346 
    347 
    348 static void
    349 tmp_write(void *buf, size_t len)
    350 {
    351 	if (write(tmp_fd, buf, len) != (ssize_t)len)
    352 		mesg(MEXIT, "error writing tmp file, %s\n", strerror(errno));
    353 }
    354 
    355 
    356 static void
    357 tmp_save_line(char *line, size_t len, cinfo_t *cip)
    358 {
    359 	if (cip && cip->cmt)
    360 		tmp_write(cip->cmt, strlen(cip->cmt));
    361 	tmp_write(line, len);
    362 }
    363 
    364 
    365 /*
    366  * Filter conf lines and write them to the tmp file.
    367  */
    368 static void
    369 filter(char *line, size_t len, cinfo_t *cip)
    370 {
    371 	int selected;
    372 
    373 	/*
    374 	 * Lines from an alt conf file are selected when either:
    375 	 * cip is NULL (keyword not matched, probably an old-style device),
    376 	 * OR: it's both OK to accept the conf line (alt) AND either:
    377 	 * preference is not set (NULL checkup) OR the cpr/pm preference
    378 	 * (checkup) matches conftab status.
    379 	 */
    380 	selected = (cip == NULL || (cip->alt &&
    381 	    (checkup == NULL || checkup == cip->status)));
    382 	mesg(MDEBUG, "filter: set \"%s\", selected %d\n",
    383 	    cip ? cip->status->set : "none", selected);
    384 	if (selected)
    385 		tmp_save_line(line, len, cip);
    386 }
    387 
    388 
    389 /*
    390  * Set checkup for conf line selection and parse a conf file with filtering.
    391  * When pref is NULL, filter selects all conf lines from the new conf file;
    392  * otherwise filter selects only cpr or pm related lines from the new or
    393  * default conf file based on cpr or pm perm.
    394  */
    395 static void
    396 conf_scanner(prmup_t *pref)
    397 {
    398 	mesg(MDEBUG, "\nscanning set is %s\n", pref ? pref->set : "both");
    399 	checkup = pref;
    400 	parse_conf_file((pref == NULL || pref->perm)
    401 	    ? power_conf : default_conf, filter, B_FALSE);
    402 }
    403 
    404 
    405 /*
    406  * Search for any non-alt entries, call the handler routine,
    407  * and write entries to the tmp file.
    408  */
    409 static void
    410 search(char *line, size_t len, cinfo_t *cip)
    411 {
    412 	int skip;
    413 
    414 	skip = (cip == NULL || cip->alt);
    415 	mesg(MDEBUG, "search: %s\n", skip ? "skipped" : "retained");
    416 	if (skip)
    417 		return;
    418 	if (cip->status->perm)
    419 		(void) (*cip->handler)();
    420 	tmp_save_line(line, len, cip);
    421 }
    422 
    423 
    424 /*
    425  * When perm and update status are OK, write a new conf file
    426  * and rename to default_conf with the original attributes;
    427  * returns 1 if any error, otherwise 0.
    428  */
    429 static int
    430 write_conf(void)
    431 {
    432 	char *name, *err_str = NULL;
    433 	struct stat stbuf;
    434 
    435 	if ((cpr_status.perm && cpr_status.update != OKUP) ||
    436 	    (pm_status.perm && pm_status.update != OKUP)) {
    437 		mesg(MDEBUG, "\nconf not written, "
    438 		    "(cpr perm %d update %d), (pm perm %d update %d)\n",
    439 		    cpr_status.perm, cpr_status.update,
    440 		    pm_status.perm, pm_status.update);
    441 		return (1);
    442 	}
    443 
    444 	save_orig();
    445 	if ((tmp_fd = mkstemp(tmp_conf)) == -1) {
    446 		mesg(MERR, "cannot open/create tmp file \"%s\"\n", tmp_conf);
    447 		return (1);
    448 	}
    449 	tmp_write(conf_header, sizeof (conf_header) - 1);
    450 
    451 	/*
    452 	 * When both perms are set, save selected lines from the new file;
    453 	 * otherwise save selected subsets from the new and default files.
    454 	 */
    455 	if (cpr_status.perm && pm_status.perm)
    456 		conf_scanner(NULL);
    457 	else {
    458 		conf_scanner(&cpr_status);
    459 		conf_scanner(&pm_status);
    460 	}
    461 
    462 	/*
    463 	 * "dtpower" will craft an alt conf file with modified content from
    464 	 * /etc/power.conf, but any alt conf file is not a trusted source;
    465 	 * since some alt conf lines may be skipped, the trusted source is
    466 	 * searched for those lines to retain their functionality.
    467 	 */
    468 	parse_conf_file(default_conf, search, B_FALSE);
    469 
    470 	(void) close(tmp_fd);
    471 
    472 	if (stat(name = default_conf, &stbuf) == -1)
    473 		err_str = "stat";
    474 	else if (chmod(name = tmp_conf, stbuf.st_mode) == -1)
    475 		err_str = "chmod";
    476 	else if (chown(tmp_conf, stbuf.st_uid, stbuf.st_gid) == -1)
    477 		err_str = "chown";
    478 	else if (rename(tmp_conf, default_conf) == -1)
    479 		err_str = "rename";
    480 	else
    481 		mesg(MDEBUG, "\n\"%s\" renamed to \"%s\"\n",
    482 		    tmp_conf, default_conf);
    483 	if (err_str)
    484 		mesg(MERR, "cannot %s \"%s\", %s\n",
    485 		    err_str, name, strerror(errno));
    486 
    487 	return (err_str != NULL);
    488 }
    489 
    490 
    491 /* ARGSUSED */
    492 int
    493 main(int cnt, char **vec)
    494 {
    495 	int rval = 0;
    496 
    497 	(void) setlocale(LC_ALL, "");
    498 	(void) textdomain(TEXT_DOMAIN);
    499 
    500 	for (prog = *vec++; *vec && **vec == '-'; vec++) {
    501 		if (strlen(*vec) > 2)
    502 			usage();
    503 		switch (*(*vec + 1)) {
    504 		case 'd':
    505 			debug = 1;
    506 			break;
    507 		case 'f':
    508 			fflag = 1;
    509 			if ((power_conf = *++vec) == NULL)
    510 				usage();
    511 			break;
    512 		case 'r':
    513 			rflag = 1;
    514 			break;
    515 		case 'W':
    516 			whitelist_only = 0;
    517 			break;
    518 		case 'v':
    519 			verify = 1;
    520 			break;
    521 		default:
    522 			usage();
    523 			break;
    524 		}
    525 	}
    526 	if (rflag && fflag)
    527 		usage();
    528 
    529 	lookup_perms();
    530 	mesg(MDEBUG, "ruid %d, perms: cpr %d, pm %d\n",
    531 	    ruid, cpr_status.perm, pm_status.perm);
    532 
    533 	if ((!cpr_status.perm && !pm_status.perm) ||
    534 	    (rflag && !(cpr_status.perm && pm_status.perm)))
    535 		mesg(MEXIT, "%s\n", strerror(EACCES));
    536 	if (rflag == 0 && access(power_conf, R_OK))
    537 		mesg(MEXIT, "\"%s\" is not readable\n", power_conf);
    538 
    539 	get_cpr_info();
    540 
    541 	if (pm_status.perm)
    542 		pm_rem_reset();
    543 	get_powerd_pid();
    544 	(void) umask(022);
    545 	if (rflag)
    546 		return (update_cprconfig() || restart_powerd());
    547 	if (stat(default_conf, &def_info) == -1)
    548 		mesg(MEXIT, "cannot stat %s, %s\n", default_conf,
    549 		    strerror(errno));
    550 	new_cc.loadaverage_thold = DFLT_THOLD;
    551 	parse_conf_file(power_conf, NULL, B_TRUE);
    552 	if (fflag)
    553 		rval = write_conf();
    554 	cleanup();
    555 	if (pm_status.perm)
    556 		(void) close(pm_fd);
    557 	if (rval == 0)
    558 		rval = (update_cprconfig() || restart_powerd());
    559 
    560 	return (rval);
    561 }
    562