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