Annotation of embedaddon/mtr/mtr.c, revision 1.1.1.3
1.1 misho 1: /*
2: mtr -- a network diagnostic tool
3: Copyright (C) 1997,1998 Matt Kimball
4:
5: This program is free software; you can redistribute it and/or modify
6: it under the terms of the GNU General Public License version 2 as
7: published by the Free Software Foundation.
8:
9: This program is distributed in the hope that it will be useful,
10: but WITHOUT ANY WARRANTY; without even the implied warranty of
11: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12: GNU General Public License for more details.
13:
14: You should have received a copy of the GNU General Public License
15: along with this program; if not, write to the Free Software
16: Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17: */
18:
1.1.1.2 misho 19: #include "config.h"
20:
1.1 misho 21: #include <sys/types.h>
22: #include <stdio.h>
23: #include <stdlib.h>
24: #include <string.h>
25: #include <unistd.h>
26: #include <errno.h>
27: #include <string.h>
1.1.1.2 misho 28: #include <strings.h>
29:
30: #include <netdb.h>
31: #include <netinet/in.h>
32: #include <sys/socket.h>
33: #include <time.h>
1.1 misho 34: #include <ctype.h>
35: #include <assert.h>
36: #include <fcntl.h>
37: #include <sys/stat.h>
38:
39: #include "mtr.h"
40: #include "mtr-curses.h"
1.1.1.3 ! misho 41: #include <getopt.h>
1.1 misho 42: #include "display.h"
43: #include "dns.h"
44: #include "report.h"
45: #include "net.h"
46: #include "asn.h"
47: #include "version.h"
48:
49:
50: #ifdef ENABLE_IPV6
51: #define DEFAULT_AF AF_UNSPEC
52: #else
53: #define DEFAULT_AF AF_INET
54: #endif
55:
56:
57: #ifdef NO_HERROR
58: #define herror(str) fprintf(stderr, str ": error looking up \"%s\"\n", Hostname);
59: #endif
60:
61:
62: int DisplayMode;
63: int display_mode;
64: int Interactive = 1;
65: int PrintVersion = 0;
66: int PrintHelp = 0;
67: int MaxPing = 10;
68: int ForceMaxPing = 0;
69: float WaitTime = 1.0;
70: char *Hostname = NULL;
71: char *InterfaceAddress = NULL;
72: char LocalHostname[128];
73: int dns = 1;
74: int show_ips = 0;
75: int enablempls = 0;
76: int cpacketsize = 64; /* default packet size */
77: int bitpattern = 0;
78: int tos = 0;
1.1.1.2 misho 79: #ifdef SO_MARK
80: int mark = -1;
81: #endif
1.1 misho 82: int reportwide = 0;
83: int af = DEFAULT_AF;
84: int mtrtype = IPPROTO_ICMP; /* Use ICMP as default packet type */
85:
86: /* begin ttl windows addByMin */
87: int fstTTL = 1; /* default start at first hop */
88: /*int maxTTL = MaxHost-1; */ /* max you can go is 255 hops */
89: int maxTTL = 30; /* inline with traceroute */
90: /* end ttl window stuff. */
91: int remoteport = 80; /* for TCP tracing */
92: int timeout = 10 * 1000000; /* for TCP tracing */
93:
94:
95: /* default display field(defined by key in net.h) and order */
96: unsigned char fld_active[2*MAXFLD] = "LS NABWV";
97: int fld_index[256];
98: char available_options[MAXFLD];
99:
100:
101: struct fields data_fields[MAXFLD] = {
102: /* key, Remark, Header, Format, Width, CallBackFunc */
103: {' ', "<sp>: Space between fields", " ", " ", 1, &net_drop },
104: {'L', "L: Loss Ratio", "Loss%", " %4.1f%%", 6, &net_loss },
105: {'D', "D: Dropped Packets", "Drop", " %4d", 5, &net_drop },
106: {'R', "R: Received Packets", "Rcv", " %5d", 6, &net_returned},
107: {'S', "S: Sent Packets", "Snt", " %5d", 6, &net_xmit },
108: {'N', "N: Newest RTT(ms)", "Last", " %5.1f", 6, &net_last },
109: {'B', "B: Min/Best RTT(ms)", "Best", " %5.1f", 6, &net_best },
110: {'A', "A: Average RTT(ms)", "Avg", " %5.1f", 6, &net_avg },
111: {'W', "W: Max/Worst RTT(ms)", "Wrst", " %5.1f", 6, &net_worst },
112: {'V', "V: Standard Deviation", "StDev", " %5.1f", 6, &net_stdev },
113: {'G', "G: Geometric Mean", "Gmean", " %5.1f", 6, &net_gmean },
114: {'J', "J: Current Jitter", "Jttr", " %4.1f", 5, &net_jitter},
115: {'M', "M: Jitter Mean/Avg.", "Javg", " %4.1f", 5, &net_javg },
116: {'X', "X: Worst Jitter", "Jmax", " %4.1f", 5, &net_jworst},
117: {'I', "I: Interarrival Jitter", "Jint", " %4.1f", 5, &net_jinta },
118: {'\0', NULL, NULL, NULL, 0, NULL}
119: };
120:
121: typedef struct names {
122: char* name;
123: struct names* next;
124: } names_t;
125: static names_t *names = NULL;
126:
127: char *
128: trim(char * s) {
129:
130: char * p = s;
131: int l = strlen(p);
132:
133: while(isspace(p[l-1]) && l) p[--l] = 0;
134: while(*p && isspace(*p) && l) ++p, --l;
135:
136: return p;
137: }
138:
139: static void
140: append_to_names(const char* progname, const char* item) {
141:
142: names_t* name = calloc(1, sizeof(names_t));
143: if (name == NULL) {
144: fprintf(stderr, "%s: memory allocation failure\n", progname);
145: exit(EXIT_FAILURE);
146: }
147: name->name = strdup(item);
148: name->next = names;
149: names = name;
150: }
151:
152: static void
153: read_from_file(const char* progname, const char *filename) {
154:
155: FILE *in;
156: char line[512];
157:
158: if (! filename || strcmp(filename, "-") == 0) {
159: clearerr(stdin);
160: in = stdin;
161: } else {
162: in = fopen(filename, "r");
163: if (! in) {
164: fprintf(stderr, "%s: fopen: %s\n", progname, strerror(errno));
165: exit(EXIT_FAILURE);
166: }
167: }
168:
169: while (fgets(line, sizeof(line), in)) {
170: char* name = trim(line);
171: append_to_names(progname, name);
172: }
173:
174: if (ferror(in)) {
175: fprintf(stderr, "%s: ferror: %s\n", progname, strerror(errno));
176: exit(EXIT_FAILURE);
177: }
178:
179: if (in != stdin) fclose(in);
180: }
181:
182: /*
183: * If the file stream is associated with a regular file, lock the file
184: * in order coordinate writes to a common file from multiple mtr
185: * instances. This is useful if, for example, multiple mtr instances
186: * try to append results to a common file.
187: */
188:
189: static void
190: lock(const char* progname, FILE *f) {
191: int fd;
192: struct stat buf;
193: static struct flock lock;
194:
195: assert(f);
196:
197: lock.l_type = F_WRLCK;
198: lock.l_start = 0;
199: lock.l_whence = SEEK_END;
200: lock.l_len = 0;
201: lock.l_pid = getpid();
202:
203: fd = fileno(f);
204: if ((fstat(fd, &buf) == 0) && S_ISREG(buf.st_mode)) {
205: if (fcntl(fd, F_SETLKW, &lock) == -1) {
206: fprintf(stderr, "%s: fcntl: %s (ignored)\n",
207: progname, strerror(errno));
208: }
209: }
210: }
211:
212: /*
213: * If the file stream is associated with a regular file, unlock the
214: * file (which presumably has previously been locked).
215: */
216:
217: static void
218: unlock(const char* progname, FILE *f) {
219: int fd;
220: struct stat buf;
221: static struct flock lock;
222:
223: assert(f);
224:
225: lock.l_type = F_UNLCK;
226: lock.l_start = 0;
227: lock.l_whence = SEEK_END;
228: lock.l_len = 0;
229: lock.l_pid = getpid();
230:
231: fd = fileno(f);
232: if ((fstat(fd, &buf) == 0) && S_ISREG(buf.st_mode)) {
233: if (fcntl(fd, F_SETLKW, &lock) == -1) {
234: fprintf(stderr, "%s: fcntl: %s (ignored)\n",
235: progname, strerror(errno));
236: }
237: }
238: }
239:
240:
241: void init_fld_options (void)
242: {
243: int i;
244:
245: for (i=0;i < 256;i++)
246: fld_index[i] = -1;
247:
248: for (i=0;data_fields[i].key != 0;i++) {
249: available_options[i] = data_fields[i].key;
250: fld_index[data_fields[i].key] = i;
251: }
252: available_options[i] = 0;
253: }
254:
255:
256: void parse_arg (int argc, char **argv)
257: {
258: int opt;
259: int i;
1.1.1.2 misho 260: /* IMPORTANT: when adding or modifying an option:
261: 1/ mind the order of options, there is some logic;
262: 2/ update the getopt_long call below;
263: 3/ update the man page (use the same order);
264: 4/ update the help message showed when using --help.
265: */
1.1 misho 266: static struct option long_options[] = {
267: { "help", 0, 0, 'h' },
1.1.1.2 misho 268: { "version", 0, 0, 'v' },
269:
270: { "inet", 0, 0, '4' }, /* IPv4 only */
271: { "inet6", 0, 0, '6' }, /* IPv6 only */
272:
273: { "filename", 1, 0, 'F' },
1.1 misho 274:
275: { "report", 0, 0, 'r' },
276: { "report-wide", 0, 0, 'w' },
277: { "xml", 0, 0, 'x' },
278: { "curses", 0, 0, 't' },
279: { "gtk", 0, 0, 'g' },
280: { "raw", 0, 0, 'l' },
281: { "csv", 0, 0, 'C' },
282: { "split", 0, 0, 'p' }, /* BL */
283: /* maybe above should change to -d 'x' */
284:
1.1.1.2 misho 285: { "no-dns", 0, 0, 'n' },
286: { "show-ips", 0, 0, 'b' },
287: { "order", 1, 0, 'o' }, /* fields to display & their order */
288: #ifdef IPINFO
289: { "ipinfo", 1, 0, 'y' }, /* IP info lookup */
290: { "aslookup", 0, 0, 'z' }, /* Do AS lookup (--ipinfo 0) */
291: #endif
1.1 misho 292:
293: { "interval", 1, 0, 'i' },
294: { "report-cycles", 1, 0, 'c' },
295: { "psize", 1, 0, 's' }, /* changed 'p' to 's' to match ping option
296: overload psize<0, ->rand(min,max) */
297: { "bitpattern", 1, 0, 'B' },/* overload b>255, ->rand(0,255) */
298: { "tos", 1, 0, 'Q' }, /* typeof service (0,255) */
299: { "mpls", 0, 0, 'e' },
300: { "address", 1, 0, 'a' },
301: { "first-ttl", 1, 0, 'f' }, /* -f & -m are borrowed from traceroute */
302: { "max-ttl", 1, 0, 'm' },
303: { "udp", 0, 0, 'u' }, /* UDP (default is ICMP) */
304: { "tcp", 0, 0, 'T' }, /* TCP (default is ICMP) */
305: { "port", 1, 0, 'P' }, /* target port number for TCP */
306: { "timeout", 1, 0, 'Z' }, /* timeout for TCP sockets */
1.1.1.2 misho 307: #ifdef SO_MARK
308: { "mark", 1, 0, 'M' }, /* use SO_MARK */
1.1 misho 309: #endif
310: { 0, 0, 0, 0 }
311: };
312:
313: opt = 0;
314: while(1) {
315: opt = getopt_long(argc, argv,
1.1.1.2 misho 316: "hv46F:rwxtglCpnbo:y:zi:c:s:B:Q:ea:f:m:uTP:Z:M:", long_options, NULL);
1.1 misho 317: if(opt == -1)
318: break;
319:
320: switch(opt) {
321: case 'v':
322: PrintVersion = 1;
323: break;
324: case 'h':
325: PrintHelp = 1;
326: break;
327:
328: case 'r':
329: DisplayMode = DisplayReport;
330: break;
331: case 'w':
332: reportwide = 1;
333: DisplayMode = DisplayReport;
334: break;
335: case 't':
336: DisplayMode = DisplayCurses;
337: break;
338: case 'g':
339: DisplayMode = DisplayGTK;
340: break;
341: case 'p': /* BL */
342: DisplayMode = DisplaySplit;
343: break;
344: case 'l':
345: DisplayMode = DisplayRaw;
346: break;
347: case 'C':
348: DisplayMode = DisplayCSV;
349: break;
350: case 'x':
351: DisplayMode = DisplayXML;
352: break;
353:
354: case 'c':
355: MaxPing = atoi (optarg);
356: ForceMaxPing = 1;
357: break;
358: case 's':
359: cpacketsize = atoi (optarg);
360: break;
361: case 'a':
362: InterfaceAddress = optarg;
363: break;
364: case 'e':
365: enablempls = 1;
366: break;
367: case 'n':
368: dns = 0;
369: break;
370: case 'i':
371: WaitTime = atof (optarg);
372: if (WaitTime <= 0.0) {
373: fprintf (stderr, "mtr: wait time must be positive\n");
374: exit (1);
375: }
376: if (getuid() != 0 && WaitTime < 1.0) {
377: fprintf (stderr, "non-root users cannot request an interval < 1.0 seconds\r\n");
378: exit (1);
379: }
380: break;
381: case 'f':
382: fstTTL = atoi (optarg);
383: if (fstTTL > maxTTL) {
384: fstTTL = maxTTL;
385: }
386: if (fstTTL < 1) { /* prevent 0 hop */
387: fstTTL = 1;
388: }
389: break;
390: case 'F':
391: read_from_file(argv[0], optarg);
392: break;
393: case 'm':
394: maxTTL = atoi (optarg);
395: if (maxTTL > (MaxHost - 1)) {
396: maxTTL = MaxHost-1;
397: }
398: if (maxTTL < 1) { /* prevent 0 hop */
399: maxTTL = 1;
400: }
401: if (fstTTL > maxTTL) { /* don't know the pos of -m or -f */
402: fstTTL = maxTTL;
403: }
404: break;
405: case 'o':
406: /* Check option before passing it on to fld_active. */
407: if (strlen (optarg) > MAXFLD) {
408: fprintf (stderr, "Too many fields: %s\n", optarg);
409: exit (1);
410: }
411: for (i=0; optarg[i]; i++) {
412: if(!strchr (available_options, optarg[i])) {
413: fprintf (stderr, "Unknown field identifier: %c\n", optarg[i]);
414: exit (1);
415: }
416: }
417: strcpy ((char*)fld_active, optarg);
418: break;
419: case 'B':
420: bitpattern = atoi (optarg);
421: if (bitpattern > 255)
422: bitpattern = -1;
423: break;
424: case 'Q':
425: tos = atoi (optarg);
426: if (tos > 255 || tos < 0) {
427: /* error message, should do more checking for valid values,
428: * details in rfc2474 */
429: tos = 0;
430: }
431: break;
432: case 'u':
433: if (mtrtype != IPPROTO_ICMP) {
434: fprintf(stderr, "-u and -T are mutually exclusive.\n");
435: exit(EXIT_FAILURE);
436: }
437: mtrtype = IPPROTO_UDP;
438: break;
439: case 'T':
440: if (mtrtype != IPPROTO_ICMP) {
441: fprintf(stderr, "-u and -T are mutually exclusive.\n");
442: exit(EXIT_FAILURE);
443: }
444: mtrtype = IPPROTO_TCP;
445: break;
446: case 'b':
447: show_ips = 1;
448: break;
449: case 'P':
450: remoteport = atoi(optarg);
451: if (remoteport > 65535 || remoteport < 1) {
452: fprintf(stderr, "Illegal port number.\n");
453: exit(EXIT_FAILURE);
454: }
455: break;
456: case 'Z':
457: timeout = atoi(optarg);
458: timeout *= 1000000;
459: break;
460: case '4':
461: af = AF_INET;
462: break;
463: case '6':
464: #ifdef ENABLE_IPV6
465: af = AF_INET6;
466: break;
467: #else
468: fprintf( stderr, "IPv6 not enabled.\n" );
469: break;
470: #endif
1.1.1.2 misho 471: #ifdef IPINFO
1.1 misho 472: case 'y':
473: ipinfo_no = atoi (optarg);
474: if (ipinfo_no < 0)
475: ipinfo_no = 0;
476: break;
477: case 'z':
478: ipinfo_no = 0;
479: break;
1.1.1.2 misho 480: #else
481: case 'y':
482: case 'z':
483: fprintf( stderr, "IPINFO not enabled.\n" );
484: break;
485: #endif
486: #ifdef SO_MARK
487: case 'M':
488: mark = atoi (optarg);
489: if (mark < 0) {
490: fprintf( stderr, "SO_MARK must be positive.\n" );
491: exit(EXIT_FAILURE);
492: }
493: break;
494: #else
495: case 'M':
496: fprintf( stderr, "SO_MARK not enabled.\n" );
497: break;
1.1 misho 498: #endif
499: }
500: }
501:
502: if (DisplayMode == DisplayReport ||
503: DisplayMode == DisplayTXT ||
504: DisplayMode == DisplayXML ||
505: DisplayMode == DisplayRaw ||
506: DisplayMode == DisplayCSV)
507: Interactive = 0;
508:
509: if (optind > argc - 1)
510: return;
511:
512: }
513:
514:
515: void parse_mtr_options (char *string)
516: {
517: int argc;
518: char *argv[128], *p;
519:
520: if (!string) return;
521:
522: argv[0] = "mtr";
523: argc = 1;
524: p = strtok (string, " \t");
525: while (p != NULL && ((size_t) argc < (sizeof(argv)/sizeof(argv[0])))) {
526: argv[argc++] = p;
527: p = strtok (NULL, " \t");
528: }
529: if (p != NULL) {
530: fprintf (stderr, "Warning: extra arguments ignored: %s", p);
531: }
532:
533: parse_arg (argc, argv);
534: optind = 0;
535: }
536:
537:
538: int main(int argc, char **argv)
539: {
540: struct hostent * host = NULL;
541: int net_preopen_result;
542: #ifdef ENABLE_IPV6
543: struct addrinfo hints, *res;
544: int error;
545: struct hostent trhost;
546: char * alptr[2];
547: struct sockaddr_in * sa4;
548: struct sockaddr_in6 * sa6;
549: #endif
550:
551: /* Get the raw sockets first thing, so we can drop to user euid immediately */
552:
553: if ( ( net_preopen_result = net_preopen () ) ) {
554: fprintf( stderr, "mtr: unable to get raw sockets.\n" );
555: exit( EXIT_FAILURE );
556: }
557:
558: /* Now drop to user permissions */
559: if (setgid(getgid()) || setuid(getuid())) {
560: fprintf (stderr, "mtr: Unable to drop permissions.\n");
561: exit(1);
562: }
563:
564: /* Double check, just in case */
565: if ((geteuid() != getuid()) || (getegid() != getgid())) {
566: fprintf (stderr, "mtr: Unable to drop permissions.\n");
567: exit(1);
568: }
569:
570: /* reset the random seed */
571: srand (getpid());
572:
573: display_detect(&argc, &argv);
574:
575: /* The field options are now in a static array all together,
576: but that requires a run-time initialization. */
577: init_fld_options ();
578:
579: parse_mtr_options (getenv ("MTR_OPTIONS"));
580:
581: parse_arg (argc, argv);
582:
583: while (optind < argc) {
584: char* name = argv[optind++];
585: append_to_names(argv[0], name);
586: }
587:
588: /* Now that we know mtrtype we can select which socket to use */
589: if (net_selectsocket() != 0) {
590: fprintf( stderr, "mtr: Couldn't determine raw socket type.\n" );
591: exit( EXIT_FAILURE );
592: }
593:
594: if (PrintVersion) {
595: printf ("mtr " MTR_VERSION "\n");
596: exit(0);
597: }
598:
599: if (PrintHelp) {
1.1.1.2 misho 600: printf("usage: %s [--help] [--version] [-4|-6] [-F FILENAME]\n"
601: "\t\t[--report] [--report-wide]\n"
602: "\t\t[--xml] [--gtk] [--curses] [--raw] [--csv] [--split]\n"
603: "\t\t[--no-dns] [--show-ips] [-o FIELDS] [-y IPINFO] [--aslookup]\n"
604: "\t\t[-i INTERVAL] [-c COUNT] [-s PACKETSIZE] [-B BITPATTERN]\n"
605: "\t\t[-Q TOS] [--mpls]\n"
606: "\t\t[-a ADDRESS] [-f FIRST-TTL] [-m MAX-TTL]\n"
607: "\t\t[--udp] [--tcp] [-P PORT] [-Z TIMEOUT]\n"
608: "\t\t[-M MARK] HOSTNAME\n", argv[0]);
609: printf("See the man page for details.\n");
1.1 misho 610: exit(0);
611: }
612:
613: time_t now = time(NULL);
1.1.1.2 misho 614:
615: if (!names) append_to_names (argv[0], "localhost"); // default: localhost.
616:
1.1 misho 617: names_t* head = names;
618: while (names != NULL) {
619:
620: Hostname = names->name;
1.1.1.2 misho 621: // if (Hostname == NULL) Hostname = "localhost"; // no longer necessary.
1.1 misho 622: if (gethostname(LocalHostname, sizeof(LocalHostname))) {
1.1.1.2 misho 623: strcpy(LocalHostname, "UNKNOWNHOST");
1.1 misho 624: }
625:
626: if (net_preopen_result != 0) {
627: fprintf(stderr, "mtr: Unable to get raw socket. (Executable not suid?)\n");
628: if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE);
629: else {
630: names = names->next;
631: continue;
632: }
633: }
634:
635: #ifdef ENABLE_IPV6
636: /* gethostbyname2() is deprecated so we'll use getaddrinfo() instead. */
637: bzero( &hints, sizeof hints );
638: hints.ai_family = af;
639: hints.ai_socktype = SOCK_DGRAM;
640: error = getaddrinfo( Hostname, NULL, &hints, &res );
641: if ( error ) {
642: if (error == EAI_SYSTEM)
643: perror ("Failed to resolve host");
644: else
645: fprintf (stderr, "Failed to resolve host: %s\n", gai_strerror(error));
646:
647: if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE);
648: else {
649: names = names->next;
650: continue;
651: }
652: }
653: /* Convert the first addrinfo into a hostent. */
654: host = &trhost;
655: bzero( host, sizeof trhost );
656: host->h_name = res->ai_canonname;
657: host->h_aliases = NULL;
658: host->h_addrtype = res->ai_family;
659: af = res->ai_family;
660: host->h_length = res->ai_addrlen;
661: host->h_addr_list = alptr;
662: switch ( af ) {
663: case AF_INET:
664: sa4 = (struct sockaddr_in *) res->ai_addr;
665: alptr[0] = (void *) &(sa4->sin_addr);
666: break;
667: case AF_INET6:
668: sa6 = (struct sockaddr_in6 *) res->ai_addr;
669: alptr[0] = (void *) &(sa6->sin6_addr);
670: break;
671: default:
672: fprintf( stderr, "mtr unknown address type\n" );
673: if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE);
674: else {
675: names = names->next;
676: continue;
677: }
678: }
679: alptr[1] = NULL;
680: #else
681: host = gethostbyname(Hostname);
682: if (host == NULL) {
683: herror("mtr gethostbyname");
684: if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE);
685: else {
686: names = names->next;
687: continue;
688: }
689: }
690: af = host->h_addrtype;
691: #endif
692:
693: if (net_open(host) != 0) {
694: fprintf(stderr, "mtr: Unable to start net module.\n");
695: if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE);
696: else {
697: names = names->next;
698: continue;
699: }
700: }
701:
702: if (net_set_interfaceaddress (InterfaceAddress) != 0) {
703: fprintf( stderr, "mtr: Couldn't set interface address.\n" );
704: if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE);
705: else {
706: names = names->next;
707: continue;
708: }
709: }
710:
711: lock(argv[0], stdout);
712: display_open();
713: dns_open();
714:
715: display_mode = 0;
716: display_loop();
717:
718: net_end_transit();
719: display_close(now);
720: unlock(argv[0], stdout);
721:
722: if ( DisplayMode != DisplayCSV ) break;
723: else names = names->next;
724:
725: }
726:
727: net_close();
728:
729: while (head != NULL) {
730: names_t* item = head;
731: free(item->name); item->name = NULL;
732: head = head->next;
733: free(item); item = NULL;
734: }
735: head=NULL;
736:
737: return 0;
738: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>