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