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