Watt-32 tcp/ip  2.2 dev-rel.10
tftp.c
Go to the documentation of this file.
1 
37 #include "socket.h"
38 #include "pcdns.h"
39 #include "run.h"
40 #include "tftp.h"
41 
42 #if defined(USE_TFTP)
43 
44 #if defined(USE_DEBUG)
45  #define TRACE(x) (*_printf) x
46 #else
47  #define TRACE(x) ((void)0)
48 #endif
49 
50 
51 /*
52  * Error codes private to this file:
53  */
54 #define ERR_INV 1 /* invalid packet size */
55 #define ERR_ERR 2 /* error packet received */
56 #define ERR_OP 3 /* invalid opcode */
57 #define ERR_BLOCK 4 /* invalid block number */
58 #define ERR_TIMEOUT 5 /* timeout while receiving */
59 #define ERR_UNKNOWN 6 /* unknown error */
60 
61 /*
62  * Various definitions:
63  */
64 #define TFTP_RETRY 5 /* Maximum number of retries */
65 #define TFTP_TIMEOUT 8 /* Default 8 seconds timeout */
66 #define TFTP_HEADSIZE 4 /* th_opcode/th_block size */
67 #define TFTP_PORT_LOW 1024 /* lowest legal local port */
68 #define TFTP_PORT_HIGH USHRT_MAX /* higest legal local port */
69 #define OCTET_STR "octet" /* name for 8-bit raw format */
70 #define NETASCII_STR "netascii" /* name for netascii format */
71 #define MAIL_STR "mail" /* name for mail format */
72 
73 /*
74  * Public data
75  */
76 int (W32_CALL *_tftp_write) (const void*, size_t) = NULL;
77 int (W32_CALL *_tftp_close) (void) = NULL;
78 
79 /*
80  * Local variables
81  */
82 static struct tftphdr *inbuf; /* TFTP input buffer */
83 static struct tftphdr *outbuf; /* TFTP output buffer */
84 static sock_type *sock; /* Socket for UDP recv/xmit */
85 
86 static DWORD currblock; /* Current data block */
87 static int blocksize; /* Server's block size */
88 static int ibuflen; /* Size of data in input buffer */
89 static int isopen; /* TRUE if connection is open */
90 
91 static int tftp_errno = 0;
92 static DWORD tftp_server = 0;
93 static int tftp_timeout = TFTP_TIMEOUT;
94 static int tftp_retry = TFTP_RETRY;
95 static int tftp_lport = 0;
96 
97 static char tftp_server_name[MAX_HOSTLEN] = "";
98 static char tftp_xfer_mode [MAX_VALUELEN] = OCTET_STR;
99 static char *tftp_boot_remote_file = NULL;
100 static char *tftp_boot_local_file = NULL;
101 static char *tftp_openmode = NULL;
102 
103 /*
104  * Send a tftp request packet
105  */
106 static void send_req (char request, const char *fname)
107 {
108  char *cp, *mode;
109  int len;
110  int fnamlen = strlen (fname);
111 
112  /* The output buffer is setup with the request code, the file name,
113  * and the name of the data format.
114  */
115  memset (outbuf, 0, sizeof(*outbuf));
116  outbuf->th_opcode = intel16 (request);
117  len = SEGSIZE - sizeof(outbuf->th_opcode) - strlen(tftp_xfer_mode) - 1;
118  cp = (char*) &outbuf->th_stuff[0];
119 
120  for ( ; *fname && len > 0 && fnamlen > 0; len--, fnamlen--)
121  *cp++ = *fname++;
122  *cp++ = '\0';
123 
124  for (mode = tftp_xfer_mode; *mode; )
125  *cp++ = *mode++;
126  *cp++ = '\0';
127 
128  /* Finally send the request
129  */
130  len = (int) (cp - (char*)outbuf);
131  sock_fastwrite (sock, (BYTE*)outbuf, len);
132 }
133 
134 
135 /*
136  * Send a tftp acknowledge packet
137  */
138 static void send_ack (WORD block)
139 {
140  struct tftphdr ack;
141 
142  ack.th_opcode = intel16 (ACK);
143  ack.th_block = intel16 (block);
144  sock_fastwrite (sock, (BYTE*)&ack, TFTP_HEADSIZE);
145 }
146 
147 #if defined(USE_DEBUG)
148 /*
149  * Return error string for 'th_code'
150  */
151 static const char *tftp_strerror (int code)
152 {
153  static const char *err_tab[] = {
154  "EUNDEF",
155  "ENOTFOUND",
156  "EACCESS",
157  "ENOSPACE",
158  "EBADOP",
159  "EBADID",
160  "EEXISTS",
161  "ENOUSER",
162  "EOPTNEG"
163  };
164  if (code < 0 || code >= DIM(err_tab))
165  return ("?");
166  return (err_tab[code]);
167 }
168 #endif
169 
170 
171 /*
172  * Watch out for "ICMP port unreachable".
173  */
174 static void udp_callback (_udp_Socket *s, int icmp_type, int icmp_code)
175 {
176  if (s->ip_type == UDP_PROTO && s == (_udp_Socket*)sock &&
177  (s->locflags & LF_GOT_ICMP) && icmp_type == ICMP_UNREACH)
178  {
179  /* In lack of a better way, pretend we got a FIN.
180  * This causes sock_wait_input() below to break it's loop.
181  */
182  s->locflags |= LF_GOT_FIN;
183  s->err_msg = icmp_type_str [ICMP_UNREACH];
184  }
185  ARGSUSED (icmp_code);
186 }
187 
188 /*
189  * Receive a TFTP data packet
190  */
191 static int recv_packet (DWORD block)
192 {
193  int len, status = 0;
194 
195  /* Use a callback since first block sent might cause a "ICMP
196  * port unreachable" to be sent back. Note that the normal mechanism
197  * of detecting ICMP errors (through _udp_cancel) doesn't work since
198  * we did set 'sock->udp.hisaddr = 0'.
199  *
200  * Note: 'block' is 32-bit, but 16-bit in tftp-header.
201  * We allow the header block-counter to wrap (allowing > 32MB files).
202  */
203  if (block == 1UL)
204  sock->udp.icmp_callb = (icmp_upcall) udp_callback;
205  else sock->udp.icmp_callb = NULL;
206 
207  /* Read packet with timeout
208  */
209  sock_wait_input (sock, tftp_timeout, NULL, &status);
210 
211  len = sock_fastread (sock, (BYTE*)inbuf, TFTP_HEADSIZE+SEGSIZE);
212 
213  /* Check that the packet has a correct length
214  */
215  len -= TFTP_HEADSIZE;
216  if (len < 0 || len > SEGSIZE)
217  {
218  TRACE (("tftp: Invalid packet, len = %d\n", len));
219  tftp_errno = ERR_INV;
220  return (-1);
221  }
222 
223  /* Check if we got an error packet
224  */
225  if (intel16(inbuf->th_opcode) == ERROR)
226  {
227 #if defined(USE_DEBUG)
228  int code = intel16 (inbuf->th_code);
229  const char *str = tftp_strerror (code);
230 
231  TRACE (("tftp: Error: %s (%d): %.*s\n",
232  str, code, SEGSIZE, inbuf->th_data));
233 #endif
234  tftp_errno = ERR_ERR;
235  return (-1);
236  }
237 
238  /* Check if we got a valid data packet at all
239  */
240  if (intel16(inbuf->th_opcode) != DATA)
241  {
242  TRACE (("tftp: Invalid opcode %d\n", intel16(inbuf->th_opcode)));
243  tftp_errno = ERR_OP;
244  return (-1);
245  }
246 
247  /* Check if the block number of the data packet is correct
248  */
249  if (intel16(inbuf->th_block) != (WORD)block)
250  {
251  TRACE (("tftp: Block %u != %u\n", intel16(inbuf->th_block), (WORD)block));
252  tftp_errno = ERR_BLOCK;
253  return (-1);
254  }
255 
256  tftp_errno = 0;
257  if (debug_on)
258  (*_outch) ('#'); /* Write 1 hash-mark per block */
259 
260  return (len);
261 
262 sock_err:
263  if (status == -1)
264  {
265  if (debug_on)
266  (*_outch) ('T');
267 
268  tftp_errno = ERR_TIMEOUT;
269  return (-1);
270  }
271 
272  /* most likely "Port unreachable"
273  */
274  TRACE (("tftp: %s\n", sockerr(sock)));
275  tftp_errno = ERR_UNKNOWN;
276  return (-1);
277 }
278 
279 
280 /*
281  * Open a TFTP connection on a random local port (our transaction ID).
282  * Send the request, wait for first data block and send the first ACK.
283  */
284 static int tftp_open (DWORD server, const char *fname)
285 {
286  int retry;
287  WORD port = 69;
288 
289 #if defined(USE_BSD_API)
290  struct servent *sp = getservbyname ("tftp", "udp");
291 
292  if (sp)
293  port = intel16 ((WORD)sp->s_port);
294 #endif
295 
296  currblock = 0UL;
297  blocksize = 0;
298 
299  for (retry = 0; retry < tftp_retry; retry++)
300  {
301  WORD our_tid; /* our transaction ID (local port) */
302 
303  if (tftp_lport && tftp_lport < TFTP_PORT_LOW)
304  outsnl (_LANG("tftp: Illegal local port."));
305 
306  if (tftp_lport >= TFTP_PORT_LOW)
307  our_tid = tftp_lport;
308  else our_tid = Random (TFTP_PORT_LOW, TFTP_PORT_HIGH);
309 
310  /* Try to open a TFTP connection to the server
311  */
312  if (!udp_open(&sock->udp, our_tid, server, port, NULL))
313  {
314  TRACE (("tftp: %s\n", sockerr(sock)));
315  return (0);
316  }
317  sock->udp.locflags |= LF_NOCLOSE; /* don't close socket on timeout */
318 
319  /* Send the file request block, and then wait for the first data
320  * block. If there is no response to the query, retry it with
321  * another transaction ID (local port), so that all old packets get
322  * discarded automatically.
323  */
324  send_req (RRQ, fname);
325 
326  /* This hack makes it work because the response is sent back on
327  * a source-port different from port 69; i.e. the server TID
328  * uses a random port. Force the response packet to match a passive
329  * socket in udp_handler().
330  */
331  sock->udp.hisaddr = 0;
332 
333  ibuflen = recv_packet (1);
334  if (ibuflen >= 0)
335  {
336  blocksize = ibuflen;
337  isopen = TRUE;
338  send_ack (1);
339  return (1);
340  }
341 
342  /* If an error (except timeout) occurred, retries are useless
343  */
344  if (tftp_errno == ERR_ERR || tftp_errno == ERR_UNKNOWN)
345  break;
346  }
347  return (0);
348 }
349 
350 /*
351  * Close the TFTP connection
352  */
353 static void tftp_close (void)
354 {
355  if (_tftp_close)
356  (*_tftp_close)();
357 
358  if (debug_on)
359  outs ("\n");
360 
361  if (sock)
362  {
363  sock_close (sock);
364  free (sock);
365  sock = NULL;
366  }
367  DO_FREE (inbuf);
368  DO_FREE (outbuf);
369 }
370 
371 /*
372  * Set the name of TFTP server
373  */
374 char *tftp_set_server (const char *name, int len)
375 {
376  len = min (len+1, SIZEOF(tftp_server_name));
377  return _strlcpy (tftp_server_name, name, len);
378 }
379 
380 /*
381  * Set the name of remote/local file to load from TFTP server.
382  * Format is "tftp.boot_file = remote [local].
383  * Note: `remote' name cannot contain spaces.
384  */
385 char *tftp_set_boot_fname (const char *name, int len)
386 {
387  char *p, buf [MAX_PATHLEN];
388 
389  len = min (len+1, SIZEOF(buf));
390  _strlcpy (buf, name, len);
391  tftp_boot_remote_file = strdup (buf);
392  tftp_boot_local_file = tftp_boot_remote_file;
393 
394  if (tftp_boot_local_file)
395  {
396  p = strchr (tftp_boot_local_file, ' ');
397  if (p)
398  {
399  *p++ = '\0';
400  tftp_boot_local_file = p;
401  }
402  }
403  return (tftp_boot_remote_file);
404 }
405 
406 /*
407  * Set the mode used for transfer
408  */
409 static char *tftp_set_xfer_mode (const char *name)
410 {
411  return _strlcpy (tftp_xfer_mode, name, sizeof(tftp_xfer_mode));
412 }
413 
414 /*
415  * Read the next data packet from a TFTP connection
416  */
417 static int tftp_get_block (const char **buf)
418 {
419  int retry;
420 
421  /* Don't do anything if no TFTP connection is active.
422  */
423  if (!isopen)
424  return (0);
425 
426  /* If the block number is 0 then we are still dealing with the first
427  * data block after opening a connection. If the data size is smaller
428  * than 'blocksize' just close the connection again.
429  */
430  if (currblock == 0UL)
431  {
432  currblock++;
433  if (ibuflen < blocksize)
434  isopen = FALSE;
435  *buf = (const char*) &inbuf->th_data[0];
436  return (ibuflen);
437  }
438 
439  /* Wait for the next data packet. If no data packet is coming in,
440  * resend the ACK for the last packet to restart the sender. Maybe
441  * he didn't get our first ACK.
442  */
443  for (retry = 0; retry < tftp_retry; retry++)
444  {
445  ibuflen = recv_packet (currblock+1);
446  if (ibuflen >= 0)
447  {
448  currblock++;
449  send_ack ((WORD)currblock);
450  if (ibuflen < blocksize) /* last block received */
451  isopen = FALSE;
452  *buf = (const char*) &inbuf->th_data[0];
453  return (ibuflen);
454  }
455  if (tftp_errno == ERR_ERR || tftp_errno == ERR_UNKNOWN)
456  break;
457 
458  send_ack ((WORD)currblock);
459  }
460  isopen = FALSE;
461  return (-1);
462 }
463 
464 /*
465  * Load the BOOT-file from TFTP server
466  */
467 int tftp_boot_load (void)
468 {
469  int rc = 0;
470 
471  /* Allocate socket and buffers
472  */
473  sock = malloc (sizeof(sock->udp));
474  inbuf = malloc (TFTP_HEADSIZE+SEGSIZE);
475  outbuf = malloc (TFTP_HEADSIZE+SEGSIZE);
476 
477  if (!sock || !inbuf || !outbuf)
478  {
479  outsnl (_LANG("No memory for TFTP boot."));
480  return (0);
481  }
482 
483  if (!tftp_boot_remote_file)
484  {
485  outsnl (_LANG("No remote TFTP boot filename defined."));
486  return (0);
487  }
488 
489  if (tftp_server_name[0] && !tftp_server)
490  tftp_server = resolve (tftp_server_name);
491 
492  if (!tftp_server)
493  {
494  outsnl (_LANG("Cannot resolve TFTP-server "));
495  return (0);
496  }
497 
498  if (debug_on)
499  outs (_LANG("Doing TFTP boot load..."));
500 
501  /* Open connection and request file
502  */
503  if (!tftp_open(tftp_server, tftp_boot_remote_file))
504  {
505  tftp_close();
506  return (0);
507  }
508 
509  while (1)
510  {
511  const char *buf;
512  int size = tftp_get_block (&buf);
513 
514  if (size < 0) /* error in transfer */
515  {
516  rc = 0;
517  break;
518  }
519  if (size > 0 && (*_tftp_write)(buf,size) < 0)
520  {
521  rc = -1; /* writer failed, errno set */
522  break;
523  }
524  if (size < blocksize) /* got last block */
525  {
526  rc = 1;
527  break;
528  }
529  }
530  tftp_close();
531  return (rc);
532 }
533 
534 /*
535  * Config-file handler for TFTP-client
536  */
537 static void (W32_CALL *prev_hook) (const char*, const char*) = NULL;
538 
539 static void W32_CALL tftp_cfg_hook (const char *name, const char *value)
540 {
541  static const struct config_table tftp_cfg[] = {
542  { "BOOT_FILE", ARG_FUNC, (void*)tftp_set_boot_fname },
543  { "SERVER", ARG_RESOLVE, (void*)&tftp_server },
544  { "TIMEOUT", ARG_ATOI, (void*)&tftp_timeout },
545  { "RETRY", ARG_ATOI, (void*)&tftp_retry },
546  { "MODE", ARG_FUNC, (void*)tftp_set_xfer_mode },
547  { "OPENMODE", ARG_STRDUP, (void*)&tftp_openmode },
548  { "PORT", ARG_ATOI, (void*)&tftp_lport },
549  { NULL, 0, NULL }
550  };
551  if (!parse_config_table(tftp_cfg, "TFTP.", name, value) && prev_hook)
552  (*prev_hook) (name, value);
553 }
554 
558 static void tftp_exit (void)
559 {
560  if (!_watt_fatal_error)
561  {
562  DO_FREE (tftp_boot_remote_file);
563  DO_FREE (tftp_openmode);
564  }
565 }
566 
570 int tftp_init (void)
571 {
572  prev_hook = usr_init;
573  usr_init = tftp_cfg_hook;
574  RUNDOWN_ADD (tftp_exit, 261);
575  return (TRUE);
576 }
577 
578 /*
579  * A small test program, for djgpp/Watcom only
580  */
581 #if defined(TEST_PROG)
582 
583 #ifndef _MSC_VER
584 #include <unistd.h>
585 #endif
586 
587 #include "netaddr.h"
588 #include "pcdbug.h"
589 #include "pcarp.h"
590 
591 static FILE *file;
592 static char *fname;
593 static DWORD tot_size;
594 static time_t start;
595 
596 static int W32_CALL close_func (void)
597 {
598  if (file && fname)
599  {
600  time_t now = time (NULL);
601  double speed;
602 
603  if (now == start)
604  now++;
605  speed = (double)tot_size / (double)(now - start);
606  fprintf (stderr, "closing `%s' (%.2f kB/s)\n", fname, speed/1024.0);
607  fclose (file);
608  file = NULL;
609  return (1);
610  }
611  return (0);
612 }
613 
614 static int W32_CALL write_func (const void *buf, size_t length)
615 {
616  static DWORD block = 0;
617 
618  if (debug_on < 2)
619  debug_on = 2;
620 
621  if (block == 0)
622  {
623  tot_size = 0UL;
624  start = time (NULL);
625  fname = tftp_boot_local_file;
626  fprintf (stderr, "opening `%s'\n", fname);
627  file = fopen (fname, tftp_openmode ? tftp_openmode : "wb");
628  }
629  if (!file)
630  {
631  perror (fname);
632  return (-1);
633  }
634 
635 #if defined(__DJGPP__) && 0
636  /*
637  * Look for optional Watt-32 stubinfo in block 4.
638  * If .exe file isn't newer, kill the connection
639  */
640  if (block == 4 && is_exe && check_timestamp(buf) < our_timestamp)
641  {
642  close_func();
643  return (-1);
644  }
645 #endif
646 
647  if (fwrite (buf, 1, length, file) < length)
648  return (-1);
649  tot_size += length;
650  block++;
651  return (0);
652 }
653 
654 
655 void usage (char *argv0)
656 {
657  printf ("Usage: %s [[-d] [-n] [-a] [-h host] [-f file]\n"
658  "\t\t [-i ip] [-m mask]] [-t timeout] [-r retry]\n"
659  "\t -d enable WATTCP.DBG file\n"
660  "\t -n run with no config file\n"
661  "\t -a add random MAC address for tftp host\n"
662  "\t -h specify tftp host\n"
663  "\t -f specify remote file to load\n"
664  "\t -i specify ip-address (default 192.168.0.1)\n"
665  "\t -m specify network mask (default 255.255.0.0)\n"
666  "\t -t specify total timeout (default %d)\n"
667  "\t -r specify retry count (default %d)\n",
668  argv0, tftp_timeout, tftp_retry);
669  exit (-1);
670 }
671 
672 int main (int argc, char **argv)
673 {
674  eth_address eth = { 1,2,3,4,5,6 };
675  int a_flag = 0;
676  int h_flag = 0;
677  int i_flag = 0;
678  int f_flag = 0;
679  int n_flag = 0;
680  int m_flag = 0;
681  int d_flag = 0;
682  int ch;
683 
684  while ((ch = getopt(argc, argv, "adn?h:i:f:m:t:r:")) != EOF)
685  switch (ch)
686  {
687  case 'a':
688  a_flag = 1;
689  break;
690  case 'd':
691  d_flag = 1;
692  break;
693  case 'n':
694  n_flag = 1;
695  break;
696  case 'h':
697  h_flag = 1;
698  tftp_server = aton (optarg);
699  break;
700  case 'i':
701  i_flag = 1;
702  my_ip_addr = aton (optarg);
703  break;
704  case 'f':
705  f_flag = 1;
706  tftp_set_boot_fname (optarg, strlen(optarg));
707  break;
708  case 'm':
709  m_flag = 1;
710  sin_mask = aton (optarg);
711  break;
712  case 't':
713  tftp_timeout = atoi (optarg);
714  break;
715  case 'r':
716  tftp_retry = atoi (optarg);
717  break;
718  case '?':
719  default:
720  usage (argv[0]);
721  }
722 
723  if (n_flag) /* Demonstrate running with no config file */
724  {
725  _watt_no_config = 1;
726  dbg_mode_all = 1;
727  dbg_print_stat = 1;
728  debug_on = 3;
729 
730  if (!m_flag)
731  sin_mask = aton ("255.255.0.0");
732  if (!i_flag)
733  my_ip_addr = aton ("192.168.0.1");
734  if (!h_flag)
735  tftp_server = aton ("192.168.0.2");
736  if (!f_flag)
737  tftp_set_boot_fname ("test.fil", 8);
738  if (a_flag)
739  _arp_cache_add (tftp_server, (const eth_address*)&eth, FALSE);
740  }
741  else if (m_flag || i_flag || h_flag || f_flag || a_flag)
742  {
743  puts ("This option requires the `-n' flag");
744  return (-1);
745  }
746 
747  if (d_flag)
748  dbug_init();
749 
750  if (n_flag)
751  dbug_open();
752 
753  /* Must set our hook first
754  */
755  _tftp_write = write_func;
756  _tftp_close = close_func;
757 
758  sock_init();
759 
760 #ifdef WIN32
761  Sleep (1000); /* drain network buffers */
762 #else
763  sleep (1);
764 #endif
765  tcp_tick (NULL);
766  return (0);
767 }
768 #endif /* TEST_PROG */
769 #endif /* USE_TFTP */
int W32_CALL parse_config_table(const struct config_table *tab, const char *section, const char *name, const char *value)
Parse the config-table and if a match is found for ('section'+'.
Definition: pcconfig.c:379
unsigned W32_CALL Random(unsigned a, unsigned b)
Returns a random integer in range [a..b].
Definition: misc.c:742
DWORD W32_CALL aton(const char *str)
Converts [a.b.c.d] or a.b.c.d to 32 bit IPv4 address.
Definition: netaddr.c:86
convert to int
Definition: tcp.h:424
call convertion function
Definition: tcp.h:434
int W32_CALL udp_open(_udp_Socket *s, WORD lport, DWORD ip, WORD port, ProtoHandler handler)
UDP active open.
Definition: pctcp.c:192
BOOL W32_CALL _arp_cache_add(DWORD ip, const void *eth, BOOL expires)
Add given IP/Ether address to ARP-cache.
Definition: pcarp.c:1352
int tftp_init(void)
Initialize config-hook for TFTP protocol.
Definition: tftp.c:570
BOOL _watt_no_config
run with no config file (embedded/diskless)
Definition: sock_ini.c:135
Definition: netdb.h:122
int W32_CALL sock_close(sock_type *s)
Close a UDP/TCP socket.
Definition: pctcp.c:3139
int W32_CALL sock_fastwrite(sock_type *s, const BYTE *data, int len)
Simpler, non-blocking (non-looping) version of sock_write().
Definition: pctcp.c:3018
resolve host to IPv4-address
Definition: tcp.h:433
Definition: tftp.h:62
static void tftp_exit(void)
Free allocated memory.
Definition: tftp.c:558
char * _strlcpy(char *dst, const char *src, size_t len)
Similar to strncpy(), but always returns 'dst' with 0-termination.
Definition: strings.c:226
DWORD sin_mask
our net-mask, 255.255.255.0
Definition: pctcp.c:71
int W32_CALL sock_fastread(sock_type *s, BYTE *buf, int len)
Read a socket with maximum 'len' bytes.
Definition: pctcp.c:2931
Definition: zinftree.h:24
duplicate string value
Definition: tcp.h:431
DWORD my_ip_addr
our IP address
Definition: pctcp.c:70
BOOL _watt_fatal_error
Definition: misc.c:60
BOOL dbg_mode_all
These are public so they can be set by application if running without a config-file.
Definition: pcdbug.c:190
WORD W32_CALL tcp_tick(sock_type *s)
Must be called periodically by user application (or BSD socket API).
Definition: pctcp.c:1389
DWORD W32_CALL resolve(const char *name)
Convert host name to an address.
Definition: pcdns.c:775
int main(int argc, char **argv)
Definition: echo.c:223