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 2015 Gary Mills
23 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#include "pmconfig.h"
28#include <sys/mkdev.h>
29#include <sys/syslog.h>
30#include <sys/openpromio.h>
31#include <sys/mnttab.h>
32#include <sys/vtoc.h>
33#include <sys/efi_partition.h>
34#include <syslog.h>
35#include <stdlib.h>
36#include <sys/pm.h>
37#include <kstat.h>
38#include <sys/smbios.h>
39#include <libzfs.h>
40
41
42#define	STRCPYLIM(dst, src, str) strcpy_limit(dst, src, sizeof (dst), str)
43#define	LASTBYTE(str) (str + strlen(str) - 1)
44
45static char nerr_fmt[] = "number is out of range (%s)\n";
46static char alloc_fmt[] = "cannot allocate space for \"%s\", %s\n";
47static char set_thresh_fmt[] = "error setting threshold(s) for \"%s\", %s\n";
48static char bad_thresh_fmt[] = "bad threshold(s)\n";
49static char stat_fmt[] = "cannot stat \"%s\", %s\n";
50static char always_on[] = "always-on";
51
52#define	PM_DEFAULT_ALGORITHM -1
53/*
54 * When lines in a config file (usually "/etc/power.conf") start with
55 * a recognized keyword, a "handler" routine is called for specific
56 * CPR or PM -related action(s).  Each routine returns a status code
57 * indicating whether all tasks were successful; if any errors occured,
58 * future CPR or PM updates are skipped.  Following are the handler
59 * routines for all keywords:
60 */
61
62
63static char pm_cmd_string[32];
64
65static char *
66pm_map(int cmd)
67{
68	pm_req_t req;
69
70	req.value = cmd;
71	req.data = (void *)pm_cmd_string;
72	req.datasize = sizeof (pm_cmd_string);
73
74	if (ioctl(pm_fd, PM_GET_CMD_NAME, &req) < 0) {
75		perror(gettext("PM_GET_CMD_NAME failed:"));
76		return ("??");
77	}
78	return (pm_cmd_string);
79}
80
81static int
82isonlist(char *listname, const char *man, const char *prod)
83{
84	pm_searchargs_t sl;
85	int ret;
86
87	sl.pms_listname = listname;
88	sl.pms_manufacturer = (char *)man;
89	sl.pms_product = (char *)prod;
90	ret = ioctl(pm_fd, PM_SEARCH_LIST, &sl);
91	mesg(MDEBUG, "PM_SEARCH_LIST %s for %s,%s returns %d\n",
92	    listname, man, prod, ret);
93	return (ret == 0);
94}
95
96static int
97do_ioctl(int ioctl_cmd, char *keyword, char *behavior, int suppress)
98{
99	mesg(MDEBUG, "doing ioctl %s for %s ", pm_map(ioctl_cmd), keyword);
100	if (ioctl(pm_fd, ioctl_cmd, NULL) == -1) {
101		int suppressed = suppress == -1 || suppress == errno;
102		if (!suppressed) {
103			mesg(MERR, "%s %s failed, %s\n", keyword, behavior,
104			    strerror(errno));
105			return (NOUP);
106		} else {
107			mesg(MDEBUG, "%s %s failed, %s\n", keyword, behavior,
108			    strerror(errno));
109			return (OKUP);
110		}
111	}
112	mesg(MDEBUG, "succeeded\n");
113	return (OKUP);
114}
115
116/*
117 * Check for valid cpupm behavior and communicate it to the kernel.
118 */
119int
120cpupm(void)
121{
122	struct bmtoc {
123		char *behavior;
124		char *mode;
125		int cmd;
126		int Errno;
127	};
128
129	static struct bmtoc bmlist[] = {
130		"disable",	"\0",		PM_STOP_CPUPM,		EINVAL,
131		"enable",	"poll-mode",	PM_START_CPUPM_POLL,	EBUSY,
132		"enable",	"event-mode",	PM_START_CPUPM_EV,	EBUSY,
133		"enable",	"\0",		PM_START_CPUPM,		EBUSY,
134		NULL,		0,		0,			0
135	};
136	struct bmtoc *bp;
137	char *behavior;
138	char *mode;
139
140	behavior = LINEARG(1);
141	if ((mode = LINEARG(2)) == NULL)
142		mode = "\0";
143
144	for (bp = bmlist; bp->cmd; bp++) {
145		if (strcmp(behavior, bp->behavior) == 0 &&
146		    strcmp(mode, bp->mode) == 0) {
147			break;
148		}
149	}
150	if (bp->cmd == 0) {
151		if (LINEARG(2) == NULL) {
152			mesg(MERR, "invalid cpupm behavior \"%s\"\n", behavior);
153		} else {
154			mesg(MERR, "invalid cpupm behavior \"%s %s\"\n",
155			    behavior, mode);
156		}
157		return (NOUP);
158	}
159	if (ioctl(pm_fd, bp->cmd, NULL) == -1 && errno != bp->Errno) {
160		mesg(MERR, "cpupm %s failed, %s\n",
161		    behavior, strerror(errno));
162		return (NOUP);
163	}
164	return (OKUP);
165}
166
167/*
168 * Check for valid cpu_deep_idle option and communicate it to the kernel.
169 */
170int
171cpuidle(void)
172{
173	struct btoc {
174		char *behavior;
175		int cmd;
176		int Errno;
177	};
178	static struct btoc blist[] = {
179		"disable",	PM_DISABLE_CPU_DEEP_IDLE, EINVAL,
180		"enable",	PM_ENABLE_CPU_DEEP_IDLE, EBUSY,
181		"default",	PM_DEFAULT_CPU_DEEP_IDLE, EBUSY,
182		NULL,		0, 0
183	};
184	struct btoc *bp;
185	char *behavior;
186
187	for (behavior = LINEARG(1), bp = blist; bp->cmd; bp++) {
188		if (strcmp(behavior, bp->behavior) == 0)
189			break;
190	}
191	if (bp->cmd == 0) {
192		mesg(MERR, "invalid cpu_deep_idle behavior \"%s\"\n", behavior);
193		return (NOUP);
194	}
195	if (ioctl(pm_fd, bp->cmd, NULL) == -1 && errno != bp->Errno) {
196		mesg(MERR, "cpu_deep_idle %s failed, %s\n",
197		    behavior, strerror(errno));
198		return (NOUP);
199	}
200	return (OKUP);
201}
202
203/*
204 * Two decisions are identical except for the list names and ioctl commands
205 * inputs: whitelist, blacklist, yes, no
206 * if (! ("S3" kstat exists))
207 *	return (no)
208 * if (SystemInformation.Manufacturer == "Sun Microsystems" &&
209 *    (Pref_PM_Profile == Workstation || Pref_PM_Profile == Desktop)) {
210 *	if (platform on blacklist)
211 *		return (no)
212 *	return (yes)
213 * } else {
214 *	if (platform on whitelist)
215 *		return (yes)
216 *	return (no)
217 * }
218 */
219
220int
221S3_helper(char *whitelist, char *blacklist, int yes, int no, char *keyword,
222	char *behavior, int *didyes, int suppress)
223{
224	int oflags = SMB_O_NOCKSUM | SMB_O_NOVERS;
225	smbios_hdl_t *shp;
226	smbios_system_t sys;
227	id_t id;
228	int ret;
229	kstat_ctl_t *kc;
230	kstat_t *ksp;
231	kstat_named_t *dp;
232	smbios_info_t info;
233	int preferred_pm_profile = 0;
234	char yesstr[32], nostr[32];	/* DEBUG */
235
236	*didyes = 0;
237
238	(void) strncpy(yesstr, pm_map(yes), sizeof (yesstr));
239	(void) strncpy(nostr, pm_map(no), sizeof (nostr));
240	mesg(MDEBUG, "S3_helper(%s, %s, %s, %s, %s, %s)\n", whitelist,
241	    blacklist, yesstr, nostr, keyword, behavior);
242	if ((kc = kstat_open()) == NULL) {
243		mesg(MDEBUG, "kstat_open failed\n");
244		return (OKUP);
245	}
246	ksp = kstat_lookup(kc, "acpi", -1, "acpi");
247	if (ksp == NULL) {
248		mesg(MDEBUG, "kstat_lookup 'acpi', -1, 'acpi' failed\n");
249		(void) kstat_close(kc);
250		return (OKUP);
251	}
252	(void) kstat_read(kc, ksp,  NULL);
253	dp = kstat_data_lookup(ksp, "S3");
254	if (dp == NULL || dp->value.l == 0) {
255		mesg(MDEBUG, "kstat_data_lookup 'S3' fails\n");
256		if (dp != NULL)
257			mesg(MDEBUG, "value.l %lx\n", dp->value.l);
258		(void) kstat_close(kc);
259		return (do_ioctl(no, keyword, behavior, suppress));
260	}
261	mesg(MDEBUG, "kstat indicates S3 support (%lx)\n", dp->value.l);
262
263	if (!whitelist_only) {
264		/*
265		 * We still have an ACPI ksp, search it again for
266		 * 'preferred_pm_profile' (needs to be valid if we don't
267		 * aren't only using a whitelist).
268		 */
269		dp = kstat_data_lookup(ksp, "preferred_pm_profile");
270		if (dp == NULL) {
271			mesg(MDEBUG, "kstat_data_lookup 'ppmp fails\n");
272			(void) kstat_close(kc);
273			return (do_ioctl(no, keyword, behavior, suppress));
274		}
275		mesg(MDEBUG, "kstat indicates preferred_pm_profile is %lx\n",
276		    dp->value.l);
277		preferred_pm_profile = dp->value.l;
278	}
279	(void) kstat_close(kc);
280
281	if ((shp = smbios_open(NULL,
282	    SMB_VERSION, oflags, &ret)) == NULL) {
283		/* we promised not to complain */
284		/* we bail leaving it to the kernel default */
285		mesg(MDEBUG, "smbios_open failed %d\n", errno);
286		return (OKUP);
287	}
288	if ((id = smbios_info_system(shp, &sys)) == SMB_ERR) {
289		mesg(MDEBUG, "smbios_info_system failed %d\n", errno);
290		smbios_close(shp);
291		return (OKUP);
292	}
293	if (smbios_info_common(shp, id, &info) == SMB_ERR) {
294		mesg(MDEBUG, "smbios_info_common failed %d\n", errno);
295		smbios_close(shp);
296		return (OKUP);
297	}
298	mesg(MDEBUG, "Manufacturer: %s\n", info.smbi_manufacturer);
299	mesg(MDEBUG, "Product: %s\n", info.smbi_product);
300	smbios_close(shp);
301
302	if (!whitelist_only) {
303#define	PPP_DESKTOP 1
304#define	PPP_WORKSTATION 3
305		if (strcmp(info.smbi_manufacturer, "Sun Microsystems") == 0 &&
306		    (preferred_pm_profile == PPP_DESKTOP ||
307		    preferred_pm_profile == PPP_WORKSTATION)) {
308			if (isonlist(blacklist,
309			    info.smbi_manufacturer, info.smbi_product)) {
310				return (do_ioctl(no, keyword, behavior,
311				    suppress));
312			} else {
313				ret = do_ioctl(yes, keyword, behavior,
314				    suppress);
315				*didyes = (ret == OKUP);
316				return (ret);
317			}
318		}
319	}
320	if (isonlist(whitelist,
321	    info.smbi_manufacturer, info.smbi_product)) {
322		ret = do_ioctl(yes, keyword, behavior, suppress);
323		*didyes = (ret == OKUP);
324		return (ret);
325	} else {
326		return (do_ioctl(no, keyword, behavior, suppress));
327	}
328}
329
330int
331S3sup(void)	/* S3-support keyword handler */
332{
333	struct btoc {
334		char *behavior;
335		int cmd;
336	};
337	static struct btoc blist[] = {
338		"default",	PM_DEFAULT_ALGORITHM,
339		"enable",	PM_ENABLE_S3,
340		"disable",	PM_DISABLE_S3,
341		NULL,		0
342	};
343	struct btoc *bp;
344	char *behavior;
345	int dontcare;
346
347	for (behavior = LINEARG(1), bp = blist; bp->cmd; bp++) {
348		if (strcmp(behavior, bp->behavior) == 0)
349			break;
350	}
351	if (bp->cmd == 0) {
352		mesg(MERR, "invalid S3-support behavior \"%s\"\n", behavior);
353		return (NOUP);
354	}
355
356
357	switch (bp->cmd) {
358
359	case PM_ENABLE_S3:
360	case PM_DISABLE_S3:
361		return (do_ioctl(bp->cmd, "S3-support", behavior, EBUSY));
362
363	case PM_DEFAULT_ALGORITHM:
364		/*
365		 * we suppress errors in the "default" case because we
366		 * already did an invisible default call, so we know we'll
367		 * get EBUSY
368		 */
369		return (S3_helper("S3-support-enable", "S3-support-disable",
370		    PM_ENABLE_S3, PM_DISABLE_S3, "S3-support", behavior,
371		    &dontcare, EBUSY));
372
373	default:
374		mesg(MERR, "S3-support %s failed, %s\n", behavior,
375		    strerror(errno));
376		return (NOUP);
377	}
378}
379
380/*
381 * Check for valid autoS3 behavior and save after ioctl success.
382 */
383int
384autoS3(void)
385{
386	struct btoc {
387		char *behavior;
388		int cmd;
389	};
390	static struct btoc blist[] = {
391		"default",	PM_DEFAULT_ALGORITHM,
392		"disable",	PM_STOP_AUTOS3,
393		"enable",	PM_START_AUTOS3,
394		NULL,		0
395	};
396	struct btoc *bp;
397	char *behavior;
398	int dontcare;
399
400	for (behavior = LINEARG(1), bp = blist; bp->cmd; bp++) {
401		if (strcmp(behavior, bp->behavior) == 0)
402			break;
403	}
404	if (bp->cmd == 0) {
405		mesg(MERR, "invalid autoS3 behavior \"%s\"\n", behavior);
406		return (NOUP);
407	}
408
409	switch (bp->cmd) {
410	default:
411		mesg(MERR, "autoS3 %s failed, %s\n",
412		    behavior, strerror(errno));
413		mesg(MDEBUG, "unknown command\n", bp->cmd);
414		return (OKUP);
415
416	case PM_STOP_AUTOS3:
417	case PM_START_AUTOS3:
418		return (do_ioctl(bp->cmd, "autoS3", behavior, EBUSY));
419
420	case PM_DEFAULT_ALGORITHM:
421		return (S3_helper("S3-autoenable", "S3-autodisable",
422		    PM_START_AUTOS3, PM_STOP_AUTOS3, "autoS3", behavior,
423		    &dontcare, EBUSY));
424	}
425}
426
427
428/*
429 * Check for valid autopm behavior and save after ioctl success.
430 */
431int
432autopm(void)
433{
434	struct btoc {
435		char *behavior;
436		int cmd, Errno, isdef;
437	};
438	static struct btoc blist[] = {
439		"default",	PM_START_PM,	-1,	1,
440		"disable",	PM_STOP_PM,	EINVAL,	0,
441		"enable",	PM_START_PM,	EBUSY,	0,
442		NULL,		0,		0,	0,
443	};
444	struct btoc *bp;
445	char *behavior;
446
447	for (behavior = LINEARG(1), bp = blist; bp->cmd; bp++) {
448		if (strcmp(behavior, bp->behavior) == 0)
449			break;
450	}
451	if (bp->cmd == 0) {
452		mesg(MERR, "invalid autopm behavior \"%s\"\n", behavior);
453		return (NOUP);
454	}
455
456	/*
457	 * for "default" behavior, do not enable autopm if not ESTAR_V3
458	 */
459#if defined(__sparc)
460	if (!bp->isdef || (estar_vers == ESTAR_V3)) {
461		if (ioctl(pm_fd, bp->cmd, NULL) == -1 && errno != bp->Errno) {
462			mesg(MERR, "autopm %s failed, %s\n",
463			    behavior, strerror(errno));
464			return (NOUP);
465		}
466	}
467	(void) strcpy(new_cc.apm_behavior, behavior);
468	return (OKUP);
469#endif
470#if defined(__x86)
471	if (!bp->isdef) {
472		if (ioctl(pm_fd, bp->cmd, NULL) == -1 && errno != bp->Errno) {
473			mesg(MERR, "autopm %s failed, %s\n",
474			    behavior, strerror(errno));
475			return (NOUP);
476		}
477		mesg(MDEBUG, "autopm %s succeeded\n", behavior);
478
479		return (OKUP);
480	} else {
481		int didenable;
482		int ret = S3_helper("autopm-enable", "autopm-disable",
483		    PM_START_PM, PM_STOP_PM, "autopm", behavior, &didenable,
484		    bp->Errno);
485		if (didenable) {
486			/* tell powerd to attach all devices */
487			new_cc.is_autopm_default = 1;
488			(void) strcpy(new_cc.apm_behavior, behavior);
489		}
490		return (ret);
491	}
492#endif
493}
494
495
496static int
497gethm(char *src, int *hour, int *min)
498{
499	if (sscanf(src, "%d:%d", hour, min) != 2) {
500		mesg(MERR, "bad time format (%s)\n", src);
501		return (-1);
502	}
503	return (0);
504}
505
506
507static void
508strcpy_limit(char *dst, char *src, size_t limit, char *info)
509{
510	if (strlcpy(dst, src, limit) >= limit)
511		mesg(MEXIT, "%s is too long (%s)\n", info, src);
512}
513
514
515/*
516 * Convert autoshutdown idle and start/finish times;
517 * check and record autoshutdown behavior.
518 */
519int
520autosd(void)
521{
522	char **bp, *behavior;
523	char *unrec = gettext("unrecognized autoshutdown behavior");
524	static char *blist[] = {
525		"autowakeup", "default", "noshutdown",
526		"shutdown", "unconfigured", NULL
527	};
528
529	new_cc.as_idle = atoi(LINEARG(1));
530	if (gethm(LINEARG(2), &new_cc.as_sh, &new_cc.as_sm) ||
531	    gethm(LINEARG(3), &new_cc.as_fh, &new_cc.as_fm))
532		return (NOUP);
533	mesg(MDEBUG, "idle %d, start %d:%02d, finish %d:%02d\n",
534	    new_cc.as_idle, new_cc.as_sh, new_cc.as_sm,
535	    new_cc.as_fh, new_cc.as_fm);
536
537	for (behavior = LINEARG(4), bp = blist; *bp; bp++) {
538		if (strcmp(behavior, *bp) == 0)
539			break;
540	}
541	if (*bp == NULL) {
542		mesg(MERR, "%s: \"%s\"\n", unrec, behavior);
543		return (NOUP);
544	}
545	STRCPYLIM(new_cc.as_behavior, *bp, unrec);
546	return (OKUP);
547}
548
549
550/*
551 * Check for a real device and try to resolve to a full path.
552 * The orig/resolved path may be modified into a prom pathname,
553 * and an allocated copy of the result is stored at *destp;
554 * the caller will need to free that space.  Returns 1 for any
555 * error, otherwise 0; also sets *errp after an alloc error.
556 */
557static int
558devpath(char **destp, char *src, int *errp)
559{
560	struct stat stbuf;
561	char buf[PATH_MAX];
562	char *cp, *dstr;
563	int devok, dcs = 0;
564	size_t len;
565
566	/*
567	 * When there's a real device, try to resolve the path
568	 * and trim the leading "/devices" component.
569	 */
570	if ((devok = (stat(src, &stbuf) == 0 && stbuf.st_rdev)) != 0) {
571		if (realpath(src, buf) == NULL) {
572			mesg(MERR, "realpath cannot resolve \"%s\"\n",
573			    src, strerror(errno));
574			return (1);
575		}
576		src = buf;
577		dstr = "/devices";
578		len = strlen(dstr);
579		dcs = (strncmp(src, dstr, len) == 0);
580		if (dcs)
581			src += len;
582	} else
583		mesg(MDEBUG, stat_fmt, src, strerror(errno));
584
585	/*
586	 * When the path has ":anything", display an error for
587	 * a non-device or truncate a resolved+modifed path.
588	 */
589	if ((cp = strchr(src, ':')) != NULL) {
590		if (devok == 0) {
591			mesg(MERR, "physical path may not contain "
592			    "a minor string (%s)\n", src);
593			return (1);
594		} else if (dcs)
595			*cp = '\0';
596	}
597
598	if ((*destp = strdup(src)) == NULL) {
599		*errp = NOUP;
600		mesg(MERR, alloc_fmt, src, strerror(errno));
601	}
602	return (*destp == NULL);
603}
604
605
606/*
607 * Call pm ioctl request(s) to set property/device dependencies.
608 */
609static int
610dev_dep_common(int isprop)
611{
612	int cmd, argn, upval = OKUP;
613	char *src, *first, **destp;
614	pm_req_t pmreq;
615
616	bzero(&pmreq, sizeof (pmreq));
617	src = LINEARG(1);
618	if (isprop) {
619		cmd = PM_ADD_DEPENDENT_PROPERTY;
620		first = NULL;
621		pmreq.pmreq_kept = src;
622	} else {
623		cmd = PM_ADD_DEPENDENT;
624		if (devpath(&first, src, &upval))
625			return (upval);
626		pmreq.pmreq_kept = first;
627	}
628	destp = &pmreq.pmreq_keeper;
629
630	/*
631	 * Now loop through any dependents.
632	 */
633	for (argn = 2; (src = LINEARG(argn)) != NULL; argn++) {
634		if (devpath(destp, src, &upval)) {
635			if (upval != OKUP)
636				return (upval);
637			break;
638		}
639		if ((upval = ioctl(pm_fd, cmd, &pmreq)) == -1) {
640			mesg(MDEBUG, "pm ioctl, cmd %d, errno %d\n"
641			    "kept \"%s\", keeper \"%s\"\n",
642			    cmd, errno, pmreq.pmreq_kept, pmreq.pmreq_keeper);
643			mesg(MERR, "cannot set \"%s\" dependency "
644			    "for \"%s\", %s\n", pmreq.pmreq_keeper,
645			    pmreq.pmreq_kept, strerror(errno));
646		}
647		free(*destp);
648		*destp = NULL;
649		if (upval != OKUP)
650			break;
651	}
652
653	free(first);
654	return (upval);
655}
656
657
658int
659ddprop(void)
660{
661	return (dev_dep_common(1));
662}
663
664
665int
666devdep(void)
667{
668	return (dev_dep_common(0));
669}
670
671
672/*
673 * Convert a numeric string (with a possible trailing scaling byte)
674 * into an integer.  Returns a converted value and *nerrp unchanged,
675 * or 0 with *nerrp set to 1 for a conversion error.
676 */
677static int
678get_scaled_value(char *str, int *nerrp)
679{
680	longlong_t svalue = 0, factor = 1;
681	char *sp;
682
683	errno = 0;
684	svalue = strtol(str, &sp, 0);
685	if (errno || (*str != '-' && (*str < '0' || *str > '9')))
686		*nerrp = 1;
687	else if (sp && *sp != '\0') {
688		if (*sp == 'h')
689			factor = 3600;
690		else if (*sp == 'm')
691			factor = 60;
692		else if (*sp != 's')
693			*nerrp = 1;
694	}
695	/* any bytes following sp are ignored */
696
697	if (*nerrp == 0) {
698		svalue *= factor;
699		if (svalue < INT_MIN || svalue > INT_MAX)
700			*nerrp = 1;
701	}
702	if (*nerrp)
703		mesg(MERR, nerr_fmt, str);
704	mesg(MDEBUG, "got scaled value %d\n", (int)svalue);
705	return ((int)svalue);
706}
707
708
709/*
710 * Increment the count of threshold values,
711 * reallocate *vlistp and append another element.
712 * Returns 1 on error, otherwise 0.
713 */
714static int
715vlist_append(int **vlistp, int *vcntp, int value)
716{
717	(*vcntp)++;
718	if ((*vlistp = realloc(*vlistp, *vcntp * sizeof (**vlistp))) != NULL)
719		*(*vlistp + *vcntp - 1) = value;
720	else
721		mesg(MERR, alloc_fmt, "threshold list", strerror(errno));
722	return (*vlistp == NULL);
723}
724
725
726/*
727 * Convert a single threshold string or paren groups of thresh's as
728 * described below.  All thresh's are saved to an allocated list at
729 * *vlistp; the caller will need to free that space.  On return:
730 * *vcntp is the count of the vlist array, and vlist is either
731 * a single thresh or N groups of thresh's with a trailing zero:
732 * (cnt_1 thr_1a thr_1b [...]) ... (cnt_N thr_Na thr_Nb [...]) 0.
733 * Returns 0 when all conversions were OK, and 1 for any syntax,
734 * conversion, or alloc error.
735 */
736static int
737get_thresh(int **vlistp, int *vcntp)
738{
739	int argn, value, gci = 0, grp_cnt = 0, paren = 0, nerr = 0;
740	char *rp, *src;
741
742	for (argn = 2; (src = LINEARG(argn)) != NULL; argn++) {
743		if (*src == LPAREN) {
744			gci = *vcntp;
745			if ((nerr = vlist_append(vlistp, vcntp, 0)) != 0)
746				break;
747			paren = 1;
748			src++;
749		}
750		if (*(rp = LASTBYTE(src)) == RPAREN) {
751			if (paren) {
752				grp_cnt = *vcntp - gci;
753				*(*vlistp + gci) = grp_cnt;
754				paren = 0;
755				*rp = '\0';
756			} else {
757				nerr = 1;
758				break;
759			}
760		}
761
762		value = get_scaled_value(src, &nerr);
763		if (nerr || (nerr = vlist_append(vlistp, vcntp, value)))
764			break;
765	}
766
767	if (nerr == 0 && grp_cnt)
768		nerr = vlist_append(vlistp, vcntp, 0);
769	return (nerr);
770}
771
772
773/*
774 * Set device thresholds from (3) formats:
775 * 	path	"always-on"
776 * 	path	time-spec: [0-9]+[{h,m,s}]
777 *	path	(ts1 ts2 ...)+
778 */
779int
780devthr(void)
781{
782	int cmd, upval = OKUP, nthresh = 0, *vlist = NULL;
783	pm_req_t pmreq;
784
785	bzero(&pmreq, sizeof (pmreq));
786	if (devpath(&pmreq.physpath, LINEARG(1), &upval))
787		return (upval);
788
789	if (strcmp(LINEARG(2), always_on) == 0) {
790		cmd = PM_SET_DEVICE_THRESHOLD;
791		pmreq.value = INT_MAX;
792	} else if (get_thresh(&vlist, &nthresh)) {
793		mesg(MERR, bad_thresh_fmt);
794		upval = NOUP;
795	} else if (nthresh == 1) {
796		pmreq.value = *vlist;
797		cmd = PM_SET_DEVICE_THRESHOLD;
798	} else {
799		pmreq.data = vlist;
800		pmreq.datasize = (nthresh * sizeof (*vlist));
801		cmd = PM_SET_COMPONENT_THRESHOLDS;
802	}
803
804	if (upval != NOUP && (upval = ioctl(pm_fd, cmd, &pmreq)) == -1)
805		mesg(MERR, set_thresh_fmt, pmreq.physpath, strerror(errno));
806
807	free(vlist);
808	free(pmreq.physpath);
809	return (upval);
810}
811
812
813static int
814scan_int(char *src, int *dst)
815{
816	long lval;
817
818	errno = 0;
819
820	lval = strtol(LINEARG(1), NULL, 0);
821	if (errno || lval > INT_MAX || lval < 0) {
822		mesg(MERR, nerr_fmt, src);
823		return (NOUP);
824	}
825
826	*dst = (int)lval;
827	return (OKUP);
828}
829
830static int
831scan_float(char *src, float *dst)
832{
833	float fval;
834
835	errno = 0;
836
837	fval = strtof(src, NULL);
838	if (errno || fval < 0.0) {
839		mesg(MERR, nerr_fmt, src);
840		return (NOUP);
841	}
842
843	*dst = fval;
844	return (OKUP);
845}
846
847
848int
849dreads(void)
850{
851	return (scan_int(LINEARG(1), &new_cc.diskreads_thold));
852}
853
854
855/*
856 * Set pathname for idlecheck;
857 * an overflowed pathname is treated as a fatal error.
858 */
859int
860idlechk(void)
861{
862	STRCPYLIM(new_cc.idlecheck_path, LINEARG(1), "idle path");
863	return (OKUP);
864}
865
866
867int
868loadavg(void)
869{
870	return (scan_float(LINEARG(1), &new_cc.loadaverage_thold));
871}
872
873
874int
875nfsreq(void)
876{
877	return (scan_int(LINEARG(1), &new_cc.nfsreqs_thold));
878}
879
880#ifdef sparc
881static char open_fmt[] = "cannot open \"%s\", %s\n";
882
883/*
884 * Verify the filesystem type for a regular statefile is "ufs"
885 * or verify a block device is not in use as a mounted filesystem.
886 * Returns 1 if any error, otherwise 0.
887 */
888static int
889check_mount(char *sfile, dev_t sfdev, int ufs)
890{
891	char *src, *err_fmt = NULL, *mnttab = MNTTAB;
892	int rgent, match = 0;
893	struct mnttab zroot = { 0 };
894	struct mnttab entry;
895	struct extmnttab ent;
896	FILE *fp;
897
898	if ((fp = fopen(mnttab, "r")) == NULL) {
899		mesg(MERR, open_fmt, mnttab, strerror(errno));
900		return (1);
901	}
902
903	if (ufs) {
904		zroot.mnt_mountp = "/";
905		zroot.mnt_fstype = "zfs";
906		if (getmntany(fp, &entry, &zroot) == 0) {
907			err_fmt = "ufs statefile with zfs root is not"
908			    " supported\n";
909			mesg(MERR, err_fmt, sfile);
910			(void) fclose(fp);
911			return (1);
912		}
913		resetmnttab(fp);
914	}
915	/*
916	 * Search for a matching dev_t;
917	 * ignore non-ufs filesystems for a regular statefile.
918	 */
919	while ((rgent = getextmntent(fp, &ent, sizeof (ent))) != -1) {
920		if (rgent > 0) {
921			mesg(MERR, "error reading \"%s\"\n", mnttab);
922			(void) fclose(fp);
923			return (1);
924		} else if (ufs && strcmp(ent.mnt_fstype, "ufs"))
925			continue;
926		else if (makedev(ent.mnt_major, ent.mnt_minor) == sfdev) {
927			match = 1;
928			break;
929		}
930	}
931
932	/*
933	 * No match is needed for a block device statefile,
934	 * a match is needed for a regular statefile.
935	 */
936	if (match == 0) {
937		if (new_cc.cf_type != CFT_UFS)
938			STRCPYLIM(new_cc.cf_devfs, sfile, "block statefile");
939		else
940			err_fmt = "cannot find ufs mount point for \"%s\"\n";
941	} else if (new_cc.cf_type == CFT_UFS) {
942		STRCPYLIM(new_cc.cf_fs, ent.mnt_mountp, "mnt entry");
943		STRCPYLIM(new_cc.cf_devfs, ent.mnt_special, "mnt special");
944		while (*(sfile + 1) == '/') sfile++;
945		src = sfile + strlen(ent.mnt_mountp);
946		while (*src == '/') src++;
947		STRCPYLIM(new_cc.cf_path, src, "statefile path");
948	} else
949		err_fmt = "statefile device \"%s\" is a mounted filesystem\n";
950	(void) fclose(fp);
951	if (err_fmt)
952		mesg(MERR, err_fmt, sfile);
953	return (err_fmt != NULL);
954}
955
956
957/*
958 * Convert a Unix device to a prom device and save on success,
959 * log any ioctl/conversion error.
960 */
961static int
962utop(char *fs_name, char *prom_name)
963{
964	union obpbuf {
965		char	buf[OBP_MAXPATHLEN + sizeof (uint_t)];
966		struct	openpromio oppio;
967	};
968	union obpbuf oppbuf;
969	struct openpromio *opp;
970	char *promdev = "/dev/openprom";
971	int fd, upval;
972
973	if ((fd = open(promdev, O_RDONLY)) == -1) {
974		mesg(MERR, open_fmt, promdev, strerror(errno));
975		return (NOUP);
976	}
977
978	opp = &oppbuf.oppio;
979	opp->oprom_size = OBP_MAXPATHLEN;
980	strcpy_limit(opp->oprom_array, fs_name,
981	    OBP_MAXPATHLEN, "statefile device");
982	upval = ioctl(fd, OPROMDEV2PROMNAME, opp);
983	(void) close(fd);
984	if (upval == OKUP) {
985		strcpy_limit(prom_name, opp->oprom_array, OBP_MAXPATHLEN,
986		    "prom device");
987	} else {
988		openlog("pmconfig", 0, LOG_DAEMON);
989		syslog(LOG_NOTICE,
990		    gettext("cannot convert \"%s\" to prom device"),
991		    fs_name);
992		closelog();
993	}
994
995	return (upval);
996}
997
998/*
999 * given the path to a zvol, return the cXtYdZ name
1000 * returns < 0 on error, 0 if it isn't a zvol, > 1 on success
1001 */
1002static int
1003ztop(char *arg, char *diskname)
1004{
1005	zpool_handle_t *zpool_handle;
1006	nvlist_t *config, *nvroot;
1007	nvlist_t **child;
1008	uint_t children;
1009	libzfs_handle_t *lzfs;
1010	char *vname;
1011	char *p;
1012	char pool_name[MAXPATHLEN];
1013
1014	if (strncmp(arg, "/dev/zvol/dsk/", 14)) {
1015		return (0);
1016	}
1017	arg += 14;
1018	(void) strncpy(pool_name, arg, MAXPATHLEN);
1019	if ((p = strchr(pool_name, '/')) != NULL)
1020		*p = '\0';
1021	STRCPYLIM(new_cc.cf_fs, p + 1, "statefile path");
1022
1023	if ((lzfs = libzfs_init()) == NULL) {
1024		mesg(MERR, "failed to initialize ZFS library\n");
1025		return (-1);
1026	}
1027	if ((zpool_handle = zpool_open(lzfs, pool_name)) == NULL) {
1028		mesg(MERR, "couldn't open pool '%s'\n", pool_name);
1029		libzfs_fini(lzfs);
1030		return (-1);
1031	}
1032	config = zpool_get_config(zpool_handle, NULL);
1033	if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
1034	    &nvroot) != 0) {
1035		zpool_close(zpool_handle);
1036		libzfs_fini(lzfs);
1037		return (-1);
1038	}
1039	verify(nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
1040	    &child, &children) == 0);
1041	if (children != 1) {
1042		mesg(MERR, "expected one vdev, got %d\n", children);
1043		zpool_close(zpool_handle);
1044		libzfs_fini(lzfs);
1045		return (-1);
1046	}
1047	vname = zpool_vdev_name(lzfs, zpool_handle, child[0], B_FALSE);
1048	if (vname == NULL) {
1049		mesg(MERR, "couldn't determine vdev name\n");
1050		zpool_close(zpool_handle);
1051		libzfs_fini(lzfs);
1052		return (-1);
1053	}
1054	(void) strcpy(diskname, "/dev/dsk/");
1055	(void) strcat(diskname, vname);
1056	free(vname);
1057	zpool_close(zpool_handle);
1058	libzfs_fini(lzfs);
1059	return (1);
1060}
1061
1062/*
1063 * returns NULL if the slice is good (e.g. does not start at block
1064 * zero, or a string describing the error if it doesn't
1065 */
1066static boolean_t
1067is_good_slice(char *sfile, char **err)
1068{
1069	int fd, rc;
1070	struct vtoc vtoc;
1071	dk_gpt_t *gpt;
1072	char rdskname[MAXPATHLEN];
1073	char *x, *y;
1074
1075	*err = NULL;
1076	/* convert from dsk to rdsk */
1077	STRCPYLIM(rdskname, sfile, "disk name");
1078	x = strstr(rdskname, "dsk/");
1079	y = strstr(sfile, "dsk/");
1080	if (x != NULL) {
1081		*x++ = 'r';
1082		(void) strcpy(x, y);
1083	}
1084
1085	if ((fd = open(rdskname, O_RDONLY)) == -1) {
1086		*err = "could not open '%s'\n";
1087	} else if ((rc = read_vtoc(fd, &vtoc)) >= 0) {
1088		/*
1089		 * we got a slice number; now check the block
1090		 * number where the slice starts
1091		 */
1092		if (vtoc.v_part[rc].p_start < 2)
1093			*err = "using '%s' would clobber the disk label\n";
1094		(void) close(fd);
1095		return (*err ? B_FALSE : B_TRUE);
1096	} else if ((rc == VT_ENOTSUP) &&
1097	    (efi_alloc_and_read(fd, &gpt)) >= 0) {
1098		/* EFI slices don't clobber the disk label */
1099		free(gpt);
1100		(void) close(fd);
1101		return (B_TRUE);
1102	} else
1103		*err = "could not read partition table from '%s'\n";
1104	return (B_FALSE);
1105}
1106
1107/*
1108 * Check for a valid statefile pathname, inode and mount status.
1109 */
1110int
1111sfpath(void)
1112{
1113	static int statefile;
1114	char *err_fmt = NULL;
1115	char *sfile, *sp, ch;
1116	char diskname[256];
1117	struct stat stbuf;
1118	int dir = 0;
1119	dev_t dev = NODEV;
1120
1121	if (statefile) {
1122		mesg(MERR, "ignored redundant statefile entry\n");
1123		return (OKUP);
1124	} else if (ua_err) {
1125		if (ua_err != ENOTSUP)
1126			mesg(MERR, "uadmin(A_FREEZE, A_CHECK, 0): %s\n",
1127			    strerror(ua_err));
1128		return (NOUP);
1129	}
1130
1131	/*
1132	 * Check for an absolute path and trim any trailing '/'.
1133	 */
1134	sfile = LINEARG(1);
1135	if (*sfile != '/') {
1136		mesg(MERR, "statefile requires an absolute path\n");
1137		return (NOUP);
1138	}
1139	for (sp = sfile + strlen(sfile) - 1; sp > sfile && *sp == '/'; sp--)
1140		*sp = '\0';
1141
1142	/*
1143	 * If the statefile doesn't exist, the leading path must be a dir.
1144	 */
1145	if (stat(sfile, &stbuf) == -1) {
1146		if (errno == ENOENT) {
1147			dir = 1;
1148			if ((sp = strrchr(sfile, '/')) == sfile)
1149				sp++;
1150			ch = *sp;
1151			*sp = '\0';
1152			if (stat(sfile, &stbuf) == -1)
1153				err_fmt = stat_fmt;
1154			*sp = ch;
1155		} else
1156			err_fmt = stat_fmt;
1157		if (err_fmt) {
1158			mesg(MERR, err_fmt, sfile, strerror(errno));
1159			return (NOUP);
1160		}
1161	}
1162
1163	/*
1164	 * Check for regular/dir/block types, set cf_type and dev.
1165	 */
1166	if (S_ISREG(stbuf.st_mode) || (dir && S_ISDIR(stbuf.st_mode))) {
1167		new_cc.cf_type = CFT_UFS;
1168		dev = stbuf.st_dev;
1169	} else if (S_ISBLK(stbuf.st_mode)) {
1170		if (is_good_slice(sfile, &err_fmt)) {
1171			switch (ztop(sfile, diskname)) {
1172				case 1:
1173					new_cc.cf_type = CFT_ZVOL;
1174					break;
1175				case 0:
1176					new_cc.cf_type = CFT_SPEC;
1177					break;
1178				case -1:
1179				default:
1180					return (NOUP);
1181			}
1182			dev = stbuf.st_rdev;
1183		}
1184	} else
1185		err_fmt = "bad file type for \"%s\"\n"
1186		    "statefile must be a regular file or block device\n";
1187	if (err_fmt) {
1188		mesg(MERR, err_fmt, sfile);
1189		return (NOUP);
1190	}
1191	if (check_mount(sfile, dev, (new_cc.cf_type == CFT_UFS)))
1192		return (NOUP);
1193	if (new_cc.cf_type == CFT_ZVOL) {
1194		if (utop(diskname, new_cc.cf_dev_prom))
1195			return (NOUP);
1196	} else if (utop(new_cc.cf_devfs, new_cc.cf_dev_prom)) {
1197		return (NOUP);
1198	}
1199	new_cc.cf_magic = CPR_CONFIG_MAGIC;
1200	statefile = 1;
1201	return (OKUP);
1202}
1203#endif /* sparc */
1204
1205
1206/*
1207 * Common function to set a system or cpu threshold.
1208 */
1209static int
1210cmnthr(int req)
1211{
1212	int value, nerr = 0, upval = OKUP;
1213	char *thresh = LINEARG(1);
1214
1215	if (strcmp(thresh, always_on) == 0)
1216		value = INT_MAX;
1217	else if ((value = get_scaled_value(thresh, &nerr)) < 0 || nerr) {
1218		mesg(MERR, "%s must be a positive value\n", LINEARG(0));
1219		upval = NOUP;
1220	}
1221	if (upval == OKUP)
1222		(void) ioctl(pm_fd, req, value);
1223	return (upval);
1224}
1225
1226
1227/*
1228 * Try setting system threshold.
1229 */
1230int
1231systhr(void)
1232{
1233	return (cmnthr(PM_SET_SYSTEM_THRESHOLD));
1234}
1235
1236
1237/*
1238 * Try setting cpu threshold.
1239 */
1240int
1241cputhr(void)
1242{
1243	return (cmnthr(PM_SET_CPU_THRESHOLD));
1244}
1245
1246
1247int
1248tchars(void)
1249{
1250	return (scan_int(LINEARG(1), &new_cc.ttychars_thold));
1251}
1252