home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume21 / amd / part09 / nfs_ops.c < prev    next >
C/C++ Source or Header  |  1990-04-10  |  15KB  |  653 lines

  1. /*
  2.  * $Id: nfs_ops.c,v 5.1.1.2 90/01/11 17:12:34 jsp Exp Locker: jsp $
  3.  *
  4.  * Copyright (c) 1990 Jan-Simon Pendry
  5.  * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
  6.  * Copyright (c) 1990 The Regents of the University of California.
  7.  * All rights reserved.
  8.  *
  9.  * This code is derived from software contributed to Berkeley by
  10.  * Jan-Simon Pendry at Imperial College, London.
  11.  *
  12.  * Redistribution and use in source and binary forms are permitted
  13.  * provided that the above copyright notice and this paragraph are
  14.  * duplicated in all such forms and that any documentation,
  15.  * advertising materials, and other materials related to such
  16.  * distribution and use acknowledge that the software was developed
  17.  * by Imperial College of Science, Technology and Medicine, London, UK.
  18.  * The names of the College and University may not be used to endorse
  19.  * or promote products derived from this software without specific
  20.  * prior written permission.
  21.  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
  22.  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
  23.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  24.  *
  25.  *    %W% (Berkeley) %G%
  26.  */
  27.  
  28. #include "am.h"
  29.  
  30. #ifdef HAS_NFS
  31.  
  32. #define NFS
  33. #define NFSCLIENT
  34. #ifdef NFS_3
  35. typedef nfs_fh fhandle_t;
  36. #endif
  37. #ifdef NFS_HDR
  38. #include NFS_HDR
  39. #endif
  40. #include <sys/mount.h>
  41. #include "mount.h"
  42.  
  43. /*
  44.  * Network file system
  45.  */
  46.  
  47. /*
  48.  * Convert from nfsstat to UN*X error code
  49.  */
  50. #define unx_error(e)    ((int)(e))
  51.  
  52. /*
  53.  * The NFS layer maintains a cache of file handles.
  54.  * This is *fundamental* to the implementation and
  55.  * also allows quick remounting when a filesystem
  56.  * is accessed soon after timing out.
  57.  *
  58.  * The NFS server layer knows to flush this cache
  59.  * when a server goes down so avoiding stale handles.
  60.  *
  61.  * Each cache entry keeps a hard reference to
  62.  * the corresponding server.  This ensures that
  63.  * the server keepalive information is maintained.
  64.  *
  65.  * The copy of the sockaddr_in here is taken so
  66.  * that the port can be twiddled to talk to mountd
  67.  * instead of portmap or the NFS server as used
  68.  * elsewhere.
  69.  * The port# is flushed if a server goes down.
  70.  * The IP address is never flushed - we assume
  71.  * that the address of a mounted machine never
  72.  * changes.  If it does, then you have other
  73.  * problems...
  74.  */
  75. typedef struct fh_cache fh_cache;
  76. struct fh_cache {
  77.     qelem    fh_q;            /* List header */
  78.     voidp    fh_wchan;        /* Wait channel */
  79.     int    fh_error;        /* Valid data? */
  80.     int    fh_id;            /* Unique id */
  81.     int    fh_cid;            /* Callout id */
  82.     struct fhstatus fh_handle;    /* Handle on filesystem */
  83.     struct sockaddr_in fh_sin;    /* Address of mountd */
  84.     fserver *fh_fs;            /* Server holding filesystem */
  85.     char    *fh_path;        /* Filesystem on host */
  86. };
  87.  
  88. /*
  89.  * FH_TTL is the time a file handle will remain in the cache since
  90.  * last being used.  If the file handle becomes invalid, then it
  91.  * will be flushed anyway.
  92.  */
  93. #define    FH_TTL        (5 * 60)        /* five minutes */
  94. #define    FH_TTL_ERROR    (30)            /* 30 seconds */
  95.  
  96. static int fh_id = 0;
  97. #define    FHID_ALLOC()    (++fh_id)
  98. extern qelem fh_head;
  99. qelem fh_head = { &fh_head, &fh_head };
  100.  
  101. static int call_mountd P((fh_cache*, unsigned long, fwd_fun, voidp));
  102.  
  103. AUTH *nfs_auth;
  104.  
  105. static fh_cache *find_fhandle_cache P((voidp idv, int done));
  106. static fh_cache *find_fhandle_cache(idv, done)
  107. voidp idv;
  108. int done;
  109. {
  110.     fh_cache *fp, *fp2 = 0;
  111.     int id = (int) idv;
  112.  
  113.     ITER(fp, fh_cache, &fh_head) {
  114.         if (fp->fh_id == id) {
  115.             fp2 = fp;
  116.             break;
  117.         }
  118.     }
  119.  
  120. #ifdef DEBUG
  121.     if (fp2) {
  122.         dlog("fh cache gives fp %#x, fs %s", fp2, fp2->fh_path);
  123.     } else {
  124.         dlog("fh cache search failed");
  125.     }
  126. #endif
  127.  
  128.     if (fp2 && !done) {
  129.         fp2->fh_error = ETIMEDOUT;
  130.         return 0;
  131.     }
  132.  
  133.     return fp2;
  134. }
  135.  
  136. /*
  137.  * Called when a filehandle appears
  138.  */
  139. static void got_nfs_fh P((voidp pkt, int len, struct sockaddr_in *sa,
  140.                 struct sockaddr_in *ia, voidp idv, int done));
  141. static void got_nfs_fh(pkt, len, sa, ia, idv, done)
  142. voidp pkt;
  143. int len;
  144. struct sockaddr_in *sa, *ia;
  145. voidp idv;
  146. int done;
  147. {
  148.     fh_cache *fp = find_fhandle_cache(idv, done);
  149.     if (fp) {
  150.         fp->fh_error = pickup_rpc_reply(pkt, len, (voidp) &fp->fh_handle, xdr_fhstatus);
  151.         if (!fp->fh_error) {
  152. #ifdef DEBUG
  153.             dlog("got filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
  154. #endif
  155.             /*
  156.              * Wakeup anything sleeping on this filehandle
  157.              */
  158.             if (fp->fh_wchan) {
  159. #ifdef DEBUG
  160.                 dlog("Calling wakeup on %#x", fp->fh_wchan);
  161. #endif
  162.                 wakeup(fp->fh_wchan);
  163.             }
  164.         }
  165.     }
  166. }
  167.  
  168. void flush_fhandle_cache P((fserver *fs));
  169. void flush_fhandle_cache(fs)
  170. fserver *fs;
  171. {
  172.     fh_cache *fp;
  173.     ITER(fp, fh_cache, &fh_head) {
  174.         if (fp->fh_fs == fs) {
  175.             fp->fh_sin.sin_port = (u_short) 0;
  176.             fp->fh_error = -1;
  177.         }
  178.     }
  179. }
  180.  
  181. static void discard_fh P((fh_cache *fp));
  182. static void discard_fh(fp)
  183. fh_cache *fp;
  184. {
  185.     rem_que(&fp->fh_q);
  186. #ifdef DEBUG
  187.     dlog("Discarding filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
  188. #endif
  189.     free_srvr(fp->fh_fs);
  190.     free(fp->fh_path);
  191.     free(fp);
  192. }
  193.  
  194. /*
  195.  * Determine the file handle for a node
  196.  */
  197. static int prime_fhandle_cache P((char *path, fserver *fs, struct fhstatus *fhbuf, voidp wchan));
  198. static int prime_fhandle_cache(path, fs, fhbuf, wchan)
  199. char *path;
  200. fserver *fs;
  201. struct fhstatus *fhbuf;
  202. voidp wchan;
  203. {
  204.     fh_cache *fp, *fp_save = 0;
  205.     int error;
  206.     int reuse_id = FALSE;
  207.  
  208. #ifdef DEBUG
  209.     dlog("Searching cache for %s:%s", fs->fs_host, path);
  210. #endif
  211.  
  212.     /*
  213.      * First search the cache
  214.      */
  215.     ITER(fp, fh_cache, &fh_head) {
  216.         if (fs == fp->fh_fs && strcmp(path, fp->fh_path) == 0) {
  217.             switch (fp->fh_error) {
  218.             case 0:
  219.                 error = fp->fh_error = unx_error(fp->fh_handle.fhs_status);
  220.                 if (error == 0) {
  221.                     if (fhbuf)
  222.                         bcopy((voidp) &fp->fh_handle, (voidp) fhbuf,
  223.                             sizeof(fp->fh_handle));
  224.                     if (fp->fh_cid)
  225.                         untimeout(fp->fh_cid);
  226.                     fp->fh_cid = timeout(FH_TTL, discard_fh, (voidp) fp);
  227.                 } else if (error == EACCES) {
  228.                     /*
  229.                      * Now decode the file handle return code.
  230.                      */
  231.                     plog(XLOG_INFO, "Filehandle denied for \"%s:%s\"",
  232.                         fs->fs_host, path);
  233.                 } else {
  234.                     errno = error;    /* XXX */
  235.                     plog(XLOG_INFO, "Filehandle error for \"%s:%s\": %m",
  236.                         fs->fs_host, path);
  237.                 }
  238.  
  239.                 /*
  240.                  * The error was returned from the remote mount daemon.
  241.                  * Policy: this error will be cached for now...
  242.                  */
  243.                 return error;
  244.  
  245.             case -1:
  246.                 /*
  247.                  * Still thinking about it, but we can re-use.
  248.                  */
  249.                 fp_save = fp;
  250.                 reuse_id = TRUE;
  251.                 break;
  252.  
  253.             default:
  254.                 /*
  255.                  * Return the error.
  256.                  * Policy: make sure we recompute if required again
  257.                  * in case this was caused by a network failure.
  258.                  * This can thrash mountd's though...  If you find
  259.                  * your mountd going slowly then:
  260.                  * 1.  Add a fork() loop to main.
  261.                  * 2.  Remove the call to innetgr() and don't use
  262.                  *     netgroups, especially if you don't use YP.
  263.                  */
  264.                 error = fp->fh_error;
  265.                 fp->fh_error = -1;
  266.                 return error;
  267.             }
  268.             break;
  269.         }
  270.     }
  271.  
  272.     /*
  273.      * Not in cache
  274.      */
  275.     if (fp_save) {
  276.         fp = fp_save;
  277.         /*
  278.          * Re-use existing slot
  279.          */
  280.         untimeout(fp->fh_cid);
  281.         free_srvr(fp->fh_fs);
  282.         free(fp->fh_path);
  283.     } else {
  284.         fp = ALLOC(fh_cache);
  285.         bzero((voidp) fp, sizeof(*fp));
  286.         ins_que(&fp->fh_q, &fh_head);
  287.     }
  288.     if (!reuse_id)
  289.         fp->fh_id = FHID_ALLOC();
  290.     fp->fh_wchan = wchan;
  291.     fp->fh_error = -1;
  292.     fp->fh_cid = timeout(FH_TTL, discard_fh, (voidp) fp);
  293.  
  294.     /*
  295.      * If the address has changed then don't try to re-use the
  296.      * port information
  297.      */
  298.     if (fp->fh_sin.sin_addr.s_addr != fs->fs_ip->sin_addr.s_addr) {
  299.         fp->fh_sin = *fs->fs_ip;
  300.         fp->fh_sin.sin_port = 0;
  301.     }
  302.     fp->fh_fs = dup_srvr(fs);
  303.     fp->fh_path = strdup(path);
  304.  
  305.     error = call_mountd(fp, MOUNTPROC_MNT, got_nfs_fh, wchan);
  306.     if (error) {
  307.         /*
  308.          * Local error - cache for a short period
  309.          * just to prevent thrashing.
  310.          */
  311.         untimeout(fp->fh_cid);
  312.         fp->fh_cid = timeout(error < 0 ? 2 * ALLOWED_MOUNT_TIME : FH_TTL_ERROR,
  313.                         discard_fh, (voidp) fp);
  314.         fp->fh_error = error;
  315.     } else {
  316.         error = fp->fh_error;
  317.     }
  318.     return error;
  319. }
  320.  
  321. static int call_mountd P((fh_cache *fp, u_long proc, fwd_fun f, voidp wchan));
  322. static int call_mountd(fp, proc, f, wchan)
  323. fh_cache *fp;
  324. u_long proc;
  325. fwd_fun f;
  326. voidp wchan;
  327. {
  328.     struct rpc_msg mnt_msg;
  329.     int len;
  330.     char iobuf[8192];
  331.     int error;
  332.  
  333.     if (!nfs_auth) {
  334.         nfs_auth = authunix_create_default();
  335.         if (!nfs_auth)
  336.             return ENOBUFS;
  337.     }
  338.  
  339.     if (fp->fh_sin.sin_port == 0) {
  340.         u_short port;
  341.         error = nfs_srvr_port(fp->fh_fs, &port, wchan);
  342.         if (error)
  343.             return error;
  344.         fp->fh_sin.sin_port = port;
  345.     }
  346.  
  347.     rpc_msg_init(&mnt_msg, MOUNTPROG, MOUNTVERS, (unsigned long) 0);
  348.     len = make_rpc_packet(iobuf, sizeof(iobuf), proc,
  349.             &mnt_msg, (voidp) &fp->fh_path, xdr_nfspath,  nfs_auth);
  350.  
  351.     if (len > 0) {
  352.         error = fwd_packet(MK_RPC_XID(RPC_XID_MOUNTD, fp->fh_id),
  353.             (voidp) iobuf, len, &fp->fh_sin, &fp->fh_sin, (voidp) fp->fh_id, f);
  354.     } else {
  355.         error = -len;
  356.     }
  357.     return error;
  358. }
  359.  
  360. /*-------------------------------------------------------------------------*/
  361.  
  362. /*
  363.  * NFS needs the local filesystem, remote filesystem
  364.  * remote hostname.
  365.  * Local filesystem defaults to remote and vice-versa.
  366.  */
  367. static int nfs_match(fo)
  368. am_opts *fo;
  369. {
  370.     if (fo->opt_fs && !fo->opt_rfs)
  371.         fo->opt_rfs = fo->opt_fs;
  372.     if (!fo->opt_rfs) {
  373.         plog(XLOG_USER, "nfs: no remote filesystem specified");
  374.         return 0;
  375.     }
  376.     if (!fo->opt_rhost) {
  377.         plog(XLOG_USER, "nfs: no remote host specified");
  378.         return 0;
  379.     }
  380.     /*
  381.      * Determine magic cookie to put in mtab
  382.      */
  383.     fo->fs_mtab = (char *) xrealloc(fo->fs_mtab, strlen(fo->opt_rhost) +
  384.                 strlen(fo->opt_rfs) + 2);
  385.     sprintf(fo->fs_mtab, "%s:%s", fo->opt_rhost, fo->opt_rfs);
  386. #ifdef DEBUG
  387.     dlog("NFS: mounting remote server \"%s\", remote fs \"%s\" on \"%s\"",
  388.         fo->opt_rhost, fo->opt_rfs, fo->opt_fs);
  389. #endif
  390.  
  391.     return 1;
  392. }
  393.  
  394. /*
  395.  * Initialise am structure for nfs
  396.  */
  397. static int nfs_init(mf)
  398. mntfs *mf;
  399. {
  400.     int error;
  401.     char *colon = strchr(mf->mf_info, ':');
  402.     if (colon == 0)
  403.         return ENOENT;
  404.  
  405.     error = prime_fhandle_cache(colon+1, mf->mf_server, (struct fhstatus *) 0, (voidp) mf);
  406.  
  407.     return error;
  408. }
  409.  
  410. static mount_nfs(dir, fs_name, opts, mf)
  411. char *dir;
  412. char *fs_name;
  413. char *opts;
  414. mntfs *mf;
  415. {
  416.     struct nfs_args nfs_args;
  417.     struct mntent mnt;
  418.     int retry;
  419.     struct fhstatus fhs;
  420.     int error;
  421.     char *colon;
  422.     char *path;
  423.     char host[MAXHOSTNAMELEN + MAXPATHLEN + 2];
  424.     fserver *fs = mf->mf_server;
  425.     int flags;
  426. #ifdef notdef
  427.     unsigned short port;
  428. #endif
  429.  
  430.     MTYPE_TYPE type = MOUNT_TYPE_NFS;
  431.  
  432.     bzero((voidp) &nfs_args, sizeof(nfs_args));    /* Paranoid */
  433.  
  434.     /*
  435.      * Extract host name to give to kernel
  436.      */
  437.     if (!(colon = strchr(fs_name, ':')))
  438.         return ENOENT;
  439. #ifndef NFS_ARGS_NEEDS_PATH
  440.     *colon = '\0';
  441. #endif
  442.     strncpy(host, fs_name, sizeof(host));
  443. #ifndef NFS_ARGS_NEEDS_PATH
  444.     *colon = ':';
  445. #endif
  446.     path = colon + 1;
  447.  
  448.     bzero((voidp) &nfs_args, sizeof(nfs_args));
  449.  
  450.     mnt.mnt_dir = dir;
  451.     mnt.mnt_fsname = fs_name;
  452.     mnt.mnt_type = MTAB_TYPE_NFS;
  453.     mnt.mnt_opts = opts;
  454.     mnt.mnt_freq = 0;
  455.     mnt.mnt_passno = 0;
  456.  
  457.     retry = hasmntval(&mnt, "retry");
  458.     if (retry <= 0)
  459.         retry = 1;    /* XXX */
  460.  
  461. /*again:*/
  462. #ifdef DEBUG
  463.     dlog("locating fhandle for %s", fs_name);
  464. #endif
  465.     error = prime_fhandle_cache(path, mf->mf_server, &fhs, (voidp) 0);
  466.  
  467.     if (error)
  468.         return error;
  469.  
  470.     /*
  471.      * set mount args
  472.      */
  473.     nfs_args.fh = (NFS_FH_TYPE) fhs.fhstatus_u.fhs_fhandle;
  474.  
  475. #ifdef ULTRIX_HACK
  476.     nfs_args.optstr = mnt.mnt_opts;
  477. #endif
  478.  
  479.     nfs_args.hostname = host;
  480.     nfs_args.flags |= NFSMNT_HOSTNAME;
  481. #ifdef HOSTNAMESZ
  482.     /*
  483.      * Most kernels have a name length restriction.
  484.      */
  485.     if (strlen(host) >= HOSTNAMESZ)
  486.         strcpy(host + HOSTNAMESZ - 3, "..");
  487. #endif
  488.  
  489.     if (nfs_args.rsize = hasmntval(&mnt, "rsize"))
  490.         nfs_args.flags |= NFSMNT_RSIZE;
  491.  
  492.     if (nfs_args.wsize = hasmntval(&mnt, "wsize"))
  493.         nfs_args.flags |= NFSMNT_WSIZE;
  494.  
  495.     if (nfs_args.timeo = hasmntval(&mnt, "timeo"))
  496.         nfs_args.flags |= NFSMNT_TIMEO;
  497.  
  498.     if (nfs_args.retrans = hasmntval(&mnt, "retrans"))
  499.         nfs_args.flags |= NFSMNT_RETRANS;
  500.  
  501. #ifdef notdef
  502. /*
  503.  * This isn't supported by the ping algorithm yet.
  504.  * In any case, it is all done in nfs_init().
  505.  */
  506.      if (port = hasmntval(&mnt, "port"))
  507.         sin.sin_port = htons(port);
  508.     else
  509.         sin.sin_port = htons(NFS_PORT);    /* XXX should use portmapper */
  510. #endif
  511.  
  512.     if (hasmntopt(&mnt, MNTOPT_SOFT) != NULL)
  513.         nfs_args.flags |= NFSMNT_SOFT;
  514.  
  515. #ifdef MNTOPT_INTR
  516.     if (hasmntopt(&mnt, MNTOPT_INTR) != NULL)
  517.         nfs_args.flags |= NFSMNT_INT;
  518. #endif
  519.  
  520. #ifdef MNTOPT_NODEVS
  521.     if (hasmntopt(&mnt, MNTOPT_NODEVS) != NULL)
  522.         nfs_args.flags |= NFSMNT_NODEVS;
  523. #endif
  524.  
  525. #ifdef NFSMNT_PGTHRESH
  526.     if (nfs_args.pg_thresh = hasmntval(&mnt, "pgthresh"))
  527.         nfs_args.flags |= NFSMNT_PGTHRESH;
  528. #endif
  529.  
  530.     nfs_args.addr = fs->fs_ip;
  531.  
  532.     flags = compute_mount_flags(&mnt);
  533.  
  534. #ifdef ULTRIX_HACK
  535.     /*
  536.      * Ultrix passes the flags argument as part of the
  537.      * mount data structure, rather than using the
  538.      * flags argument to the system call.  This is
  539.      * confusing...
  540.      */
  541.     if (!(nfs_args.flags & NFSMNT_PGTHRESH)) {
  542.         nfs_args.pg_thresh = 64; /* 64k - XXX */
  543.         nfs_args.flags |= NFSMNT_PGTHRESH;
  544.     }
  545.     nfs_args.gfs_flags = flags;
  546.     flags &= M_RDONLY;
  547.     if (flags & M_RDONLY)
  548.         nfs_args.flags |= NFSMNT_RONLY;
  549. #endif
  550.  
  551.     return mount_fs(&mnt, flags, (caddr_t) &nfs_args, retry, type);
  552. }
  553.  
  554. static int nfs_mount(mp)
  555. am_node *mp;
  556. {
  557.     mntfs *mf = mp->am_mnt;
  558.  
  559.     int error = mount_nfs(mf->mf_mount, mf->mf_info,
  560.             mf->mf_fo->opt_opts, mf);
  561.  
  562. #ifdef DEBUG
  563.     if (error) {
  564.         errno = error;
  565.         dlog("mount_nfs: %m");
  566.     }
  567. #endif
  568.     return error;
  569. }
  570.  
  571. static int nfs_umount(mp)
  572. am_node *mp;
  573. {
  574.     mntfs *mf = mp->am_mnt;
  575.  
  576.     int error = UMOUNT_FS(mf->mf_mount);
  577.     if (error)
  578.         return error;
  579.  
  580.     return 0;
  581. }
  582.  
  583. static void nfs_umounted(mp)
  584. am_node *mp;
  585. {
  586. #ifdef INFORM_MOUNTD
  587.     /*
  588.      * Don't bother to inform remote mountd
  589.      * that we are finished.  Until a full
  590.      * track of filehandles is maintained
  591.      * the mountd unmount callback cannot
  592.      * be done correctly anyway...
  593.      */
  594.  
  595.     mntfs *mf = mp->am_mnt;
  596.     fserver *fs;
  597.     char *colon, *path;
  598.  
  599.     if (mf->mf_error || mf->mf_refc > 1)
  600.         return;
  601.  
  602.     fs = mf->mf_server;
  603.  
  604.     /*
  605.      * Call the mount daemon on the server to
  606.      * announce that we are not using the fs any more.
  607.      *
  608.      * This is *wrong*.  The mountd should be called
  609.      * when the fhandle is flushed from the cache, and
  610.      * a reference held to the cached entry while the
  611.      * fs is mounted...
  612.      */
  613.     colon = path = strchr(mf->mf_info, ':');
  614.     if (fs && colon) {
  615.         fh_cache f;
  616. #ifdef DEBUG
  617.         dlog("calling mountd for %s", mf->mf_info);
  618. #endif
  619.         *path++ = '\0';
  620.         f.fh_path = path;
  621.         f.fh_sin = *fs->fs_ip;
  622.         f.fh_sin.sin_port = (u_short) 0;
  623.         f.fh_fs = fs;
  624.         f.fh_id = 0;
  625.         f.fh_error = 0;
  626.         (void) prime_fhandle_cache(colon+1, mf->mf_server, (struct fhstatus *) 0, (voidp) mf);
  627.         (void) call_mountd(&f, MOUNTPROC_UMNT, (fwd_fun) 0, (voidp) 0);
  628.         *colon = ':';
  629.     }
  630. #endif
  631. }
  632.  
  633. /*
  634.  * Network file system
  635.  */
  636. am_ops nfs_ops = {
  637.     "nfs",
  638.     nfs_match,
  639.     nfs_init,
  640.     nfs_mount,
  641.     nfs_umount,
  642.     efs_lookuppn,
  643.     efs_readdir,
  644.     0, /* nfs_readlink */
  645.     0, /* nfs_mounted */
  646.     nfs_umounted,
  647.     find_nfs_srvr,
  648.     FS_MKMNT|FS_BACKGROUND|FS_AMQINFO,
  649.     &nfs_srvr_list,
  650. };
  651.  
  652. #endif /* HAS_NFS */
  653.