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