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