xref: /openbsd-src/sbin/sysctl/sysctl.c (revision a4afd6dad3fba28f80e70208181c06c482259988)
1 /*	$OpenBSD: sysctl.c,v 1.4 1996/11/25 08:22:43 mickey Exp $	*/
2 /*	$NetBSD: sysctl.c,v 1.9 1995/09/30 07:12:50 thorpej Exp $	*/
3 
4 /*
5  * Copyright (c) 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by the University of
19  *	California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #ifndef lint
38 static char copyright[] =
39 "@(#) Copyright (c) 1993\n\
40 	The Regents of the University of California.  All rights reserved.\n";
41 #endif /* not lint */
42 
43 #ifndef lint
44 #if 0
45 static char sccsid[] = "@(#)sysctl.c	8.1 (Berkeley) 6/6/93";
46 #else
47 static char *rcsid = "$OpenBSD: sysctl.c,v 1.4 1996/11/25 08:22:43 mickey Exp $";
48 #endif
49 #endif /* not lint */
50 
51 #include <sys/param.h>
52 #include <sys/gmon.h>
53 #include <sys/stat.h>
54 #include <sys/sysctl.h>
55 #include <sys/socket.h>
56 #include <vm/vm_param.h>
57 #include <machine/cpu.h>
58 
59 #include <netinet/in.h>
60 #include <netinet/in_systm.h>
61 #include <netinet/ip.h>
62 #include <netinet/ip_icmp.h>
63 #include <netinet/icmp_var.h>
64 #include <netinet/ip_var.h>
65 #include <netinet/udp.h>
66 #include <netinet/udp_var.h>
67 #include <netinet/tcp.h>
68 #include <netinet/tcp_timer.h>
69 #include <netinet/tcp_var.h>
70 
71 #include <netipx/ipx.h>
72 #include <netipx/ipx_var.h>
73 #include <netipx/spx_var.h>
74 #include <ddb/db_var.h>
75 
76 #include <errno.h>
77 #include <stdio.h>
78 #include <stdlib.h>
79 #include <string.h>
80 #include <ctype.h>
81 
82 struct ctlname topname[] = CTL_NAMES;
83 struct ctlname kernname[] = CTL_KERN_NAMES;
84 struct ctlname vmname[] = CTL_VM_NAMES;
85 struct ctlname fsname[] = CTL_FS_NAMES;
86 struct ctlname netname[] = CTL_NET_NAMES;
87 struct ctlname hwname[] = CTL_HW_NAMES;
88 struct ctlname username[] = CTL_USER_NAMES;
89 struct ctlname debugname[CTL_DEBUG_MAXID];
90 #ifdef CTL_MACHDEP_NAMES
91 struct ctlname machdepname[] = CTL_MACHDEP_NAMES;
92 #endif
93 struct ctlname ddbname[] = CTL_DDB_NAMES;
94 char names[BUFSIZ];
95 
96 struct list {
97 	struct	ctlname *list;
98 	int	size;
99 };
100 struct list toplist = { topname, CTL_MAXID };
101 struct list secondlevel[] = {
102 	{ 0, 0 },			/* CTL_UNSPEC */
103 	{ kernname, KERN_MAXID },	/* CTL_KERN */
104 	{ vmname, VM_MAXID },		/* CTL_VM */
105 	{ fsname, FS_MAXID },		/* CTL_FS */
106 	{ netname, NET_MAXID },		/* CTL_NET */
107 	{ 0, CTL_DEBUG_MAXID },		/* CTL_DEBUG */
108 	{ hwname, HW_MAXID },		/* CTL_HW */
109 #ifdef CTL_MACHDEP_NAMES
110 	{ machdepname, CPU_MAXID },	/* CTL_MACHDEP */
111 #else
112 	{ 0, 0 },			/* CTL_MACHDEP */
113 #endif
114 	{ username, USER_MAXID },	/* CTL_USER_NAMES */
115 	{ ddbname, DBCTL_MAXID },	/* CTL_DDB_NAMES */
116 };
117 
118 int	Aflag, aflag, nflag, wflag;
119 
120 /*
121  * Variables requiring special processing.
122  */
123 #define	CLOCK		0x00000001
124 #define	BOOTTIME	0x00000002
125 #define	CONSDEV		0x00000004
126 
127 /* prototypes */
128 void usage();
129 void debuginit();
130 void parse __P((	char *string, int flags));
131 void listall __P((char *prefix, 	struct list *lp));
132 int findname __P((char *string, 	char *level, char **bufp, struct list *namelist));
133 int sysctl_inet __P((char *string, char **bufpp, 	int mib[], int flags, int *typep));
134 int sysctl_ipx __P((char *string, char **bufpp, 	int mib[], int flags, int *typep));
135 int sysctl_fs __P((char *string, char **bufpp, 	int mib[], int flags, int *typep));
136 
137 int
138 main(argc, argv)
139 	int argc;
140 	char *argv[];
141 {
142 	extern char *optarg;
143 	extern int optind;
144 	int ch, lvl1;
145 
146 	while ((ch = getopt(argc, argv, "Aanw")) != EOF) {
147 		switch (ch) {
148 
149 		case 'A':
150 			Aflag = 1;
151 			break;
152 
153 		case 'a':
154 			aflag = 1;
155 			break;
156 
157 		case 'n':
158 			nflag = 1;
159 			break;
160 
161 		case 'w':
162 			wflag = 1;
163 			break;
164 
165 		default:
166 			usage();
167 		}
168 	}
169 	argc -= optind;
170 	argv += optind;
171 
172 	if (Aflag || aflag) {
173 		debuginit();
174 		for (lvl1 = 1; lvl1 < CTL_MAXID; lvl1++)
175 			listall(topname[lvl1].ctl_name, &secondlevel[lvl1]);
176 		exit(0);
177 	}
178 	if (argc == 0)
179 		usage();
180 	while (argc-- > 0)
181 		parse(*argv++, 1);
182 	exit(0);
183 }
184 
185 /*
186  * List all variables known to the system.
187  */
188 void
189 listall(prefix, lp)
190 	char *prefix;
191 	struct list *lp;
192 {
193 	int lvl2;
194 	char *cp, name[BUFSIZ];
195 
196 	if (lp->list == 0)
197 		return;
198 	strncpy(name, prefix, BUFSIZ-1);
199 	cp = &name[strlen(name)];
200 	*cp++ = '.';
201 	for (lvl2 = 0; lvl2 < lp->size; lvl2++) {
202 		if (lp->list[lvl2].ctl_name == 0)
203 			continue;
204 		strcpy(cp, lp->list[lvl2].ctl_name);
205 		parse(name, Aflag);
206 	}
207 }
208 
209 /*
210  * Parse a name into a MIB entry.
211  * Lookup and print out the MIB entry if it exists.
212  * Set a new value if requested.
213  */
214 void
215 parse(string, flags)
216 	char *string;
217 	int flags;
218 {
219 	int indx, type, state, len;
220 	int special = 0;
221 	void *newval = 0;
222 	int intval, newsize = 0;
223 	quad_t quadval;
224 	size_t size;
225 	struct list *lp;
226 	int mib[CTL_MAXNAME];
227 	char *cp, *bufp, buf[BUFSIZ];
228 
229 	bufp = buf;
230 	snprintf(buf, BUFSIZ, "%s", string);
231 	if ((cp = strchr(string, '=')) != NULL) {
232 		if (!wflag) {
233 			fprintf(stderr, "Must specify -w to set variables\n");
234 			exit(2);
235 		}
236 		*strchr(buf, '=') = '\0';
237 		*cp++ = '\0';
238 		while (isspace(*cp))
239 			cp++;
240 		newval = cp;
241 		newsize = strlen(cp);
242 	}
243 	if ((indx = findname(string, "top", &bufp, &toplist)) == -1)
244 		return;
245 	mib[0] = indx;
246 	if (indx == CTL_DEBUG)
247 		debuginit();
248 	lp = &secondlevel[indx];
249 	if (lp->list == 0) {
250 		fprintf(stderr, "%s: class is not implemented\n",
251 		    topname[indx].ctl_name);
252 		return;
253 	}
254 	if (bufp == NULL) {
255 		listall(topname[indx].ctl_name, lp);
256 		return;
257 	}
258 	if ((indx = findname(string, "second", &bufp, lp)) == -1)
259 		return;
260 	mib[1] = indx;
261 	type = lp->list[indx].ctl_type;
262 	len = 2;
263 	switch (mib[0]) {
264 
265 	case CTL_KERN:
266 		switch (mib[1]) {
267 		case KERN_PROF:
268 			mib[2] = GPROF_STATE;
269 			size = sizeof state;
270 			if (sysctl(mib, 3, &state, &size, NULL, 0) < 0) {
271 				if (flags == 0)
272 					return;
273 				if (!nflag)
274 					fprintf(stdout, "%s: ", string);
275 				fprintf(stderr,
276 				    "kernel is not compiled for profiling\n");
277 				return;
278 			}
279 			if (!nflag)
280 				fprintf(stdout, "%s: %s\n", string,
281 				    state == GMON_PROF_OFF ? "off" : "running");
282 			return;
283 		case KERN_VNODE:
284 		case KERN_FILE:
285 			if (flags == 0)
286 				return;
287 			fprintf(stderr,
288 			    "Use pstat to view %s information\n", string);
289 			return;
290 		case KERN_PROC:
291 			if (flags == 0)
292 				return;
293 			fprintf(stderr,
294 			    "Use ps to view %s information\n", string);
295 			return;
296 		case KERN_NTPTIME:
297 			if (flags == 0)
298 				return;
299 			fprintf(stderr,
300 			    "Use xntpd to view %s information\n", string);
301 			return;
302 		case KERN_CLOCKRATE:
303 			special |= CLOCK;
304 			break;
305 		case KERN_BOOTTIME:
306 			special |= BOOTTIME;
307 			break;
308 		}
309 		break;
310 
311 	case CTL_HW:
312 		break;
313 
314 	case CTL_VM:
315 		if (mib[1] == VM_LOADAVG) {
316 			double loads[3];
317 
318 			getloadavg(loads, 3);
319 			if (!nflag)
320 				fprintf(stdout, "%s: ", string);
321 			fprintf(stdout, "%.2f %.2f %.2f\n",
322 			    loads[0], loads[1], loads[2]);
323 			return;
324 		}
325 		if (flags == 0)
326 			return;
327 		fprintf(stderr,
328 		    "Use vmstat or systat to view %s information\n", string);
329 		return;
330 
331 	case CTL_NET:
332 		if (mib[1] == PF_INET) {
333 			len = sysctl_inet(string, &bufp, mib, flags, &type);
334 			if (len >= 0)
335 				break;
336 			return;
337 		}
338 		if (mib[1] == PF_IPX) {
339 			len = sysctl_ipx(string, &bufp, mib, flags, &type);
340 			if (len >= 0)
341 				break;
342 			return;
343 		}
344 		if (flags == 0)
345 			return;
346 		fprintf(stderr, "Use netstat to view %s information\n", string);
347 		return;
348 
349 	case CTL_DEBUG:
350 		mib[2] = CTL_DEBUG_VALUE;
351 		len = 3;
352 		break;
353 
354 	case CTL_MACHDEP:
355 #ifdef CPU_CONSDEV
356 		if (mib[1] == CPU_CONSDEV)
357 			special |= CONSDEV;
358 #endif
359 		break;
360 
361 	case CTL_FS:
362 		len = sysctl_fs(string, &bufp, mib, flags, &type);
363 		if (len >= 0)
364 			break;
365 		return;
366 
367 	case CTL_USER:
368 	case CTL_DDB:
369 		break;
370 
371 	default:
372 		fprintf(stderr, "Illegal top level value: %d\n", mib[0]);
373 		return;
374 
375 	}
376 	if (bufp) {
377 		fprintf(stderr, "name %s in %s is unknown\n", bufp, string);
378 		return;
379 	}
380 	if (newsize > 0) {
381 		switch (type) {
382 		case CTLTYPE_INT:
383 			intval = atoi(newval);
384 			newval = &intval;
385 			newsize = sizeof intval;
386 			break;
387 
388 		case CTLTYPE_QUAD:
389 			sscanf(newval, "%qd", &quadval);
390 			newval = &quadval;
391 			newsize = sizeof quadval;
392 			break;
393 		}
394 	}
395 	size = BUFSIZ;
396 	if (sysctl(mib, len, buf, &size, newsize ? newval : 0, newsize) == -1) {
397 		if (flags == 0)
398 			return;
399 		switch (errno) {
400 		case EOPNOTSUPP:
401 			fprintf(stderr, "%s: value is not available\n", string);
402 			return;
403 		case ENOTDIR:
404 			fprintf(stderr, "%s: specification is incomplete\n",
405 			    string);
406 			return;
407 		case ENOMEM:
408 			fprintf(stderr, "%s: type is unknown to this program\n",
409 			    string);
410 			return;
411 		default:
412 			perror(string);
413 			return;
414 		}
415 	}
416 	if (special & CLOCK) {
417 		struct clockinfo *clkp = (struct clockinfo *)buf;
418 
419 		if (!nflag)
420 			fprintf(stdout, "%s: ", string);
421 		fprintf(stdout,
422 		    "tick = %d, tickadj = %d, hz = %d, profhz = %d, stathz = %d\n",
423 		    clkp->tick, clkp->tickadj, clkp->hz, clkp->profhz, clkp->stathz);
424 		return;
425 	}
426 	if (special & BOOTTIME) {
427 		struct timeval *btp = (struct timeval *)buf;
428 		time_t boottime;
429 
430 		if (!nflag) {
431 			boottime = btp->tv_sec;
432 			fprintf(stdout, "%s = %s\n", string, ctime(&boottime));
433 		} else
434 			fprintf(stdout, "%ld\n", btp->tv_sec);
435 		return;
436 	}
437 	if (special & CONSDEV) {
438 		dev_t dev = *(dev_t *)buf;
439 
440 		if (!nflag)
441 			fprintf(stdout, "%s = %s\n", string,
442 			    devname(dev, S_IFCHR));
443 		else
444 			fprintf(stdout, "0x%x\n", dev);
445 		return;
446 	}
447 	switch (type) {
448 	case CTLTYPE_INT:
449 		if (newsize == 0) {
450 			if (!nflag)
451 				fprintf(stdout, "%s = ", string);
452 			fprintf(stdout, "%d\n", *(int *)buf);
453 		} else {
454 			if (!nflag)
455 				fprintf(stdout, "%s: %d -> ", string,
456 				    *(int *)buf);
457 			fprintf(stdout, "%d\n", *(int *)newval);
458 		}
459 		return;
460 
461 	case CTLTYPE_STRING:
462 		if (newsize == 0) {
463 			if (!nflag)
464 				fprintf(stdout, "%s = ", string);
465 			fprintf(stdout, "%s\n", buf);
466 		} else {
467 			if (!nflag)
468 				fprintf(stdout, "%s: %s -> ", string, buf);
469 			fprintf(stdout, "%s\n", (char *)newval);
470 		}
471 		return;
472 
473 	case CTLTYPE_QUAD:
474 		if (newsize == 0) {
475 			if (!nflag)
476 				fprintf(stdout, "%s = ", string);
477 			fprintf(stdout, "%qd\n", *(quad_t *)buf);
478 		} else {
479 			if (!nflag)
480 				fprintf(stdout, "%s: %qd -> ", string,
481 				    *(quad_t *)buf);
482 			fprintf(stdout, "%qd\n", *(quad_t *)newval);
483 		}
484 		return;
485 
486 	case CTLTYPE_STRUCT:
487 		fprintf(stderr, "%s: unknown structure returned\n",
488 		    string);
489 		return;
490 
491 	default:
492 	case CTLTYPE_NODE:
493 		fprintf(stderr, "%s: unknown type returned\n",
494 		    string);
495 		return;
496 	}
497 }
498 
499 /*
500  * Initialize the set of debugging names
501  */
502 void
503 debuginit()
504 {
505 	int mib[3], loc, i;
506 	size_t size;
507 
508 	if (secondlevel[CTL_DEBUG].list != 0)
509 		return;
510 	secondlevel[CTL_DEBUG].list = debugname;
511 	mib[0] = CTL_DEBUG;
512 	mib[2] = CTL_DEBUG_NAME;
513 	for (loc = 0, i = 0; i < CTL_DEBUG_MAXID; i++) {
514 		mib[1] = i;
515 		size = BUFSIZ - loc;
516 		if (sysctl(mib, 3, &names[loc], &size, NULL, 0) == -1)
517 			continue;
518 		debugname[i].ctl_name = &names[loc];
519 		debugname[i].ctl_type = CTLTYPE_INT;
520 		loc += size;
521 	}
522 }
523 
524 struct ctlname posixname[] = CTL_FS_POSIX_NAMES;
525 struct list fslist = { posixname, FS_POSIX_MAXID };
526 
527 /*
528  * handle file system requests
529  */
530 int
531 sysctl_fs(string, bufpp, mib, flags, typep)
532 	char *string;
533 	char **bufpp;
534 	int mib[];
535 	int flags;
536 	int *typep;
537 {
538 	int indx;
539 
540 	if (*bufpp == NULL) {
541 		listall(string, &fslist);
542 		return (-1);
543 	}
544 	if ((indx = findname(string, "third", bufpp, &fslist)) == -1)
545 		return (-1);
546 	mib[2] = indx;
547 	*typep = fslist.list[indx].ctl_type;
548 	return (3);
549 }
550 
551 struct ctlname inetname[] = CTL_IPPROTO_NAMES;
552 struct ctlname ipname[] = IPCTL_NAMES;
553 struct ctlname icmpname[] = ICMPCTL_NAMES;
554 struct ctlname tcpname[] = TCPCTL_NAMES;
555 struct ctlname udpname[] = UDPCTL_NAMES;
556 struct list inetlist = { inetname, IPPROTO_MAXID };
557 struct list inetvars[] = {
558 	{ ipname, IPCTL_MAXID },	/* ip */
559 	{ icmpname, ICMPCTL_MAXID },	/* icmp */
560 	{ 0, 0 },			/* igmp */
561 	{ 0, 0 },			/* ggmp */
562 	{ 0, 0 },
563 	{ 0, 0 },
564 	{ tcpname, TCPCTL_MAXID },	/* tcp */
565 	{ 0, 0 },
566 	{ 0, 0 },			/* egp */
567 	{ 0, 0 },
568 	{ 0, 0 },
569 	{ 0, 0 },
570 	{ 0, 0 },			/* pup */
571 	{ 0, 0 },
572 	{ 0, 0 },
573 	{ 0, 0 },
574 	{ 0, 0 },
575 	{ udpname, UDPCTL_MAXID },	/* udp */
576 };
577 
578 /*
579  * handle internet requests
580  */
581 int
582 sysctl_inet(string, bufpp, mib, flags, typep)
583 	char *string;
584 	char **bufpp;
585 	int mib[];
586 	int flags;
587 	int *typep;
588 {
589 	struct list *lp;
590 	int indx;
591 
592 	if (*bufpp == NULL) {
593 		listall(string, &inetlist);
594 		return (-1);
595 	}
596 	if ((indx = findname(string, "third", bufpp, &inetlist)) == -1)
597 		return (-1);
598 	mib[2] = indx;
599 	if (indx <= IPPROTO_UDP && inetvars[indx].list != NULL)
600 		lp = &inetvars[indx];
601 	else if (!flags)
602 		return (-1);
603 	else {
604 		fprintf(stderr, "%s: no variables defined for this protocol\n",
605 		    string);
606 		return (-1);
607 	}
608 	if (*bufpp == NULL) {
609 		listall(string, lp);
610 		return (-1);
611 	}
612 	if ((indx = findname(string, "fourth", bufpp, lp)) == -1)
613 		return (-1);
614 	mib[3] = indx;
615 	*typep = lp->list[indx].ctl_type;
616 	return (4);
617 }
618 
619 struct ctlname ipxname[] = CTL_IPXPROTO_NAMES;
620 struct ctlname ipxpname[] = IPXCTL_NAMES;
621 struct ctlname spxpname[] = SPXCTL_NAMES;
622 struct list ipxlist = { ipxname, IPXCTL_MAXID };
623 struct list ipxvars[] = {
624 	{ ipxpname, IPXCTL_MAXID },	/* ipx */
625 	{ 0, 0 },
626 	{ 0, 0 },
627 	{ 0, 0 },
628 	{ 0, 0 },
629 	{ spxpname, SPXCTL_MAXID },
630 };
631 
632 /*
633  * handle internet requests
634  */
635 int
636 sysctl_ipx(string, bufpp, mib, flags, typep)
637 	char *string;
638 	char **bufpp;
639 	int mib[];
640 	int flags;
641 	int *typep;
642 {
643 	struct list *lp;
644 	int indx;
645 
646 	if (*bufpp == NULL) {
647 		listall(string, &ipxlist);
648 		return (-1);
649 	}
650 	if ((indx = findname(string, "third", bufpp, &ipxlist)) == -1)
651 		return (-1);
652 	mib[2] = indx;
653 	if (indx <= IPXPROTO_SPX && ipxvars[indx].list != NULL)
654 		lp = &ipxvars[indx];
655 	else if (!flags)
656 		return (-1);
657 	else {
658 		fprintf(stderr, "%s: no variables defined for this protocol\n",
659 		    string);
660 		return (-1);
661 	}
662 	if (*bufpp == NULL) {
663 		listall(string, lp);
664 		return (-1);
665 	}
666 	if ((indx = findname(string, "fourth", bufpp, lp)) == -1)
667 		return (-1);
668 	mib[3] = indx;
669 	*typep = lp->list[indx].ctl_type;
670 	return (4);
671 }
672 
673 /*
674  * Scan a list of names searching for a particular name.
675  */
676 int
677 findname(string, level, bufp, namelist)
678 	char *string;
679 	char *level;
680 	char **bufp;
681 	struct list *namelist;
682 {
683 	char *name;
684 	int i;
685 
686 	if (namelist->list == 0 || (name = strsep(bufp, ".")) == NULL) {
687 		fprintf(stderr, "%s: incomplete specification\n", string);
688 		return (-1);
689 	}
690 	for (i = 0; i < namelist->size; i++)
691 		if (namelist->list[i].ctl_name != NULL &&
692 		    strcmp(name, namelist->list[i].ctl_name) == 0)
693 			break;
694 	if (i == namelist->size) {
695 		fprintf(stderr, "%s level name %s in %s is invalid\n",
696 		    level, name, string);
697 		return (-1);
698 	}
699 	return (i);
700 }
701 
702 void
703 usage()
704 {
705 
706 	(void)fprintf(stderr, "usage:\t%s\n\t%s\n\t%s\n\t%s\n",
707 	    "sysctl [-n] variable ...", "sysctl [-n] -w variable=value ...",
708 	    "sysctl [-n] -a", "sysctl [-n] -A");
709 	exit(1);
710 }
711