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