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