xref: /illumos-gate/usr/src/cmd/power/conf.c (revision 71269a2275bf5a143dad6461eee2710a344e7261)
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 /*
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 		fprintf(stderr, "%s: ", prog);
125 		if (lineno)
126 			fprintf(stderr, "\"%s\" line %d, ", power_conf, lineno);
127 	}
128 
129 	va_start(vargs, fmt);
130 	(void) vfprintf(stderr, gettext(fmt), vargs);
131 	va_end(vargs);
132 
133 	if (code == MEXIT) {
134 		cleanup();
135 		exit(MEXIT);
136 	}
137 }
138 
139 
140 static void
141 usage(void)
142 {
143 	(void) fprintf(stderr, gettext("Usage: %s [-r]\n"), prog);
144 	exit(1);
145 }
146 
147 
148 /*
149  * Lookup estar version, check if uadmin() service is supported,
150  * and read cpr_config info from disk.
151  */
152 static void
153 get_cpr_info(void)
154 {
155 	ssize_t nread;
156 	char *err_fmt;
157 	int fd;
158 
159 #ifdef sparc
160 	lookup_estar_vers();
161 	if (estar_vers == ESTAR_V2)
162 		new_cc.is_cpr_default = 1;
163 	else if (estar_vers == ESTAR_V3)
164 		new_cc.is_autopm_default = 1;
165 
166 	if (uadmin(A_FREEZE, AD_CHECK, 0) == 0)
167 		new_cc.is_cpr_capable = 1;
168 	else
169 		ua_err = errno;
170 
171 	if ((fd = open("/dev/tod", O_RDONLY)) != -1) {
172 		new_cc.is_autowakeup_capable = 1;
173 		(void) close(fd);
174 	}
175 #endif /* sparc */
176 
177 	/*
178 	 * Read in the cpr conf file.  If any open or read error occurs,
179 	 * display an error message only for a non-root user.  The file
180 	 * may not exist on a newly installed system.
181 	 */
182 	err_fmt = "%s %s; please rerun %s as root\n";
183 	if ((fd = open(cpr_conf, O_RDONLY)) == -1) {
184 		if (ruid)
185 			mesg(MEXIT, err_fmt, gettext("cannot open"),
186 			    cpr_conf, prog);
187 	} else {
188 		nread = read(fd, &disk_cc, sizeof (disk_cc));
189 		(void) close(fd);
190 		if (nread != (ssize_t)sizeof (disk_cc)) {
191 			if (ruid)
192 				mesg(MEXIT, err_fmt, cpr_conf,
193 				    gettext("file corrupted"), prog);
194 			else {
195 				(void) unlink(cpr_conf);
196 				bzero(&disk_cc, sizeof (disk_cc));
197 			}
198 		}
199 	}
200 }
201 
202 
203 /*
204  * Unconfigure and reset PM, device is left open for later use.
205  */
206 static void
207 pm_rem_reset(void)
208 {
209 	char *err_fmt = NULL;
210 
211 	if ((pm_fd = open("/dev/pm", O_RDWR)) == -1)
212 		err_fmt = "cannot open \"/dev/pm\": %s\n";
213 	else if (ioctl(pm_fd, PM_RESET_PM, 0) == -1)
214 		err_fmt = "cannot reset pm state: %s\n";
215 	if (err_fmt)
216 		mesg(MEXIT, err_fmt, strerror(errno));
217 }
218 
219 
220 static void
221 get_powerd_pid(void)
222 {
223 	char pidstr[16];
224 	int fd;
225 
226 	if ((fd = open(PIDPATH, O_RDONLY)) == -1)
227 		return;
228 	bzero(pidstr, sizeof (pidstr));
229 	if (read(fd, pidstr, sizeof (pidstr)) > 0) {
230 		powerd_pid = atoi(pidstr);
231 		mesg(MDEBUG, "got powerd pid %ld\n", powerd_pid);
232 	}
233 	(void) close(fd);
234 }
235 
236 
237 /*
238  * Write revised cprconfig struct to disk based on perms;
239  * returns 1 if any error, otherwise 0.
240  */
241 static int
242 update_cprconfig(void)
243 {
244 	struct cprconfig *wrt_cc = &new_cc;
245 	char *err_fmt = NULL;
246 	int fd;
247 
248 	if (rflag) {
249 		/* For "pmconfig -r" case, copy select cpr-related fields. */
250 		new_cc.cf_magic = disk_cc.cf_magic;
251 		new_cc.cf_type = disk_cc.cf_type;
252 		MCCPY_FIELD(new_cc, disk_cc, cf_path);
253 		MCCPY_FIELD(new_cc, disk_cc, cf_fs);
254 		MCCPY_FIELD(new_cc, disk_cc, cf_devfs);
255 		MCCPY_FIELD(new_cc, disk_cc, cf_dev_prom);
256 	}
257 
258 	if (!pm_status.perm) {
259 		if (cpr_status.update == NOUP)
260 			return (1);
261 		/* save new struct data with old autopm setting */
262 		MCCPY_FIELD(new_cc, disk_cc, apm_behavior);
263 	} else if (!cpr_status.perm) {
264 		if (pm_status.update == NOUP)
265 			return (1);
266 		/* save original struct with new autopm setting */
267 		MCCPY_FIELD(disk_cc, new_cc, apm_behavior);
268 		wrt_cc = &disk_cc;
269 	} else if (cpr_status.update == NOUP || pm_status.update == NOUP)
270 		return (1);
271 
272 	if ((fd = open(cpr_conf, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1)
273 		err_fmt = "cannot open/create \"%s\", %s\n";
274 	else if (write(fd, wrt_cc, sizeof (*wrt_cc)) != sizeof (*wrt_cc))
275 		err_fmt = "error writing \"%s\", %s\n";
276 	if (err_fmt)
277 		mesg(MERR, err_fmt, cpr_conf, strerror(errno));
278 	if (fd != -1)
279 		(void) close(fd);
280 	return (err_fmt != NULL);
281 }
282 
283 
284 /*
285  * Signal old powerd when there's a valid pid, or start a new one;
286  * returns 1 if any error, otherwise 0.
287  */
288 static int
289 restart_powerd(void)
290 {
291 	char *powerd = "/usr/lib/power/powerd";
292 	int status = 0;
293 	pid_t pid, wp;
294 
295 	if (powerd_pid > 0) {
296 		if (sigsend(P_PID, powerd_pid, SIGHUP) == 0)
297 			return (0);
298 		else if (errno != ESRCH) {
299 			mesg(MERR, "cannot deliver hangup to powerd\n");
300 			return (1);
301 		}
302 	}
303 
304 	if ((pid = fork()) == NOPID)
305 		wp = -1;
306 	else if (pid == P_MYPID) {
307 		(void) setreuid(0, 0);
308 		(void) setregid(0, 0);
309 		(void) setgroups(0, NULL);
310 		if (debug)
311 			(void) execle(powerd, powerd, "-d", NULL, NULL);
312 		else
313 			(void) execle(powerd, powerd, NULL, NULL);
314 		exit(1);
315 	} else {
316 		do {
317 			wp = waitpid(pid, &status, 0);
318 		} while (wp == -1 && errno == EINTR);
319 	}
320 
321 	if (wp == -1)
322 		mesg(MERR, "could not start %s\n", powerd);
323 	return (wp == -1 || status != 0);
324 }
325 
326 
327 static void
328 save_orig(void)
329 {
330 	static char *args[] = { "/usr/bin/cp", default_conf, orig_conf, NULL };
331 	struct stat stbuf;
332 	int pid;
333 
334 	if (stat(orig_conf, &stbuf) == 0 && stbuf.st_size)
335 		return;
336 	pid = fork();
337 	if (pid == NOPID)
338 		return;
339 	else if (pid == P_MYPID) {
340 		(void) execve(args[0], args, NULL);
341 		exit(1);
342 	} else
343 		(void) waitpid(pid, NULL, 0);
344 }
345 
346 
347 static void
348 tmp_write(void *buf, size_t len)
349 {
350 	if (write(tmp_fd, buf, len) != (ssize_t)len)
351 		mesg(MEXIT, "error writing tmp file, %s\n", strerror(errno));
352 }
353 
354 
355 static void
356 tmp_save_line(char *line, size_t len, cinfo_t *cip)
357 {
358 	if (cip && cip->cmt)
359 		tmp_write(cip->cmt, strlen(cip->cmt));
360 	tmp_write(line, len);
361 }
362 
363 
364 /*
365  * Filter conf lines and write them to the tmp file.
366  */
367 static void
368 filter(char *line, size_t len, cinfo_t *cip)
369 {
370 	int selected;
371 
372 	/*
373 	 * Lines from an alt conf file are selected when either:
374 	 * cip is NULL (keyword not matched, probably an old-style device),
375 	 * OR: it's both OK to accept the conf line (alt) AND either:
376 	 * preference is not set (NULL checkup) OR the cpr/pm preference
377 	 * (checkup) matches conftab status.
378 	 */
379 	selected = (cip == NULL || (cip->alt &&
380 	    (checkup == NULL || checkup == cip->status)));
381 	mesg(MDEBUG, "filter: set \"%s\", selected %d\n",
382 	    cip ? cip->status->set : "none", selected);
383 	if (selected)
384 		tmp_save_line(line, len, cip);
385 }
386 
387 
388 /*
389  * Set checkup for conf line selection and parse a conf file with filtering.
390  * When pref is NULL, filter selects all conf lines from the new conf file;
391  * otherwise filter selects only cpr or pm related lines from the new or
392  * default conf file based on cpr or pm perm.
393  */
394 static void
395 conf_scanner(prmup_t *pref)
396 {
397 	mesg(MDEBUG, "\nscanning set is %s\n", pref ? pref->set : "both");
398 	checkup = pref;
399 	parse_conf_file((pref == NULL || pref->perm)
400 	    ? power_conf : default_conf, filter);
401 }
402 
403 
404 /*
405  * Search for any non-alt entries, call the handler routine,
406  * and write entries to the tmp file.
407  */
408 static void
409 search(char *line, size_t len, cinfo_t *cip)
410 {
411 	int skip;
412 
413 	skip = (cip == NULL || cip->alt);
414 	mesg(MDEBUG, "search: %s\n", skip ? "skipped" : "retained");
415 	if (skip)
416 		return;
417 	if (cip->status->perm)
418 		(void) (*cip->handler)();
419 	tmp_save_line(line, len, cip);
420 }
421 
422 
423 /*
424  * When perm and update status are OK, write a new conf file
425  * and rename to default_conf with the original attributes;
426  * returns 1 if any error, otherwise 0.
427  */
428 static int
429 write_conf(void)
430 {
431 	char *name, *err_str = NULL;
432 	struct stat stbuf;
433 
434 	if ((cpr_status.perm && cpr_status.update != OKUP) ||
435 	    (pm_status.perm && pm_status.update != OKUP)) {
436 		mesg(MDEBUG, "\nconf not written, "
437 		    "(cpr perm %d update %d), (pm perm %d update %d)\n",
438 		    cpr_status.perm, cpr_status.update,
439 		    pm_status.perm, pm_status.update);
440 		return (1);
441 	}
442 
443 	save_orig();
444 	if ((tmp_fd = mkstemp(tmp_conf)) == -1) {
445 		mesg(MERR, "cannot open/create tmp file \"%s\"\n", tmp_conf);
446 		return (1);
447 	}
448 	tmp_write(conf_header, sizeof (conf_header) - 1);
449 
450 	/*
451 	 * When both perms are set, save selected lines from the new file;
452 	 * otherwise save selected subsets from the new and default files.
453 	 */
454 	if (cpr_status.perm && pm_status.perm)
455 		conf_scanner(NULL);
456 	else {
457 		conf_scanner(&cpr_status);
458 		conf_scanner(&pm_status);
459 	}
460 
461 	/*
462 	 * "dtpower" will craft an alt conf file with modified content from
463 	 * /etc/power.conf, but any alt conf file is not a trusted source;
464 	 * since some alt conf lines may be skipped, the trusted source is
465 	 * searched for those lines to retain their functionality.
466 	 */
467 	parse_conf_file(default_conf, search);
468 
469 	(void) close(tmp_fd);
470 
471 	if (stat(name = default_conf, &stbuf) == -1)
472 		err_str = "stat";
473 	else if (chmod(name = tmp_conf, stbuf.st_mode) == -1)
474 		err_str = "chmod";
475 	else if (chown(tmp_conf, stbuf.st_uid, stbuf.st_gid) == -1)
476 		err_str = "chown";
477 	else if (rename(tmp_conf, default_conf) == -1)
478 		err_str = "rename";
479 	else
480 		mesg(MDEBUG, "\n\"%s\" renamed to \"%s\"\n",
481 		    tmp_conf, default_conf);
482 	if (err_str)
483 		mesg(MERR, "cannot %s \"%s\", %s\n",
484 		    err_str, name, strerror(errno));
485 
486 	return (err_str != NULL);
487 }
488 
489 
490 /* ARGSUSED */
491 int
492 main(int cnt, char **vec)
493 {
494 	int rval = 0;
495 
496 	(void) setlocale(LC_ALL, "");
497 	(void) textdomain(TEXT_DOMAIN);
498 
499 	for (prog = *vec++; *vec && **vec == '-'; vec++) {
500 		if (strlen(*vec) > 2)
501 			usage();
502 		switch (*(*vec + 1)) {
503 		case 'd':
504 			debug = 1;
505 			break;
506 		case 'f':
507 			fflag = 1;
508 			if ((power_conf = *++vec) == NULL)
509 				usage();
510 			break;
511 		case 'r':
512 			rflag = 1;
513 			break;
514 		case 'W':
515 			whitelist_only = 0;
516 			break;
517 		case 'v':
518 			verify = 1;
519 			break;
520 		default:
521 			usage();
522 			break;
523 		}
524 	}
525 	if (rflag && fflag)
526 		usage();
527 
528 	lookup_perms();
529 	mesg(MDEBUG, "ruid %d, perms: cpr %d, pm %d\n",
530 	    ruid, cpr_status.perm, pm_status.perm);
531 
532 	if ((!cpr_status.perm && !pm_status.perm) ||
533 	    (rflag && !(cpr_status.perm && pm_status.perm)))
534 		mesg(MEXIT, "%s\n", strerror(EACCES));
535 	if (rflag == 0 && access(power_conf, R_OK))
536 		mesg(MEXIT, "\"%s\" is not readable\n", power_conf);
537 
538 	get_cpr_info();
539 
540 	if (pm_status.perm)
541 		pm_rem_reset();
542 	get_powerd_pid();
543 	(void) umask(022);
544 	if (rflag)
545 		return (update_cprconfig() || restart_powerd());
546 	if (stat(default_conf, &def_info) == -1)
547 		mesg(MEXIT, "cannot stat %s, %s\n", default_conf,
548 		    strerror(errno));
549 	new_cc.loadaverage_thold = DFLT_THOLD;
550 	parse_conf_file(power_conf, NULL);
551 	if (pm_status.perm)
552 		(void) close(pm_fd);
553 	if (fflag)
554 		rval = write_conf();
555 	cleanup();
556 	if (rval == 0)
557 		rval = (update_cprconfig() || restart_powerd());
558 
559 	return (rval);
560 }
561