Moved protocols to proto/
[people/xl0/gpxe.git] / src / proto / nfs.c
1 #ifdef  DOWNLOAD_PROTO_NFS
2
3 #include "etherboot.h"
4 #include "nic.h"
5
6 /* NOTE: the NFS code is heavily inspired by the NetBSD netboot code (read:
7  * large portions are copied verbatim) as distributed in OSKit 0.97.  A few
8  * changes were necessary to adapt the code to Etherboot and to fix several
9  * inconsistencies.  Also the RPC message preparation is done "by hand" to
10  * avoid adding netsprintf() which I find hard to understand and use.  */
11
12 /* NOTE 2: Etherboot does not care about things beyond the kernel image, so
13  * it loads the kernel image off the boot server (ARP_SERVER) and does not
14  * access the client root disk (root-path in dhcpd.conf), which would use
15  * ARP_ROOTSERVER.  The root disk is something the operating system we are
16  * about to load needs to use.  This is different from the OSKit 0.97 logic.  */
17
18 /* NOTE 3: Symlink handling introduced by Anselm M Hoffmeister, 2003-July-14
19  * If a symlink is encountered, it is followed as far as possible (recursion
20  * possible, maximum 16 steps). There is no clearing of ".."'s inside the
21  * path, so please DON'T DO THAT. thx. */
22
23 #define START_OPORT 700         /* mountd usually insists on secure ports */
24 #define OPORT_SWEEP 200         /* make sure we don't leave secure range */
25
26 static int oport = START_OPORT;
27 static int mount_port = -1;
28 static int nfs_port = -1;
29 static int fs_mounted = 0;
30 static unsigned long rpc_id;
31
32 /**************************************************************************
33 RPC_INIT - set up the ID counter to something fairly random
34 **************************************************************************/
35 void rpc_init(void)
36 {
37         unsigned long t;
38
39         t = currticks();
40         rpc_id = t ^ (t << 8) ^ (t << 16);
41 }
42
43
44 /**************************************************************************
45 RPC_PRINTERROR - Print a low level RPC error message
46 **************************************************************************/
47 static void rpc_printerror(struct rpc_t *rpc)
48 {
49         if (rpc->u.reply.rstatus || rpc->u.reply.verifier ||
50             rpc->u.reply.astatus) {
51                 /* rpc_printerror() is called for any RPC related error,
52                  * suppress output if no low level RPC error happened.  */
53                 printf("RPC error: (%d,%d,%d)\n", ntohl(rpc->u.reply.rstatus),
54                         ntohl(rpc->u.reply.verifier),
55                         ntohl(rpc->u.reply.astatus));
56         }
57 }
58
59 /**************************************************************************
60 AWAIT_RPC - Wait for an rpc packet
61 **************************************************************************/
62 static int await_rpc(int ival, void *ptr,
63         unsigned short ptype, struct iphdr *ip, struct udphdr *udp)
64 {
65         struct rpc_t *rpc;
66         if (!udp) 
67                 return 0;
68         if (arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr)
69                 return 0;
70         if (ntohs(udp->dest) != ival)
71                 return 0;
72         if (nic.packetlen < ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr) + 8)
73                 return 0;
74         rpc = (struct rpc_t *)&nic.packet[ETH_HLEN];
75         if (*(unsigned long *)ptr != ntohl(rpc->u.reply.id))
76                 return 0;
77         if (MSG_REPLY != ntohl(rpc->u.reply.type))
78                 return 0;
79         return 1;
80 }
81
82 /**************************************************************************
83 RPC_LOOKUP - Lookup RPC Port numbers
84 **************************************************************************/
85 static int rpc_lookup(int addr, int prog, int ver, int sport)
86 {
87         struct rpc_t buf, *rpc;
88         unsigned long id;
89         int retries;
90         long *p;
91
92         id = rpc_id++;
93         buf.u.call.id = htonl(id);
94         buf.u.call.type = htonl(MSG_CALL);
95         buf.u.call.rpcvers = htonl(2);  /* use RPC version 2 */
96         buf.u.call.prog = htonl(PROG_PORTMAP);
97         buf.u.call.vers = htonl(2);     /* portmapper is version 2 */
98         buf.u.call.proc = htonl(PORTMAP_GETPORT);
99         p = (long *)buf.u.call.data;
100         *p++ = 0; *p++ = 0;                             /* auth credential */
101         *p++ = 0; *p++ = 0;                             /* auth verifier */
102         *p++ = htonl(prog);
103         *p++ = htonl(ver);
104         *p++ = htonl(IP_UDP);
105         *p++ = 0;
106         for (retries = 0; retries < MAX_RPC_RETRIES; retries++) {
107                 long timeout;
108                 udp_transmit(arptable[addr].ipaddr.s_addr, sport, SUNRPC_PORT,
109                         (char *)p - (char *)&buf, &buf);
110                 timeout = rfc2131_sleep_interval(TIMEOUT, retries);
111                 if (await_reply(await_rpc, sport, &id, timeout)) {
112                         rpc = (struct rpc_t *)&nic.packet[ETH_HLEN];
113                         if (rpc->u.reply.rstatus || rpc->u.reply.verifier ||
114                             rpc->u.reply.astatus) {
115                                 rpc_printerror(rpc);
116                                 return -1;
117                         } else {
118                                 return ntohl(rpc->u.reply.data[0]);
119                         }
120                 }
121         }
122         return -1;
123 }
124
125 /**************************************************************************
126 RPC_ADD_CREDENTIALS - Add RPC authentication/verifier entries
127 **************************************************************************/
128 static long *rpc_add_credentials(long *p)
129 {
130         int hl;
131
132         /* Here's the executive summary on authentication requirements of the
133          * various NFS server implementations:  Linux accepts both AUTH_NONE
134          * and AUTH_UNIX authentication (also accepts an empty hostname field
135          * in the AUTH_UNIX scheme).  *BSD refuses AUTH_NONE, but accepts
136          * AUTH_UNIX (also accepts an empty hostname field in the AUTH_UNIX
137          * scheme).  To be safe, use AUTH_UNIX and pass the hostname if we have
138          * it (if the BOOTP/DHCP reply didn't give one, just use an empty
139          * hostname).  */
140
141         hl = (hostnamelen + 3) & ~3;
142
143         /* Provide an AUTH_UNIX credential.  */
144         *p++ = htonl(1);                /* AUTH_UNIX */
145         *p++ = htonl(hl+20);            /* auth length */
146         *p++ = htonl(0);                /* stamp */
147         *p++ = htonl(hostnamelen);      /* hostname string */
148         if (hostnamelen & 3) {
149                 *(p + hostnamelen / 4) = 0; /* add zero padding */
150         }
151         memcpy(p, hostname, hostnamelen);
152         p += hl / 4;
153         *p++ = 0;                       /* uid */
154         *p++ = 0;                       /* gid */
155         *p++ = 0;                       /* auxiliary gid list */
156
157         /* Provide an AUTH_NONE verifier.  */
158         *p++ = 0;                       /* AUTH_NONE */
159         *p++ = 0;                       /* auth length */
160
161         return p;
162 }
163
164 /**************************************************************************
165 NFS_PRINTERROR - Print a NFS error message
166 **************************************************************************/
167 static void nfs_printerror(int err)
168 {
169         switch (-err) {
170         case NFSERR_PERM:
171                 printf("Not owner\n");
172                 break;
173         case NFSERR_NOENT:
174                 printf("No such file or directory\n");
175                 break;
176         case NFSERR_ACCES:
177                 printf("Permission denied\n");
178                 break;
179         case NFSERR_ISDIR:
180                 printf("Directory given where filename expected\n");
181                 break;
182         case NFSERR_INVAL:
183                 printf("Invalid filehandle\n");
184                 break; // INVAL is not defined in NFSv2, some NFS-servers
185                 // seem to use it in answers to v2 nevertheless.
186         case 9998:
187                 printf("low-level RPC failure (parameter decoding problem?)\n");
188                 break;
189         case 9999:
190                 printf("low-level RPC failure (authentication problem?)\n");
191                 break;
192         default:
193                 printf("Unknown NFS error %d\n", -err);
194         }
195 }
196
197 /**************************************************************************
198 NFS_MOUNT - Mount an NFS Filesystem
199 **************************************************************************/
200 static int nfs_mount(int server, int port, char *path, char *fh, int sport)
201 {
202         struct rpc_t buf, *rpc;
203         unsigned long id;
204         int retries;
205         long *p;
206         int pathlen = strlen(path);
207
208         id = rpc_id++;
209         buf.u.call.id = htonl(id);
210         buf.u.call.type = htonl(MSG_CALL);
211         buf.u.call.rpcvers = htonl(2);  /* use RPC version 2 */
212         buf.u.call.prog = htonl(PROG_MOUNT);
213         buf.u.call.vers = htonl(1);     /* mountd is version 1 */
214         buf.u.call.proc = htonl(MOUNT_ADDENTRY);
215         p = rpc_add_credentials((long *)buf.u.call.data);
216         *p++ = htonl(pathlen);
217         if (pathlen & 3) {
218                 *(p + pathlen / 4) = 0; /* add zero padding */
219         }
220         memcpy(p, path, pathlen);
221         p += (pathlen + 3) / 4;
222         for (retries = 0; retries < MAX_RPC_RETRIES; retries++) {
223                 long timeout;
224                 udp_transmit(arptable[server].ipaddr.s_addr, sport, port,
225                         (char *)p - (char *)&buf, &buf);
226                 timeout = rfc2131_sleep_interval(TIMEOUT, retries);
227                 if (await_reply(await_rpc, sport, &id, timeout)) {
228                         rpc = (struct rpc_t *)&nic.packet[ETH_HLEN];
229                         if (rpc->u.reply.rstatus || rpc->u.reply.verifier ||
230                             rpc->u.reply.astatus || rpc->u.reply.data[0]) {
231                                 rpc_printerror(rpc);
232                                 if (rpc->u.reply.rstatus) {
233                                         /* RPC failed, no verifier, data[0] */
234                                         return -9999;
235                                 }
236                                 if (rpc->u.reply.astatus) {
237                                         /* RPC couldn't decode parameters */
238                                         return -9998;
239                                 }
240                                 return -ntohl(rpc->u.reply.data[0]);
241                         } else {
242                                 fs_mounted = 1;
243                                 memcpy(fh, rpc->u.reply.data + 1, NFS_FHSIZE);
244                                 return 0;
245                         }
246                 }
247         }
248         return -1;
249 }
250
251 /**************************************************************************
252 NFS_UMOUNTALL - Unmount all our NFS Filesystems on the Server
253 **************************************************************************/
254 void nfs_umountall(int server)
255 {
256         struct rpc_t buf, *rpc;
257         unsigned long id;
258         int retries;
259         long *p;
260
261         if (!arptable[server].ipaddr.s_addr) {
262                 /* Haven't sent a single UDP packet to this server */
263                 return;
264         }
265         if ((mount_port == -1) || (!fs_mounted)) {
266                 /* Nothing mounted, nothing to umount */
267                 return;
268         }
269         id = rpc_id++;
270         buf.u.call.id = htonl(id);
271         buf.u.call.type = htonl(MSG_CALL);
272         buf.u.call.rpcvers = htonl(2);  /* use RPC version 2 */
273         buf.u.call.prog = htonl(PROG_MOUNT);
274         buf.u.call.vers = htonl(1);     /* mountd is version 1 */
275         buf.u.call.proc = htonl(MOUNT_UMOUNTALL);
276         p = rpc_add_credentials((long *)buf.u.call.data);
277         for (retries = 0; retries < MAX_RPC_RETRIES; retries++) {
278                 long timeout = rfc2131_sleep_interval(TIMEOUT, retries);
279                 udp_transmit(arptable[server].ipaddr.s_addr, oport, mount_port,
280                         (char *)p - (char *)&buf, &buf);
281                 if (await_reply(await_rpc, oport, &id, timeout)) {
282                         rpc = (struct rpc_t *)&nic.packet[ETH_HLEN];
283                         if (rpc->u.reply.rstatus || rpc->u.reply.verifier ||
284                             rpc->u.reply.astatus) {
285                                 rpc_printerror(rpc);
286                         }
287                         fs_mounted = 0;
288                         return;
289                 }
290         }
291 }
292 /***************************************************************************
293  * NFS_READLINK (AH 2003-07-14)
294  * This procedure is called when read of the first block fails -
295  * this probably happens when it's a directory or a symlink
296  * In case of successful readlink(), the dirname is manipulated,
297  * so that inside the nfs() function a recursion can be done.
298  **************************************************************************/
299 static int nfs_readlink(int server, int port, char *fh, char *path, char *nfh,
300         int sport)
301 {
302         struct rpc_t buf, *rpc;
303         unsigned long id;
304         long *p;
305         int retries;
306         int pathlen = strlen(path);
307
308         id = rpc_id++;
309         buf.u.call.id = htonl(id);
310         buf.u.call.type = htonl(MSG_CALL);
311         buf.u.call.rpcvers = htonl(2);  /* use RPC version 2 */
312         buf.u.call.prog = htonl(PROG_NFS);
313         buf.u.call.vers = htonl(2);     /* nfsd is version 2 */
314         buf.u.call.proc = htonl(NFS_READLINK);
315         p = rpc_add_credentials((long *)buf.u.call.data);
316         memcpy(p, nfh, NFS_FHSIZE);
317         p += (NFS_FHSIZE / 4);
318         for (retries = 0; retries < MAX_RPC_RETRIES; retries++) {
319                 long timeout = rfc2131_sleep_interval(TIMEOUT, retries);
320                 udp_transmit(arptable[server].ipaddr.s_addr, sport, port,
321                         (char *)p - (char *)&buf, &buf);
322                 if (await_reply(await_rpc, sport, &id, timeout)) {
323                         rpc = (struct rpc_t *)&nic.packet[ETH_HLEN];
324                         if (rpc->u.reply.rstatus || rpc->u.reply.verifier ||
325                             rpc->u.reply.astatus || rpc->u.reply.data[0]) {
326                                 rpc_printerror(rpc);
327                                 if (rpc->u.reply.rstatus) {
328                                         /* RPC failed, no verifier, data[0] */
329                                         return -9999;
330                                 }
331                                 if (rpc->u.reply.astatus) {
332                                         /* RPC couldn't decode parameters */
333                                         return -9998;
334                                 }
335                                 return -ntohl(rpc->u.reply.data[0]);
336                         } else {
337                                 // It *is* a link.
338                                 // If it's a relative link, append everything to dirname, filename TOO!
339                                 retries = strlen ( (char *)(&(rpc->u.reply.data[2]) ));
340                                 if ( *((char *)(&(rpc->u.reply.data[2]))) != '/' ) {
341                                         path[pathlen++] = '/';
342                                         while ( ( retries + pathlen ) > 298 ) {
343                                                 retries--;
344                                         }
345                                         if ( retries > 0 ) {
346                                                 memcpy(path + pathlen, &(rpc->u.reply.data[2]), retries + 1);
347                                         } else { retries = 0; }
348                                         path[pathlen + retries] = 0;
349                                 } else {
350                                         // Else make it the only path.
351                                         if ( retries > 298 ) { retries = 298; }
352                                         memcpy ( path, &(rpc->u.reply.data[2]), retries + 1 );
353                                         path[retries] = 0;
354                                 }
355                                 return 0;
356                         }
357                 }
358         }
359         return -1;
360 }
361 /**************************************************************************
362 NFS_LOOKUP - Lookup Pathname
363 **************************************************************************/
364 static int nfs_lookup(int server, int port, char *fh, char *path, char *nfh,
365         int sport)
366 {
367         struct rpc_t buf, *rpc;
368         unsigned long id;
369         long *p;
370         int retries;
371         int pathlen = strlen(path);
372
373         id = rpc_id++;
374         buf.u.call.id = htonl(id);
375         buf.u.call.type = htonl(MSG_CALL);
376         buf.u.call.rpcvers = htonl(2);  /* use RPC version 2 */
377         buf.u.call.prog = htonl(PROG_NFS);
378         buf.u.call.vers = htonl(2);     /* nfsd is version 2 */
379         buf.u.call.proc = htonl(NFS_LOOKUP);
380         p = rpc_add_credentials((long *)buf.u.call.data);
381         memcpy(p, fh, NFS_FHSIZE);
382         p += (NFS_FHSIZE / 4);
383         *p++ = htonl(pathlen);
384         if (pathlen & 3) {
385                 *(p + pathlen / 4) = 0; /* add zero padding */
386         }
387         memcpy(p, path, pathlen);
388         p += (pathlen + 3) / 4;
389         for (retries = 0; retries < MAX_RPC_RETRIES; retries++) {
390                 long timeout = rfc2131_sleep_interval(TIMEOUT, retries);
391                 udp_transmit(arptable[server].ipaddr.s_addr, sport, port,
392                         (char *)p - (char *)&buf, &buf);
393                 if (await_reply(await_rpc, sport, &id, timeout)) {
394                         rpc = (struct rpc_t *)&nic.packet[ETH_HLEN];
395                         if (rpc->u.reply.rstatus || rpc->u.reply.verifier ||
396                             rpc->u.reply.astatus || rpc->u.reply.data[0]) {
397                                 rpc_printerror(rpc);
398                                 if (rpc->u.reply.rstatus) {
399                                         /* RPC failed, no verifier, data[0] */
400                                         return -9999;
401                                 }
402                                 if (rpc->u.reply.astatus) {
403                                         /* RPC couldn't decode parameters */
404                                         return -9998;
405                                 }
406                                 return -ntohl(rpc->u.reply.data[0]);
407                         } else {
408                                 memcpy(nfh, rpc->u.reply.data + 1, NFS_FHSIZE);
409                                 return 0;
410                         }
411                 }
412         }
413         return -1;
414 }
415
416 /**************************************************************************
417 NFS_READ - Read File on NFS Server
418 **************************************************************************/
419 static int nfs_read(int server, int port, char *fh, int offset, int len,
420                     int sport)
421 {
422         struct rpc_t buf, *rpc;
423         unsigned long id;
424         int retries;
425         long *p;
426
427         static int tokens=0;
428         /*
429          * Try to implement something similar to a window protocol in
430          * terms of response to losses. On successful receive, increment
431          * the number of tokens by 1 (cap at 256). On failure, halve it.
432          * When the number of tokens is >= 2, use a very short timeout.
433          */
434
435         id = rpc_id++;
436         buf.u.call.id = htonl(id);
437         buf.u.call.type = htonl(MSG_CALL);
438         buf.u.call.rpcvers = htonl(2);  /* use RPC version 2 */
439         buf.u.call.prog = htonl(PROG_NFS);
440         buf.u.call.vers = htonl(2);     /* nfsd is version 2 */
441         buf.u.call.proc = htonl(NFS_READ);
442         p = rpc_add_credentials((long *)buf.u.call.data);
443         memcpy(p, fh, NFS_FHSIZE);
444         p += NFS_FHSIZE / 4;
445         *p++ = htonl(offset);
446         *p++ = htonl(len);
447         *p++ = 0;               /* unused parameter */
448         for (retries = 0; retries < MAX_RPC_RETRIES; retries++) {
449                 long timeout = rfc2131_sleep_interval(TIMEOUT, retries);
450                 if (tokens >= 2)
451                         timeout = TICKS_PER_SEC/2;
452
453                 udp_transmit(arptable[server].ipaddr.s_addr, sport, port,
454                         (char *)p - (char *)&buf, &buf);
455                 if (await_reply(await_rpc, sport, &id, timeout)) {
456                         if (tokens < 256)
457                                 tokens++;
458                         rpc = (struct rpc_t *)&nic.packet[ETH_HLEN];
459                         if (rpc->u.reply.rstatus || rpc->u.reply.verifier ||
460                             rpc->u.reply.astatus || rpc->u.reply.data[0]) {
461                                 rpc_printerror(rpc);
462                                 if (rpc->u.reply.rstatus) {
463                                         /* RPC failed, no verifier, data[0] */
464                                         return -9999;
465                                 }
466                                 if (rpc->u.reply.astatus) {
467                                         /* RPC couldn't decode parameters */
468                                         return -9998;
469                                 }
470                                 return -ntohl(rpc->u.reply.data[0]);
471                         } else {
472                                 return 0;
473                         }
474                 } else
475                         tokens >>= 1;
476         }
477         return -1;
478 }
479
480 /**************************************************************************
481 NFS - Download extended BOOTP data, or kernel image from NFS server
482 **************************************************************************/
483 int nfs(const char *name, int (*fnc)(unsigned char *, unsigned int, unsigned int, int))
484 {
485         static int recursion = 0;
486         int sport;
487         int err, namelen = strlen(name);
488         char dirname[300], *fname;
489         char dirfh[NFS_FHSIZE];         /* file handle of directory */
490         char filefh[NFS_FHSIZE];        /* file handle of kernel image */
491         unsigned int block;
492         int rlen, size, offs, len;
493         struct rpc_t *rpc;
494
495         rx_qdrain();
496
497         sport = oport++;
498         if (oport > START_OPORT+OPORT_SWEEP) {
499                 oport = START_OPORT;
500         }
501         if ( name != dirname ) {
502                 memcpy(dirname, name, namelen + 1);
503         }
504         recursion = 0;
505 nfssymlink:
506         if ( recursion > NFS_MAXLINKDEPTH ) {
507                 printf ( "\nRecursion: More than %d symlinks followed. Abort.\n", NFS_MAXLINKDEPTH );
508                 return  0;
509         }
510         recursion++;
511         fname = dirname + (namelen - 1);
512         while (fname >= dirname) {
513                 if (*fname == '/') {
514                         *fname = '\0';
515                         fname++;
516                         break;
517                 }
518                 fname--;
519         }
520         if (fname < dirname) {
521                 printf("can't parse file name %s\n", name);
522                 return 0;
523         }
524
525         if (mount_port == -1) {
526                 mount_port = rpc_lookup(ARP_SERVER, PROG_MOUNT, 1, sport);
527         }
528         if (nfs_port == -1) {
529                 nfs_port = rpc_lookup(ARP_SERVER, PROG_NFS, 2, sport);
530         }
531         if (nfs_port == -1 || mount_port == -1) {
532                 printf("can't get nfs/mount ports from portmapper\n");
533                 return 0;
534         }
535
536
537         err = nfs_mount(ARP_SERVER, mount_port, dirname, dirfh, sport);
538         if (err) {
539                 printf("mounting %s: ", dirname);
540                 nfs_printerror(err);
541                 /* just to be sure... */
542                 nfs_umountall(ARP_SERVER);
543                 return 0;
544         }
545
546         err = nfs_lookup(ARP_SERVER, nfs_port, dirfh, fname, filefh, sport);
547         if (err) {
548                 printf("looking up %s: ", fname);
549                 nfs_printerror(err);
550                 nfs_umountall(ARP_SERVER);
551                 return 0;
552         }
553
554         offs = 0;
555         block = 1;      /* blocks are numbered starting from 1 */
556         size = -1;      /* will be set properly with the first reply */
557         len = NFS_READ_SIZE;    /* first request is always full size */
558         do {
559                 err = nfs_read(ARP_SERVER, nfs_port, filefh, offs, len, sport);
560                 if ((err <= -NFSERR_ISDIR)&&(err >= -NFSERR_INVAL) && (offs == 0)) {
561                         // An error occured. NFS servers tend to sending
562                         // errors 21 / 22 when symlink instead of real file
563                         // is requested. So check if it's a symlink!
564                         block = nfs_readlink(ARP_SERVER, nfs_port, dirfh, dirname,
565                                         filefh, sport);
566                         if ( 0 == block ) {
567                                 printf("\nLoading symlink:%s ..",dirname);
568                                 goto nfssymlink;
569                         }
570                         nfs_printerror(err);
571                         nfs_umountall(ARP_SERVER);
572                         return 0;
573                 }
574                 if (err) {
575                         printf("reading at offset %d: ", offs);
576                         nfs_printerror(err);
577                         nfs_umountall(ARP_SERVER);
578                         return 0;
579                 }
580
581                 rpc = (struct rpc_t *)&nic.packet[ETH_HLEN];
582
583                 /* size must be found out early to allow EOF detection */
584                 if (size == -1) {
585                         size = ntohl(rpc->u.reply.data[6]);
586                 }
587                 rlen = ntohl(rpc->u.reply.data[18]);
588                 if (rlen > len) {
589                         rlen = len;     /* shouldn't happen...  */
590                 }
591
592                 err = fnc((char *)&rpc->u.reply.data[19], block, rlen,
593                         (offs+rlen == size));
594                 if (err <= 0) {
595                         nfs_umountall(ARP_SERVER);
596                         return err;
597                 }
598
599                 block++;
600                 offs += rlen;
601                 /* last request is done with matching requested read size */
602                 if (size-offs < NFS_READ_SIZE) {
603                         len = size-offs;
604                 }
605         } while (len != 0);
606         /* len == 0 means that all the file has been read */
607         return 1;
608 }
609
610 #endif  /* DOWNLOAD_PROTO_NFS */