/* SPDX-License-Identifier: LGPL-2.1-only */ /* * lib/utils.c Utility Functions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * Copyright (c) 2003-2012 Thomas Graf */ /** * @ingroup core * @defgroup utils Utilities * * Collection of helper functions * * @{ * * Header * ------ * ~~~~{.c} * #include * ~~~~ */ #include #include #include #include #include #include /* exit() */ #ifdef HAVE_STRERROR_L #include #endif /** * Global variable indicating the desired level of debugging output. * * Level | Messages Printed * ----- | --------------------------------------------------------- * 0 | Debugging output disabled * 1 | Warnings, important events and notifications * 2 | More or less important debugging messages * 3 | Repetitive events causing a flood of debugging messages * 4 | Even less important messages * * If available, the variable will be initialized to the value of the * environment variable `NLDBG`. The default value is 0 (disabled). * * For more information, see section @core_doc{_debugging, Debugging}. */ int nl_debug = 0; /** @cond SKIP */ #ifdef NL_DEBUG struct nl_dump_params nl_debug_dp = { .dp_type = NL_DUMP_DETAILS, }; static void __init nl_debug_init(void) { char *nldbg, *end; if ((nldbg = getenv("NLDBG"))) { long level = strtol(nldbg, &end, 0); if (nldbg != end) nl_debug = level; } nl_debug_dp.dp_fd = stderr; } #endif int __nl_read_num_str_file(const char *path, int (*cb)(long, const char *)) { FILE *fd; char buf[128]; fd = fopen(path, "re"); if (fd == NULL) return -nl_syserr2nlerr(errno); while (fgets(buf, sizeof(buf), fd)) { int goodlen, err; long num; char *end; if (*buf == '#' || *buf == '\n' || *buf == '\r') continue; num = strtol(buf, &end, 0); if (end == buf) { fclose(fd); return -NLE_INVAL; } if (num == LONG_MIN || num == LONG_MAX) { fclose(fd); return -NLE_RANGE; } while (*end == ' ' || *end == '\t') end++; goodlen = strcspn(end, "#\r\n\t "); if (goodlen == 0) { fclose(fd); return -NLE_INVAL; } end[goodlen] = '\0'; err = cb(num, end); if (err < 0) { fclose(fd); return err; } } fclose(fd); return 0; } const char *nl_strerror_l(int err) { const char *buf; #ifdef HAVE_STRERROR_L int errno_save = errno; locale_t loc = newlocale(LC_MESSAGES_MASK, "", (locale_t)0); if (loc == (locale_t)0) { if (errno == ENOENT) loc = newlocale(LC_MESSAGES_MASK, "POSIX", (locale_t)0); } if (loc != (locale_t)0) { buf = strerror_l(err, loc); freelocale(loc); } else { buf = "newlocale() failed"; } errno = errno_save; #else buf = strerror(err); #endif return buf; } /** @endcond */ /** * @name Pretty Printing of Numbers * @{ */ /** * Cancel down a byte counter * @arg l byte counter * @arg unit destination unit pointer * * Cancels down a byte counter until it reaches a reasonable * unit. The chosen unit is assigned to \a unit. * This function assume 1024 bytes in one kilobyte * * @return The cancelled down byte counter in the new unit. */ double nl_cancel_down_bytes(unsigned long long l, char **unit) { if (l >= 1099511627776LL) { *unit = "TiB"; return ((double) l) / 1099511627776LL; } else if (l >= 1073741824) { *unit = "GiB"; return ((double) l) / 1073741824; } else if (l >= 1048576) { *unit = "MiB"; return ((double) l) / 1048576; } else if (l >= 1024) { *unit = "KiB"; return ((double) l) / 1024; } else { *unit = "B"; return (double) l; } } /** * Cancel down a bit counter * @arg l bit counter * @arg unit destination unit pointer * * Cancels down bit counter until it reaches a reasonable * unit. The chosen unit is assigned to \a unit. * This function assume 1000 bits in one kilobit * * @return The cancelled down bit counter in the new unit. */ double nl_cancel_down_bits(unsigned long long l, char **unit) { if (l >= 1000000000000ULL) { *unit = "Tbit"; return ((double) l) / 1000000000000ULL; } if (l >= 1000000000) { *unit = "Gbit"; return ((double) l) / 1000000000; } if (l >= 1000000) { *unit = "Mbit"; return ((double) l) / 1000000; } if (l >= 1000) { *unit = "Kbit"; return ((double) l) / 1000; } *unit = "bit"; return (double) l; } int nl_rate2str(unsigned long long rate, int type, char *buf, size_t len) { char *unit; double frac; switch (type) { case NL_BYTE_RATE: frac = nl_cancel_down_bytes(rate, &unit); break; case NL_BIT_RATE: frac = nl_cancel_down_bits(rate, &unit); break; default: BUG(); } return snprintf(buf, len, "%.2f%s/s", frac, unit); } /** * Cancel down a micro second value * @arg l micro seconds * @arg unit destination unit pointer * * Cancels down a microsecond counter until it reaches a * reasonable unit. The chosen unit is assigned to \a unit. * * @return The cancelled down microsecond in the new unit */ double nl_cancel_down_us(uint32_t l, char **unit) { if (l >= 1000000) { *unit = "s"; return ((double) l) / 1000000; } else if (l >= 1000) { *unit = "ms"; return ((double) l) / 1000; } else { *unit = "us"; return (double) l; } } /** @} */ /** * @name Generic Unit Translations * @{ */ /** * Convert a character string to a size * @arg str size encoded as character string * * Converts the specified size as character to the corresponding * number of bytes. * * Supported formats are: * - b,kb/k,m/mb,gb/g for bytes * - bit,kbit/mbit/gbit * * This function assume 1000 bits in one kilobit and * 1024 bytes in one kilobyte * * @return The number of bytes or -1 if the string is unparseable */ long nl_size2int(const char *str) { char *p; long l = strtol(str, &p, 0); if (p == str) return -NLE_INVAL; if (*p) { if (!strcasecmp(p, "kb") || !strcasecmp(p, "k")) l *= 1024; else if (!strcasecmp(p, "gb") || !strcasecmp(p, "g")) l *= 1024*1024*1024; else if (!strcasecmp(p, "gbit")) l *= 1000000000L/8; else if (!strcasecmp(p, "mb") || !strcasecmp(p, "m")) l *= 1024*1024; else if (!strcasecmp(p, "mbit")) l *= 1000000/8; else if (!strcasecmp(p, "kbit")) l *= 1000/8; else if (!strcasecmp(p, "bit")) l /= 8; else if (strcasecmp(p, "b") != 0) return -NLE_INVAL; } return l; } static const struct { double limit; const char *unit; } size_units[] = { { 1024. * 1024. * 1024. * 1024. * 1024., "EiB" }, { 1024. * 1024. * 1024. * 1024., "TiB" }, { 1024. * 1024. * 1024., "GiB" }, { 1024. * 1024., "MiB" }, { 1024., "KiB" }, { 0., "B" }, }; /** * Convert a size toa character string * @arg size Size in number of bytes * @arg buf Buffer to write character string to * @arg len Size of buf * * This function converts a value in bytes to a human readable representation * of it. The function uses IEC prefixes: * * @code * 1024 bytes => 1 KiB * 1048576 bytes => 1 MiB * @endcode * * The highest prefix is used which ensures a result of >= 1.0, the result * is provided as floating point number with a maximum precision of 2 digits: * @code * 965176 bytes => 942.55 KiB * @endcode * * @return pointer to buf */ char *nl_size2str(const size_t size, char *buf, const size_t len) { size_t i; if (size == 0) { snprintf(buf, len, "0B"); return buf; } for (i = 0; i < ARRAY_SIZE(size_units); i++) { if (size >= size_units[i].limit) { snprintf(buf, len, "%.2g%s", (double) size / size_units[i].limit, size_units[i].unit); return buf; } } BUG(); } /** * Convert a character string to a probability * @arg str probability encoded as character string * * Converts the specified probability as character to the * corresponding probability number. * * Supported formats are: * - 0.0-1.0 * - 0%-100% * * @return The probability relative to NL_PROB_MIN and NL_PROB_MAX */ long nl_prob2int(const char *str) { char *p; double d = strtod(str, &p); if (p == str) return -NLE_INVAL; if (d > 1.0) d /= 100.0f; if (d > 1.0f || d < 0.0f) return -NLE_RANGE; if (*p && strcmp(p, "%") != 0) return -NLE_INVAL; return (long) (((d * NL_PROB_MAX) + 0.5)); } /** @} */ /** * @name Time Translations * @{ */ #ifndef USER_HZ #define USER_HZ 100 #endif static uint32_t user_hz = USER_HZ; static uint32_t psched_hz = USER_HZ; static double ticks_per_usec = 1.0f; /* Retrieves the configured HZ and ticks/us value in the kernel. * The value is cached. Supported ways of getting it: * * 1) environment variable * 2) /proc/net/psched and sysconf * * Supports the environment variables: * PROC_NET_PSCHED - may point to psched file in /proc * PROC_ROOT - may point to /proc fs */ static void get_psched_settings(void) { char name[FILENAME_MAX]; FILE *fd; int got_hz = 0; static volatile int initialized = 0; const char *ev; NL_LOCK(mutex); if (initialized == 1) return; nl_lock(&mutex); if (initialized == 1) return; if ((ev = getenv("HZ"))) { long hz = strtol(ev, NULL, 0); if (LONG_MIN != hz && LONG_MAX != hz) { user_hz = hz; got_hz = 1; } } if (!got_hz) user_hz = sysconf(_SC_CLK_TCK); psched_hz = user_hz; if ((ev = getenv("TICKS_PER_USEC"))) { double t = strtod(ev, NULL); ticks_per_usec = t; } else { if ((ev = getenv("PROC_NET_PSCHED"))) snprintf(name, sizeof(name), "%s", ev); else if ((ev = getenv("PROC_ROOT"))) snprintf(name, sizeof(name), "%s/net/psched", ev); else strncpy(name, "/proc/net/psched", sizeof(name) - 1); if ((fd = fopen(name, "re"))) { unsigned int ns_per_usec, ns_per_tick, nom, denom; if (fscanf(fd, "%08x %08x %08x %08x", &ns_per_usec, &ns_per_tick, &nom, &denom) != 4) { NL_DBG(1, "Fatal error: can not read psched settings from \"%s\". " \ "Try to set TICKS_PER_USEC, PROC_NET_PSCHED or PROC_ROOT " \ "environment variables\n", name); exit(1); } ticks_per_usec = (double) ns_per_usec / (double) ns_per_tick; if (nom == 1000000) psched_hz = denom; fclose(fd); } } initialized = 1; nl_unlock(&mutex); } /** * Return the value of HZ */ int nl_get_user_hz(void) { get_psched_settings(); return user_hz; } /** * Return the value of packet scheduler HZ */ int nl_get_psched_hz(void) { get_psched_settings(); return psched_hz; } /** * Convert micro seconds to ticks * @arg us micro seconds * @return number of ticks */ uint32_t nl_us2ticks(uint32_t us) { get_psched_settings(); return us * ticks_per_usec; } /** * Convert ticks to micro seconds * @arg ticks number of ticks * @return microseconds */ uint32_t nl_ticks2us(uint32_t ticks) { get_psched_settings(); return ticks / ticks_per_usec; } int nl_str2msec(const char *str, uint64_t *result) { uint64_t total = 0, l; int plen; char *p; do { l = strtoul(str, &p, 0); if (p == str) return -NLE_INVAL; else if (*p) { plen = strcspn(p, " \t"); if (!plen) total += l; else if (!strncasecmp(p, "sec", plen)) total += (l * 1000); else if (!strncasecmp(p, "min", plen)) total += (l * 1000*60); else if (!strncasecmp(p, "hour", plen)) total += (l * 1000*60*60); else if (!strncasecmp(p, "day", plen)) total += (l * 1000*60*60*24); else return -NLE_INVAL; str = p + plen; } else total += l; } while (*str && *p); *result = total; return 0; } /** * Convert milliseconds to a character string * @arg msec number of milliseconds * @arg buf destination buffer * @arg len buffer length * * Converts milliseconds to a character string split up in days, hours, * minutes, seconds, and milliseconds and stores it in the specified * destination buffer. * * @return The destination buffer. */ char * nl_msec2str(uint64_t msec, char *buf, size_t len) { uint64_t split[5]; size_t i; static const char *units[5] = {"d", "h", "m", "s", "msec"}; char * const buf_orig = buf; if (msec == 0) { snprintf(buf, len, "0msec"); return buf_orig; } #define _SPLIT(idx, unit) if ((split[idx] = msec / unit)) msec %= unit _SPLIT(0, 86400000); /* days */ _SPLIT(1, 3600000); /* hours */ _SPLIT(2, 60000); /* minutes */ _SPLIT(3, 1000); /* seconds */ #undef _SPLIT split[4] = msec; for (i = 0; i < ARRAY_SIZE(split) && len; i++) { int l; if (split[i] == 0) continue; l = snprintf(buf, len, "%s%" PRIu64 "%s", (buf==buf_orig) ? "" : " ", split[i], units[i]); buf += l; len -= l; } return buf_orig; } /** @} */ /** * @name Netlink Family Translations * @{ */ static const struct trans_tbl nlfamilies[] = { __ADD(NETLINK_ROUTE,route), __ADD(NETLINK_USERSOCK,usersock), __ADD(NETLINK_FIREWALL,firewall), __ADD(NETLINK_INET_DIAG,inetdiag), __ADD(NETLINK_NFLOG,nflog), __ADD(NETLINK_XFRM,xfrm), __ADD(NETLINK_SELINUX,selinux), __ADD(NETLINK_ISCSI,iscsi), __ADD(NETLINK_AUDIT,audit), __ADD(NETLINK_FIB_LOOKUP,fib_lookup), __ADD(NETLINK_CONNECTOR,connector), __ADD(NETLINK_NETFILTER,netfilter), __ADD(NETLINK_IP6_FW,ip6_fw), __ADD(NETLINK_DNRTMSG,dnrtmsg), __ADD(NETLINK_KOBJECT_UEVENT,kobject_uevent), __ADD(NETLINK_GENERIC,generic), __ADD(NETLINK_SCSITRANSPORT,scsitransport), __ADD(NETLINK_ECRYPTFS,ecryptfs), __ADD(NETLINK_RDMA,rdma), __ADD(NETLINK_CRYPTO,crypto), }; char * nl_nlfamily2str(int family, char *buf, size_t size) { return __type2str(family, buf, size, nlfamilies, ARRAY_SIZE(nlfamilies)); } int nl_str2nlfamily(const char *name) { return __str2type(name, nlfamilies, ARRAY_SIZE(nlfamilies)); } /** * @} */ /** * @name Link Layer Protocol Translations * @{ */ static const struct trans_tbl llprotos[] = { {0, "generic"}, __ADD(ARPHRD_NETROM,netrom), __ADD(ARPHRD_ETHER,ether), __ADD(ARPHRD_EETHER,eether), __ADD(ARPHRD_AX25,ax25), __ADD(ARPHRD_PRONET,pronet), __ADD(ARPHRD_CHAOS,chaos), __ADD(ARPHRD_IEEE802,ieee802), __ADD(ARPHRD_ARCNET,arcnet), __ADD(ARPHRD_APPLETLK,atalk), __ADD(ARPHRD_DLCI,dlci), __ADD(ARPHRD_ATM,atm), __ADD(ARPHRD_METRICOM,metricom), __ADD(ARPHRD_IEEE1394,ieee1394), __ADD(ARPHRD_EUI64,eui64), __ADD(ARPHRD_INFINIBAND,infiniband), __ADD(ARPHRD_SLIP,slip), __ADD(ARPHRD_CSLIP,cslip), __ADD(ARPHRD_SLIP6,slip6), __ADD(ARPHRD_CSLIP6,cslip6), __ADD(ARPHRD_RSRVD,rsrvd), __ADD(ARPHRD_ADAPT,adapt), __ADD(ARPHRD_ROSE,rose), __ADD(ARPHRD_X25,x25), __ADD(ARPHRD_HWX25,hwx25), __ADD(ARPHRD_CAN,can), __ADD(ARPHRD_PPP,ppp), __ADD(ARPHRD_CISCO,cisco), __ADD(ARPHRD_HDLC,hdlc), __ADD(ARPHRD_LAPB,lapb), __ADD(ARPHRD_DDCMP,ddcmp), __ADD(ARPHRD_RAWHDLC,rawhdlc), __ADD(ARPHRD_TUNNEL,ipip), __ADD(ARPHRD_TUNNEL6,tunnel6), __ADD(ARPHRD_FRAD,frad), __ADD(ARPHRD_SKIP,skip), __ADD(ARPHRD_LOOPBACK,loopback), __ADD(ARPHRD_LOCALTLK,localtlk), __ADD(ARPHRD_FDDI,fddi), __ADD(ARPHRD_BIF,bif), __ADD(ARPHRD_SIT,sit), __ADD(ARPHRD_IPDDP,ip/ddp), __ADD(ARPHRD_IPGRE,gre), __ADD(ARPHRD_PIMREG,pimreg), __ADD(ARPHRD_HIPPI,hippi), __ADD(ARPHRD_ASH,ash), __ADD(ARPHRD_ECONET,econet), __ADD(ARPHRD_IRDA,irda), __ADD(ARPHRD_FCPP,fcpp), __ADD(ARPHRD_FCAL,fcal), __ADD(ARPHRD_FCPL,fcpl), __ADD(ARPHRD_FCFABRIC,fcfb_0), __ADD(ARPHRD_FCFABRIC+1,fcfb_1), __ADD(ARPHRD_FCFABRIC+2,fcfb_2), __ADD(ARPHRD_FCFABRIC+3,fcfb_3), __ADD(ARPHRD_FCFABRIC+4,fcfb_4), __ADD(ARPHRD_FCFABRIC+5,fcfb_5), __ADD(ARPHRD_FCFABRIC+6,fcfb_6), __ADD(ARPHRD_FCFABRIC+7,fcfb_7), __ADD(ARPHRD_FCFABRIC+8,fcfb_8), __ADD(ARPHRD_FCFABRIC+9,fcfb_9), __ADD(ARPHRD_FCFABRIC+10,fcfb_10), __ADD(ARPHRD_FCFABRIC+11,fcfb_11), __ADD(ARPHRD_FCFABRIC+12,fcfb_12), __ADD(ARPHRD_IEEE802_TR,tr), __ADD(ARPHRD_IEEE80211,ieee802.11), __ADD(ARPHRD_IEEE80211_PRISM,ieee802.11_prism), __ADD(ARPHRD_IEEE80211_RADIOTAP,ieee802.11_radiotap), __ADD(ARPHRD_IEEE802154,ieee802.15.4), __ADD(ARPHRD_IEEE802154_MONITOR,ieee802.15.4_monitor), __ADD(ARPHRD_PHONET,phonet), __ADD(ARPHRD_PHONET_PIPE,phonet_pipe), __ADD(ARPHRD_CAIF,caif), __ADD(ARPHRD_IP6GRE,ip6gre), __ADD(ARPHRD_NETLINK,netlink), __ADD(ARPHRD_6LOWPAN,6lowpan), __ADD(ARPHRD_VOID,void), __ADD(ARPHRD_NONE,nohdr), }; char * nl_llproto2str(int llproto, char *buf, size_t len) { return __type2str(llproto, buf, len, llprotos, ARRAY_SIZE(llprotos)); } int nl_str2llproto(const char *name) { return __str2type(name, llprotos, ARRAY_SIZE(llprotos)); } /** @} */ /** * @name Ethernet Protocol Translations * @{ */ static const struct trans_tbl ether_protos[] = { __ADD(ETH_P_LOOP,loop), __ADD(ETH_P_PUP,pup), __ADD(ETH_P_PUPAT,pupat), __ADD(ETH_P_IP,ip), __ADD(ETH_P_X25,x25), __ADD(ETH_P_ARP,arp), __ADD(ETH_P_BPQ,bpq), __ADD(ETH_P_IEEEPUP,ieeepup), __ADD(ETH_P_IEEEPUPAT,ieeepupat), __ADD(ETH_P_DEC,dec), __ADD(ETH_P_DNA_DL,dna_dl), __ADD(ETH_P_DNA_RC,dna_rc), __ADD(ETH_P_DNA_RT,dna_rt), __ADD(ETH_P_LAT,lat), __ADD(ETH_P_DIAG,diag), __ADD(ETH_P_CUST,cust), __ADD(ETH_P_SCA,sca), __ADD(ETH_P_TEB,teb), __ADD(ETH_P_RARP,rarp), __ADD(ETH_P_ATALK,atalk), __ADD(ETH_P_AARP,aarp), #ifdef ETH_P_8021Q __ADD(ETH_P_8021Q,802.1q), #endif __ADD(ETH_P_IPX,ipx), __ADD(ETH_P_IPV6,ipv6), __ADD(ETH_P_PAUSE,pause), __ADD(ETH_P_SLOW,slow), #ifdef ETH_P_WCCP __ADD(ETH_P_WCCP,wccp), #endif __ADD(ETH_P_PPP_DISC,ppp_disc), __ADD(ETH_P_PPP_SES,ppp_ses), __ADD(ETH_P_MPLS_UC,mpls_uc), __ADD(ETH_P_MPLS_MC,mpls_mc), __ADD(ETH_P_ATMMPOA,atmmpoa), __ADD(ETH_P_LINK_CTL,link_ctl), __ADD(ETH_P_ATMFATE,atmfate), __ADD(ETH_P_PAE,pae), __ADD(ETH_P_AOE,aoe), __ADD(ETH_P_TIPC,tipc), __ADD(ETH_P_1588,ieee1588), __ADD(ETH_P_FCOE,fcoe), __ADD(ETH_P_FIP,fip), __ADD(ETH_P_EDSA,edsa), __ADD(ETH_P_EDP2,edp2), __ADD(ETH_P_802_3,802.3), __ADD(ETH_P_AX25,ax25), __ADD(ETH_P_ALL,all), __ADD(ETH_P_802_2,802.2), __ADD(ETH_P_SNAP,snap), __ADD(ETH_P_DDCMP,ddcmp), __ADD(ETH_P_WAN_PPP,wan_ppp), __ADD(ETH_P_PPP_MP,ppp_mp), __ADD(ETH_P_LOCALTALK,localtalk), __ADD(ETH_P_CAN,can), __ADD(ETH_P_PPPTALK,ppptalk), __ADD(ETH_P_TR_802_2,tr_802.2), __ADD(ETH_P_MOBITEX,mobitex), __ADD(ETH_P_CONTROL,control), __ADD(ETH_P_IRDA,irda), __ADD(ETH_P_ECONET,econet), __ADD(ETH_P_HDLC,hdlc), __ADD(ETH_P_ARCNET,arcnet), __ADD(ETH_P_DSA,dsa), __ADD(ETH_P_TRAILER,trailer), __ADD(ETH_P_PHONET,phonet), __ADD(ETH_P_IEEE802154,ieee802154), __ADD(ETH_P_CAIF,caif), }; char *nl_ether_proto2str(int eproto, char *buf, size_t len) { return __type2str(eproto, buf, len, ether_protos, ARRAY_SIZE(ether_protos)); } int nl_str2ether_proto(const char *name) { return __str2type(name, ether_protos, ARRAY_SIZE(ether_protos)); } /** @} */ /** * @name IP Protocol Translations * @{ */ char *nl_ip_proto2str(int proto, char *buf, size_t len) { struct protoent *p = getprotobynumber(proto); if (p) { snprintf(buf, len, "%s", p->p_name); return buf; } snprintf(buf, len, "0x%x", proto); return buf; } int nl_str2ip_proto(const char *name) { struct protoent *p = getprotobyname(name); unsigned long l; char *end; if (p) return p->p_proto; l = strtoul(name, &end, 0); if (l == ULONG_MAX || *end != '\0') return -NLE_OBJ_NOTFOUND; return (int) l; } /** @} */ /** * @name Dumping Helpers * @{ */ /** * Handle a new line while dumping * @arg params Dumping parameters * * This function must be called before dumping any onto a * new line. It will ensure proper prefixing as specified * by the dumping parameters. * * @note This function will NOT dump any newlines itself */ void nl_new_line(struct nl_dump_params *params) { params->dp_line++; if (params->dp_prefix) { int i; for (i = 0; i < params->dp_prefix; i++) { if (params->dp_fd) fprintf(params->dp_fd, " "); else if (params->dp_buf) strncat(params->dp_buf, " ", params->dp_buflen - strlen(params->dp_buf) - 1); } } if (params->dp_nl_cb) params->dp_nl_cb(params, params->dp_line); } static void dump_one(struct nl_dump_params *parms, const char *fmt, va_list args) { if (parms->dp_fd) vfprintf(parms->dp_fd, fmt, args); else if (parms->dp_buf || parms->dp_cb) { char *buf = NULL; if (vasprintf(&buf, fmt, args) >= 0) { if (parms->dp_cb) parms->dp_cb(parms, buf); else strncat(parms->dp_buf, buf, parms->dp_buflen - strlen(parms->dp_buf) - 1); free(buf); } } } /** * Dump a formatted character string * @arg params Dumping parameters * @arg fmt printf style formatting string * @arg ... Arguments to formatting string * * Dumps a printf style formatting string to the output device * as specified by the dumping parameters. */ void nl_dump(struct nl_dump_params *params, const char *fmt, ...) { va_list args; va_start(args, fmt); dump_one(params, fmt, args); va_end(args); } void nl_dump_line(struct nl_dump_params *parms, const char *fmt, ...) { va_list args; nl_new_line(parms); va_start(args, fmt); dump_one(parms, fmt, args); va_end(args); } /** @} */ /** @cond SKIP */ int __trans_list_add(int i, const char *a, struct nl_list_head *head) { struct trans_list *tl; tl = calloc(1, sizeof(*tl)); if (!tl) return -NLE_NOMEM; tl->i = i; tl->a = strdup(a); nl_list_add_tail(&tl->list, head); return 0; } void __trans_list_clear(struct nl_list_head *head) { struct trans_list *tl, *next; nl_list_for_each_entry_safe(tl, next, head, list) { free(tl->a); free(tl); } nl_init_list_head(head); } char *__type2str(int type, char *buf, size_t len, const struct trans_tbl *tbl, size_t tbl_len) { size_t i; for (i = 0; i < tbl_len; i++) { if (tbl[i].i == type) { snprintf(buf, len, "%s", tbl[i].a); return buf; } } snprintf(buf, len, "0x%x", type); return buf; } char *__list_type2str(int type, char *buf, size_t len, struct nl_list_head *head) { struct trans_list *tl; nl_list_for_each_entry(tl, head, list) { if (tl->i == type) { snprintf(buf, len, "%s", tl->a); return buf; } } snprintf(buf, len, "0x%x", type); return buf; } char *__flags2str(int flags, char *buf, size_t len, const struct trans_tbl *tbl, size_t tbl_len) { size_t i; int tmp = flags; memset(buf, 0, len); for (i = 0; i < tbl_len; i++) { if (tbl[i].i & tmp) { tmp &= ~tbl[i].i; strncat(buf, tbl[i].a, len - strlen(buf) - 1); if ((tmp & flags)) strncat(buf, ",", len - strlen(buf) - 1); } } return buf; } int __str2type(const char *buf, const struct trans_tbl *tbl, size_t tbl_len) { unsigned long l; char *end; size_t i; if (*buf == '\0') return -NLE_INVAL; for (i = 0; i < tbl_len; i++) if (!strcasecmp(tbl[i].a, buf)) return tbl[i].i; l = strtoul(buf, &end, 0); if (l == ULONG_MAX || *end != '\0') return -NLE_OBJ_NOTFOUND; return (int) l; } int __list_str2type(const char *buf, struct nl_list_head *head) { struct trans_list *tl; unsigned long l; char *end; if (*buf == '\0') return -NLE_INVAL; nl_list_for_each_entry(tl, head, list) { if (!strcasecmp(tl->a, buf)) return tl->i; } l = strtoul(buf, &end, 0); if (l == ULONG_MAX || *end != '\0') return -NLE_OBJ_NOTFOUND; return (int) l; } int __str2flags(const char *buf, const struct trans_tbl *tbl, size_t tbl_len) { int flags = 0; size_t i; size_t len; /* ptrdiff_t ? */ char *p = (char *) buf, *t; for (;;) { if (*p == ' ') p++; t = strchr(p, ','); len = t ? t - p : strlen(p); for (i = 0; i < tbl_len; i++) if (len == strlen(tbl[i].a) && !strncasecmp(tbl[i].a, p, len)) flags |= tbl[i].i; if (!t) return flags; p = ++t; } return 0; } void dump_from_ops(struct nl_object *obj, struct nl_dump_params *params) { int type = params->dp_type; if (type < 0 || type > NL_DUMP_MAX) BUG(); params->dp_line = 0; if (params->dp_dump_msgtype) { #if 0 /* XXX */ char buf[64]; dp_dump_line(params, 0, "%s ", nl_cache_mngt_type2name(obj->ce_ops, obj->ce_ops->co_protocol, obj->ce_msgtype, buf, sizeof(buf))); #endif params->dp_pre_dump = 1; } if (obj->ce_ops->oo_dump[type]) obj->ce_ops->oo_dump[type](obj, params); } /** * Check for library capabilities * * @arg capability capability identifier * * Check whether the loaded libnl library supports a certain capability. * This is useful so that applications can workaround known issues of * libnl that are fixed in newer library versions, without * having a hard dependency on the new version. It is also useful, for * capabilities that cannot easily be detected using autoconf tests. * The capabilities are integer constants with name NL_CAPABILITY_*. * * As this function is intended to detect capabilities at runtime, * you might not want to depend during compile time on the NL_CAPABILITY_* * names. Instead you can use their numeric values which are guaranteed not to * change meaning. * * @return non zero if libnl supports a certain capability, 0 otherwise. **/ int nl_has_capability (int capability) { static const uint8_t caps[ ( NL_CAPABILITY_MAX + 7 ) / 8 ] = { #define _NL_ASSERT(expr) ( 0 * sizeof(struct { unsigned int x: ( (!!(expr)) ? 1 : -1 ); }) ) #define _NL_SETV(i, r, v) \ ( _NL_ASSERT( (v) == 0 || (i) * 8 + (r) == (v) - 1 ) + \ ( (v) == 0 ? 0 : (1 << (r)) ) ) #define _NL_SET(i, v0, v1, v2, v3, v4, v5, v6, v7) \ [(i)] = ( \ _NL_SETV((i), 0, (v0)) | _NL_SETV((i), 4, (v4)) | \ _NL_SETV((i), 1, (v1)) | _NL_SETV((i), 5, (v5)) | \ _NL_SETV((i), 2, (v2)) | _NL_SETV((i), 6, (v6)) | \ _NL_SETV((i), 3, (v3)) | _NL_SETV((i), 7, (v7)) ) _NL_SET(0, NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE, NL_CAPABILITY_ROUTE_LINK_VETH_GET_PEER_OWN_REFERENCE, NL_CAPABILITY_ROUTE_LINK_CLS_ADD_ACT_OWN_REFERENCE, NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE, NL_CAPABILITY_ROUTE_LINK_GET_KERNEL_FAIL_OPNOTSUPP, NL_CAPABILITY_ROUTE_ADDR_COMPARE_CACHEINFO, NL_CAPABILITY_VERSION_3_2_26, NL_CAPABILITY_NL_RECV_FAIL_TRUNC_NO_PEEK), _NL_SET(1, NL_CAPABILITY_LINK_BUILD_CHANGE_REQUEST_SET_CHANGE, NL_CAPABILITY_RTNL_NEIGH_GET_FILTER_AF_UNSPEC_FIX, NL_CAPABILITY_VERSION_3_2_27, NL_CAPABILITY_RTNL_LINK_VLAN_PROTOCOL_SERIALZE, NL_CAPABILITY_RTNL_LINK_PARSE_GRE_REMOTE, NL_CAPABILITY_RTNL_LINK_VLAN_INGRESS_MAP_CLEAR, NL_CAPABILITY_RTNL_LINK_VXLAN_IO_COMPARE, NL_CAPABILITY_NL_OBJECT_DIFF64), _NL_SET (2, NL_CAPABILITY_XFRM_SA_KEY_SIZE, NL_CAPABILITY_RTNL_ADDR_PEER_FIX, NL_CAPABILITY_VERSION_3_2_28, NL_CAPABILITY_RTNL_ADDR_PEER_ID_FIX, NL_CAPABILITY_NL_ADDR_FILL_SOCKADDR, NL_CAPABILITY_XFRM_SEC_CTX_LEN, NL_CAPABILITY_LINK_BUILD_ADD_REQUEST_SET_CHANGE, NL_CAPABILITY_NL_RECVMSGS_PEEK_BY_DEFAULT), _NL_SET (3, NL_CAPABILITY_VERSION_3_2_29, NL_CAPABILITY_XFRM_SP_SEC_CTX_LEN, NL_CAPABILITY_VERSION_3_3_0, NL_CAPABILITY_VERSION_3_4_0, NL_CAPABILITY_ROUTE_FIX_VLAN_SET_EGRESS_MAP, NL_CAPABILITY_VERSION_3_5_0, 0, 0), /* IMPORTANT: these capability numbers are intended to be universal and stable * for libnl3. Don't allocate new numbers on your own that differ from upstream * libnl3. * * Instead register a capability number upstream too. We will take patches * for that. We especially take patches to register a capability number that is * only implemented in your fork of libnl3. * * If you really don't want that, use capabilities in the range 0x7000 to 0x7FFF. * (NL_CAPABILITY_IS_USER_RESERVED). Upstream libnl3 will not register conflicting * capabilities in that range. * * Obviously, only backport capability numbers to libnl versions that actually * implement that capability as well. */ #undef _NL_SET #undef _NL_SETV #undef _NL_ASSERT }; if (capability <= 0 || capability > NL_CAPABILITY_MAX) return 0; capability--; return (caps[capability / 8] & (1 << (capability % 8))) != 0; } /** @endcond */ /** @} */