From c434a5c49ff1c00c98c8728adb87b53351dcdb2c Mon Sep 17 00:00:00 2001 From: Yu Jiung Date: Tue, 21 Jun 2016 14:54:53 +0900 Subject: [PATCH] Replace ping command with BSD-3 clause based for temporal use Change-Id: I57de29f91d945f2122c27f57bc8fd2a833eb0b74 Signed-off-by: Yu jiung --- packaging/config | 3 +- toys/pending/ping.c | 110 ----------- toys/samsung/ping.c | 512 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 514 insertions(+), 111 deletions(-) delete mode 100644 toys/pending/ping.c create mode 100644 toys/samsung/ping.c diff --git a/packaging/config b/packaging/config index 9e442eb..4d2b88d 100644 --- a/packaging/config +++ b/packaging/config @@ -16,6 +16,7 @@ CONFIG_TOYBOX_FORK=y # Samsung developed commands # CONFIG_NSLOOKUP=y +CONFIG_PING=y # # Posix commands @@ -157,7 +158,7 @@ CONFIG_DUMPLEASES=y # CONFIG_OPENVT is not set # CONFIG_DEALLOCVT is not set # CONFIG_PGREP is not set -CONFIG_PING=y +# CONFIG_PING=y # CONFIG_PS is not set # CONFIG_ROUTE is not set # CONFIG_SH is not set diff --git a/toys/pending/ping.c b/toys/pending/ping.c deleted file mode 100644 index 629f98b..0000000 --- a/toys/pending/ping.c +++ /dev/null @@ -1,110 +0,0 @@ -/* ping.c - check network connectivity - * - * Copyright 2014 Rob Landley - * - * Not in SUSv4. - -USE_PING(NEWTOY(ping, "<1>1t#<0>255c#<0s#<0>65535I:W#<0w#<0q46[-46]", TOYFLAG_ROOTONLY|TOYFLAG_USR|TOYFLAG_BIN)) - -config PING - bool "ping" - default n - help - usage: ping [OPTIONS] HOST - - Check network connectivity by sending packets to a host and reporting - its response. - - Send ICMP ECHO_REQUEST packets to ipv4 or ipv6 addresses and prints each - echo it receives back, with round trip time. - - Options: - -4, -6 Force IPv4 or IPv6 - -c CNT Send CNT many packets - -I IFACE/IP Source interface or address - -q Quiet, only displays output at start and when finished - -s SIZE Packet SIZE in bytes (default 56) - -t TTL Set Time (number of hops) To Live - -W SEC Seconds to wait for response after all packets sent (default 10) - -w SEC Exit after this many seconds -*/ - -#define FOR_ping -#include "toys.h" - -#include - -GLOBALS( - long wait_exit; - long wait_resp; - char *iface; - long size; - long count; - long ttl; - - int sock; -) - -void ping_main(void) -{ - int family, protocol; - union { - struct in_addr in; - struct in6_addr in6; - } src_addr; - char *host = 0; - - // Determine IPv4 vs IPv6 type - - if(!(toys.optflags & (FLAG_4|FLAG_6))) { -// todo getaddrinfo instead? - if (inet_pton(AF_INET6, toys.optargs[0], (void*)&src_addr)) - toys.optflags |= FLAG_6; - } - - if (toys.optflags & FLAG_6) { - family = AF_INET6; - protocol = IPPROTO_ICMPV6; - } else { - family = AF_INET; - protocol = IPPROTO_ICMP; - } - - if (!(toys.optflags & FLAG_s)) TT.size = 56; // 64-PHDR_LEN - - if (TT.iface) { - memset(&src_addr, 0, sizeof(src_addr)); - - // IP address? - if (!inet_pton(family, TT.iface, &src_addr)) { - struct ifaddrs *ifsave, *ifa = 0; - - // Interface name? - if (!getifaddrs(&ifsave)) { - for (ifa = ifsave; ifa; ifa = ifa->ifa_next) { - if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != family) continue; - if (!strcmp(ifa->ifa_name, TT.iface)) { - if (family == AF_INET) - memcpy(&src_addr, - &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, - sizeof(struct in_addr)); - else memcpy(&src_addr, - &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr, - sizeof(struct in6_addr)); - break; - } - } - freeifaddrs(ifsave); - } - if (!ifa) - error_exit("no v%d addr for -I %s", 4+2*(family==AF_INET6), TT.iface); - } - inet_ntop(family, &src_addr, toybuf, sizeof(toybuf)); - host = xstrdup(toybuf); - } - -printf("host=%s\n", host); - - // Open raw socket - TT.sock = xsocket(family, SOCK_RAW, protocol); -} diff --git a/toys/samsung/ping.c b/toys/samsung/ping.c new file mode 100644 index 0000000..ca2af9f --- /dev/null +++ b/toys/samsung/ping.c @@ -0,0 +1,512 @@ +/* ping.c - ping program. + * + * Copyright 2012 Ashwini Kumar + * + * Not in SUSv4. + +USE_PING(NEWTOY(ping, "<1>1Q#<0>255t#<0>255c#<1s#<0>65535I:W#<0w#<0q46", TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN)) + +config PING + bool "ping" + default y + help + usage: ping [OPTIONS] HOST + + Send ICMP ECHO_REQUEST packets to network hosts + + Options: + -4, -6 Force IP or IPv6 name resolution + -c CNT Send only CNT pings + -s SIZE Send SIZE data bytes in packets (default:56) + -t TTL Set TTL + -I IFACE/IP Use interface or IP address as source + -W SEC Seconds to wait for the first response (default:10) + (after all -c CNT packets are sent) + -w SEC Seconds until ping exits (default:infinite) + (can exit earlier with -c CNT) + -q Quiet, only displays output at start + and when finished + -Q ToS Set Quality of Service +*/ +#define FOR_ping +#include "toys.h" + +#include +#include +#include +#include + +GLOBALS( + long wait_exit; + long wait_resp; + char *iface; + long size; + long count; + long ttl; + long tos; + int ntransmitted; + int nreceived; + int ident; + int nrepeats; + int sock; +) + +#define F_QUIET 0x0001 // minimize all output +#define F_TIMING 0x0002 // room for a timestamp +#define F_SOURCE_ADDR 0x0004 // set source IP address/interface + +u_char rcvd_tbl[2048]; //MAX_DUP_CHK +#define A(seq) rcvd_tbl[(seq/8)%sizeof(rcvd_tbl)] // byte in array +#define B(seq) (1 << (seq & 0x07)) // bit in byte +#define SET(seq) (A(seq) |= B(seq)) +#define CLR(seq) (A(seq) &= (~B(seq))) +#define TST(seq) (A(seq) & B(seq)) + +struct tv32 { + int32_t tv32_sec; + int32_t tv32_usec; +}; + +u_char *packet; +int packlen, pingflags = 0, bufspace = IP_MAXPACKET; +double tmax = 0.0, tsum = 0.0, tmin = 999999999.0; +struct timeval now; +struct sockaddr_in src_addr, send_addr; //from where to Who + +#define PHDR_LEN sizeof(struct tv32) // size of timestamp header +#define MAXHOSTNAMELEN 64 +char hostname[MAXHOSTNAMELEN + 1]; +static struct { + union { + u_char u_buf[(IP_MAXPACKET-60-8)+offsetof(struct icmp, icmp_data)]; + struct icmp u_icmp; + } o_u; +} out_pack; +#define opack_icmp out_pack.o_u.u_icmp + +/* Compute the IP checksum + * This assumes the packet is less than 32K long. + */ +static u_int16_t in_cksum(u_int16_t *p, u_int len) +{ + u_int32_t sum = 0; + int nwords = len >> 1; + + while (nwords--) sum += *p++; + if (len & 1) { + union { + u_int16_t w; + u_int8_t c[2]; + } u; + u.c[0] = *(u_char *)p; + u.c[1] = 0; + sum += u.w; + } + + // end-around-carry + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + return (~sum); +} + +/* + * Print statistics. + * Heavily buffered STDIO is used here, so that all the statistics + * will be written with 1 sys-write call. This is nice when more + * than one copy of the program is running on a terminal; it prevents + * the statistics output from becomming intermingled. + */ +static void summary(int header) +{ + if (header) xprintf("\n--- %s PING Statistics ---\n", hostname); + xprintf("%d packets transmitted, ", TT.ntransmitted); + xprintf("%d packets received, ", TT.nreceived); + if (TT.nrepeats) xprintf("+%d duplicates, ", TT.nrepeats); + if (TT.ntransmitted) { + if (TT.nreceived > TT.ntransmitted) xprintf("-- somebody's duplicating packets!"); + else xprintf("%.1f%% packet loss", (((TT.ntransmitted-TT.nreceived)*100.0)/TT.ntransmitted)); + } + xputc('\n'); + if (TT.nreceived && (pingflags & F_TIMING)) { + double n = TT.nreceived + TT.nrepeats; + double avg = (tsum / n); + + xprintf("round-trip min/avg/max = %.3f/%.3f/%.3f ms\n", + tmin * 1000.0, avg * 1000.0, tmax * 1000.0); + } +} + +// Print statistics when SIGINFO is received. +static void prtsig(int dummy) +{ + summary(0); +} + +// Print statistics and give up. +static void finish(int dummy) +{ + signal(SIGQUIT, SIG_DFL); + summary(1); + exit(TT.nreceived > 0 ? 0 : 2); +} + +// Print a descriptive string about an ICMP header other than an echo reply. +static int pr_icmph(struct icmp *icp) +{ + switch (icp->icmp_type ) { + case ICMP_UNREACH: + xprintf("Destination Unreachable"); + break; + case ICMP_SOURCEQUENCH: + xprintf("Source Quench"); + break; + case ICMP_REDIRECT: + xprintf("Redirect (change route)"); + break; + case ICMP_ECHO: + xprintf("Echo Request"); + break; + case ICMP_ECHOREPLY: + // displaying other's pings is too noisy + return 0; + case ICMP_TIME_EXCEEDED: + xprintf("Time Exceeded"); + break; + case ICMP_PARAMETERPROB: + xprintf("Parameter Problem"); + break; + case ICMP_TIMESTAMP: + xprintf("Timestamp Request"); + break; + case ICMP_TIMESTAMPREPLY: + xprintf("Timestamp Reply"); + break; + case ICMP_INFO_REQUEST: + xprintf("Information Request"); + break; + case ICMP_INFO_REPLY: + xprintf("Information Reply"); + break; + case ICMP_ADDRESS: + xprintf("Address Mask Request"); + break; + case ICMP_ADDRESSREPLY: + xprintf("Address Mask Reply"); + break; + default: + xprintf("Bad ICMP type: %d", icp->icmp_type); + break; + } + return 1; +} + +static void get_ifaddr(char *addrname, const char* name) +{ + struct ifaddrs *ifaddr_list, *ifa_item; + int family, s; + char host[NI_MAXHOST]; + + if (getifaddrs(&ifaddr_list) == -1) perror_exit("getifaddrs"); + + for (ifa_item = ifaddr_list; ifa_item; ifa_item = ifa_item->ifa_next) { + if (!ifa_item->ifa_addr) continue; + + family = ifa_item->ifa_addr->sa_family; + if ((family == AF_INET) && !(strcmp(ifa_item->ifa_name,name))) { + s = getnameinfo(ifa_item->ifa_addr, sizeof(struct sockaddr_in), + host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + if (s) error_exit("getnameinfo() failed: %s\n", gai_strerror(s)); + strncpy(addrname, host, MAXHOSTNAMELEN); + break; + } + } + freeifaddrs(ifaddr_list); +} + +static void gethost(const char *arg, const char *name, struct sockaddr_in *sa, + char *realname, int realname_len) +{ + struct hostent *hp; + unsigned int if_idx = 0, len = strlen(arg); + char addrname[MAXHOSTNAMELEN+1]; + + memset(sa, 0, sizeof(*sa)); + sa->sin_family = AF_INET; + + if (inet_aton(name, &sa->sin_addr)) { + if (realname) strncpy(realname, name, realname_len -1); + if (len) { + pingflags |= F_SOURCE_ADDR; + TT.iface = NULL; + } + return; + } + + if (len) { + if ((if_idx = if_nametoindex(name))) get_ifaddr(addrname, name); + else perror_exit("unknown interface '%s'",name); + } else strncpy(addrname,name, MAXHOSTNAMELEN); + + addrname[MAXHOSTNAMELEN] = '\0'; + hp = gethostbyname(addrname); + if (!hp) error_exit("Cannot resolve \"%s\" (%s)",name,hstrerror(h_errno)); + if (hp->h_addrtype != AF_INET) error_exit("%s only supported with IP", arg); + memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr)); + if (realname) strncpy(realname, hp->h_name, realname_len -1); +} + +/* + * Print out the packet, if it came from us. This logic is necessary + * because ALL readers of the ICMP socket get a copy of ALL ICMP packets + * which arrive ('tis only fair). This permits multiple copies of this + * program to be run without having intermingled output (or statistics!). + */ +static void pr_pack(u_char *buf, int tot_len, struct sockaddr_in *from) +{ + struct ip *ip; + struct icmp *icp; + int net_len, hlen, dupflag = 0; + double triptime = 0.0; + + // Check the IP header + ip = (struct ip *) buf; + hlen = ip->ip_hl << 2; + if (tot_len < TT.size + ICMP_MINLEN) return; + + // Now the ICMP part + net_len = tot_len - hlen; + icp = (struct icmp *)(buf + hlen); + if (ntohs(icp->icmp_id) != TT.ident) return; + if (icp->icmp_type == ICMP_ECHOREPLY) { + TT.nreceived++; + if (pingflags & F_TIMING) { + struct timeval tv; + struct tv32 tv32; + + memcpy(&tv32, icp->icmp_data, sizeof(tv32)); + tv.tv_sec = ntohl(tv32.tv32_sec); + tv.tv_usec = ntohl(tv32.tv32_usec); + triptime = ((now.tv_sec - tv.tv_sec)*1.0 + + (now.tv_usec - tv.tv_usec)/1000000.0); + tsum += triptime; + if (triptime < tmin) tmin = triptime; + if (triptime > tmax) tmax = triptime; + } + + if (TST(ntohs((u_int16_t)icp->icmp_seq))) { + TT.nrepeats++, TT.nreceived--; + dupflag = 1; + } else SET(ntohs((u_int16_t)icp->icmp_seq)); + + if (!dupflag) { + static u_int16_t last_seqno = 0xffff; + u_int16_t seqno = ntohs((u_int16_t)icp->icmp_seq); + u_int16_t gap = seqno - (last_seqno + 1); + + if (gap < 0x8000) last_seqno = seqno; + } + + if (pingflags & F_QUIET) return; + + xprintf("%d bytes from %s: seq=%u ttl=%d", net_len, inet_ntoa(from->sin_addr), + ntohs((u_int16_t)icp->icmp_seq), ip->ip_ttl); + if (pingflags & F_TIMING) xprintf(" time=%.3f ms", triptime*1000.0); + if (dupflag) xprintf(" (DUP!)"); + } else if (icp->icmp_type != ICMP_ECHO) pr_icmph(icp); + else return; + xputc('\n'); +} + +/* + * Compose and transmit an ICMP ECHO REQUEST packet. The IP packet + * will be added on by the kernel. The ID field is our UNIX process ID, + * and the sequence number is an ascending integer. The first PHDR_LEN bytes + * of the data portion are used to hold a UNIX "timeval" struct in VAX + * byte-order, to compute the round-trip time. + */ +static void pinger(void) +{ + struct tv32 tv32; + int i, cc; + + gettimeofday(&now,0); + opack_icmp.icmp_code = 0; + opack_icmp.icmp_seq = htons((u_int16_t)(TT.ntransmitted)); + opack_icmp.icmp_type = ICMP_ECHO; + opack_icmp.icmp_id = htons(TT.ident); + tv32.tv32_sec = htonl(now.tv_sec); + tv32.tv32_usec = htonl(now.tv_usec); + if (pingflags & F_TIMING) memcpy(&opack_icmp.icmp_data[0], &tv32, sizeof(tv32)); + cc = TT.size + PHDR_LEN; + opack_icmp.icmp_cksum = 0;//reset before Checksum computation + opack_icmp.icmp_cksum = in_cksum((u_int16_t *)&opack_icmp, cc); + + i = sendto(TT.sock, (char *) &opack_icmp, cc, 0, + (struct sockaddr *)&send_addr, sizeof(struct sockaddr_in)); + if (i != cc) { + if (i < 0) perror_exit("sendto"); + else error_msg("wrote %s %d chars, ret=%d", hostname, cc, i); + } + CLR(TT.ntransmitted); + TT.ntransmitted++; +} + +static void send_ping(int dummy) +{ + struct itimerval itimer; + if (TT.wait_exit && (TT.wait_exit == TT.ntransmitted)) finish(0); + if (TT.count && (TT.ntransmitted >= TT.count)) { + //waiting for the last response. + if (TT.nreceived) { + itimer.it_value.tv_sec = 2 * tmax/1000; + if (itimer.it_value.tv_sec == 0) itimer.it_value.tv_sec = 1; + } else itimer.it_value.tv_sec = (TT.wait_resp) ? TT.wait_resp : 10; //default 10 secs + itimer.it_value.tv_usec = 0; + itimer.it_interval.tv_sec = itimer.it_interval.tv_usec = 0; + signal(SIGALRM, finish); + setitimer(ITIMER_REAL, &itimer, NULL); + } else pinger(); +} + +static void doit(void) +{ + int cc; + struct sockaddr_in from; + socklen_t fromlen; + + for(;;) { + fromlen = sizeof(from); + cc = recvfrom(TT.sock, (char *) packet, packlen, + 0, (struct sockaddr *)&from, &fromlen); + if (cc < 0) { + if (errno != EINTR) perror_msg("recvfrom"); + continue; + } + gettimeofday(&now, 0); + pr_pack(packet, cc, &from); + if(TT.count && (TT.nreceived >= TT.count)) break; + } + finish(0); +} + +void ping_main(void) +{ + int const_int_1 = 1, i; + struct in6_addr in6; + struct itimerval itimer; + struct timeval interval_tv = {1, 0}; //1sec interval + + if(!(toys.optflags & FLAG_4) && (inet_pton(AF_INET6, toys.optargs[0], (void*)&in6))) + toys.optflags |= FLAG_6; + if (toys.optflags & FLAG_6) { + //ping6 support 4 options + //1 for cmdname 1 for NULL and toys.optc + int cnt = 0, opt = 0; + char **argv6 = xzalloc((6 + toys.optc) * sizeof(char*)); + argv6[cnt++] = "ping6"; + if (toys.optflags & FLAG_c) argv6[cnt++] = xmprintf("-c%d",TT.count); + if (toys.optflags & FLAG_s) argv6[cnt++] = xmprintf("-s%d",TT.size); + if (toys.optflags & FLAG_I) argv6[cnt++] = xmprintf("-I%s",TT.iface); + if (toys.optflags & FLAG_q) argv6[cnt++] = "-q"; + + while(opt < toys.optc) argv6[cnt++] = toys.optargs[opt++]; + xexec(argv6); + } + + TT.sock = xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (!(toys.optflags & FLAG_s)) TT.size = 64 - PHDR_LEN; + if (toys.optflags & FLAG_I) gethost("-I", TT.iface, &src_addr, 0, 0); + if (toys.optflags & FLAG_q) pingflags |= F_QUIET; + + gethost("", toys.optargs[0], &send_addr, hostname, sizeof(hostname)); + if (TT.size >= PHDR_LEN) pingflags |= F_TIMING; + packlen = TT.size + 60 + 76; /* MAXIP + MAXICMP */ + packet = xmalloc(packlen); + + TT.ident = rand() & 0xFFFF; + for (i = PHDR_LEN; i < (int)TT.size; i++) opack_icmp.icmp_data[i] = i; + + if (TT.ttl) { + if (setsockopt(TT.sock, IPPROTO_IP, IP_TTL, &TT.ttl, sizeof(TT.ttl)) < 0) + perror_exit("Can't set time-to-live"); + if (setsockopt(TT.sock, IPPROTO_IP, IP_MULTICAST_TTL, &TT.ttl, sizeof(TT.ttl)) < 0) + perror_exit("Can't set multicast time-to-live"); + } + if ((toys.optflags & FLAG_Q) && + setsockopt(TT.sock, IPPROTO_IP, IP_TOS, &TT.tos, sizeof(TT.tos)) < 0) + perror_exit("IP_TOS %d failed ", TT.tos); + + if (pingflags & F_SOURCE_ADDR) { + if (setsockopt(TT.sock, IPPROTO_IP, IP_MULTICAST_IF, + (char *) &src_addr.sin_addr, + sizeof(src_addr.sin_addr)) < 0) + perror_exit("Can't set source interface/address"); + if (bind(TT.sock, (struct sockaddr*)&src_addr, sizeof(src_addr))) + perror_exit("bind"); + } + if (TT.iface && memcmp(&src_addr, &send_addr, sizeof(send_addr))) { + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + xstrncpy(ifr.ifr_name, TT.iface, IFNAMSIZ); + if (setsockopt(TT.sock, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr))) + perror_msg("can't bind to interface %s", TT.iface); + } + //enable PING to Broadcast address + setsockopt(TT.sock, SOL_SOCKET, SO_BROADCAST, &const_int_1, sizeof(const_int_1)); + xprintf("PING %s (%s):", hostname, inet_ntoa(send_addr.sin_addr)); + if (toys.optflags & FLAG_I) xprintf(" from %s:", inet_ntoa(src_addr.sin_addr)); + xprintf(" %d data bytes.\n", (int)TT.size); + + /* When pinging the broadcast address, you can get a lot + * of answers. Doing something so evil is useful if you + * are trying to stress the ethernet, or just want to + * fill the arp cache to get some stuff for /etc/ethers. + */ + while (0 > setsockopt(TT.sock, SOL_SOCKET, SO_RCVBUF, + (char*)&bufspace, sizeof(bufspace))) + if ((bufspace -= 4096) <= 0) + perror_exit("Cannot set the receive buffer size"); + + signal(SIGINT, finish); + signal(SIGQUIT, prtsig); + signal(SIGCONT, prtsig); + itimer.it_interval = interval_tv; + itimer.it_value = interval_tv; + signal(SIGALRM, send_ping); + setitimer(ITIMER_REAL, &itimer, NULL); //interval timer between ping + send_ping(0); + doit(); +} + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Muuss. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ -- 2.7.4