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