Initial import of binutils 2.22 on the new vendor branch
[dragonfly.git] / contrib / amd / amd / amfs_host.c
1 /*
2  * Copyright (c) 1997-1999 Erez Zadok
3  * Copyright (c) 1990 Jan-Simon Pendry
4  * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
5  * Copyright (c) 1990 The Regents of the University of California.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Jan-Simon Pendry at Imperial College, London.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgment:
21  *      This product includes software developed by the University of
22  *      California, Berkeley and its contributors.
23  * 4. Neither the name of the University nor the names of its contributors
24  *    may be used to endorse or promote products derived from this software
25  *    without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37  * SUCH DAMAGE.
38  *
39  *      %W% (Berkeley) %G%
40  *
41  * $Id: amfs_host.c,v 1.3 1999/01/13 23:30:57 ezk Exp $
42  *
43  */
44
45 /*
46  * NFS host file system.
47  * Mounts all exported filesystems from a given host.
48  * This has now degenerated into a mess but will not
49  * be rewritten.  Amd 6 will support the abstractions
50  * needed to make this work correctly.
51  */
52
53 #ifdef HAVE_CONFIG_H
54 # include <config.h>
55 #endif /* HAVE_CONFIG_H */
56 #include <am_defs.h>
57 #include <amd.h>
58
59 static char *amfs_host_match(am_opts *fo);
60 static int amfs_host_fmount(mntfs *mf);
61 static int amfs_host_fumount(mntfs *mf);
62 static int amfs_host_init(mntfs *mf);
63 static void amfs_host_umounted(am_node *mp);
64
65 /*
66  * Ops structure
67  */
68 am_ops amfs_host_ops =
69 {
70   "host",
71   amfs_host_match,
72   amfs_host_init,
73   amfs_auto_fmount,
74   amfs_host_fmount,
75   amfs_auto_fumount,
76   amfs_host_fumount,
77   amfs_error_lookuppn,
78   amfs_error_readdir,
79   0,                            /* amfs_host_readlink */
80   0,                            /* amfs_host_mounted */
81   amfs_host_umounted,
82   find_nfs_srvr,
83   FS_MKMNT | FS_BACKGROUND | FS_AMQINFO
84 };
85
86
87 /*
88  * Determine the mount point:
89  *
90  * The next change we put in to better handle PCs.  This is a bit
91  * disgusting, so you'd better sit down.  We change the make_mntpt function
92  * to look for exported file systems without a leading '/'.  If they don't
93  * have a leading '/', we add one.  If the export is 'a:' through 'z:'
94  * (without a leading slash), we change it to 'a%' (or b% or z%).  This
95  * allows the entire PC disk to be mounted.
96  */
97 static void
98 make_mntpt(char *mntpt, const exports ex, const mntfs *mf)
99 {
100   if (ex->ex_dir[0] == '/') {
101     if (ex->ex_dir[1] == 0)
102       strcpy(mntpt, (mf)->mf_mount);
103     else
104       sprintf(mntpt, "%s%s", mf->mf_mount, ex->ex_dir);
105   } else if (ex->ex_dir[0] >= 'a' &&
106              ex->ex_dir[0] <= 'z' &&
107              ex->ex_dir[1] == ':' &&
108              ex->ex_dir[2] == '/' &&
109              ex->ex_dir[3] == 0)
110     sprintf(mntpt, "%s/%c%%", mf->mf_mount, ex->ex_dir[0]);
111   else
112     sprintf(mntpt, "%s/%s", mf->mf_mount, ex->ex_dir);
113 }
114
115
116 /*
117  * Execute needs the same as NFS plus a helper command
118  */
119 static char *
120 amfs_host_match(am_opts *fo)
121 {
122   extern am_ops nfs_ops;
123
124   /*
125    * Make sure rfs is specified to keep nfs_match happy...
126    */
127   if (!fo->opt_rfs)
128     fo->opt_rfs = "/";
129
130   return (*nfs_ops.fs_match) (fo);
131 }
132
133
134 static int
135 amfs_host_init(mntfs *mf)
136 {
137   fserver *fs;
138   u_short port;
139
140   if (strchr(mf->mf_info, ':') == 0)
141     return ENOENT;
142
143   /*
144    * This is primarily to schedule a wakeup so that as soon
145    * as our fileserver is ready, we can continue setting up
146    * the host filesystem.  If we don't do this, the standard
147    * amfs_auto code will set up a fileserver structure, but it will
148    * have to wait for another nfs request from the client to come
149    * in before finishing.  Our way is faster since we don't have
150    * to wait for the client to resend its request (which could
151    * take a second or two).
152    */
153   /*
154    * First, we find the fileserver for this mntfs and then call
155    * nfs_srvr_port with our mntfs passed as the wait channel.
156    * nfs_srvr_port will check some things and then schedule
157    * it so that when the fileserver is ready, a wakeup is done
158    * on this mntfs.   amfs_auto_cont() is already sleeping on this mntfs
159    * so as soon as that wakeup happens amfs_auto_cont() is called and
160    * this mount is retried.
161    */
162   if ((fs = mf->mf_server))
163     /*
164      * We don't really care if there's an error returned.
165      * Since this is just to help speed things along, the
166      * error will get handled properly elsewhere.
167      */
168     (void) nfs_srvr_port(fs, &port, (voidp) mf);
169
170   return 0;
171 }
172
173
174 static int
175 do_mount(am_nfs_handle_t *fhp, char *dir, char *fs_name, char *opts, mntfs *mf)
176 {
177   struct stat stb;
178
179 #ifdef DEBUG
180   dlog("amfs_host: mounting fs %s on %s\n", fs_name, dir);
181 #endif /* DEBUG */
182
183   (void) mkdirs(dir, 0555);
184   if (stat(dir, &stb) < 0 || (stb.st_mode & S_IFMT) != S_IFDIR) {
185     plog(XLOG_ERROR, "No mount point for %s - skipping", dir);
186     return ENOENT;
187   }
188
189   return mount_nfs_fh(fhp, dir, fs_name, opts, mf);
190 }
191
192
193 static int
194 sortfun(const voidp x, const voidp y)
195 {
196   exports *a = (exports *) x;
197   exports *b = (exports *) y;
198
199   return strcmp((*a)->ex_dir, (*b)->ex_dir);
200 }
201
202
203 /*
204  * Get filehandle
205  */
206 static int
207 fetch_fhandle(CLIENT * client, char *dir, am_nfs_handle_t *fhp, u_long nfs_version)
208 {
209   struct timeval tv;
210   enum clnt_stat clnt_stat;
211
212   /*
213    * Pick a number, any number...
214    */
215   tv.tv_sec = 20;
216   tv.tv_usec = 0;
217
218 #ifdef DEBUG
219   dlog("Fetching fhandle for %s", dir);
220 #endif /* DEBUG */
221
222   /*
223    * Call the mount daemon on the remote host to
224    * get the filehandle.  Use NFS version specific call.
225    */
226
227   plog(XLOG_INFO, "fetch_fhandle: NFS version %d", (int) nfs_version);
228 #ifdef HAVE_FS_NFS3
229   if (nfs_version == NFS_VERSION3) {
230     memset((char *) &fhp->v3, 0, sizeof(fhp->v3));
231     clnt_stat = clnt_call(client,
232                           MOUNTPROC_MNT,
233                           (XDRPROC_T_TYPE) xdr_dirpath,
234                           (SVC_IN_ARG_TYPE) &dir,
235                           (XDRPROC_T_TYPE) xdr_mountres3,
236                           (SVC_IN_ARG_TYPE) &fhp->v3,
237                           tv);
238     if (clnt_stat != RPC_SUCCESS) {
239       plog(XLOG_ERROR, "mountd rpc failed: %s", clnt_sperrno(clnt_stat));
240       return EIO;
241     }
242     /* Check the status of the filehandle */
243     if ((errno = fhp->v3.fhs_status)) {
244 #ifdef DEBUG
245       dlog("fhandle fetch for mount version 3 failed: %m");
246 #endif /* DEBUG */
247       return errno;
248     }
249   } else {                      /* not NFS_VERSION3 mount */
250 #endif /* HAVE_FS_NFS3 */
251     clnt_stat = clnt_call(client,
252                           MOUNTPROC_MNT,
253                           (XDRPROC_T_TYPE) xdr_dirpath,
254                           (SVC_IN_ARG_TYPE) &dir,
255                           (XDRPROC_T_TYPE) xdr_fhstatus,
256                           (SVC_IN_ARG_TYPE) &fhp->v2,
257                           tv);
258     if (clnt_stat != RPC_SUCCESS) {
259       const char *msg = clnt_sperrno(clnt_stat);
260       plog(XLOG_ERROR, "mountd rpc failed: %s", msg);
261       return EIO;
262     }
263     /* Check status of filehandle */
264     if (fhp->v2.fhs_status) {
265       errno = fhp->v2.fhs_status;
266 #ifdef DEBUG
267       dlog("fhandle fetch for mount version 1 failed: %m");
268 #endif /* DEBUG */
269       return errno;
270     }
271 #ifdef HAVE_FS_NFS3
272   } /* end of "if (nfs_version == NFS_VERSION3)" statement */
273 #endif /* HAVE_FS_NFS3 */
274
275   /* all is well */
276   return 0;
277 }
278
279
280 /*
281  * Scan mount table to see if something already mounted
282  */
283 static int
284 already_mounted(mntlist *mlist, char *dir)
285 {
286   mntlist *ml;
287
288   for (ml = mlist; ml; ml = ml->mnext)
289     if (STREQ(ml->mnt->mnt_dir, dir))
290       return 1;
291   return 0;
292 }
293
294
295 /*
296  * Mount the export tree from a host
297  */
298 static int
299 amfs_host_fmount(mntfs *mf)
300 {
301   struct timeval tv2;
302   CLIENT *client;
303   enum clnt_stat clnt_stat;
304   int n_export;
305   int j, k;
306   exports exlist = 0, ex;
307   exports *ep = 0;
308   am_nfs_handle_t *fp = 0;
309   char *host;
310   int error = 0;
311   struct sockaddr_in sin;
312   int sock = RPC_ANYSOCK;
313   int ok = FALSE;
314   mntlist *mlist;
315   char fs_name[MAXPATHLEN], *rfs_dir;
316   char mntpt[MAXPATHLEN];
317   struct timeval tv;
318   u_long mnt_version;
319
320   /*
321    * Read the mount list
322    */
323   mlist = read_mtab(mf->mf_mount, mnttab_file_name);
324
325 #ifdef MOUNT_TABLE_ON_FILE
326   /*
327    * Unlock the mount list
328    */
329   unlock_mntlist();
330 #endif /* MOUNT_TABLE_ON_FILE */
331
332   /*
333    * Take a copy of the server hostname, address, and nfs version
334    * to mount version conversion.
335    */
336   host = mf->mf_server->fs_host;
337   sin = *mf->mf_server->fs_ip;
338   plog(XLOG_INFO, "amfs_host_fmount: NFS version %d", (int) mf->mf_server->fs_version);
339 #ifdef HAVE_FS_NFS3
340   if (mf->mf_server->fs_version == NFS_VERSION3)
341     mnt_version = MOUNTVERS3;
342   else
343 #endif /* HAVE_FS_NFS3 */
344     mnt_version = MOUNTVERS;
345
346   /*
347    * The original 10 second per try timeout is WAY too large, especially
348    * if we're only waiting 10 or 20 seconds max for the response.
349    * That would mean we'd try only once in 10 seconds, and we could
350    * lose the transmit or receive packet, and never try again.
351    * A 2-second per try timeout here is much more reasonable.
352    * 09/28/92 Mike Mitchell, mcm@unx.sas.com
353    */
354   tv.tv_sec = 2;
355   tv.tv_usec = 0;
356
357   /*
358    * Create a client attached to mountd
359    */
360   client = get_mount_client(host, &sin, &tv, &sock, mnt_version);
361   if (client == NULL) {
362 #ifdef HAVE_CLNT_SPCREATEERROR
363     plog(XLOG_ERROR, "get_mount_client failed for %s: %s",
364          host, clnt_spcreateerror(""));
365 #else /* not HAVE_CLNT_SPCREATEERROR */
366     plog(XLOG_ERROR, "get_mount_client failed for %s", host);
367 #endif /* not HAVE_CLNT_SPCREATEERROR */
368     error = EIO;
369     goto out;
370   }
371   if (!nfs_auth) {
372     error = make_nfs_auth();
373     if (error)
374       goto out;
375   }
376   client->cl_auth = nfs_auth;
377
378 #ifdef DEBUG
379   dlog("Fetching export list from %s", host);
380 #endif /* DEBUG */
381
382   /*
383    * Fetch the export list
384    */
385   tv2.tv_sec = 10;
386   tv2.tv_usec = 0;
387   clnt_stat = clnt_call(client,
388                         MOUNTPROC_EXPORT,
389                         (XDRPROC_T_TYPE) xdr_void,
390                         0,
391                         (XDRPROC_T_TYPE) xdr_exports,
392                         (SVC_IN_ARG_TYPE) & exlist,
393                         tv2);
394   if (clnt_stat != RPC_SUCCESS) {
395     const char *msg = clnt_sperrno(clnt_stat);
396     plog(XLOG_ERROR, "host_fmount rpc failed: %s", msg);
397     /* clnt_perror(client, "rpc"); */
398     error = EIO;
399     goto out;
400   }
401
402   /*
403    * Figure out how many exports were returned
404    */
405   for (n_export = 0, ex = exlist; ex; ex = ex->ex_next) {
406     /* printf("export %s\n", ex->ex_dir); */
407     n_export++;
408   }
409
410   /*
411    * Allocate an array of pointers into the list
412    * so that they can be sorted.  If the filesystem
413    * is already mounted then ignore it.
414    */
415   ep = (exports *) xmalloc(n_export * sizeof(exports));
416   for (j = 0, ex = exlist; ex; ex = ex->ex_next) {
417     make_mntpt(mntpt, ex, mf);
418     if (!already_mounted(mlist, mntpt))
419       ep[j++] = ex;
420   }
421   n_export = j;
422
423   /*
424    * Sort into order.
425    * This way the mounts are done in order down the tree,
426    * instead of any random order returned by the mount
427    * daemon (the protocol doesn't specify...).
428    */
429   qsort(ep, n_export, sizeof(exports), sortfun);
430
431   /*
432    * Allocate an array of filehandles
433    */
434   fp = (am_nfs_handle_t *) xmalloc(n_export * sizeof(am_nfs_handle_t));
435
436   /*
437    * Try to obtain filehandles for each directory.
438    * If a fetch fails then just zero out the array
439    * reference but discard the error.
440    */
441   for (j = k = 0; j < n_export; j++) {
442     /* Check and avoid a duplicated export entry */
443     if (j > k && ep[k] && STREQ(ep[j]->ex_dir, ep[k]->ex_dir)) {
444 #ifdef DEBUG
445       dlog("avoiding dup fhandle requested for %s", ep[j]->ex_dir);
446 #endif /* DEBUG */
447       ep[j] = 0;
448     } else {
449       k = j;
450       error = fetch_fhandle(client, ep[j]->ex_dir, &fp[j],
451                             mf->mf_server->fs_version);
452       if (error)
453         ep[j] = 0;
454     }
455   }
456
457   /*
458    * Mount each filesystem for which we have a filehandle.
459    * If any of the mounts succeed then mark "ok" and return
460    * error code 0 at the end.  If they all fail then return
461    * the last error code.
462    */
463   strncpy(fs_name, mf->mf_info, sizeof(fs_name));
464   if ((rfs_dir = strchr(fs_name, ':')) == (char *) 0) {
465     plog(XLOG_FATAL, "amfs_host_fmount: mf_info has no colon");
466     error = EINVAL;
467     goto out;
468   }
469   ++rfs_dir;
470   for (j = 0; j < n_export; j++) {
471     ex = ep[j];
472     if (ex) {
473       strcpy(rfs_dir, ex->ex_dir);
474       make_mntpt(mntpt, ex, mf);
475       if (do_mount(&fp[j], mntpt, fs_name, mf->mf_mopts, mf) == 0)
476         ok = TRUE;
477     }
478   }
479
480   /*
481    * Clean up and exit
482    */
483 out:
484   discard_mntlist(mlist);
485   if (ep)
486     XFREE(ep);
487   if (fp)
488     XFREE(fp);
489   if (sock != RPC_ANYSOCK)
490     (void) amu_close(sock);
491   if (client)
492     clnt_destroy(client);
493   if (exlist)
494     xdr_pri_free((XDRPROC_T_TYPE) xdr_exports, (caddr_t) &exlist);
495   if (ok)
496     return 0;
497   return error;
498 }
499
500
501 /*
502  * Return true if pref is a directory prefix of dir.
503  *
504  * XXX TODO:
505  * Does not work if pref is "/".
506  */
507 static int
508 directory_prefix(char *pref, char *dir)
509 {
510   int len = strlen(pref);
511
512   if (!NSTREQ(pref, dir, len))
513     return FALSE;
514   if (dir[len] == '/' || dir[len] == '\0')
515     return TRUE;
516   return FALSE;
517 }
518
519
520 /*
521  * Unmount a mount tree
522  */
523 static int
524 amfs_host_fumount(mntfs *mf)
525 {
526   mntlist *ml, *mprev;
527   int xerror = 0;
528
529   /*
530    * Read the mount list
531    */
532   mntlist *mlist = read_mtab(mf->mf_mount, mnttab_file_name);
533
534 #ifdef MOUNT_TABLE_ON_FILE
535   /*
536    * Unlock the mount list
537    */
538   unlock_mntlist();
539 #endif /* MOUNT_TABLE_ON_FILE */
540
541   /*
542    * Reverse list...
543    */
544   ml = mlist;
545   mprev = 0;
546   while (ml) {
547     mntlist *ml2 = ml->mnext;
548     ml->mnext = mprev;
549     mprev = ml;
550     ml = ml2;
551   }
552   mlist = mprev;
553
554   /*
555    * Unmount all filesystems...
556    */
557   for (ml = mlist; ml && !xerror; ml = ml->mnext) {
558     char *dir = ml->mnt->mnt_dir;
559     if (directory_prefix(mf->mf_mount, dir)) {
560       int error;
561 #ifdef DEBUG
562       dlog("amfs_host: unmounts %s", dir);
563 #endif /* DEBUG */
564       /*
565        * Unmount "dir"
566        */
567       error = UMOUNT_FS(dir, mnttab_file_name);
568       /*
569        * Keep track of errors
570        */
571       if (error) {
572         if (!xerror)
573           xerror = error;
574         if (error != EBUSY) {
575           errno = error;
576           plog(XLOG_ERROR, "Tree unmount of %s failed: %m", ml->mnt->mnt_dir);
577         }
578       } else {
579         (void) rmdirs(dir);
580       }
581     }
582   }
583
584   /*
585    * Throw away mount list
586    */
587   discard_mntlist(mlist);
588
589   /*
590    * Try to remount, except when we are shutting down.
591    */
592   if (xerror && amd_state != Finishing) {
593     xerror = amfs_host_fmount(mf);
594     if (!xerror) {
595       /*
596        * Don't log this - it's usually too verbose
597        plog(XLOG_INFO, "Remounted host %s", mf->mf_info);
598        */
599       xerror = EBUSY;
600     }
601   }
602   return xerror;
603 }
604
605
606 /*
607  * Tell mountd we're done.
608  * This is not quite right, because we may still
609  * have other filesystems mounted, but the existing
610  * mountd protocol is badly broken anyway.
611  */
612 static void
613 amfs_host_umounted(am_node *mp)
614 {
615   mntfs *mf = mp->am_mnt;
616   char *host;
617   CLIENT *client;
618   enum clnt_stat clnt_stat;
619   struct sockaddr_in sin;
620   int sock = RPC_ANYSOCK;
621   struct timeval tv;
622   u_long mnt_version;
623
624   if (mf->mf_error || mf->mf_refc > 1 || !mf->mf_server)
625     return;
626
627   /*
628    * Take a copy of the server hostname, address, and NFS version
629    * to mount version conversion.
630    */
631   host = mf->mf_server->fs_host;
632   sin = *mf->mf_server->fs_ip;
633   plog(XLOG_INFO, "amfs_host_umounted: NFS version %d", (int) mf->mf_server->fs_version);
634 #ifdef HAVE_FS_NFS3
635   if (mf->mf_server->fs_version == NFS_VERSION3)
636     mnt_version = MOUNTVERS3;
637   else
638 #endif /* HAVE_FS_NFS3 */
639     mnt_version = MOUNTVERS;
640
641   /*
642    * Create a client attached to mountd
643    */
644   tv.tv_sec = 10;
645   tv.tv_usec = 0;
646   client = get_mount_client(host, &sin, &tv, &sock, mnt_version);
647   if (client == NULL) {
648 #ifdef HAVE_CLNT_SPCREATEERROR
649     plog(XLOG_ERROR, "get_mount_client failed for %s: %s",
650          host, clnt_spcreateerror(""));
651 #else /* not HAVE_CLNT_SPCREATEERROR */
652     plog(XLOG_ERROR, "get_mount_client failed for %s", host);
653 #endif /* not HAVE_CLNT_SPCREATEERROR */
654     goto out;
655   }
656
657   if (!nfs_auth) {
658     if (make_nfs_auth())
659       goto out;
660   }
661   client->cl_auth = nfs_auth;
662
663 #ifdef DEBUG
664   dlog("Unmounting all from %s", host);
665 #endif /* DEBUG */
666
667   clnt_stat = clnt_call(client,
668                         MOUNTPROC_UMNTALL,
669                         (XDRPROC_T_TYPE) xdr_void,
670                         0,
671                         (XDRPROC_T_TYPE) xdr_void,
672                         0,
673                         tv);
674   if (clnt_stat != RPC_SUCCESS && clnt_stat != RPC_SYSTEMERROR) {
675     /* RPC_SYSTEMERROR seems to be returned for no good reason ... */
676     const char *msg = clnt_sperrno(clnt_stat);
677     plog(XLOG_ERROR, "unmount all from %s rpc failed: %s", host, msg);
678     goto out;
679   }
680
681 out:
682   if (sock != RPC_ANYSOCK)
683     (void) amu_close(sock);
684   if (client)
685     clnt_destroy(client);
686 }