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 #include "pmconfig.h" 27 #include <deflt.h> 28 #include <pwd.h> 29 30 #ifdef sparc 31 #include <libdevinfo.h> 32 static char sf_cmt[] = "# Statefile\t\tPath\n"; 33 #endif 34 35 static char as_cmt[] = 36 "# Auto-Shutdown\t\tIdle(min)\tStart/Finish(hh:mm)\tBehavior\n"; 37 38 char **line_args; 39 int lineno = 0; 40 41 /* 42 * cpr and pm combined permission/update status 43 */ 44 prmup_t cpr_status = { 0, OKUP, "cpr" }; 45 prmup_t pm_status = { 0, OKUP, "pm" }; 46 47 48 /* 49 * For config file parsing to work correctly/efficiently, this table 50 * needs to be sorted by .keyword and any longer string like "device" 51 * must appear before a substring like "dev". 52 */ 53 static cinfo_t conftab[] = { 54 "S3-support", S3sup, &pm_status, NULL, 2, 0, 1, 55 "autoS3", autoS3, &pm_status, NULL, 2, 0, 1, 56 "autopm", autopm, &pm_status, NULL, 2, 0, 1, 57 "autoshutdown", autosd, &cpr_status, as_cmt, 5, 0, 1, 58 "cpu-threshold", cputhr, &pm_status, NULL, 2, 0, 1, 59 "cpu_deep_idle", cpuidle, &pm_status, NULL, 2, 0, 1, 60 "cpupm", cpupm, &pm_status, NULL, 2, 1, 1, 61 "device-dependency-property", 62 ddprop, &pm_status, NULL, 3, 1, 1, 63 "device-dependency", devdep, &pm_status, NULL, 3, 1, 1, 64 "device-thresholds", devthr, &pm_status, NULL, 3, 1, 1, 65 "diskreads", dreads, &cpr_status, NULL, 2, 0, 1, 66 "idlecheck", idlechk, &cpr_status, NULL, 2, 0, 0, 67 "loadaverage", loadavg, &cpr_status, NULL, 2, 0, 1, 68 "nfsreqs", nfsreq, &cpr_status, NULL, 2, 0, 1, 69 #ifdef sparc 70 "statefile", sfpath, &cpr_status, sf_cmt, 2, 0, 0, 71 #endif 72 "system-threshold", systhr, &pm_status, NULL, 2, 0, 1, 73 "ttychars", tchars, &cpr_status, NULL, 2, 0, 1, 74 NULL, NULL, NULL, NULL, 0, 0, 0, 75 }; 76 77 78 /* 79 * Set cpr/pm permission from default file info. 80 */ 81 static void 82 set_perm(char *defstr, char *user, int *perm, int cons) 83 { 84 char *dinfo, *tk; 85 86 /* 87 * /etc/default/power entries are: 88 * all (all users + root) 89 * - (none + root) 90 * <user1[, user2...> (list users + root) 91 * console-owner (console onwer + root) 92 * Any error in reading/parsing the file limits the 93 * access requirement to root. 94 */ 95 dinfo = defread(defstr); 96 mesg(MDEBUG, "set_perm: \"%s\", value \"%s\"\n", 97 defstr, dinfo ? dinfo : "NULL"); 98 if (dinfo == NULL) 99 return; 100 else if (strcmp(dinfo, "all") == 0) 101 *perm = 1; 102 else if (strcmp(dinfo, "console-owner") == 0) 103 *perm = cons; 104 else if (user != NULL && 105 (*dinfo == '<') && (tk = strrchr(++dinfo, '>'))) { 106 /* Scan dinfo for a matching user. */ 107 for (*tk = '\0'; tk = strtok(dinfo, ", "); dinfo = NULL) { 108 mesg(MDEBUG, "match_user: cmp (\"%s\", \"%s\")\n", 109 tk, user); 110 if (strcmp(tk, user) == 0) { 111 *perm = 1; 112 break; 113 } 114 } 115 } 116 } 117 118 119 /* 120 * Lookup cpr/pm user permissions in "/etc/default/power". 121 */ 122 void 123 lookup_perms(void) 124 { 125 struct passwd *pent; 126 struct stat stbuf; 127 int cons_perm; 128 char *user; 129 130 if ((ruid = getuid()) == 0) { 131 cpr_status.perm = pm_status.perm = 1; 132 return; 133 } else if ((pent = getpwuid(ruid)) != NULL) { 134 user = pent->pw_name; 135 } else { 136 user = NULL; 137 } 138 139 if (defopen("/etc/default/power") == -1) 140 return; 141 if (stat("/dev/console", &stbuf) == -1) 142 cons_perm = 0; 143 else 144 cons_perm = (ruid == stbuf.st_uid); 145 146 set_perm("PMCHANGEPERM=", user, &pm_status.perm, cons_perm); 147 set_perm("CPRCHANGEPERM=", user, &cpr_status.perm, cons_perm); 148 149 (void) defopen(NULL); 150 } 151 152 153 #ifdef sparc 154 /* 155 * Lookup energystar-v[23] property and set estar_vers. 156 */ 157 void 158 lookup_estar_vers(void) 159 { 160 char es_prop[] = "energystar-v?", *fmt = "%s init/access error\n"; 161 di_prom_handle_t ph; 162 di_node_t node; 163 uchar_t *prop_data; 164 int last; 165 char ch; 166 167 if ((node = di_init("/", DINFOPROP)) == DI_NODE_NIL) { 168 mesg(MERR, fmt, "di_init"); 169 return; 170 } else if ((ph = di_prom_init()) == DI_PROM_HANDLE_NIL) { 171 mesg(MERR, fmt, "di_prom_init"); 172 di_fini(node); 173 return; 174 } 175 last = strlen(es_prop) - 1; 176 for (ch = ESTAR_V2; ch <= ESTAR_V3; ch++) { 177 es_prop[last] = ch; 178 if (di_prom_prop_lookup_bytes(ph, node, 179 es_prop, &prop_data) == 0) { 180 mesg(MDEBUG, "get_estar_vers: %s prop found\n", 181 es_prop); 182 estar_vers = ch; 183 break; 184 } 185 } 186 di_prom_fini(ph); 187 di_fini(node); 188 } 189 #endif /* sparc */ 190 191 192 /* 193 * limit open() to the real user 194 */ 195 static int 196 pmc_open(char *name, int oflag) 197 { 198 uid_t euid; 199 int fd; 200 201 euid = geteuid(); 202 if (seteuid(ruid) == -1) 203 mesg(MEXIT, "cannot reset euid to %d, %s\n", 204 ruid, strerror(errno)); 205 fd = open(name, oflag); 206 (void) seteuid(euid); 207 return (fd); 208 } 209 210 211 /* 212 * Alloc space and read a config file; caller needs to free the space. 213 */ 214 static char * 215 get_conf_data(char *name) 216 { 217 struct stat stbuf; 218 ssize_t nread; 219 size_t size; 220 char *buf; 221 int fd; 222 223 if ((fd = pmc_open(name, O_RDONLY)) == -1) 224 mesg(MEXIT, "cannot open %s\n", name); 225 else if (fstat(fd, &stbuf) == -1) 226 mesg(MEXIT, "cannot stat %s\n", name); 227 size = (size_t)stbuf.st_size; 228 def_src = (stbuf.st_ino == def_info.st_ino && 229 stbuf.st_dev == def_info.st_dev); 230 if ((buf = malloc(size + 1)) == NULL) 231 mesg(MEXIT, "cannot allocate %u for \"%s\"\n", size + 1, name); 232 nread = read(fd, buf, size); 233 (void) close(fd); 234 if (nread != (ssize_t)size) 235 mesg(MEXIT, "read error, expect %u, got %d, file \"%s\"\n", 236 size, nread, name); 237 *(buf + size) = '\0'; 238 return (buf); 239 } 240 241 242 /* 243 * Add an arg to line_args, adding space if needed. 244 */ 245 static void 246 newarg(char *arg, int index) 247 { 248 static int alcnt; 249 size_t size; 250 251 if ((index + 1) > alcnt) { 252 alcnt += 4; 253 size = alcnt * sizeof (*line_args); 254 if ((line_args = realloc(line_args, size)) == NULL) 255 mesg(MEXIT, "cannot alloc %u for line args\n", size); 256 } 257 *(line_args + index) = arg; 258 } 259 260 261 /* 262 * Convert blank-delimited words into an arg vector and return 263 * the arg count; character strings get null-terminated in place. 264 */ 265 static int 266 build_args(char *cline, char *tail) 267 { 268 extern int debug; 269 char **vec, *arg, *cp; 270 int cnt = 0; 271 272 /* 273 * Search logic: look for "\\\n" as a continuation marker, 274 * treat any other "\\*" as ordinary arg data, scan until a 275 * white-space delimiter is found, and if the arg has length, 276 * null-terminate and save arg to line_args. The scan includes 277 * tail so the last arg is found without any special-case code. 278 */ 279 for (arg = cp = cline; cp <= tail; cp++) { 280 if (*cp == '\\') { 281 if (*(cp + 1) && *(cp + 1) != '\n') { 282 cp++; 283 continue; 284 } 285 } else if (strchr(" \t\n", *cp) == NULL) 286 continue; 287 if (cp - arg) { 288 *cp = '\0'; 289 newarg(arg, cnt++); 290 } 291 arg = cp + 1; 292 } 293 newarg(NULL, cnt); 294 295 if (debug) { 296 mesg(MDEBUG, "\nline %d, found %d args:\n", lineno, cnt); 297 for (vec = line_args; *vec; vec++) 298 mesg(MDEBUG, " \"%s\"\n", *vec); 299 } 300 301 return (cnt); 302 } 303 304 305 /* 306 * Match leading keyword from a conf line and 307 * return a reference to a config info struct. 308 */ 309 static cinfo_t * 310 get_cinfo(void) 311 { 312 cinfo_t *cip, *info = NULL; 313 char *keyword; 314 int chr_diff; 315 316 /* 317 * Scan the config table for a matching keyword; since the table 318 * is sorted by keyword strings, a few optimizations can be done: 319 * first compare only the first byte of the keyword, skip any 320 * table string that starts with a lower ASCII value, compare the 321 * full string only when the first byte matches, and stop checking 322 * if the table string starts with a higher ASCII value. 323 */ 324 keyword = LINEARG(0); 325 for (cip = conftab; cip->keyword; cip++) { 326 chr_diff = (int)(*cip->keyword - *keyword); 327 #if 0 328 mesg(MDEBUG, "line %d, ('%c' - '%c') = %d\n", 329 lineno, *cip->keyword, *line, chr_diff); 330 #endif 331 if (chr_diff < 0) 332 continue; 333 else if (chr_diff == 0) { 334 if (strcmp(keyword, cip->keyword) == 0) { 335 info = cip; 336 break; 337 } 338 } else 339 break; 340 } 341 return (info); 342 } 343 344 345 /* 346 * Find the end of a [possibly continued] conf line 347 * and record the real/lf-delimited line count at *lcnt. 348 */ 349 static char * 350 find_line_end(char *line, int *lcnt) 351 { 352 char *next, *lf; 353 354 *lcnt = 0; 355 next = line; 356 while (lf = strchr(next, '\n')) { 357 (*lcnt)++; 358 if (lf == line || (*(lf - 1) != '\\') || *(lf + 1) == '\0') 359 break; 360 next = lf + 1; 361 } 362 return (lf); 363 } 364 365 366 /* 367 * Parse the named conf file and for each conf line 368 * call the action routine or conftab handler routine. 369 */ 370 void 371 parse_conf_file(char *name, vact_t action, boolean_t first_parse) 372 { 373 char *file_buf, *cline, *line, *lend; 374 cinfo_t *cip; 375 int linc, cnt; 376 size_t llen; 377 int dontcare; 378 379 /* 380 * Do the "implied default" for autoS3, but only before we 381 * start parsing the first conf file. 382 */ 383 if (first_parse) { 384 (void) S3_helper("S3-support-enable", "S3-support-disable", 385 PM_ENABLE_S3, PM_DISABLE_S3, "S3-support", "default", 386 &dontcare, -1); 387 } 388 389 file_buf = get_conf_data(name); 390 mesg(MDEBUG, "\nnow parsing \"%s\"...\n", name); 391 392 lineno = 1; 393 line = file_buf; 394 while (lend = find_line_end(line, &linc)) { 395 /* 396 * Each line should start with valid data 397 * but leading white-space can be ignored 398 */ 399 while (line < lend) { 400 if (*line != ' ' && *line != '\t') 401 break; 402 line++; 403 } 404 405 /* 406 * Copy line into allocated space and null-terminate 407 * without the trailing line-feed. 408 */ 409 if ((llen = (lend - line)) != 0) { 410 if ((cline = malloc(llen + 1)) == NULL) 411 mesg(MEXIT, "cannot alloc %u bytes " 412 "for line copy\n", llen); 413 (void) memcpy(cline, line, llen); 414 *(cline + llen) = '\0'; 415 } else 416 cline = NULL; 417 418 /* 419 * For blank and comment lines: possibly show a debug 420 * message and otherwise ignore them. For other lines: 421 * parse into an arg vector and try to match the first 422 * arg with conftab keywords. When a match is found, 423 * check for exact or minimum arg count, and call the 424 * action or handler routine; if handler does not return 425 * OKUP, set the referenced update value to NOUP so that 426 * later CPR or PM updates are skipped. 427 */ 428 if (llen == 0) 429 mesg(MDEBUG, "\nline %d, blank...\n", lineno); 430 else if (*line == '#') 431 mesg(MDEBUG, "\nline %d, comment...\n", lineno); 432 else if (cnt = build_args(cline, cline + llen)) { 433 if ((cip = get_cinfo()) == NULL) { 434 mesg(MEXIT, "unrecognized keyword \"%s\"\n", 435 LINEARG(0)); 436 } else if (cnt != cip->argc && 437 (cip->any == 0 || cnt < cip->argc)) { 438 mesg(MEXIT, "found %d args, expect %d%s\n", 439 cnt, cip->argc, cip->any ? "+" : ""); 440 } else if (action) 441 (*action)(line, llen + 1, cip); 442 else if (cip->status->perm && (def_src || cip->alt)) { 443 if ((*cip->handler)() != OKUP) 444 cip->status->update = NOUP; 445 } else { 446 mesg(MDEBUG, 447 "==> handler skipped: %s_perm %d, " 448 "def_src %d, alt %d\n", cip->status->set, 449 cip->status->perm, def_src, cip->alt); 450 } 451 } 452 453 if (cline) 454 free(cline); 455 line = lend + 1; 456 lineno += linc; 457 } 458 lineno = 0; 459 460 free(file_buf); 461 462 if (verify) { 463 int ret = ioctl(pm_fd, PM_GET_PM_STATE, NULL); 464 if (ret < 0) { 465 mesg(MDEBUG, "Cannot get PM state: %s\n", 466 strerror(errno)); 467 } 468 switch (ret) { 469 case PM_SYSTEM_PM_ENABLED: 470 mesg(MDEBUG, "Autopm Enabled\n"); 471 break; 472 case PM_SYSTEM_PM_DISABLED: 473 mesg(MDEBUG, "Autopm Disabled\n"); 474 break; 475 } 476 ret = ioctl(pm_fd, PM_GET_S3_SUPPORT_STATE, NULL); 477 if (ret < 0) { 478 mesg(MDEBUG, "Cannot get PM state: %s\n", 479 strerror(errno)); 480 } 481 switch (ret) { 482 case PM_S3_SUPPORT_ENABLED: 483 mesg(MDEBUG, "S3 support Enabled\n"); 484 break; 485 case PM_S3_SUPPORT_DISABLED: 486 mesg(MDEBUG, "S3 support Disabled\n"); 487 break; 488 } 489 ret = ioctl(pm_fd, PM_GET_AUTOS3_STATE, NULL); 490 if (ret < 0) { 491 mesg(MDEBUG, "Cannot get PM state: %s\n", 492 strerror(errno)); 493 } 494 switch (ret) { 495 case PM_AUTOS3_ENABLED: 496 mesg(MDEBUG, "AutoS3 Enabled\n"); 497 break; 498 case PM_AUTOS3_DISABLED: 499 mesg(MDEBUG, "AutoS3 Disabled\n"); 500 break; 501 } 502 } 503 } 504