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