unified MX27, MX25, MX37 trees
[karo-tx-redboot.git] / packages / net / ftpclient / v2_0 / src / ftpclient.c
1 //==========================================================================
2 //
3 //      ftpclient.c
4 //
5 //      A simple FTP client
6 //
7 //==========================================================================
8 //####ECOSGPLCOPYRIGHTBEGIN####
9 // -------------------------------------------
10 // This file is part of eCos, the Embedded Configurable Operating System.
11 // Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
12 // Copyright (C) 2002 Andrew Lunn.
13 // Copyright (C) 2004 Gary Thomas
14 //
15 // eCos is free software; you can redistribute it and/or modify it under
16 // the terms of the GNU General Public License as published by the Free
17 // Software Foundation; either version 2 or (at your option) any later version.
18 //
19 // eCos is distributed in the hope that it will be useful, but WITHOUT ANY
20 // WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
22 // for more details.
23 //
24 // You should have received a copy of the GNU General Public License along
25 // with eCos; if not, write to the Free Software Foundation, Inc.,
26 // 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
27 //
28 // As a special exception, if other files instantiate templates or use macros
29 // or inline functions from this file, or you compile this file and link it
30 // with other works to produce a work based on this file, this file does not
31 // by itself cause the resulting work to be covered by the GNU General Public
32 // License. However the source code for this file must still be made available
33 // in accordance with section (3) of the GNU General Public License.
34 //
35 // This exception does not invalidate any other reasons why a work based on
36 // this file might be covered by the GNU General Public License.
37 //
38 // Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
39 // at http://sources.redhat.com/ecos/ecos-license/
40 // -------------------------------------------
41 //####ECOSGPLCOPYRIGHTEND####
42 //==========================================================================
43 //#####DESCRIPTIONBEGIN####
44 //
45 // Author(s):    andrew.lunn@ascom.ch
46 // Contributors: andrew.lunn@ascom.ch
47 // Date:         2001-11-4
48 // Purpose:      
49 // Description:  
50 //              
51 //####DESCRIPTIONEND####
52 //
53 //==========================================================================
54
55 #include <pkgconf/system.h>
56 #include <pkgconf/net_ftpclient.h>
57
58 #include <network.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <sys/socket.h>
62 #include <netinet/in.h>
63 #include <arpa/inet.h>
64 #include <ctype.h>
65 #include "ftpclient.h"
66
67 /* Build one command to send to the FTP server */
68 static int 
69 build_cmd(char *buf,
70           unsigned bufsize,
71           char *cmd, 
72           char *arg1)
73 {
74   int cnt;
75   
76   if (arg1) {
77     cnt = snprintf(buf,bufsize,"%s %s\r\n",cmd,arg1);
78   } else {
79     cnt = snprintf(buf,bufsize,"%s\r\n",cmd);
80   }
81   
82   if (cnt < bufsize) 
83     return 1;
84   
85   return 0;
86 }
87
88
89 /* Read one line from the server, being careful not to overrun the
90    buffer. If we do reach the end of the buffer, discard the rest of
91    the line. */
92 static int 
93 get_line(int s, char *buf, unsigned buf_size, ftp_printf_t ftp_printf) 
94 {
95   int eol = 0;
96   int cnt = 0;
97   int ret;
98   char c;
99   
100   while(!eol) {
101     ret = read(s,&c,1);
102     
103     if (ret != 1) {
104       ftp_printf(1,"read %s\n",strerror(errno));
105       return FTP_BAD;
106     }
107     
108     if (c == '\n') {
109       eol = 1;
110       continue;
111     }
112
113     if (cnt < buf_size) {
114       buf[cnt++] = c;
115     }   
116   }
117   if (cnt < buf_size) {
118     buf[cnt++] = '\0';
119   } else {
120     buf[buf_size -1] = '\0';
121   }
122   return 0;
123 }  
124
125 /* Read the reply from the server and return the MSB from the return
126    code. This gives us a basic idea if the command failed/worked. The
127    reply can be spread over multiple lines. When this happens the line
128    will start with a - to indicate there is more*/
129 static int 
130 get_reply(int s, ftp_printf_t ftp_printf) 
131 {
132   char buf[BUFSIZ];
133   int more = 0;
134   int ret;
135   int first_line=1;
136   int code=0;
137   
138   do {
139     
140     if ((ret=get_line(s,buf,sizeof(buf),ftp_printf)) < 0) {
141       return(ret);
142     }
143     
144     ftp_printf(0,"FTP: %s\n",buf);
145     
146     if (first_line) {
147       code = strtoul(buf,NULL,0);
148       first_line=0;
149       more = (buf[3] == '-');
150     } else {
151       if (isdigit(buf[0]) && isdigit(buf[1]) && isdigit(buf[2]) &&
152           (code == strtoul(buf,NULL,0)) && 
153           buf[3]==' ') {
154         more=0;
155       } else {
156         more =1;
157       }
158     }
159   } while (more);
160
161   return (buf[0] - '0');
162 }
163
164 /* Send a command to the server */
165 static int 
166 send_cmd(int s, char * msgbuf, ftp_printf_t ftp_printf) 
167 {  
168   int len;
169   int slen = strlen(msgbuf);
170   
171   if ((len = write(s,msgbuf,slen)) != slen) {
172     if (len < 0) {
173       ftp_printf(1,"write %s\n",strerror(errno));
174       return FTP_BAD;
175     } else {
176       ftp_printf(1,"write truncated!\n");
177       return FTP_BAD;
178     }
179   }
180   return 1;
181 }
182
183 /* Send a complete command to the server and receive the reply. Return the 
184    MSB of the reply code. */
185 static int 
186 command(char * cmd, 
187         char * arg, 
188         int s, 
189         char *msgbuf, 
190         int msgbuflen,
191         ftp_printf_t ftp_printf) 
192 {
193   int err;
194   
195   if (!build_cmd(msgbuf,msgbuflen,cmd,arg)) {
196     ftp_printf(1,"FTP: %s command to long\n",cmd);
197     return FTP_BAD;
198   }
199
200   ftp_printf(0,"FTP: Sending %s command\n",cmd);
201   
202   if ((err=send_cmd(s,msgbuf,ftp_printf)) < 0) {
203     return(err);
204   }
205
206   return (get_reply(s,ftp_printf));
207 }
208
209 /* Open a socket and connect it to the server. Also print out the
210    address of the server for debug purposes.*/
211
212 static int
213 connect_to_server(char *hostname, 
214                   struct sockaddr * local,
215                   ftp_printf_t ftp_printf) 
216
217   int s;
218   socklen_t len;
219   int error;
220   struct addrinfo *res, *nai;
221   char name[80];
222   char port[8];
223
224   error = getaddrinfo(hostname, "ftp", NULL, &res);
225   if (error != EAI_NONE) {
226     return FTP_NOSUCHHOST;
227   }
228
229   nai=res;
230   while (nai) {
231     s = socket(nai->ai_family, nai->ai_socktype,nai->ai_protocol);
232     if (s < 0) {
233       nai = nai->ai_next;
234       continue;
235     }
236     
237     if (connect(s, nai->ai_addr, nai->ai_addrlen) < 0) {
238       getnameinfo(nai->ai_addr, nai->ai_addrlen, 
239                   name, sizeof(name), NULL,0, NI_NUMERICHOST);
240       ftp_printf(1,"FTP Connect to %s failed: %s\n",name, strerror(errno));
241       close (s);
242       nai = nai->ai_next;
243       continue;
244     }
245     
246     len = sizeof(struct sockaddr);
247     if (getsockname(s, (struct sockaddr *)local, &len) < 0) {
248       ftp_printf(1,"getsockname failed %s\n",strerror(errno));
249       close(s);
250       nai = nai->ai_next;
251       continue;
252     }
253     getnameinfo(nai->ai_addr, nai->ai_addrlen, 
254                 name, sizeof(name), port, sizeof(port), 
255                 NI_NUMERICHOST|NI_NUMERICSERV);
256     
257     ftp_printf(0,"FTP: Connected to %s:%s\n", name, port);
258     freeaddrinfo(res);
259     return (s);
260   }
261   freeaddrinfo(res);
262   return FTP_NOSUCHHOST;
263 }
264
265 /* Perform a login to the server. Pass the username and password and
266    put the connection into binary mode. This assumes a passwd is
267    always needed. Is this true? */
268
269 static int 
270 login(char * username, 
271       char *passwd, 
272       int s, 
273       char *msgbuf, 
274       unsigned msgbuflen,
275       ftp_printf_t ftp_printf) {
276   
277   int ret;
278
279   ret = command("USER",username,s,msgbuf,msgbuflen,ftp_printf);
280   if (ret != 3) {
281     ftp_printf(1,"FTP: User %s not accepted\n",username);
282     return (FTP_BADUSER);
283   }
284   
285   ret = command("PASS",passwd,s,msgbuf,msgbuflen,ftp_printf);
286   if (ret < 0) {
287     return (ret);
288   }
289   if (ret != 2) {
290     ftp_printf(1,"FTP: Login failed for User %s\n",username);
291     return (FTP_BADUSER);
292   }
293   
294   ftp_printf(0,"FTP: Login sucessfull\n");
295   
296   ret = command("TYPE","I",s,msgbuf,msgbuflen,ftp_printf);
297   if (ret < 0) {
298     return (ret);
299   }
300   if (ret != 2) {
301     ftp_printf(1,"FTP: TYPE failed!\n");
302     return (FTP_BAD);
303   }
304   return (ret);
305 }
306
307
308 /* Open a data socket. This is a client socket, i.e. its listening
309 waiting for the FTP server to connect to it. Once the socket has been
310 opened send the port command to the server so the server knows which
311 port we are listening on.*/
312 static int 
313 opendatasock(int ctrl_s,
314              struct sockaddr *ctrl, 
315              char *msgbuf, 
316              unsigned msgbuflen,
317              ftp_printf_t ftp_printf) 
318 {
319     struct sockaddr local;
320     char name[64];
321     char port[10];
322     socklen_t len;
323     int on = 1;
324     char buf[80];
325     int ret;
326     int s;
327
328     s = socket(ctrl->sa_family, SOCK_STREAM, 0);
329     if (s < 0) {
330         ftp_printf(1,"socket: %s\n",strerror(errno));
331         return FTP_BAD;
332     }
333   
334     if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on)) < 0) {
335         ftp_printf(1,"setsockopt: %s\n",strerror(errno));
336         close(s);
337         return FTP_BAD;
338     }
339   
340     memcpy(&local,ctrl,sizeof(struct sockaddr));
341     switch (ctrl->sa_family) {
342     case AF_INET: {
343         struct sockaddr_in * sa4 = (struct sockaddr_in *) &local;
344         sa4->sin_port = 0;
345         break;
346     }
347 #ifdef CYGPKG_NET_INET6
348     case AF_INET6: {
349         struct sockaddr_in6 * sa6 = (struct sockaddr_in6 *) &local;
350         sa6->sin6_port = 0;
351         break;
352     }
353 #endif
354     default:
355         close (s);
356         return FTP_BAD;
357     }
358
359     if (bind(s,&local,local.sa_len) < 0) {
360         ftp_printf(1,"bind: %s\n",strerror(errno));
361         close(s);
362         return FTP_BAD;   
363     }
364   
365     len = sizeof(local);
366     if (getsockname(s,&local,&len) < 0) {
367         ftp_printf(1,"getsockname: %s\n",strerror(errno));
368         close(s);
369         return FTP_BAD;   
370     }
371   
372     if (listen(s, 1) < 0) {
373         ftp_printf(1,"listen: %s\n",strerror(errno));
374         close(s);
375         return FTP_BAD;   
376     }
377   
378     getnameinfo(&local, sizeof(local), name, sizeof(name), port, sizeof(port),
379                 NI_NUMERICHOST|NI_NUMERICSERV);
380     switch (local.sa_family) {
381     case AF_INET: {
382         snprintf(buf, sizeof(buf), "|1|%s|%s|", name, port);
383         break;
384     }
385 #ifdef CYGPKG_NET_INET6
386     case AF_INET6: {
387         snprintf(buf, sizeof(buf), "|2|%s|%s|", name, port);
388         break;
389     }
390 #endif
391     default:
392         close (s);
393         return (FTP_BAD);
394     }
395
396     ret = command("EPRT",buf,ctrl_s,msgbuf,msgbuflen,ftp_printf);
397     if (ret < 0) {
398         close(s);
399         return (ret);
400     }
401
402     if (ret != 2) {
403         int _port = atoi(port);
404         char *str = name;
405         while (*str) {
406             if (*str == '.') *str = ',';
407             str++;
408         }
409         snprintf(buf, sizeof(buf), "%s,%d,%d", name, _port/256, _port%256);
410         ret = command("PORT",buf,ctrl_s,msgbuf,msgbuflen,ftp_printf);
411         if (ret < 0) {
412             close(s);
413             return (ret);
414         }
415         if (ret != 2) {
416             ftp_printf(1,"FTP: PORT failed!\n");
417             close(s);
418             return (FTP_BAD);
419         }
420     }
421     return (s);
422 }
423
424 /* Receive the file into the buffer and close the data socket
425    afterwards */
426 static int 
427 receive_file(int data_s, ftp_write_t ftp_write, void *ftp_write_priv, ftp_printf_t ftp_printf)
428 {
429     char *buf;
430     int finished = 0;
431     int total_size=0;
432     int len, wlen;
433     int s;
434
435     if ((buf = (char *)malloc(CYGNUM_NET_FTPCLIENT_BUFSIZE)) == (char *)0) {
436         return FTP_NOMEMORY;
437     }
438     s = accept(data_s, NULL, 0);
439     if (s < 0) {
440         ftp_printf(1, "listen: %s\n",strerror(errno));
441         free(buf);
442         return FTP_BAD;   
443     }
444   
445     do {
446         len = read(s, buf, CYGNUM_NET_FTPCLIENT_BUFSIZE);
447         if (len < 0) {
448             ftp_printf(1, "read: %s\n",strerror(errno));
449             close(s);
450             free(buf);
451             return FTP_BAD;   
452         }
453
454         if (len == 0) {
455             finished = 1;
456         } else {
457             wlen = (*ftp_write)(buf, len, ftp_write_priv);
458             if (wlen != len) {
459                 ftp_printf(1, "FTP: File too big!\n");
460                 close(s);
461                 free(buf);
462                 return FTP_TOOBIG;
463             }
464             total_size += len;
465         }
466     } while (!finished);
467   
468     close(s);
469     free(buf);
470     return total_size;
471 }
472
473 /* Receive the file into the buffer and close the socket afterwards*/
474 static int 
475 send_file(int data_s, ftp_read_t ftp_read, void *ftp_read_priv, ftp_printf_t ftp_printf)
476 {
477     char *buf;
478     int len, rlen;
479     int s;
480   
481     if ((buf = (char *)malloc(CYGNUM_NET_FTPCLIENT_BUFSIZE)) == (char *)0) {
482         return FTP_NOMEMORY;
483     }
484     s = accept(data_s,NULL,0);
485     if (s<0) {
486         ftp_printf(1,"listen: %s\n",strerror(errno));
487         free(buf);
488         return FTP_BAD;   
489     }
490   
491     do { 
492         rlen = (*ftp_read)(buf, CYGNUM_NET_FTPCLIENT_BUFSIZE, ftp_read_priv);
493         if (rlen > 0) {
494             len = write(s, buf, rlen);
495             if (len < 0) {
496                 ftp_printf(1,"write: %s\n",strerror(errno));
497                 close(s);
498                 free(buf);
499                 return FTP_BAD;   
500             }
501         }
502     } while (rlen > 0);
503   
504     close(s);
505     free(buf);
506     return 0;
507 }
508
509 /* All done, say bye, bye */
510 static int quit(int s, 
511                 char *msgbuf, 
512                 unsigned msgbuflen,
513                 ftp_printf_t ftp_printf) {
514   
515   int ret;
516   
517   ret = command("QUIT",NULL,s,msgbuf,msgbuflen,ftp_printf);
518   if (ret < 0) {
519     return (ret);
520   }
521   if (ret != 2) {
522     ftp_printf(1,"FTP: Quit failed!\n");
523     return (FTP_BAD);
524   }
525   
526   ftp_printf(0,"FTP: Connection closed\n");
527   return (0);
528 }
529
530 /* Get a file from an FTP server. Hostname is the name/IP address of
531    the server. username is the username used to connect to the server
532    with. Passwd is the password used to authentificate the
533    username. filename is the name of the file to receive. It should be
534    the full pathname of the file. buf is a pointer to a buffer the
535    contents of the file should be placed in and buf_size is the size
536    of the buffer. If the file is bigger than the buffer, buf_size
537    bytes will be retrieved and an error code returned. ftp_printf is a
538    function to be called to perform printing. On success the number of
539    bytes received is returned. On error a negative value is returned
540    indicating the type of error. */
541
542 struct _ftp_data{
543     char *buf;
544     int   len;
545     int   max_len;
546 };
547
548 static int _ftp_read(char *buf, int len, void *priv)
549 {
550     struct _ftp_data *dp = (struct _ftp_data *)priv;
551     int res = 0;
552
553     // FTP data channel desires to write 'len' bytes.  Fetch up
554     // to that amount into 'buf'
555     if (dp->len > 0) {
556         res = dp->len;
557         if (res > len) res = len;
558         memcpy(buf, dp->buf, res);
559         dp->buf += len;
560         dp->len -= res;
561     }
562     return res;
563 }
564
565 static int _ftp_write(char *buf, int len, void *priv)
566 {
567     struct _ftp_data *dp = (struct _ftp_data *)priv;
568     int res = 0;
569
570     // FTP data channel has 'len' bytes that have been read.
571     // Move into 'buf', respecting the max size of 'buf'
572     if (dp->len < dp->max_len) {
573         res = dp->max_len - dp->len;
574         if (res > len) {
575             res = len;
576         }
577         memcpy(dp->buf, buf, res);
578         dp->buf += len;
579         dp->len += res;
580     }
581     return res;
582 }
583
584 int ftp_get(char * hostname, 
585             char * username, 
586             char * passwd, 
587             char * filename, 
588             char * buf, 
589             unsigned buf_size,
590             ftp_printf_t ftp_printf)
591 {
592     struct _ftp_data ftp_data;
593
594     ftp_data.buf = buf;
595     ftp_data.len = 0;
596     ftp_data.max_len = buf_size;
597     return ftp_get_var(hostname, username, passwd, filename, _ftp_write, &ftp_data, ftp_printf);
598 }
599
600 int ftp_get_var(char *hostname,
601                 char *username,
602                 char *passwd,
603                 char *filename,
604                 ftp_write_t ftp_write,
605                 void *ftp_write_priv,
606                 ftp_printf_t ftp_printf)
607 {
608
609   struct sockaddr local;
610   char msgbuf[256];
611   int s,data_s;
612   int bytes;
613   int ret;
614   
615   s = connect_to_server(hostname,&local,ftp_printf);
616   if (s < 0) {
617     return (s);
618   }
619   
620   /* Read the welcome message from the server */
621   if (get_reply(s,ftp_printf) != 2) {
622     ftp_printf(0,"FTP: Server refused connection\n");
623     close(s);
624     return FTP_BAD;
625   }
626
627   ret = login(username,passwd,s,msgbuf,sizeof(msgbuf),ftp_printf);
628   if (ret < 0) {
629     close(s);
630     return (ret);
631   }
632
633   /* We are now logged in and ready to transfer the file. Open the
634      data socket ready to receive the file. It also build the PORT
635      command ready to send */
636   data_s = opendatasock(s,&local,msgbuf,sizeof(msgbuf),ftp_printf);
637   if (data_s < 0) {
638     close (s);
639     return (data_s);
640   }
641
642   /* Ask for the file */
643   ret = command("RETR",filename,s,msgbuf,sizeof(msgbuf),ftp_printf);
644   if (ret < 0) {
645     close(s);
646     close(data_s);
647     return (ret);
648   }
649   
650   if (ret != 1) {
651     ftp_printf(0,"FTP: RETR failed!\n");
652     close (data_s);
653     close(s);
654     return (FTP_BADFILENAME);
655   }
656   
657   if ((bytes=receive_file(data_s,ftp_write,ftp_write_priv,ftp_printf)) < 0) {
658     ftp_printf(0,"FTP: Receiving file failed\n");
659     close (data_s);
660     close(s);
661     return (bytes);
662   }
663   
664   if (get_reply(s,ftp_printf) != 2) {
665     ftp_printf(0,"FTP: Transfer failed!\n");
666     close (data_s);
667     close(s);
668     return (FTP_BAD);
669   }
670
671   ret = quit(s,msgbuf,sizeof(msgbuf),ftp_printf);
672   if (ret < 0) {
673     close(s);
674     close(data_s);
675     return (ret);
676   }
677             
678   close (data_s);
679   close(s);
680   return bytes;
681 }
682   
683 /* Put a file on an FTP server. Hostname is the name/IP address of the
684    server. username is the username used to connect to the server
685    with. Passwd is the password used to authentificate the
686    username. filename is the name of the file to receive. It should be
687    the full pathname of the file. buf is a pointer to a buffer the
688    contents of the file should be placed in and buf_size is the size
689    of the buffer. ftp_printf is a function to be called to perform
690    printing. On success 0 is returned. On error a negative value is
691    returned indicating the type of error. */
692
693 int ftp_put(char * hostname, 
694             char * username, 
695             char * passwd, 
696             char * filename, 
697             char * buf, 
698             unsigned buf_size,
699             ftp_printf_t ftp_printf)
700 {
701     struct _ftp_data ftp_data;
702
703     ftp_data.buf = buf;
704     ftp_data.len = buf_size;
705     return ftp_put_var(hostname, username, passwd, filename, _ftp_read, &ftp_data, ftp_printf);
706 }
707
708 int ftp_put_var(char *hostname,
709                 char *username,
710                 char *passwd,
711                 char *filename,
712                 ftp_read_t ftp_read,
713                 void *ftp_read_priv,
714                 ftp_printf_t ftp_printf)
715 {
716
717   struct sockaddr local;
718   char msgbuf[256];
719   int s,data_s;
720   int ret;
721   
722   s = connect_to_server(hostname,&local,ftp_printf);
723   if (s < 0) {
724     return (s);
725   }
726   
727   /* Read the welcome message from the server */
728   if (get_reply(s,ftp_printf) != 2) {
729     ftp_printf(1,"FTP: Server refused connection\n");
730     close(s);
731     return FTP_BAD;
732   }
733
734   ret = login(username,passwd,s,msgbuf,sizeof(msgbuf),ftp_printf);
735   if (ret < 0) {
736     close(s);
737     return (ret);
738   }
739
740   /* We are now logged in and ready to transfer the file. Open the
741      data socket ready to receive the file. It also build the PORT
742      command ready to send */
743   data_s = opendatasock(s,&local,msgbuf,sizeof(msgbuf),ftp_printf);
744   if (data_s < 0) {
745     close (s);
746     return (data_s);
747   }
748
749   /* Ask for the file */
750   ret = command("STOR",filename,s,msgbuf,sizeof(msgbuf),ftp_printf);
751   if (ret < 0) {
752     close(s);
753     close(data_s);
754     return (ret);
755   }
756   
757   if (ret != 1) {
758     ftp_printf(1,"FTP: STOR failed!\n");
759     close (data_s);
760     close(s);
761     return (FTP_BADFILENAME);
762   }
763   
764   if ((ret = send_file(data_s,ftp_read,ftp_read_priv,ftp_printf)) < 0) {
765     ftp_printf(1,"FTP: Sending file failed\n");
766     close (data_s);
767     close(s);
768     return (ret);
769   }
770   
771   if (get_reply(s,ftp_printf) != 2) {
772     ftp_printf(1,"FTP: Transfer failed!\n");
773     close (data_s);
774     close(s);
775     return (FTP_BAD);
776   }
777
778   ret = quit(s,msgbuf,sizeof(msgbuf),ftp_printf);
779   if (ret < 0) {
780     close(s);
781     close(data_s);
782     return (ret);
783   }
784             
785   close (data_s);
786   close(s);
787   return 0;
788 }
789
790 /* An example ftp_printf function. This uses the standard eCos diag
791 output device for outputting error and diagnostic messages. The
792 function take one addition parameter to the normal printf function. The
793 first parameter indicates when the message is an error message when
794 true. This can be used to filter errors from diagnostic output. In
795 this example the error parameter is ignored and everything printed. */
796
797 void ftpclient_printf(unsigned error, const char *fmt, ...) 
798 {
799   va_list ap;
800   
801   va_start(ap, fmt);
802   diag_vprintf( fmt, ap);
803   va_end(ap);
804 }