Add snprintf and vsnprintf.
[dragonfly.git] / lib / libstand / tftp.c
1 /*      $NetBSD: tftp.c,v 1.4 1997/09/17 16:57:07 drochner Exp $         */
2
3 /*
4  * Copyright (c) 1996
5  *      Matthias Drochner.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *      This product includes software developed for the NetBSD Project
18  *      by Matthias Drochner.
19  * 4. The name of the author may not be used to endorse or promote products
20  *    derived from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  *
33  * $FreeBSD: src/lib/libstand/tftp.c,v 1.2.6.4 2001/07/14 14:00:03 mikeh Exp $
34  * $DragonFly: src/lib/libstand/tftp.c,v 1.4 2004/10/25 19:38:45 drhodus Exp $
35  */
36
37 /*
38  * Simple TFTP implementation for libsa.
39  * Assumes:
40  *  - socket descriptor (int) at open_file->f_devdata
41  *  - server host IP in global servip
42  * Restrictions:
43  *  - read only
44  *  - lseek only with SEEK_SET or SEEK_CUR
45  *  - no big time differences between transfers (<tftp timeout)
46  */
47
48 #include <sys/param.h>
49 #include <sys/stat.h>
50 #include <netinet/in.h>
51 #include <netinet/udp.h>
52 #include <netinet/in_systm.h>
53 #include <arpa/tftp.h>
54
55 #include <string.h>
56
57 #include "stand.h"
58 #include "net.h"
59 #include "netif.h"
60
61 #include "tftp.h"
62
63 static int      tftp_open(const char *path, struct open_file *f);
64 static int      tftp_close(struct open_file *f);
65 static int      tftp_read(struct open_file *f, void *buf, size_t size, size_t *resid);
66 static int      tftp_write(struct open_file *f, void *buf, size_t size, size_t *resid);
67 static off_t    tftp_seek(struct open_file *f, off_t offset, int where);
68 static int      tftp_stat(struct open_file *f, struct stat *sb);
69
70 struct fs_ops tftp_fsops = {
71         "tftp",
72         tftp_open,
73         tftp_close,
74         tftp_read,
75         tftp_write,
76         tftp_seek,
77         tftp_stat,
78         null_readdir
79 };
80
81 extern struct in_addr servip;
82
83 static int      tftpport = 2000;
84
85 #define RSPACE 520              /* max data packet, rounded up */
86
87 struct tftp_handle {
88         struct iodesc  *iodesc;
89         int             currblock;      /* contents of lastdata */
90         int             islastblock;    /* flag */
91         int             validsize;
92         int             off;
93         char           *path;   /* saved for re-requests */
94         struct {
95                 u_char header[HEADER_SIZE];
96                 struct tftphdr t;
97                 u_char space[RSPACE];
98         } lastdata;
99 };
100
101 static int tftperrors[8] = {
102         0,                      /* ??? */
103         ENOENT,
104         EPERM,
105         ENOSPC,
106         EINVAL,                 /* ??? */
107         EINVAL,                 /* ??? */
108         EEXIST,
109         EINVAL                  /* ??? */
110 };
111
112 static ssize_t 
113 recvtftp(d, pkt, len, tleft)
114         struct iodesc *d;
115         void  *pkt;
116         ssize_t len;
117         time_t          tleft;
118 {
119         struct tftphdr *t;
120
121         errno = 0;
122
123         len = readudp(d, pkt, len, tleft);
124
125         if (len < 4)
126                 return (-1);
127
128         t = (struct tftphdr *) pkt;
129         switch (ntohs(t->th_opcode)) {
130         case DATA: {
131                 int got;
132
133                 if (htons(t->th_block) != d->xid) {
134                         /*
135                          * Expected block?
136                          */
137                         return (-1);
138                 }
139                 if (d->xid == 1) {
140                         /*
141                          * First data packet from new port.
142                          */
143                         struct udphdr *uh;
144                         uh = (struct udphdr *) pkt - 1;
145                         d->destport = uh->uh_sport;
146                 } /* else check uh_sport has not changed??? */
147                 got = len - (t->th_data - (char *) t);
148                 return got;
149         }
150         case ERROR:
151                 if ((unsigned) ntohs(t->th_code) >= 8) {
152                         printf("illegal tftp error %d\n", ntohs(t->th_code));
153                         errno = EIO;
154                 } else {
155 #ifdef DEBUG
156                         printf("tftp-error %d\n", ntohs(t->th_code));
157 #endif
158                         errno = tftperrors[ntohs(t->th_code)];
159                 }
160                 return (-1);
161         default:
162 #ifdef DEBUG
163                 printf("tftp type %d not handled\n", ntohs(t->th_opcode));
164 #endif
165                 return (-1);
166         }
167 }
168
169 /* send request, expect first block (or error) */
170 static int 
171 tftp_makereq(h)
172         struct tftp_handle *h;
173 {
174         struct {
175                 u_char header[HEADER_SIZE];
176                 struct tftphdr  t;
177                 u_char space[FNAME_SIZE + 6];
178         } wbuf;
179         char           *wtail;
180         int             l;
181         ssize_t         res;
182         struct tftphdr *t;
183
184         wbuf.t.th_opcode = htons((u_short) RRQ);
185         wtail = wbuf.t.th_stuff;
186         l = strlen(h->path);
187         bcopy(h->path, wtail, l + 1);
188         wtail += l + 1;
189         bcopy("octet", wtail, 6);
190         wtail += 6;
191
192         t = &h->lastdata.t;
193
194         /* h->iodesc->myport = htons(--tftpport); */
195         h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff));
196         h->iodesc->destport = htons(IPPORT_TFTP);
197         h->iodesc->xid = 1;     /* expected block */
198
199         res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *) &wbuf.t,
200                        recvtftp, t, sizeof(*t) + RSPACE);
201
202         if (res == -1)
203                 return (errno);
204
205         h->currblock = 1;
206         h->validsize = res;
207         h->islastblock = 0;
208         if (res < SEGSIZE)
209                 h->islastblock = 1;     /* very short file */
210         return (0);
211 }
212
213 /* ack block, expect next */
214 static int 
215 tftp_getnextblock(h)
216         struct tftp_handle *h;
217 {
218         struct {
219                 u_char header[HEADER_SIZE];
220                 struct tftphdr t;
221         } wbuf;
222         char           *wtail;
223         int             res;
224         struct tftphdr *t;
225
226         wbuf.t.th_opcode = htons((u_short) ACK);
227         wtail = (char *) &wbuf.t.th_block;
228         wbuf.t.th_block = htons((u_short) h->currblock);
229         wtail += 2;
230
231         t = &h->lastdata.t;
232
233         h->iodesc->xid = h->currblock + 1;      /* expected block */
234
235         res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *) &wbuf.t,
236                        recvtftp, t, sizeof(*t) + RSPACE);
237
238         if (res == -1)          /* 0 is OK! */
239                 return (errno);
240
241         h->currblock++;
242         h->validsize = res;
243         if (res < SEGSIZE)
244                 h->islastblock = 1;     /* EOF */
245         return (0);
246 }
247
248 static int 
249 tftp_open(path, f)
250         const char *path;
251         struct open_file *f;
252 {
253         struct tftp_handle *tftpfile;
254         struct iodesc  *io;
255         int             res;
256
257         tftpfile = (struct tftp_handle *) malloc(sizeof(*tftpfile));
258         if (!tftpfile)
259                 return (ENOMEM);
260
261         tftpfile->iodesc = io = socktodesc(*(int *) (f->f_devdata));
262         if (io == NULL)
263                 return (EINVAL);
264
265         io->destip = servip;
266         tftpfile->off = 0;
267         tftpfile->path = strdup(path);
268         if (tftpfile->path == NULL) {
269             free(tftpfile);
270             return(ENOMEM);
271         }
272
273         res = tftp_makereq(tftpfile, path);
274
275         if (res) {
276                 free(tftpfile->path);
277                 free(tftpfile);
278                 return (res);
279         }
280         f->f_fsdata = (void *) tftpfile;
281         return (0);
282 }
283
284 static int 
285 tftp_read(f, addr, size, resid)
286         struct open_file *f;
287         void           *addr;
288         size_t          size;
289         size_t         *resid;  /* out */
290 {
291         struct tftp_handle *tftpfile;
292         static int      tc = 0;
293         tftpfile = (struct tftp_handle *) f->f_fsdata;
294
295         while (size > 0) {
296                 int needblock, count;
297
298                 if (!(tc++ % 16))
299                         twiddle();
300
301                 needblock = tftpfile->off / SEGSIZE + 1;
302
303                 if (tftpfile->currblock > needblock)    /* seek backwards */
304                         tftp_makereq(tftpfile); /* no error check, it worked
305                                                  * for open */
306
307                 while (tftpfile->currblock < needblock) {
308                         int res;
309
310                         res = tftp_getnextblock(tftpfile);
311                         if (res) {      /* no answer */
312 #ifdef DEBUG
313                                 printf("tftp: read error\n");
314 #endif
315                                 return (res);
316                         }
317                         if (tftpfile->islastblock)
318                                 break;
319                 }
320
321                 if (tftpfile->currblock == needblock) {
322                         int offinblock, inbuffer;
323
324                         offinblock = tftpfile->off % SEGSIZE;
325
326                         inbuffer = tftpfile->validsize - offinblock;
327                         if (inbuffer < 0) {
328 #ifdef DEBUG
329                                 printf("tftp: invalid offset %d\n",
330                                     tftpfile->off);
331 #endif
332                                 return (EINVAL);
333                         }
334                         count = (size < inbuffer ? size : inbuffer);
335                         bcopy(tftpfile->lastdata.t.th_data + offinblock,
336                             addr, count);
337
338                         addr += count;
339                         tftpfile->off += count;
340                         size -= count;
341
342                         if ((tftpfile->islastblock) && (count == inbuffer))
343                                 break;  /* EOF */
344                 } else {
345 #ifdef DEBUG
346                         printf("tftp: block %d not found\n", needblock);
347 #endif
348                         return (EINVAL);
349                 }
350
351         }
352
353         if (resid)
354                 *resid = size;
355         return (0);
356 }
357
358 static int 
359 tftp_close(f)
360         struct open_file *f;
361 {
362         struct tftp_handle *tftpfile;
363         tftpfile = (struct tftp_handle *) f->f_fsdata;
364
365         /* let it time out ... */
366
367         if (tftpfile) {
368                 free(tftpfile->path);
369                 free(tftpfile);
370         }
371         return (0);
372 }
373
374 static int 
375 tftp_write(f, start, size, resid)
376         struct open_file *f;
377         void           *start;
378         size_t          size;
379         size_t         *resid;  /* out */
380 {
381         return (EROFS);
382 }
383
384 static int 
385 tftp_stat(f, sb)
386         struct open_file *f;
387         struct stat    *sb;
388 {
389         struct tftp_handle *tftpfile;
390         tftpfile = (struct tftp_handle *) f->f_fsdata;
391
392         sb->st_mode = 0444 | S_IFREG;
393         sb->st_nlink = 1;
394         sb->st_uid = 0;
395         sb->st_gid = 0;
396         sb->st_size = -1;
397         return (0);
398 }
399
400 static off_t 
401 tftp_seek(f, offset, where)
402         struct open_file *f;
403         off_t           offset;
404         int             where;
405 {
406         struct tftp_handle *tftpfile;
407         tftpfile = (struct tftp_handle *) f->f_fsdata;
408
409         switch (where) {
410         case SEEK_SET:
411                 tftpfile->off = offset;
412                 break;
413         case SEEK_CUR:
414                 tftpfile->off += offset;
415                 break;
416         default:
417                 errno = EOFFSET;
418                 return (-1);
419         }
420         return (tftpfile->off);
421 }