ifmedia.c revision 25450
125450Speter/* $NetBSD: ifconfig.c,v 1.34 1997/04/21 01:17:58 lukem Exp $ */ 225450Speter/* $Id$ */ 325450Speter 425450Speter/* 525450Speter * Copyright (c) 1997 Jason R. Thorpe. 625450Speter * All rights reserved. 725450Speter * 825450Speter * Redistribution and use in source and binary forms, with or without 925450Speter * modification, are permitted provided that the following conditions 1025450Speter * are met: 1125450Speter * 1. Redistributions of source code must retain the above copyright 1225450Speter * notice, this list of conditions and the following disclaimer. 1325450Speter * 2. Redistributions in binary form must reproduce the above copyright 1425450Speter * notice, this list of conditions and the following disclaimer in the 1525450Speter * documentation and/or other materials provided with the distribution. 1625450Speter * 3. All advertising materials mentioning features or use of this software 1725450Speter * must display the following acknowledgement: 1825450Speter * This product includes software developed for the NetBSD Project 1925450Speter * by Jason R. Thorpe. 2025450Speter * 4. The name of the author may not be used to endorse or promote products 2125450Speter * derived from this software without specific prior written permission. 2225450Speter * 2325450Speter * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 2425450Speter * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 2525450Speter * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2625450Speter * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 2725450Speter * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 2825450Speter * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 2925450Speter * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 3025450Speter * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 3125450Speter * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3225450Speter * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3325450Speter * SUCH DAMAGE. 3425450Speter */ 3525450Speter 3625450Speter/* 3725450Speter * Copyright (c) 1983, 1993 3825450Speter * The Regents of the University of California. All rights reserved. 3925450Speter * 4025450Speter * Redistribution and use in source and binary forms, with or without 4125450Speter * modification, are permitted provided that the following conditions 4225450Speter * are met: 4325450Speter * 1. Redistributions of source code must retain the above copyright 4425450Speter * notice, this list of conditions and the following disclaimer. 4525450Speter * 2. Redistributions in binary form must reproduce the above copyright 4625450Speter * notice, this list of conditions and the following disclaimer in the 4725450Speter * documentation and/or other materials provided with the distribution. 4825450Speter * 3. All advertising materials mentioning features or use of this software 4925450Speter * must display the following acknowledgement: 5025450Speter * This product includes software developed by the University of 5125450Speter * California, Berkeley and its contributors. 5225450Speter * 4. Neither the name of the University nor the names of its contributors 5325450Speter * may be used to endorse or promote products derived from this software 5425450Speter * without specific prior written permission. 5525450Speter * 5625450Speter * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 5725450Speter * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 5825450Speter * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 5925450Speter * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 6025450Speter * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 6125450Speter * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 6225450Speter * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 6325450Speter * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 6425450Speter * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 6525450Speter * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 6625450Speter * SUCH DAMAGE. 6725450Speter */ 6825450Speter 6925450Speter#include <sys/param.h> 7025450Speter#include <sys/ioctl.h> 7125450Speter#include <sys/socket.h> 7225450Speter#include <sys/sysctl.h> 7325450Speter#include <sys/time.h> 7425450Speter 7525450Speter#include <net/if.h> 7625450Speter#include <net/if_var.h> 7725450Speter#include <net/if_dl.h> 7825450Speter#include <net/if_types.h> 7925450Speter#include <net/if_media.h> 8025450Speter#include <net/route.h> 8125450Speter 8225450Speter#include <ctype.h> 8325450Speter#include <err.h> 8425450Speter#include <errno.h> 8525450Speter#include <fcntl.h> 8625450Speter#include <stdio.h> 8725450Speter#include <stdlib.h> 8825450Speter#include <string.h> 8925450Speter#include <unistd.h> 9025450Speter 9125450Speter#include "ifconfig.h" 9225450Speter 9325450Speterstatic void domediaopt __P((const char *, int, int)); 9425450Speterstatic int get_media_subtype __P((int, const char *)); 9525450Speterstatic int get_media_options __P((int, const char *)); 9625450Speterstatic int lookup_media_word __P((struct ifmedia_description *, const char *)); 9725450Speterstatic void print_media_word __P((int)); 9825450Speter 9925450Spetervoid 10025450Spetermedia_status(s) 10125450Speter int s; 10225450Speter{ 10325450Speter struct ifmediareq ifmr; 10425450Speter int *media_list, i; 10525450Speter 10625450Speter (void) memset(&ifmr, 0, sizeof(ifmr)); 10725450Speter (void) strncpy(ifmr.ifm_name, name, sizeof(ifmr.ifm_name)); 10825450Speter 10925450Speter if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) { 11025450Speter /* 11125450Speter * Interface doesn't support SIOC{G,S}IFMEDIA. 11225450Speter */ 11325450Speter return; 11425450Speter } 11525450Speter 11625450Speter if (ifmr.ifm_count == 0) { 11725450Speter warnx("%s: no media types?", name); 11825450Speter return; 11925450Speter } 12025450Speter 12125450Speter media_list = (int *)malloc(ifmr.ifm_count * sizeof(int)); 12225450Speter if (media_list == NULL) 12325450Speter err(1, "malloc"); 12425450Speter ifmr.ifm_ulist = media_list; 12525450Speter 12625450Speter if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) 12725450Speter err(1, "SIOCGIFMEDIA"); 12825450Speter 12925450Speter printf("\tmedia: "); 13025450Speter print_media_word(ifmr.ifm_current); 13125450Speter if (ifmr.ifm_active != ifmr.ifm_current) { 13225450Speter putchar(' '); 13325450Speter putchar('('); 13425450Speter print_media_word(ifmr.ifm_active); 13525450Speter putchar(')'); 13625450Speter } 13725450Speter 13825450Speter if (ifmr.ifm_status & IFM_AVALID) { 13925450Speter printf(" status: "); 14025450Speter switch (IFM_TYPE(ifmr.ifm_active)) { 14125450Speter case IFM_ETHER: 14225450Speter if (ifmr.ifm_status & IFM_ACTIVE) 14325450Speter printf("active"); 14425450Speter else 14525450Speter printf("no carrier"); 14625450Speter break; 14725450Speter 14825450Speter case IFM_FDDI: 14925450Speter case IFM_TOKEN: 15025450Speter if (ifmr.ifm_status & IFM_ACTIVE) 15125450Speter printf("inserted"); 15225450Speter else 15325450Speter printf("no ring"); 15425450Speter break; 15525450Speter } 15625450Speter } 15725450Speter 15825450Speter putchar('\n'); 15925450Speter 16025450Speter if (allmedia) { 16125450Speter printf("\tsupported media:"); 16225450Speter for (i = 0; i < ifmr.ifm_count; i++) { 16325450Speter putchar(' '); 16425450Speter print_media_word(media_list[i]); 16525450Speter } 16625450Speter putchar('\n'); 16725450Speter } 16825450Speter 16925450Speter free(media_list); 17025450Speter} 17125450Speter 17225450Spetervoid 17325450Spetersetmedia(val, d, s) 17425450Speter const char *val; 17525450Speter int d; 17625450Speter int s; 17725450Speter{ 17825450Speter struct ifmediareq ifmr; 17925450Speter int first_type, subtype; 18025450Speter 18125450Speter (void) memset(&ifmr, 0, sizeof(ifmr)); 18225450Speter (void) strncpy(ifmr.ifm_name, name, sizeof(ifmr.ifm_name)); 18325450Speter 18425450Speter ifmr.ifm_count = 1; 18525450Speter ifmr.ifm_ulist = &first_type; 18625450Speter if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) { 18725450Speter /* 18825450Speter * If we get E2BIG, the kernel is telling us 18925450Speter * that there are more, so we can ignore it. 19025450Speter */ 19125450Speter if (errno != E2BIG) 19225450Speter err(1, "SIOCGIFMEDIA"); 19325450Speter } 19425450Speter 19525450Speter if (ifmr.ifm_count == 0) 19625450Speter errx(1, "%s: no media types?", name); 19725450Speter 19825450Speter /* 19925450Speter * We are primarily concerned with the top-level type. 20025450Speter * However, "current" may be only IFM_NONE, so we just look 20125450Speter * for the top-level type in the first "supported type" 20225450Speter * entry. 20325450Speter * 20425450Speter * (I'm assuming that all supported media types for a given 20525450Speter * interface will be the same top-level type..) 20625450Speter */ 20725450Speter subtype = get_media_subtype(IFM_TYPE(first_type), val); 20825450Speter 20925450Speter strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); 21025450Speter ifr.ifr_media = (ifmr.ifm_current & ~(IFM_NMASK|IFM_TMASK)) | 21125450Speter IFM_TYPE(first_type) | subtype; 21225450Speter 21325450Speter if (ioctl(s, SIOCSIFMEDIA, (caddr_t)&ifr) < 0) 21425450Speter err(1, "SIOCSIFMEDIA"); 21525450Speter} 21625450Speter 21725450Spetervoid 21825450Spetersetmediaopt(val, d, s) 21925450Speter const char *val; 22025450Speter int d; 22125450Speter int s; 22225450Speter{ 22325450Speter 22425450Speter domediaopt(val, 0, s); 22525450Speter} 22625450Speter 22725450Spetervoid 22825450Speterunsetmediaopt(val, d, s) 22925450Speter const char *val; 23025450Speter int d; 23125450Speter int s; 23225450Speter{ 23325450Speter 23425450Speter domediaopt(val, 1, s); 23525450Speter} 23625450Speter 23725450Speterstatic void 23825450Speterdomediaopt(val, clear, s) 23925450Speter const char *val; 24025450Speter int clear; 24125450Speter int s; 24225450Speter{ 24325450Speter struct ifmediareq ifmr; 24425450Speter int *mwords, options; 24525450Speter 24625450Speter (void) memset(&ifmr, 0, sizeof(ifmr)); 24725450Speter (void) strncpy(ifmr.ifm_name, name, sizeof(ifmr.ifm_name)); 24825450Speter 24925450Speter /* 25025450Speter * We must go through the motions of reading all 25125450Speter * supported media because we need to know both 25225450Speter * the current media type and the top-level type. 25325450Speter */ 25425450Speter 25525450Speter if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) 25625450Speter err(1, "SIOCGIFMEDIA"); 25725450Speter 25825450Speter if (ifmr.ifm_count == 0) 25925450Speter errx(1, "%s: no media types?", name); 26025450Speter 26125450Speter mwords = (int *)malloc(ifmr.ifm_count * sizeof(int)); 26225450Speter if (mwords == NULL) 26325450Speter err(1, "malloc"); 26425450Speter 26525450Speter ifmr.ifm_ulist = mwords; 26625450Speter if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) 26725450Speter err(1, "SIOCGIFMEDIA"); 26825450Speter 26925450Speter options = get_media_options(IFM_TYPE(mwords[0]), val); 27025450Speter 27125450Speter free(mwords); 27225450Speter 27325450Speter strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); 27425450Speter ifr.ifr_media = ifmr.ifm_current; 27525450Speter if (clear) 27625450Speter ifr.ifr_media &= ~options; 27725450Speter else 27825450Speter ifr.ifr_media |= options; 27925450Speter 28025450Speter if (ioctl(s, SIOCSIFMEDIA, (caddr_t)&ifr) < 0) 28125450Speter err(1, "SIOCSIFMEDIA"); 28225450Speter} 28325450Speter 28425450Speter/********************************************************************** 28525450Speter * A good chunk of this is duplicated from sys/net/ifmedia.c 28625450Speter **********************************************************************/ 28725450Speter 28825450Speterstatic struct ifmedia_description ifm_type_descriptions[] = 28925450Speter IFM_TYPE_DESCRIPTIONS; 29025450Speter 29125450Speterstatic struct ifmedia_description ifm_subtype_ethernet_descriptions[] = 29225450Speter IFM_SUBTYPE_ETHERNET_DESCRIPTIONS; 29325450Speter 29425450Speterstatic struct ifmedia_description ifm_subtype_ethernet_aliases[] = 29525450Speter IFM_SUBTYPE_ETHERNET_ALIASES; 29625450Speter 29725450Speterstatic struct ifmedia_description ifm_subtype_ethernet_option_descriptions[] = 29825450Speter IFM_SUBTYPE_ETHERNET_OPTION_DESCRIPTIONS; 29925450Speter 30025450Speterstatic struct ifmedia_description ifm_subtype_tokenring_descriptions[] = 30125450Speter IFM_SUBTYPE_TOKENRING_DESCRIPTIONS; 30225450Speter 30325450Speterstatic struct ifmedia_description ifm_subtype_tokenring_aliases[] = 30425450Speter IFM_SUBTYPE_TOKENRING_ALIASES; 30525450Speter 30625450Speterstatic struct ifmedia_description ifm_subtype_tokenring_option_descriptions[] = 30725450Speter IFM_SUBTYPE_TOKENRING_OPTION_DESCRIPTIONS; 30825450Speter 30925450Speterstatic struct ifmedia_description ifm_subtype_fddi_descriptions[] = 31025450Speter IFM_SUBTYPE_FDDI_DESCRIPTIONS; 31125450Speter 31225450Speterstatic struct ifmedia_description ifm_subtype_fddi_aliases[] = 31325450Speter IFM_SUBTYPE_FDDI_ALIASES; 31425450Speter 31525450Speterstatic struct ifmedia_description ifm_subtype_fddi_option_descriptions[] = 31625450Speter IFM_SUBTYPE_FDDI_OPTION_DESCRIPTIONS; 31725450Speter 31825450Speterstatic struct ifmedia_description ifm_subtype_shared_descriptions[] = 31925450Speter IFM_SUBTYPE_SHARED_DESCRIPTIONS; 32025450Speter 32125450Speterstatic struct ifmedia_description ifm_subtype_shared_aliases[] = 32225450Speter IFM_SUBTYPE_SHARED_ALIASES; 32325450Speter 32425450Speterstatic struct ifmedia_description ifm_shared_option_descriptions[] = 32525450Speter IFM_SHARED_OPTION_DESCRIPTIONS; 32625450Speter 32725450Speterstruct ifmedia_type_to_subtype { 32825450Speter struct { 32925450Speter struct ifmedia_description *desc; 33025450Speter int alias; 33125450Speter } subtypes[5]; 33225450Speter struct { 33325450Speter struct ifmedia_description *desc; 33425450Speter int alias; 33525450Speter } options[3]; 33625450Speter}; 33725450Speter 33825450Speter/* must be in the same order as IFM_TYPE_DESCRIPTIONS */ 33925450Speterstatic struct ifmedia_type_to_subtype ifmedia_types_to_subtypes[] = { 34025450Speter { 34125450Speter { 34225450Speter { &ifm_subtype_shared_descriptions[0], 0 }, 34325450Speter { &ifm_subtype_shared_aliases[0], 1 }, 34425450Speter { &ifm_subtype_ethernet_descriptions[0], 0 }, 34525450Speter { &ifm_subtype_ethernet_aliases[0], 1 }, 34625450Speter { NULL, 0 }, 34725450Speter }, 34825450Speter { 34925450Speter { &ifm_shared_option_descriptions[0], 0 }, 35025450Speter { &ifm_subtype_ethernet_option_descriptions[0], 1 }, 35125450Speter { NULL, 0 }, 35225450Speter }, 35325450Speter }, 35425450Speter { 35525450Speter { 35625450Speter { &ifm_subtype_shared_descriptions[0], 0 }, 35725450Speter { &ifm_subtype_shared_aliases[0], 1 }, 35825450Speter { &ifm_subtype_tokenring_descriptions[0], 0 }, 35925450Speter { &ifm_subtype_tokenring_aliases[0], 1 }, 36025450Speter { NULL, 0 }, 36125450Speter }, 36225450Speter { 36325450Speter { &ifm_shared_option_descriptions[0], 0 }, 36425450Speter { &ifm_subtype_tokenring_option_descriptions[0], 1 }, 36525450Speter { NULL, 0 }, 36625450Speter }, 36725450Speter }, 36825450Speter { 36925450Speter { 37025450Speter { &ifm_subtype_shared_descriptions[0], 0 }, 37125450Speter { &ifm_subtype_shared_aliases[0], 1 }, 37225450Speter { &ifm_subtype_fddi_descriptions[0], 0 }, 37325450Speter { &ifm_subtype_fddi_aliases[0], 1 }, 37425450Speter { NULL, 0 }, 37525450Speter }, 37625450Speter { 37725450Speter { &ifm_shared_option_descriptions[0], 0 }, 37825450Speter { &ifm_subtype_fddi_option_descriptions[0], 1 }, 37925450Speter { NULL, 0 }, 38025450Speter }, 38125450Speter }, 38225450Speter}; 38325450Speter 38425450Speterstatic int 38525450Speterget_media_subtype(type, val) 38625450Speter int type; 38725450Speter const char *val; 38825450Speter{ 38925450Speter struct ifmedia_description *desc; 39025450Speter struct ifmedia_type_to_subtype *ttos; 39125450Speter int rval, i; 39225450Speter 39325450Speter /* Find the top-level interface type. */ 39425450Speter for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes; 39525450Speter desc->ifmt_string != NULL; desc++, ttos++) 39625450Speter if (type == desc->ifmt_word) 39725450Speter break; 39825450Speter if (desc->ifmt_string == NULL) 39925450Speter errx(1, "unknown media type 0x%x", type); 40025450Speter 40125450Speter for (i = 0; ttos->subtypes[i].desc != NULL; i++) { 40225450Speter rval = lookup_media_word(ttos->subtypes[i].desc, val); 40325450Speter if (rval != -1) 40425450Speter return (rval); 40525450Speter } 40625450Speter errx(1, "unknown media subtype: %s", val); 40725450Speter /* NOTREACHED */ 40825450Speter} 40925450Speter 41025450Speterstatic int 41125450Speterget_media_options(type, val) 41225450Speter int type; 41325450Speter const char *val; 41425450Speter{ 41525450Speter struct ifmedia_description *desc; 41625450Speter struct ifmedia_type_to_subtype *ttos; 41725450Speter char *optlist, *optptr; 41825450Speter int option = 0, i, rval = 0; 41925450Speter 42025450Speter /* We muck with the string, so copy it. */ 42125450Speter optlist = strdup(val); 42225450Speter if (optlist == NULL) 42325450Speter err(1, "strdup"); 42425450Speter 42525450Speter /* Find the top-level interface type. */ 42625450Speter for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes; 42725450Speter desc->ifmt_string != NULL; desc++, ttos++) 42825450Speter if (type == desc->ifmt_word) 42925450Speter break; 43025450Speter if (desc->ifmt_string == NULL) 43125450Speter errx(1, "unknown media type 0x%x", type); 43225450Speter 43325450Speter /* 43425450Speter * Look up the options in the user-provided comma-separated 43525450Speter * list. 43625450Speter */ 43725450Speter optptr = optlist; 43825450Speter for (; (optptr = strtok(optptr, ",")) != NULL; optptr = NULL) { 43925450Speter for (i = 0; ttos->options[i].desc != NULL; i++) { 44025450Speter option = lookup_media_word(ttos->options[i].desc, optptr); 44125450Speter if (option != -1) 44225450Speter break; 44325450Speter } 44425450Speter if (option == 0) 44525450Speter errx(1, "unknown option: %s", optptr); 44625450Speter rval |= option; 44725450Speter } 44825450Speter 44925450Speter free(optlist); 45025450Speter return (rval); 45125450Speter} 45225450Speter 45325450Speterstatic int 45425450Speterlookup_media_word(desc, val) 45525450Speter struct ifmedia_description *desc; 45625450Speter const char *val; 45725450Speter{ 45825450Speter 45925450Speter for (; desc->ifmt_string != NULL; desc++) 46025450Speter if (strcasecmp(desc->ifmt_string, val) == 0) 46125450Speter return (desc->ifmt_word); 46225450Speter 46325450Speter return (-1); 46425450Speter} 46525450Speter 46625450Speterstatic void 46725450Speterprint_media_word(ifmw) 46825450Speter int ifmw; 46925450Speter{ 47025450Speter struct ifmedia_description *desc; 47125450Speter struct ifmedia_type_to_subtype *ttos; 47225450Speter int seen_option = 0, i; 47325450Speter 47425450Speter /* Find the top-level interface type. */ 47525450Speter for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes; 47625450Speter desc->ifmt_string != NULL; desc++, ttos++) 47725450Speter if (IFM_TYPE(ifmw) == desc->ifmt_word) 47825450Speter break; 47925450Speter if (desc->ifmt_string == NULL) { 48025450Speter printf("<unknown type>"); 48125450Speter return; 48225450Speter } 48325450Speter 48425450Speter /* 48525450Speter * Don't print the top-level type; it's not like we can 48625450Speter * change it, or anything. 48725450Speter */ 48825450Speter 48925450Speter /* Find subtype. */ 49025450Speter for (i = 0; ttos->subtypes[i].desc != NULL; i++) { 49125450Speter if (ttos->subtypes[i].alias) 49225450Speter continue; 49325450Speter for (desc = ttos->subtypes[i].desc; 49425450Speter desc->ifmt_string != NULL; desc++) { 49525450Speter if (IFM_SUBTYPE(ifmw) == desc->ifmt_word) 49625450Speter goto got_subtype; 49725450Speter } 49825450Speter } 49925450Speter 50025450Speter /* Falling to here means unknown subtype. */ 50125450Speter printf("<unknown subtype>"); 50225450Speter return; 50325450Speter 50425450Speter got_subtype: 50525450Speter printf("%s", desc->ifmt_string); 50625450Speter 50725450Speter /* Find options. */ 50825450Speter for (i = 0; ttos->options[i].desc != NULL; i++) { 50925450Speter if (ttos->options[i].alias) 51025450Speter continue; 51125450Speter for (desc = ttos->options[i].desc; 51225450Speter desc->ifmt_string != NULL; desc++) { 51325450Speter if (ifmw & desc->ifmt_word) { 51425450Speter if (seen_option == 0) 51525450Speter printf(" <"); 51625450Speter printf("%s%s", seen_option++ ? "," : "", 51725450Speter desc->ifmt_string); 51825450Speter } 51925450Speter } 52025450Speter } 52125450Speter printf("%s", seen_option ? ">" : ""); 52225450Speter} 52325450Speter 52425450Speter/********************************************************************** 52525450Speter * ...until here. 52625450Speter **********************************************************************/ 527