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