xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/ndd.c (revision 09a48d4ca0ddda4ad26cc885769745870d989baf)
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 (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 /* Copyright (c) 1990 Mentat Inc. */
25 
26 #include <assert.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <ctype.h>
30 #include <stdarg.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <stropts.h>
35 #include <inet/tunables.h>
36 #include <inet/nd.h>
37 #include <string.h>
38 #include <strings.h>
39 #include <stdlib.h>
40 #include <libdllink.h>
41 #include <libintl.h>
42 #include <libipadm.h>
43 
44 static boolean_t do_getset(int fd, int cmd, char *buf, int buf_len);
45 static int	get_value(char *msg, char *buf, int buf_len);
46 static void	name_print(char *buf);
47 static void	getset_interactive(int fd);
48 static int	open_device(void);
49 static char	*errmsg(int err);
50 static void	fatal(char *fmt, ...);
51 static void	printe(boolean_t print_errno, char *fmt, ...);
52 
53 static char	modpath[128];	/* path to module */
54 static char	gbuf[65536];	/* need large buffer to retrieve all names */
55 static char	usage_str[] =	"usage: ndd -set device_name name value\n"
56 				"       ndd [-get] device_name name [name ...]";
57 
58 /*
59  * Maps old ndd_name to the new ipadm_name. Any ndd property that is moved to
60  * libipadm should have an entry here to ensure backward compatibility
61  */
62 typedef struct ndd2ipadm_map {
63 	char	*ndd_name;
64 	char	*ipadm_name;
65 	uint_t	ipadm_proto;
66 	uint_t	ipadm_flags;
67 	uint_t	ndd_perm;
68 } ndd2ipadm_map_t;
69 
70 static ndd2ipadm_map_t map[] = {
71 	{ "ip_def_ttl",			"ttl",		MOD_PROTO_IPV4, 0, 0 },
72 	{ "ip6_def_hops",		"hoplimit",	MOD_PROTO_IPV6, 0, 0 },
73 	{ "ip_forwarding",		"forwarding",	MOD_PROTO_IPV4, 0, 0 },
74 	{ "ip6_forwarding",		"forwarding",	MOD_PROTO_IPV6, 0, 0 },
75 	{ "icmp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_RAWIP, 0, 0 },
76 	{ "icmp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_RAWIP, 0, 0 },
77 	{ "tcp_ecn_permitted",		"ecn",		MOD_PROTO_TCP, 0, 0 },
78 	{ "tcp_extra_priv_ports_add",	"extra_priv_ports",	MOD_PROTO_TCP,
79 	    IPADM_OPT_APPEND, MOD_PROP_PERM_WRITE },
80 	{ "tcp_extra_priv_ports_del",	"extra_priv_ports",	MOD_PROTO_TCP,
81 	    IPADM_OPT_REMOVE, MOD_PROP_PERM_WRITE },
82 	{ "tcp_extra_priv_ports",	"extra_priv_ports",	MOD_PROTO_TCP,
83 	    0, MOD_PROP_PERM_READ },
84 	{ "tcp_largest_anon_port",	"largest_anon_port",	MOD_PROTO_TCP,
85 	    0, 0 },
86 	{ "tcp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_TCP, 0, 0 },
87 	{ "tcp_sack_permitted",		"sack",		MOD_PROTO_TCP, 0, 0 },
88 	{ "tcp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_TCP, 0, 0 },
89 	{ "tcp_smallest_anon_port",	"smallest_anon_port",	MOD_PROTO_TCP,
90 	    0, 0 },
91 	{ "tcp_smallest_nonpriv_port",	"smallest_nonpriv_port", MOD_PROTO_TCP,
92 	    0, 0 },
93 	{ "udp_extra_priv_ports_add",	"extra_priv_ports",	MOD_PROTO_UDP,
94 	    IPADM_OPT_APPEND, MOD_PROP_PERM_WRITE },
95 	{ "udp_extra_priv_ports_del",	"extra_priv_ports",	MOD_PROTO_UDP,
96 	    IPADM_OPT_REMOVE, MOD_PROP_PERM_WRITE },
97 	{ "udp_extra_priv_ports",	"extra_priv_ports",	MOD_PROTO_UDP,
98 	    0, MOD_PROP_PERM_READ },
99 	{ "udp_largest_anon_port",	"largest_anon_port",    MOD_PROTO_UDP,
100 	    0, 0 },
101 	{ "udp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_UDP, 0, 0 },
102 	{ "udp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_UDP, 0, 0 },
103 	{ "udp_smallest_anon_port",	"smallest_anon_port",	MOD_PROTO_UDP,
104 	    0, 0 },
105 	{ "udp_smallest_nonpriv_port",	"smallest_nonpriv_port", MOD_PROTO_UDP,
106 	    0, 0 },
107 	{ "sctp_extra_priv_ports_add",	"extra_priv_ports",	MOD_PROTO_SCTP,
108 	    IPADM_OPT_APPEND, MOD_PROP_PERM_WRITE },
109 	{ "sctp_extra_priv_ports_del",	"extra_priv_ports",	MOD_PROTO_SCTP,
110 	    IPADM_OPT_REMOVE, MOD_PROP_PERM_WRITE },
111 	{ "sctp_extra_priv_ports",	"extra_priv_ports",	MOD_PROTO_SCTP,
112 	    0, MOD_PROP_PERM_READ },
113 	{ "sctp_largest_anon_port",	"largest_anon_port",	MOD_PROTO_SCTP,
114 	    0, 0 },
115 	{ "sctp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_SCTP, 0, 0 },
116 	{ "sctp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_SCTP, 0, 0 },
117 	{ "sctp_smallest_anon_port",	"smallest_anon_port",	MOD_PROTO_SCTP,
118 	    0, 0 },
119 	{ "sctp_smallest_nonpriv_port",	"smallest_nonpriv_port", MOD_PROTO_SCTP,
120 	    0, 0 },
121 	{ NULL, NULL, 0, 0, 0 }
122 };
123 
124 static uint_t
125 ndd_str2proto(const char *protostr)
126 {
127 	if (strcmp(protostr, "tcp") == 0 ||
128 	    strcmp(protostr, "tcp6") == 0) {
129 		return (MOD_PROTO_TCP);
130 	} else if (strcmp(protostr, "udp") == 0 ||
131 	    strcmp(protostr, "udp6") == 0) {
132 		return (MOD_PROTO_UDP);
133 	} else if (strcmp(protostr, "ip") == 0 ||
134 	    strcmp(protostr, "ip6") == 0 ||
135 	    strcmp(protostr, "arp") == 0) {
136 		return (MOD_PROTO_IP);
137 	} else if (strcmp(protostr, "icmp") == 0 ||
138 	    strcmp(protostr, "icmp6") == 0) {
139 		return (MOD_PROTO_RAWIP);
140 	} else if (strcmp(protostr, "sctp") == 0 ||
141 	    strcmp(protostr, "sctp6") == 0) {
142 		return (MOD_PROTO_SCTP);
143 	}
144 	return (MOD_PROTO_NONE);
145 }
146 
147 static char *
148 ndd_perm2str(uint_t perm)
149 {
150 	switch (perm) {
151 	case MOD_PROP_PERM_READ:
152 		return ("read only");
153 	case MOD_PROP_PERM_WRITE:
154 		return ("write only");
155 	case MOD_PROP_PERM_RW:
156 		return ("read and write");
157 	}
158 
159 	return (NULL);
160 }
161 
162 /*
163  * Print all the protocol properties for the given protocol name. The kernel
164  * returns all the properties for the given protocol therefore we have to
165  * apply some filters before we print them.
166  *
167  *	- convert any new ipadm name to old ndd name using the table.
168  *	  For example: `sack' --> `tcp_sack_permitted'.
169  *
170  *	- replace leading underscores with protocol name.
171  *	  For example: `_strong_iss' --> `tcp_strong_iss'
172  *
173  *	- don't print new public properties that are supported only by ipadm(1M)
174  *	  For example: `hostmodel' should be supported only from ipadm(1M).
175  *	  Such properties are identified by not having leading '_' and not
176  *	  being present in the mapping table.
177  */
178 static void
179 print_ipadm2ndd(char *oldbuf, uint_t obufsize)
180 {
181 	ndd2ipadm_map_t	*nimap;
182 	char		*pname, *rwtag, *protostr;
183 	uint_t		proto, perm;
184 	boolean_t	matched;
185 
186 	pname = oldbuf;
187 	while (pname[0] && pname < (oldbuf + obufsize - 1)) {
188 		for (protostr = pname; !isspace(*protostr); protostr++)
189 			;
190 		*protostr++ = '\0';
191 		/* protostr now points to protocol */
192 
193 		for (rwtag = protostr; !isspace(*rwtag); rwtag++)
194 			;
195 		*rwtag++ = '\0';
196 		/* rwtag now points to permissions */
197 
198 		proto = atoi(protostr);
199 		perm = atoi(rwtag);
200 		matched = B_FALSE;
201 		for (nimap = map; nimap->ndd_name != NULL; nimap++) {
202 			if (strcmp(pname, nimap->ipadm_name) != 0 ||
203 			    !(nimap->ipadm_proto & proto))
204 				continue;
205 
206 			matched = B_TRUE;
207 			if (nimap->ndd_perm != 0)
208 				perm = nimap->ndd_perm;
209 			(void) printf("%-30s (%s)\n", nimap->ndd_name,
210 			    ndd_perm2str(perm));
211 		}
212 		/*
213 		 * print only if it's a private property. We should
214 		 * not be printing any new public property in ndd(1M)
215 		 * output.
216 		 */
217 		if (!matched && pname[0] == '_') {
218 			char	tmpstr[512];
219 			int	err;
220 
221 			err = ipadm_new2legacy_propname(pname, tmpstr,
222 			    sizeof (tmpstr), proto);
223 			assert(err != -1);
224 
225 			(void) printf("%-30s (%s)\n", tmpstr,
226 			    ndd_perm2str(perm));
227 		}
228 		for (pname = rwtag; *pname++; )
229 			;
230 	}
231 }
232 
233 /*
234  * get/set the value for a given property by calling into libipadm. The
235  * IPH_LEGACY flag is used by libipadm for special handling. For some
236  * properties, libipadm.so displays strings (for e.g., on/off,
237  * never/passive/active, et al) instead of numerals. However ndd(1M) always
238  * printed numberals. This flag will help in avoiding printing strings.
239  */
240 static boolean_t
241 do_ipadm_getset(int cmd, char *buf, int buflen)
242 {
243 	ndd2ipadm_map_t	*nimap;
244 	ipadm_handle_t	iph = NULL;
245 	ipadm_status_t	status;
246 	char		*mod;
247 	uint_t		proto, perm = 0, flags = 0;
248 	char		*pname, *pvalp, nname[512];
249 	int		i;
250 
251 	if ((mod = strrchr(modpath, '/')) == NULL)
252 		mod = modpath;
253 	else
254 		++mod;
255 	if ((proto = ndd_str2proto(mod)) == MOD_PROTO_NONE)
256 		return (B_FALSE);
257 
258 	if ((status = ipadm_open(&iph, IPH_LEGACY)) != IPADM_SUCCESS)
259 		goto fail;
260 
261 	pname = buf;
262 	for (nimap = map; nimap->ndd_name != NULL; nimap++) {
263 		if (strcmp(pname, nimap->ndd_name) == 0) {
264 			pname = nimap->ipadm_name;
265 			proto = nimap->ipadm_proto;
266 			flags = nimap->ipadm_flags;
267 			perm = nimap->ndd_perm;
268 			break;
269 		}
270 	}
271 
272 	if (nimap->ndd_name == NULL && strcmp(pname, "?") != 0) {
273 		/* do not allow set/get of public properties from ndd(1M) */
274 		if (ipadm_legacy2new_propname(pname, nname, sizeof (nname),
275 		    &proto) != 0) {
276 			status = IPADM_PROP_UNKNOWN;
277 			goto fail;
278 		} else {
279 			pname = nname;
280 		}
281 	}
282 
283 	if (cmd == ND_GET) {
284 		char		propval[MAXPROPVALLEN], allprop[64536];
285 		uint_t		pvalsz;
286 		sa_family_t	af = AF_UNSPEC;
287 		int		err;
288 
289 		if (perm == MOD_PROP_PERM_WRITE)
290 			fatal("operation failed: Permission denied");
291 
292 		if (strcmp(pname, "?") == 0) {
293 			pvalp = allprop;
294 			pvalsz = sizeof (allprop);
295 		} else {
296 			pvalp = propval;
297 			pvalsz = sizeof (propval);
298 		}
299 
300 		status = ipadm_get_prop(iph, pname, pvalp, &pvalsz, proto,
301 		    IPADM_OPT_ACTIVE);
302 		if (status != IPADM_SUCCESS)
303 			goto fail;
304 
305 		if (strcmp(pname, "?") == 0) {
306 			(void) print_ipadm2ndd(pvalp, pvalsz);
307 		} else {
308 			char *tmp = pvalp;
309 
310 			/*
311 			 * For backward compatibility if there are multiple
312 			 * values print each value in it's own line.
313 			 */
314 			while (*tmp != '\0') {
315 				if (*tmp == ',')
316 					*tmp = '\n';
317 				tmp++;
318 			}
319 			(void) printf("%s\n", pvalp);
320 		}
321 		(void) fflush(stdout);
322 	} else {
323 		if (perm == MOD_PROP_PERM_READ)
324 			fatal("operation failed: Permission denied");
325 
326 		/* walk past the property name to find the property value */
327 		for (i = 0; buf[i] != '\0'; i++)
328 			;
329 
330 		pvalp = &buf[++i];
331 		status = ipadm_set_prop(iph, pname, pvalp, proto,
332 		    flags|IPADM_OPT_ACTIVE);
333 	}
334 fail:
335 	ipadm_close(iph);
336 	if (status != IPADM_SUCCESS)
337 		fatal("operation failed: %s", ipadm_status2str(status));
338 	return (B_TRUE);
339 }
340 
341 /*
342  * gldv3_warning() catches the case of /sbin/ndd abuse to administer
343  * ethernet/MII props. Note that /sbin/ndd has not been abused
344  * for administration of other datalink types, which makes it permissible
345  * to test for support of the flowctrl property.
346  */
347 static void
348 gldv3_warning(char *module)
349 {
350 	datalink_id_t	linkid;
351 	dladm_status_t	status;
352 	char		buf[DLADM_PROP_VAL_MAX], *cp;
353 	uint_t		cnt = 1;
354 	char		*link;
355 	dladm_handle_t	handle;
356 
357 	link = strrchr(module, '/');
358 	if (link == NULL)
359 		return;
360 
361 	if (dladm_open(&handle) != DLADM_STATUS_OK)
362 		return;
363 
364 	status = dladm_name2info(handle, ++link, &linkid, NULL, NULL, NULL);
365 	if (status == DLADM_STATUS_OK) {
366 		cp = buf;
367 		status = dladm_get_linkprop(handle, linkid,
368 		    DLADM_PROP_VAL_CURRENT, "flowctrl", &cp, &cnt);
369 		if (status == DLADM_STATUS_OK) {
370 			(void) fprintf(stderr, gettext(
371 			    "WARNING: The ndd commands for datalink "
372 			    "administration are obsolete and may be "
373 			    "removed in a future release of Solaris. "
374 			    "Use dladm(1M) to manage datalink tunables.\n"));
375 		}
376 	}
377 	dladm_close(handle);
378 }
379 
380 /* ARGSUSED */
381 int
382 main(int argc, char **argv)
383 {
384 	char	*cp, *value, *mod;
385 	int	cmd;
386 	int	fd = 0;
387 
388 	if (!(cp = *++argv)) {
389 		while ((fd = open_device()) != -1) {
390 			getset_interactive(fd);
391 			(void) close(fd);
392 		}
393 		return (EXIT_SUCCESS);
394 	}
395 
396 	cmd = ND_GET;
397 	if (cp[0] == '-') {
398 		if (strncmp(&cp[1], "set", 3) == 0)
399 			cmd = ND_SET;
400 		else if (strncmp(&cp[1], "get", 3) != 0)
401 			fatal(usage_str);
402 		if (!(cp = *++argv))
403 			fatal(usage_str);
404 	}
405 
406 	gldv3_warning(cp);
407 
408 	mod = strrchr(cp, '/');
409 	if (mod != NULL)
410 		mod++;
411 	else
412 		mod = cp;
413 
414 	if (ndd_str2proto(mod) == MOD_PROTO_NONE) {
415 		if ((fd = open(cp, O_RDWR)) == -1)
416 			fatal("open of %s failed: %s", cp, errmsg(errno));
417 		if (!isastream(fd))
418 			fatal("%s is not a streams device", cp);
419 	}
420 
421 	(void) strlcpy(modpath, cp, sizeof (modpath));
422 	if (!(cp = *++argv)) {
423 		getset_interactive(fd);
424 		(void) close(fd);
425 		return (EXIT_SUCCESS);
426 	}
427 
428 	if (cmd == ND_SET) {
429 		if (!(value = *++argv))
430 			fatal(usage_str);
431 		(void) snprintf(gbuf, sizeof (gbuf), "%s%c%s%c", cp, '\0',
432 		    value, '\0');
433 		if (!do_getset(fd, cmd, gbuf, sizeof (gbuf)))
434 			return (EXIT_FAILURE);
435 	} else {
436 		do {
437 			(void) memset(gbuf, '\0', sizeof (gbuf));
438 			(void) strlcpy(gbuf, cp, sizeof (gbuf));
439 			if (!do_getset(fd, cmd, gbuf, sizeof (gbuf)))
440 				return (EXIT_FAILURE);
441 			if (cp = *++argv)
442 				(void) putchar('\n');
443 		} while (cp);
444 	}
445 
446 	(void) close(fd);
447 	return (EXIT_SUCCESS);
448 }
449 
450 static void
451 name_print(char *buf)
452 {
453 	char *cp, *rwtag;
454 
455 	for (cp = buf; cp[0]; ) {
456 		for (rwtag = cp; !isspace(*rwtag); rwtag++)
457 			;
458 		*rwtag++ = '\0';
459 		while (isspace(*rwtag))
460 			rwtag++;
461 		(void) printf("%-30s%s\n", cp, rwtag);
462 		for (cp = rwtag; *cp++; )
463 			;
464 	}
465 }
466 
467 /*
468  * This function is vile, but it's better here than in the kernel.
469  */
470 static boolean_t
471 is_obsolete(const char *param)
472 {
473 	if (strcmp(param, "ip_enable_group_ifs") == 0 ||
474 	    strcmp(param, "ifgrp_status") == 0) {
475 		(void) fprintf(stderr, "The \"%s\" tunable has been superseded "
476 		    "by IP Multipathing.\nPlease see the IP Network "
477 		    "Multipathing Administration Guide for details.\n", param);
478 		return (B_TRUE);
479 	}
480 	return (B_FALSE);
481 }
482 
483 static boolean_t
484 do_getset(int fd, int cmd, char *buf, int buf_len)
485 {
486 	char	*cp;
487 	struct strioctl	stri;
488 	boolean_t	is_name_get;
489 
490 	if (is_obsolete(buf))
491 		return (B_TRUE);
492 
493 	/*
494 	 * See if libipadm can handle this request, i.e., properties on
495 	 * following modules arp, ip, ipv4, ipv6, tcp, udp and sctp
496 	 */
497 	if (do_ipadm_getset(cmd, buf, buf_len))
498 		return (B_TRUE);
499 
500 	stri.ic_cmd = cmd;
501 	stri.ic_timout = 0;
502 	stri.ic_len = buf_len;
503 	stri.ic_dp = buf;
504 	is_name_get = stri.ic_cmd == ND_GET && buf[0] == '?' && buf[1] == '\0';
505 
506 	if (ioctl(fd, I_STR, &stri) == -1) {
507 		if (errno == ENOENT)
508 			(void) printf("name is non-existent for this module\n"
509 			    "for a list of valid names, use name '?'\n");
510 		else
511 			(void) printf("operation failed: %s\n", errmsg(errno));
512 		return (B_FALSE);
513 	}
514 	if (is_name_get)
515 		name_print(buf);
516 	else if (stri.ic_cmd == ND_GET) {
517 		for (cp = buf; *cp != '\0'; cp += strlen(cp) + 1)
518 			(void) puts(cp);
519 	}
520 	(void) fflush(stdout);
521 	return (B_TRUE);
522 }
523 
524 static int
525 get_value(char *msg, char *buf, int buf_len)
526 {
527 	int	len;
528 
529 	(void) printf("%s", msg);
530 	(void) fflush(stdout);
531 
532 	buf[buf_len-1] = '\0';
533 	if (fgets(buf, buf_len-1, stdin) == NULL)
534 		exit(EXIT_SUCCESS);
535 	len = strlen(buf);
536 	if (buf[len-1] == '\n')
537 		buf[len - 1] = '\0';
538 	else
539 		len++;
540 	return (len);
541 }
542 
543 static void
544 getset_interactive(int fd)
545 {
546 	int	cmd;
547 	char	*cp;
548 	int	len, buf_len;
549 	char	len_buf[10];
550 
551 	for (;;) {
552 		(void) memset(gbuf, '\0', sizeof (gbuf));
553 		len = get_value("name to get/set ? ", gbuf, sizeof (gbuf));
554 		if (len == 1 || (gbuf[0] == 'q' && gbuf[1] == '\0'))
555 			return;
556 		for (cp = gbuf; cp < &gbuf[len]; cp++) {
557 			if (isspace(*cp))
558 				*cp = '\0';
559 		}
560 		cmd = ND_GET;
561 		if (gbuf[0] != '?' &&
562 		    get_value("value ? ", &gbuf[len], sizeof (gbuf) - len) > 1)
563 			cmd = ND_SET;
564 		if (cmd == ND_GET && gbuf[0] != '?' &&
565 		    get_value("length ? ", len_buf, sizeof (len_buf)) > 1) {
566 			if (!isdigit(len_buf[0])) {
567 				(void) printf("invalid length\n");
568 				continue;
569 			}
570 			buf_len = atoi(len_buf);
571 		} else
572 			buf_len = sizeof (gbuf);
573 		(void) do_getset(fd, cmd, gbuf, buf_len);
574 	}
575 }
576 
577 static void
578 printe(boolean_t print_errno, char *fmt, ...)
579 {
580 	va_list	ap;
581 	int error = errno;
582 
583 	va_start(ap, fmt);
584 	(void) printf("*ERROR* ");
585 	(void) vprintf(fmt, ap);
586 	va_end(ap);
587 
588 	if (print_errno)
589 		(void) printf(": %s\n", errmsg(error));
590 	else
591 		(void) printf("\n");
592 }
593 
594 static int
595 open_device()
596 {
597 	int	fd, len;
598 	char	*mod;
599 
600 	for (;;) {
601 		len = get_value("module to query ? ", modpath,
602 		    sizeof (modpath));
603 		if (len <= 1 ||
604 		    (len == 2 && (modpath[0] == 'q' || modpath[0] == 'Q')))
605 			return (-1);
606 
607 		mod = strrchr(modpath, '/');
608 		if (mod != NULL)
609 			mod++;
610 		else
611 			mod = modpath;
612 		if (ndd_str2proto(mod) == MOD_PROTO_NONE) {
613 			if ((fd = open(modpath, O_RDWR)) == -1) {
614 				printe(B_TRUE, "open of %s failed", modpath);
615 				continue;
616 			}
617 		} else {
618 			return (0);
619 		}
620 
621 		gldv3_warning(modpath);
622 
623 		if (isastream(fd))
624 			return (fd);
625 
626 		(void) close(fd);
627 		printe(B_FALSE, "%s is not a streams device", modpath);
628 	}
629 }
630 
631 static void
632 fatal(char *fmt, ...)
633 {
634 	va_list	ap;
635 
636 	va_start(ap, fmt);
637 	(void) vfprintf(stderr, fmt, ap);
638 	va_end(ap);
639 	(void) fprintf(stderr, "\n");
640 
641 	exit(EXIT_FAILURE);
642 }
643 
644 static char *
645 errmsg(int error)
646 {
647 	char *msg = strerror(error);
648 
649 	return (msg != NULL ? msg : "unknown error");
650 }
651