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>
32static char sf_cmt[] = "# Statefile\t\tPath\n";
33#endif
34
35static char as_cmt[] =
36	"# Auto-Shutdown\t\tIdle(min)\tStart/Finish(hh:mm)\tBehavior\n";
37
38char **line_args;
39int lineno = 0;
40
41/*
42 * cpr and pm combined permission/update status
43 */
44prmup_t cpr_status = { 0, OKUP, "cpr" };
45prmup_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 */
53static 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 */
81static void
82set_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, ", ")) != NULL;
108		    dinfo = NULL) {
109			mesg(MDEBUG, "match_user: cmp (\"%s\", \"%s\")\n",
110			    tk, user);
111			if (strcmp(tk, user) == 0) {
112				*perm = 1;
113				break;
114			}
115		}
116	}
117}
118
119
120/*
121 * Lookup cpr/pm user permissions in "/etc/default/power".
122 */
123void
124lookup_perms(void)
125{
126	struct passwd *pent;
127	struct stat stbuf;
128	int cons_perm;
129	char *user;
130
131	if ((ruid = getuid()) == 0) {
132		cpr_status.perm = pm_status.perm = 1;
133		return;
134	} else if ((pent = getpwuid(ruid)) != NULL) {
135		user = pent->pw_name;
136	} else {
137		user = NULL;
138	}
139
140	if (defopen("/etc/default/power") == -1)
141		return;
142	if (stat("/dev/console", &stbuf) == -1)
143		cons_perm = 0;
144	else
145		cons_perm = (ruid == stbuf.st_uid);
146
147	set_perm("PMCHANGEPERM=", user, &pm_status.perm, cons_perm);
148	set_perm("CPRCHANGEPERM=", user, &cpr_status.perm, cons_perm);
149
150	(void) defopen(NULL);
151}
152
153
154#ifdef sparc
155/*
156 * Lookup energystar-v[23] property and set estar_vers.
157 */
158void
159lookup_estar_vers(void)
160{
161	char es_prop[] = "energystar-v?", *fmt = "%s init/access error\n";
162	di_prom_handle_t ph;
163	di_node_t node;
164	uchar_t *prop_data;
165	int last;
166	char ch;
167
168	if ((node = di_init("/", DINFOPROP)) == DI_NODE_NIL) {
169		mesg(MERR, fmt, "di_init");
170		return;
171	} else if ((ph = di_prom_init()) == DI_PROM_HANDLE_NIL) {
172		mesg(MERR, fmt, "di_prom_init");
173		di_fini(node);
174		return;
175	}
176	last = strlen(es_prop) - 1;
177	for (ch = ESTAR_V2; ch <= ESTAR_V3; ch++) {
178		es_prop[last] = ch;
179		if (di_prom_prop_lookup_bytes(ph, node,
180		    es_prop, &prop_data) == 0) {
181			mesg(MDEBUG, "get_estar_vers: %s prop found\n",
182			    es_prop);
183			estar_vers = ch;
184			break;
185		}
186	}
187	di_prom_fini(ph);
188	di_fini(node);
189}
190#endif /* sparc */
191
192
193/*
194 * limit open() to the real user
195 */
196static int
197pmc_open(char *name, int oflag)
198{
199	uid_t euid;
200	int fd;
201
202	euid = geteuid();
203	if (seteuid(ruid) == -1)
204		mesg(MEXIT, "cannot reset euid to %d, %s\n",
205		    ruid, strerror(errno));
206	fd = open(name, oflag);
207	(void) seteuid(euid);
208	return (fd);
209}
210
211
212/*
213 * Alloc space and read a config file; caller needs to free the space.
214 */
215static char *
216get_conf_data(char *name)
217{
218	struct stat stbuf;
219	ssize_t nread;
220	size_t size;
221	char *buf;
222	int fd;
223
224	if ((fd = pmc_open(name, O_RDONLY)) == -1)
225		mesg(MEXIT, "cannot open %s\n", name);
226	else if (fstat(fd, &stbuf) == -1)
227		mesg(MEXIT, "cannot stat %s\n", name);
228	size = (size_t)stbuf.st_size;
229	def_src = (stbuf.st_ino == def_info.st_ino &&
230	    stbuf.st_dev == def_info.st_dev);
231	if ((buf = malloc(size + 1)) == NULL)
232		mesg(MEXIT, "cannot allocate %u for \"%s\"\n", size + 1, name);
233	nread = read(fd, buf, size);
234	(void) close(fd);
235	if (nread != (ssize_t)size)
236		mesg(MEXIT, "read error, expect %u, got %d, file \"%s\"\n",
237		    size, nread, name);
238	*(buf + size) = '\0';
239	return (buf);
240}
241
242
243/*
244 * Add an arg to line_args, adding space if needed.
245 */
246static void
247newarg(char *arg, int index)
248{
249	static int alcnt;
250	size_t size;
251
252	if ((index + 1) > alcnt) {
253		alcnt += 4;
254		size = alcnt * sizeof (*line_args);
255		if ((line_args = realloc(line_args, size)) == NULL)
256			mesg(MEXIT, "cannot alloc %u for line args\n", size);
257	}
258	*(line_args + index) = arg;
259}
260
261
262/*
263 * Convert blank-delimited words into an arg vector and return
264 * the arg count; character strings get null-terminated in place.
265 */
266static int
267build_args(char *cline, char *tail)
268{
269	extern int debug;
270	char **vec, *arg, *cp;
271	int cnt = 0;
272
273	/*
274	 * Search logic: look for "\\\n" as a continuation marker,
275	 * treat any other "\\*" as ordinary arg data, scan until a
276	 * white-space delimiter is found, and if the arg has length,
277	 * null-terminate and save arg to line_args.  The scan includes
278	 * tail so the last arg is found without any special-case code.
279	 */
280	for (arg = cp = cline; cp <= tail; cp++) {
281		if (*cp == '\\') {
282			if (*(cp + 1) && *(cp + 1) != '\n') {
283				cp++;
284				continue;
285			}
286		} else if (strchr(" \t\n", *cp) == NULL)
287			continue;
288		if (cp - arg) {
289			*cp = '\0';
290			newarg(arg, cnt++);
291		}
292		arg = cp + 1;
293	}
294	newarg(NULL, cnt);
295
296	if (debug) {
297		mesg(MDEBUG, "\nline %d, found %d args:\n", lineno, cnt);
298		for (vec = line_args; *vec; vec++)
299			mesg(MDEBUG, "    \"%s\"\n", *vec);
300	}
301
302	return (cnt);
303}
304
305
306/*
307 * Match leading keyword from a conf line and
308 * return a reference to a config info struct.
309 */
310static cinfo_t *
311get_cinfo(void)
312{
313	cinfo_t *cip, *info = NULL;
314	char *keyword;
315	int chr_diff;
316
317	/*
318	 * Scan the config table for a matching keyword; since the table
319	 * is sorted by keyword strings, a few optimizations can be done:
320	 * first compare only the first byte of the keyword, skip any
321	 * table string that starts with a lower ASCII value, compare the
322	 * full string only when the first byte matches, and stop checking
323	 * if the table string starts with a higher ASCII value.
324	 */
325	keyword = LINEARG(0);
326	for (cip = conftab; cip->keyword; cip++) {
327		chr_diff = (int)(*cip->keyword - *keyword);
328#if 0
329		mesg(MDEBUG, "line %d, ('%c' - '%c') = %d\n",
330		    lineno, *cip->keyword, *line, chr_diff);
331#endif
332		if (chr_diff < 0)
333			continue;
334		else if (chr_diff == 0) {
335			if (strcmp(keyword, cip->keyword) == 0) {
336				info = cip;
337				break;
338			}
339		} else
340			break;
341	}
342	return (info);
343}
344
345
346/*
347 * Find the end of a [possibly continued] conf line
348 * and record the real/lf-delimited line count at *lcnt.
349 */
350static char *
351find_line_end(char *line, int *lcnt)
352{
353	char *next, *lf;
354
355	*lcnt = 0;
356	next = line;
357	while ((lf = strchr(next, '\n')) != NULL) {
358		(*lcnt)++;
359		if (lf == line || (*(lf - 1) != '\\') || *(lf + 1) == '\0')
360			break;
361		next = lf + 1;
362	}
363	return (lf);
364}
365
366
367/*
368 * Parse the named conf file and for each conf line
369 * call the action routine or conftab handler routine.
370 */
371void
372parse_conf_file(char *name, vact_t action, boolean_t first_parse)
373{
374	char *file_buf, *cline, *line, *lend;
375	cinfo_t *cip;
376	int linc, cnt;
377	size_t llen;
378	int dontcare;
379
380	/*
381	 * Do the "implied default" for autoS3, but only before we
382	 * start parsing the first conf file.
383	 */
384	if (first_parse) {
385		(void) S3_helper("S3-support-enable", "S3-support-disable",
386		    PM_ENABLE_S3, PM_DISABLE_S3, "S3-support", "default",
387		    &dontcare, -1);
388	}
389
390	file_buf = get_conf_data(name);
391	mesg(MDEBUG, "\nnow parsing \"%s\"...\n", name);
392
393	lineno = 1;
394	line = file_buf;
395	while ((lend = find_line_end(line, &linc)) != NULL) {
396		/*
397		 * Each line should start with valid data
398		 * but leading white-space can be ignored
399		 */
400		while (line < lend) {
401			if (*line != ' ' && *line != '\t')
402				break;
403			line++;
404		}
405
406		/*
407		 * Copy line into allocated space and null-terminate
408		 * without the trailing line-feed.
409		 */
410		if ((llen = (lend - line)) != 0) {
411			if ((cline = malloc(llen + 1)) == NULL)
412				mesg(MEXIT, "cannot alloc %u bytes "
413				    "for line copy\n", llen);
414			(void) memcpy(cline, line, llen);
415			*(cline + llen) = '\0';
416		} else
417			cline = NULL;
418
419		/*
420		 * For blank and comment lines: possibly show a debug
421		 * message and otherwise ignore them.  For other lines:
422		 * parse into an arg vector and try to match the first
423		 * arg with conftab keywords.  When a match is found,
424		 * check for exact or minimum arg count, and call the
425		 * action or handler routine; if handler does not return
426		 * OKUP, set the referenced update value to NOUP so that
427		 * later CPR or PM updates are skipped.
428		 */
429		if (llen == 0)
430			mesg(MDEBUG, "\nline %d, blank...\n", lineno);
431		else if (*line == '#')
432			mesg(MDEBUG, "\nline %d, comment...\n", lineno);
433		else if ((cnt = build_args(cline, cline + llen)) != 0) {
434			if ((cip = get_cinfo()) == NULL) {
435				mesg(MEXIT, "unrecognized keyword \"%s\"\n",
436				    LINEARG(0));
437			} else if (cnt != cip->argc &&
438			    (cip->any == 0 || cnt < cip->argc)) {
439				mesg(MEXIT, "found %d args, expect %d%s\n",
440				    cnt, cip->argc, cip->any ? "+" : "");
441			} else if (action)
442				(*action)(line, llen + 1, cip);
443			else if (cip->status->perm && (def_src || cip->alt)) {
444				if ((*cip->handler)() != OKUP)
445					cip->status->update = NOUP;
446			} else {
447				mesg(MDEBUG,
448				    "==> handler skipped: %s_perm %d, "
449				    "def_src %d, alt %d\n", cip->status->set,
450				    cip->status->perm, def_src, cip->alt);
451			}
452		}
453
454		if (cline)
455			free(cline);
456		line = lend + 1;
457		lineno += linc;
458	}
459	lineno = 0;
460
461	free(file_buf);
462
463	if (verify) {
464		int ret = ioctl(pm_fd, PM_GET_PM_STATE, NULL);
465		if (ret < 0) {
466			mesg(MDEBUG, "Cannot get PM state: %s\n",
467			    strerror(errno));
468		}
469		switch (ret) {
470		case PM_SYSTEM_PM_ENABLED:
471			mesg(MDEBUG, "Autopm Enabled\n");
472			break;
473		case PM_SYSTEM_PM_DISABLED:
474			mesg(MDEBUG, "Autopm Disabled\n");
475			break;
476		}
477		ret = ioctl(pm_fd, PM_GET_S3_SUPPORT_STATE, NULL);
478		if (ret < 0) {
479			mesg(MDEBUG, "Cannot get PM state: %s\n",
480			    strerror(errno));
481		}
482		switch (ret) {
483		case PM_S3_SUPPORT_ENABLED:
484			mesg(MDEBUG, "S3 support Enabled\n");
485			break;
486		case PM_S3_SUPPORT_DISABLED:
487			mesg(MDEBUG, "S3 support Disabled\n");
488			break;
489		}
490		ret = ioctl(pm_fd, PM_GET_AUTOS3_STATE, NULL);
491		if (ret < 0) {
492			mesg(MDEBUG, "Cannot get PM state: %s\n",
493			    strerror(errno));
494		}
495		switch (ret) {
496		case PM_AUTOS3_ENABLED:
497			mesg(MDEBUG, "AutoS3 Enabled\n");
498			break;
499		case PM_AUTOS3_DISABLED:
500			mesg(MDEBUG, "AutoS3  Disabled\n");
501			break;
502		}
503	}
504}
505