xref: /illumos-gate/usr/src/cmd/power/handlers.c (revision d8ab6e129d75d7c3f21a7909bf811a3de65faea8)
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 
45 static char nerr_fmt[] = "number is out of range (%s)\n";
46 static char alloc_fmt[] = "cannot allocate space for \"%s\", %s\n";
47 static char set_thresh_fmt[] = "error setting threshold(s) for \"%s\", %s\n";
48 static char bad_thresh_fmt[] = "bad threshold(s)\n";
49 static char stat_fmt[] = "cannot stat \"%s\", %s\n";
50 static 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 
63 static char pm_cmd_string[32];
64 
65 static char *
66 pm_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 
81 static int
82 isonlist(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 
96 static int
97 do_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  */
119 int
120 cpupm(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  */
170 int
171 cpuidle(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 
220 int
221 S3_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 
330 int
331 S3sup(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  */
383 int
384 autoS3(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  */
431 int
432 autopm(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 
496 static int
497 gethm(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 
507 static void
508 strcpy_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  */
519 int
520 autosd(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  */
557 static int
558 devpath(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  */
609 static int
610 dev_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 
658 int
659 ddprop(void)
660 {
661 	return (dev_dep_common(1));
662 }
663 
664 
665 int
666 devdep(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  */
677 static int
678 get_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  */
714 static int
715 vlist_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  */
736 static int
737 get_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  */
779 int
780 devthr(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 
813 static int
814 scan_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 
830 static int
831 scan_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 
848 int
849 dreads(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  */
859 int
860 idlechk(void)
861 {
862 	STRCPYLIM(new_cc.idlecheck_path, LINEARG(1), "idle path");
863 	return (OKUP);
864 }
865 
866 
867 int
868 loadavg(void)
869 {
870 	return (scan_float(LINEARG(1), &new_cc.loadaverage_thold));
871 }
872 
873 
874 int
875 nfsreq(void)
876 {
877 	return (scan_int(LINEARG(1), &new_cc.nfsreqs_thold));
878 }
879 
880 #ifdef sparc
881 static 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  */
888 static int
889 check_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  */
961 static int
962 utop(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  */
1002 static int
1003 ztop(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  */
1066 static boolean_t
1067 is_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  */
1110 int
1111 sfpath(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  */
1209 static int
1210 cmnthr(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  */
1230 int
1231 systhr(void)
1232 {
1233 	return (cmnthr(PM_SET_SYSTEM_THRESHOLD));
1234 }
1235 
1236 
1237 /*
1238  * Try setting cpu threshold.
1239  */
1240 int
1241 cputhr(void)
1242 {
1243 	return (cmnthr(PM_SET_CPU_THRESHOLD));
1244 }
1245 
1246 
1247 int
1248 tchars(void)
1249 {
1250 	return (scan_int(LINEARG(1), &new_cc.ttychars_thold));
1251 }
1252