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