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