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