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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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