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
set_perm(char * defstr,char * user,int * perm,int cons)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
lookup_perms(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
lookup_estar_vers(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
pmc_open(char * name,int oflag)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 *
get_conf_data(char * name)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
newarg(char * arg,int index)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
build_args(char * cline,char * tail)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 *
get_cinfo(void)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 *
find_line_end(char * line,int * lcnt)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
parse_conf_file(char * name,vact_t action,boolean_t first_parse)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