xref: /illumos-gate/usr/src/cmd/power/parse.c (revision 07a48826732249fcd3aa8dd53c8389595e9f1fbc)
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