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/*
27 * "pmconfig" performs a mixture of Energy-Star configuration tasks
28 * for both CheckPoint-Resume and Power-Management services.
29 * Tasks include parsing a config file (usually "/etc/power.conf"),
30 * updating CPR and PM config files, and setting various PM options
31 * via ioctl requests.  From the mix, pmconfig should have a more
32 * generalized name similar to "estarconfig".
33 *
34 * OPTIONS:
35 * "-r"		reset CPR and PM options to default and exit.
36 * "-f file"	specify an alternate config file; this is a
37 *		private/non-advertised option used by "dtpower".
38 */
39
40#include "pmconfig.h"
41#include <sys/wait.h>
42#include <signal.h>
43#include <stdarg.h>
44#include <locale.h>
45#include "powerd.h"
46
47
48#define	MCCPY_FIELD(dst, src, field) \
49	(void) memccpy(&dst.field, &src.field, 0, sizeof (dst.field) - 1)
50
51
52static char conf_header[] =
53"#\n"
54"# Copyright 1996-2002 Sun Microsystems, Inc.  All rights reserved.\n"
55"# Use is subject to license terms.\n"
56"#\n"
57"#pragma ident	\"@(#)power.conf	2.1	02/03/04 SMI\"\n"
58"#\n"
59"# Power Management Configuration File\n"
60"#\n\n";
61
62static char *prog;
63static char *cpr_conf = CPR_CONFIG;
64static char tmp_conf[] = "/etc/.tmp.conf.XXXXXX";
65static char orig_conf[] = "/etc/power.conf-Orig";
66static char default_conf[] = "/etc/power.conf";
67static char *power_conf = default_conf;
68static pid_t powerd_pid;
69static prmup_t *checkup;
70static int tmp_fd;
71
72char estar_vers = ESTAR_VNONE;
73int ua_err = 0;
74int debug = 0;
75
76static struct cprconfig disk_cc;
77struct cprconfig new_cc;
78struct stat def_info;
79static int fflag, rflag;
80int pm_fd;
81uid_t ruid;
82int def_src;
83/*
84 * Until we get more graphics driver support, we only enable autopm,
85 * S3 support and autoS3 by default on X86 systems that are on our whitelist.
86 */
87int whitelist_only = 1;
88
89int verify = 0;
90
91
92static void
93cleanup(void)
94{
95	free(line_args);
96	if (access(tmp_conf, F_OK) == 0)
97		(void) unlink(tmp_conf);
98}
99
100
101/*
102 * Multi-purpose message output routine; also exits when
103 * (status == MEXIT), other status is non-fatal.
104 * VARARGS2
105 */
106void
107mesg(int code, char *fmt, ...)
108{
109	va_list vargs;
110
111	/*
112	 * debug is checked once here, avoiding N duplicate checks
113	 * before each MDEBUG caller and unnecessary text dupduplication.
114	 */
115	if (debug == 0) {
116		/*
117		 * If debug is not enabled, skip a debug message;
118		 * lead with the program name for an error message,
119		 * and follow with a filename and line number if an
120		 * error occurs while parsing a conf file.
121		 */
122		if (code == MDEBUG)
123			return;
124		(void) fprintf(stderr, "%s: ", prog);
125		if (lineno)
126			(void) fprintf(stderr,
127			    "\"%s\" line %d, ", power_conf, lineno);
128	}
129
130	va_start(vargs, fmt);
131	(void) vfprintf(stderr, gettext(fmt), vargs);
132	va_end(vargs);
133
134	if (code == MEXIT) {
135		cleanup();
136		exit(MEXIT);
137	}
138}
139
140
141static void
142usage(void)
143{
144	(void) fprintf(stderr, gettext("Usage: %s [-r]\n"), prog);
145	exit(1);
146}
147
148
149/*
150 * Lookup estar version, check if uadmin() service is supported,
151 * and read cpr_config info from disk.
152 */
153static void
154get_cpr_info(void)
155{
156	ssize_t nread;
157	char *err_fmt;
158	int fd;
159
160#ifdef sparc
161	lookup_estar_vers();
162	if (estar_vers == ESTAR_V2)
163		new_cc.is_cpr_default = 1;
164	else if (estar_vers == ESTAR_V3)
165		new_cc.is_autopm_default = 1;
166
167	if (uadmin(A_FREEZE, AD_CHECK, 0) == 0)
168		new_cc.is_cpr_capable = 1;
169	else
170		ua_err = errno;
171
172	if ((fd = open("/dev/tod", O_RDONLY)) != -1) {
173		new_cc.is_autowakeup_capable = 1;
174		(void) close(fd);
175	}
176#endif /* sparc */
177
178	/*
179	 * Read in the cpr conf file.  If any open or read error occurs,
180	 * display an error message only for a non-root user.  The file
181	 * may not exist on a newly installed system.
182	 */
183	err_fmt = "%s %s; please rerun %s as root\n";
184	if ((fd = open(cpr_conf, O_RDONLY)) == -1) {
185		if (ruid)
186			mesg(MEXIT, err_fmt, gettext("cannot open"),
187			    cpr_conf, prog);
188	} else {
189		nread = read(fd, &disk_cc, sizeof (disk_cc));
190		(void) close(fd);
191		if (nread != (ssize_t)sizeof (disk_cc)) {
192			if (ruid)
193				mesg(MEXIT, err_fmt, cpr_conf,
194				    gettext("file corrupted"), prog);
195			else {
196				(void) unlink(cpr_conf);
197				bzero(&disk_cc, sizeof (disk_cc));
198			}
199		}
200	}
201}
202
203
204/*
205 * Unconfigure and reset PM, device is left open for later use.
206 */
207static void
208pm_rem_reset(void)
209{
210	char *err_fmt = NULL;
211
212	if ((pm_fd = open("/dev/pm", O_RDWR)) == -1)
213		err_fmt = "cannot open \"/dev/pm\": %s\n";
214	else if (ioctl(pm_fd, PM_RESET_PM, 0) == -1)
215		err_fmt = "cannot reset pm state: %s\n";
216	if (err_fmt)
217		mesg(MEXIT, err_fmt, strerror(errno));
218}
219
220
221static void
222get_powerd_pid(void)
223{
224	char pidstr[16];
225	int fd;
226
227	if ((fd = open(PIDPATH, O_RDONLY)) == -1)
228		return;
229	bzero(pidstr, sizeof (pidstr));
230	if (read(fd, pidstr, sizeof (pidstr)) > 0) {
231		powerd_pid = atoi(pidstr);
232		mesg(MDEBUG, "got powerd pid %ld\n", powerd_pid);
233	}
234	(void) close(fd);
235}
236
237
238/*
239 * Write revised cprconfig struct to disk based on perms;
240 * returns 1 if any error, otherwise 0.
241 */
242static int
243update_cprconfig(void)
244{
245	struct cprconfig *wrt_cc = &new_cc;
246	char *err_fmt = NULL;
247	int fd;
248
249	if (rflag) {
250		/* For "pmconfig -r" case, copy select cpr-related fields. */
251		new_cc.cf_magic = disk_cc.cf_magic;
252		new_cc.cf_type = disk_cc.cf_type;
253		MCCPY_FIELD(new_cc, disk_cc, cf_path);
254		MCCPY_FIELD(new_cc, disk_cc, cf_fs);
255		MCCPY_FIELD(new_cc, disk_cc, cf_devfs);
256		MCCPY_FIELD(new_cc, disk_cc, cf_dev_prom);
257	}
258
259	if (!pm_status.perm) {
260		if (cpr_status.update == NOUP)
261			return (1);
262		/* save new struct data with old autopm setting */
263		MCCPY_FIELD(new_cc, disk_cc, apm_behavior);
264	} else if (!cpr_status.perm) {
265		if (pm_status.update == NOUP)
266			return (1);
267		/* save original struct with new autopm setting */
268		MCCPY_FIELD(disk_cc, new_cc, apm_behavior);
269		wrt_cc = &disk_cc;
270	} else if (cpr_status.update == NOUP || pm_status.update == NOUP)
271		return (1);
272
273	if ((fd = open(cpr_conf, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1)
274		err_fmt = "cannot open/create \"%s\", %s\n";
275	else if (write(fd, wrt_cc, sizeof (*wrt_cc)) != sizeof (*wrt_cc))
276		err_fmt = "error writing \"%s\", %s\n";
277	if (err_fmt)
278		mesg(MERR, err_fmt, cpr_conf, strerror(errno));
279	if (fd != -1)
280		(void) close(fd);
281	return (err_fmt != NULL);
282}
283
284
285/*
286 * Signal old powerd when there's a valid pid, or start a new one;
287 * returns 1 if any error, otherwise 0.
288 */
289static int
290restart_powerd(void)
291{
292	char *powerd = "/usr/lib/power/powerd";
293	int status = 0;
294	pid_t pid, wp;
295
296	if (powerd_pid > 0) {
297		if (sigsend(P_PID, powerd_pid, SIGHUP) == 0)
298			return (0);
299		else if (errno != ESRCH) {
300			mesg(MERR, "cannot deliver hangup to powerd\n");
301			return (1);
302		}
303	}
304
305	if ((pid = fork()) == NOPID)
306		wp = -1;
307	else if (pid == P_MYPID) {
308		(void) setreuid(0, 0);
309		(void) setregid(0, 0);
310		(void) setgroups(0, NULL);
311		if (debug)
312			(void) execle(powerd, powerd, "-d", NULL, NULL);
313		else
314			(void) execle(powerd, powerd, NULL, NULL);
315		exit(1);
316	} else {
317		do {
318			wp = waitpid(pid, &status, 0);
319		} while (wp == -1 && errno == EINTR);
320	}
321
322	if (wp == -1)
323		mesg(MERR, "could not start %s\n", powerd);
324	return (wp == -1 || status != 0);
325}
326
327
328static void
329save_orig(void)
330{
331	static char *args[] = { "/usr/bin/cp", default_conf, orig_conf, NULL };
332	struct stat stbuf;
333	int pid;
334
335	if (stat(orig_conf, &stbuf) == 0 && stbuf.st_size)
336		return;
337	pid = fork();
338	if (pid == NOPID)
339		return;
340	else if (pid == P_MYPID) {
341		(void) execve(args[0], args, NULL);
342		exit(1);
343	} else
344		(void) waitpid(pid, NULL, 0);
345}
346
347
348static void
349tmp_write(void *buf, size_t len)
350{
351	if (write(tmp_fd, buf, len) != (ssize_t)len)
352		mesg(MEXIT, "error writing tmp file, %s\n", strerror(errno));
353}
354
355
356static void
357tmp_save_line(char *line, size_t len, cinfo_t *cip)
358{
359	if (cip && cip->cmt)
360		tmp_write(cip->cmt, strlen(cip->cmt));
361	tmp_write(line, len);
362}
363
364
365/*
366 * Filter conf lines and write them to the tmp file.
367 */
368static void
369filter(char *line, size_t len, cinfo_t *cip)
370{
371	int selected;
372
373	/*
374	 * Lines from an alt conf file are selected when either:
375	 * cip is NULL (keyword not matched, probably an old-style device),
376	 * OR: it's both OK to accept the conf line (alt) AND either:
377	 * preference is not set (NULL checkup) OR the cpr/pm preference
378	 * (checkup) matches conftab status.
379	 */
380	selected = (cip == NULL || (cip->alt &&
381	    (checkup == NULL || checkup == cip->status)));
382	mesg(MDEBUG, "filter: set \"%s\", selected %d\n",
383	    cip ? cip->status->set : "none", selected);
384	if (selected)
385		tmp_save_line(line, len, cip);
386}
387
388
389/*
390 * Set checkup for conf line selection and parse a conf file with filtering.
391 * When pref is NULL, filter selects all conf lines from the new conf file;
392 * otherwise filter selects only cpr or pm related lines from the new or
393 * default conf file based on cpr or pm perm.
394 */
395static void
396conf_scanner(prmup_t *pref)
397{
398	mesg(MDEBUG, "\nscanning set is %s\n", pref ? pref->set : "both");
399	checkup = pref;
400	parse_conf_file((pref == NULL || pref->perm)
401	    ? power_conf : default_conf, filter, B_FALSE);
402}
403
404
405/*
406 * Search for any non-alt entries, call the handler routine,
407 * and write entries to the tmp file.
408 */
409static void
410search(char *line, size_t len, cinfo_t *cip)
411{
412	int skip;
413
414	skip = (cip == NULL || cip->alt);
415	mesg(MDEBUG, "search: %s\n", skip ? "skipped" : "retained");
416	if (skip)
417		return;
418	if (cip->status->perm)
419		(void) (*cip->handler)();
420	tmp_save_line(line, len, cip);
421}
422
423
424/*
425 * When perm and update status are OK, write a new conf file
426 * and rename to default_conf with the original attributes;
427 * returns 1 if any error, otherwise 0.
428 */
429static int
430write_conf(void)
431{
432	char *name, *err_str = NULL;
433	struct stat stbuf;
434
435	if ((cpr_status.perm && cpr_status.update != OKUP) ||
436	    (pm_status.perm && pm_status.update != OKUP)) {
437		mesg(MDEBUG, "\nconf not written, "
438		    "(cpr perm %d update %d), (pm perm %d update %d)\n",
439		    cpr_status.perm, cpr_status.update,
440		    pm_status.perm, pm_status.update);
441		return (1);
442	}
443
444	save_orig();
445	if ((tmp_fd = mkstemp(tmp_conf)) == -1) {
446		mesg(MERR, "cannot open/create tmp file \"%s\"\n", tmp_conf);
447		return (1);
448	}
449	tmp_write(conf_header, sizeof (conf_header) - 1);
450
451	/*
452	 * When both perms are set, save selected lines from the new file;
453	 * otherwise save selected subsets from the new and default files.
454	 */
455	if (cpr_status.perm && pm_status.perm)
456		conf_scanner(NULL);
457	else {
458		conf_scanner(&cpr_status);
459		conf_scanner(&pm_status);
460	}
461
462	/*
463	 * "dtpower" will craft an alt conf file with modified content from
464	 * /etc/power.conf, but any alt conf file is not a trusted source;
465	 * since some alt conf lines may be skipped, the trusted source is
466	 * searched for those lines to retain their functionality.
467	 */
468	parse_conf_file(default_conf, search, B_FALSE);
469
470	(void) close(tmp_fd);
471
472	if (stat(name = default_conf, &stbuf) == -1)
473		err_str = "stat";
474	else if (chmod(name = tmp_conf, stbuf.st_mode) == -1)
475		err_str = "chmod";
476	else if (chown(tmp_conf, stbuf.st_uid, stbuf.st_gid) == -1)
477		err_str = "chown";
478	else if (rename(tmp_conf, default_conf) == -1)
479		err_str = "rename";
480	else
481		mesg(MDEBUG, "\n\"%s\" renamed to \"%s\"\n",
482		    tmp_conf, default_conf);
483	if (err_str)
484		mesg(MERR, "cannot %s \"%s\", %s\n",
485		    err_str, name, strerror(errno));
486
487	return (err_str != NULL);
488}
489
490
491/* ARGSUSED */
492int
493main(int cnt, char **vec)
494{
495	int rval = 0;
496
497	(void) setlocale(LC_ALL, "");
498	(void) textdomain(TEXT_DOMAIN);
499
500	for (prog = *vec++; *vec && **vec == '-'; vec++) {
501		if (strlen(*vec) > 2)
502			usage();
503		switch (*(*vec + 1)) {
504		case 'd':
505			debug = 1;
506			break;
507		case 'f':
508			fflag = 1;
509			if ((power_conf = *++vec) == NULL)
510				usage();
511			break;
512		case 'r':
513			rflag = 1;
514			break;
515		case 'W':
516			whitelist_only = 0;
517			break;
518		case 'v':
519			verify = 1;
520			break;
521		default:
522			usage();
523			break;
524		}
525	}
526	if (rflag && fflag)
527		usage();
528
529	lookup_perms();
530	mesg(MDEBUG, "ruid %d, perms: cpr %d, pm %d\n",
531	    ruid, cpr_status.perm, pm_status.perm);
532
533	if ((!cpr_status.perm && !pm_status.perm) ||
534	    (rflag && !(cpr_status.perm && pm_status.perm)))
535		mesg(MEXIT, "%s\n", strerror(EACCES));
536	if (rflag == 0 && access(power_conf, R_OK))
537		mesg(MEXIT, "\"%s\" is not readable\n", power_conf);
538
539	get_cpr_info();
540
541	if (pm_status.perm)
542		pm_rem_reset();
543	get_powerd_pid();
544	(void) umask(022);
545	if (rflag)
546		return (update_cprconfig() || restart_powerd());
547	if (stat(default_conf, &def_info) == -1)
548		mesg(MEXIT, "cannot stat %s, %s\n", default_conf,
549		    strerror(errno));
550	new_cc.loadaverage_thold = DFLT_THOLD;
551	parse_conf_file(power_conf, NULL, B_TRUE);
552	if (fflag)
553		rval = write_conf();
554	cleanup();
555	if (pm_status.perm)
556		(void) close(pm_fd);
557	if (rval == 0)
558		rval = (update_cprconfig() || restart_powerd());
559
560	return (rval);
561}
562