xref: /illumos-gate/usr/src/cmd/power/conf.c (revision 2df1fe9c)
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 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * "pmconfig" performs a mixture of Energy-Star configuration tasks
30  * for both CheckPoint-Resume and Power-Management services.
31  * Tasks include parsing a config file (usually "/etc/power.conf"),
32  * updating CPR and PM config files, and setting various PM options
33  * via ioctl requests.  From the mix, pmconfig should have a more
34  * generalized name similar to "estarconfig".
35  *
36  * OPTIONS:
37  * "-r"		reset CPR and PM options to default and exit.
38  * "-f file"	specify an alternate config file; this is a
39  *		private/non-advertised option used by "dtpower".
40  */
41 
42 #include "pmconfig.h"
43 #include <sys/wait.h>
44 #include <signal.h>
45 #include <stdarg.h>
46 #include <locale.h>
47 #include "powerd.h"
48 
49 
50 #define	MCCPY_FIELD(dst, src, field) \
51 	(void) memccpy(&dst.field, &src.field, 0, sizeof (dst.field) - 1)
52 
53 
54 static char conf_header[] =
55 "#\n"
56 "# Copyright 1996-2002 Sun Microsystems, Inc.  All rights reserved.\n"
57 "# Use is subject to license terms.\n"
58 "#\n"
59 "#pragma ident	\"@(#)power.conf	2.1	02/03/04 SMI\"\n"
60 "#\n"
61 "# Power Management Configuration File\n"
62 "#\n\n";
63 
64 static char *prog;
65 static char *cpr_conf = CPR_CONFIG;
66 static char tmp_conf[] = "/etc/.tmp.conf.XXXXXX";
67 static char orig_conf[] = "/etc/power.conf-Orig";
68 static char default_conf[] = "/etc/power.conf";
69 static char *power_conf = default_conf;
70 static pid_t powerd_pid;
71 static prmup_t *checkup;
72 static int tmp_fd;
73 
74 char estar_vers = ESTAR_VNONE;
75 int ua_err = 0;
76 int debug = 0;
77 
78 static struct cprconfig disk_cc;
79 struct cprconfig new_cc;
80 struct stat def_info;
81 static int fflag, rflag;
82 int pm_fd;
83 uid_t ruid;
84 int def_src;
85 /*
86  * Until we get more graphics driver support, we only enable autopm,
87  * S3 support and autoS3 by default on X86 systems that are on our whitelist.
88  */
89 int whitelist_only = 1;
90 
91 int verify = 0;
92 
93 
94 static void
95 cleanup(void)
96 {
97 	free(line_args);
98 	if (access(tmp_conf, F_OK) == 0)
99 		(void) unlink(tmp_conf);
100 }
101 
102 
103 /*
104  * Multi-purpose message output routine; also exits when
105  * (status == MEXIT), other status is non-fatal.
106  * VARARGS2
107  */
108 void
109 mesg(int code, char *fmt, ...)
110 {
111 	va_list vargs;
112 
113 	/*
114 	 * debug is checked once here, avoiding N duplicate checks
115 	 * before each MDEBUG caller and unnecessary text dupduplication.
116 	 */
117 	if (debug == 0) {
118 		/*
119 		 * If debug is not enabled, skip a debug message;
120 		 * lead with the program name for an error message,
121 		 * and follow with a filename and line number if an
122 		 * error occurs while parsing a conf file.
123 		 */
124 		if (code == MDEBUG)
125 			return;
126 		fprintf(stderr, "%s: ", prog);
127 		if (lineno)
128 			fprintf(stderr, "\"%s\" line %d, ", power_conf, lineno);
129 	}
130 
131 	va_start(vargs, fmt);
132 	(void) vfprintf(stderr, gettext(fmt), vargs);
133 	va_end(vargs);
134 
135 	if (code == MEXIT) {
136 		cleanup();
137 		exit(MEXIT);
138 	}
139 }
140 
141 
142 static void
143 usage(void)
144 {
145 	(void) fprintf(stderr, gettext("Usage: %s [-r]\n"), prog);
146 	exit(1);
147 }
148 
149 
150 /*
151  * Lookup estar version, check if uadmin() service is supported,
152  * and read cpr_config info from disk.
153  */
154 static void
155 get_cpr_info(void)
156 {
157 	ssize_t nread;
158 	char *err_fmt;
159 	int fd;
160 
161 #ifdef sparc
162 	lookup_estar_vers();
163 	if (estar_vers == ESTAR_V2)
164 		new_cc.is_cpr_default = 1;
165 	else if (estar_vers == ESTAR_V3)
166 		new_cc.is_autopm_default = 1;
167 
168 	if (uadmin(A_FREEZE, AD_CHECK, 0) == 0)
169 		new_cc.is_cpr_capable = 1;
170 	else
171 		ua_err = errno;
172 
173 	if ((fd = open("/dev/tod", O_RDONLY)) != -1) {
174 		new_cc.is_autowakeup_capable = 1;
175 		(void) close(fd);
176 	}
177 #endif /* sparc */
178 
179 	/*
180 	 * Read in the cpr conf file.  If any open or read error occurs,
181 	 * display an error message only for a non-root user.  The file
182 	 * may not exist on a newly installed system.
183 	 */
184 	err_fmt = "%s %s; please rerun %s as root\n";
185 	if ((fd = open(cpr_conf, O_RDONLY)) == -1) {
186 		if (ruid)
187 			mesg(MEXIT, err_fmt, gettext("cannot open"),
188 			    cpr_conf, prog);
189 	} else {
190 		nread = read(fd, &disk_cc, sizeof (disk_cc));
191 		(void) close(fd);
192 		if (nread != (ssize_t)sizeof (disk_cc)) {
193 			if (ruid)
194 				mesg(MEXIT, err_fmt, cpr_conf,
195 				    gettext("file corrupted"), prog);
196 			else {
197 				(void) unlink(cpr_conf);
198 				bzero(&disk_cc, sizeof (disk_cc));
199 			}
200 		}
201 	}
202 }
203 
204 
205 /*
206  * Unconfigure and reset PM, device is left open for later use.
207  */
208 static void
209 pm_rem_reset(void)
210 {
211 	char *err_fmt = NULL;
212 
213 	if ((pm_fd = open("/dev/pm", O_RDWR)) == -1)
214 		err_fmt = "cannot open \"/dev/pm\": %s\n";
215 	else if (ioctl(pm_fd, PM_RESET_PM, 0) == -1)
216 		err_fmt = "cannot reset pm state: %s\n";
217 	if (err_fmt)
218 		mesg(MEXIT, err_fmt, strerror(errno));
219 }
220 
221 
222 static void
223 get_powerd_pid(void)
224 {
225 	char pidstr[16];
226 	int fd;
227 
228 	if ((fd = open(PIDPATH, O_RDONLY)) == -1)
229 		return;
230 	bzero(pidstr, sizeof (pidstr));
231 	if (read(fd, pidstr, sizeof (pidstr)) > 0) {
232 		powerd_pid = atoi(pidstr);
233 		mesg(MDEBUG, "got powerd pid %ld\n", powerd_pid);
234 	}
235 	(void) close(fd);
236 }
237 
238 
239 /*
240  * Write revised cprconfig struct to disk based on perms;
241  * returns 1 if any error, otherwise 0.
242  */
243 static int
244 update_cprconfig(void)
245 {
246 	struct cprconfig *wrt_cc = &new_cc;
247 	char *err_fmt = NULL;
248 	int fd;
249 
250 	if (rflag) {
251 		/* For "pmconfig -r" case, copy select cpr-related fields. */
252 		new_cc.cf_magic = disk_cc.cf_magic;
253 		new_cc.cf_type = disk_cc.cf_type;
254 		MCCPY_FIELD(new_cc, disk_cc, cf_path);
255 		MCCPY_FIELD(new_cc, disk_cc, cf_fs);
256 		MCCPY_FIELD(new_cc, disk_cc, cf_devfs);
257 		MCCPY_FIELD(new_cc, disk_cc, cf_dev_prom);
258 	}
259 
260 	if (!pm_status.perm) {
261 		if (cpr_status.update == NOUP)
262 			return (1);
263 		/* save new struct data with old autopm setting */
264 		MCCPY_FIELD(new_cc, disk_cc, apm_behavior);
265 	} else if (!cpr_status.perm) {
266 		if (pm_status.update == NOUP)
267 			return (1);
268 		/* save original struct with new autopm setting */
269 		MCCPY_FIELD(disk_cc, new_cc, apm_behavior);
270 		wrt_cc = &disk_cc;
271 	} else if (cpr_status.update == NOUP || pm_status.update == NOUP)
272 		return (1);
273 
274 	if ((fd = open(cpr_conf, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1)
275 		err_fmt = "cannot open/create \"%s\", %s\n";
276 	else if (write(fd, wrt_cc, sizeof (*wrt_cc)) != sizeof (*wrt_cc))
277 		err_fmt = "error writing \"%s\", %s\n";
278 	(void) close(fd);
279 	if (err_fmt)
280 		mesg(MERR, err_fmt, cpr_conf, strerror(errno));
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);
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);
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);
552 	if (pm_status.perm)
553 		(void) close(pm_fd);
554 	if (fflag)
555 		rval = write_conf();
556 	cleanup();
557 	if (rval == 0)
558 		rval = (update_cprconfig() || restart_powerd());
559 
560 	return (rval);
561 }
562