1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 /*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T     */
  27 /*        All Rights Reserved   */
  28 
  29 /*
  30  * University Copyright- Copyright (c) 1982, 1986, 1988
  31  * The Regents of the University of California
  32  * All Rights Reserved
  33  *
  34  * University Acknowledgment- Portions of this document are derived from
  35  * software developed by the University of California, Berkeley, and its
  36  * contributors.
  37  */
  38 
  39 #pragma ident   "%Z%%M% %I%     %E% SMI"
  40 
  41 /*
  42  * nfs umount
  43  */
  44 
  45 #include <stdio.h>
  46 #include <stdlib.h>
  47 #include <string.h>
  48 #include <stdarg.h>
  49 #include <signal.h>
  50 #include <unistd.h>
  51 #include <kstat.h>
  52 #include <rpc/rpc.h>
  53 #include <sys/mnttab.h>
  54 #include <sys/mount.h>
  55 #include <sys/mntent.h>
  56 #include <nfs/nfs.h>
  57 #include <nfs/nfs_clnt.h>
  58 #include <rpcsvc/mount.h>
  59 #include <errno.h>
  60 #include <locale.h>
  61 #include <fslib.h>
  62 #include <priv.h>
  63 #include <tsol/label.h>
  64 #include "replica.h"
  65 
  66 #define RET_OK  0
  67 #define RET_ERR 32
  68 
  69 #ifdef __STDC__
  70 static void pr_err(const char *fmt, ...);
  71 #else
  72 static void pr_err(char *fmt, va_dcl);
  73 #endif
  74 static  void    usage();
  75 static  int     nfs_unmount(char *, int);
  76 static  void    inform_server(char *, char *, bool_t);
  77 static  struct extmnttab *mnttab_find();
  78 extern int __clnt_bindresvport();
  79 
  80 static  int is_v4_mount(struct extmnttab *);
  81 
  82 static char *myname;
  83 static char typename[64];
  84 
  85 int
  86 main(int argc, char *argv[])
  87 {
  88         extern int optind;
  89         int c;
  90         int umnt_flag = 0;
  91 
  92         (void) setlocale(LC_ALL, "");
  93 
  94 #if !defined(TEXT_DOMAIN)
  95 #define TEXT_DOMAIN "SYS_TEST"
  96 #endif
  97         (void) textdomain(TEXT_DOMAIN);
  98 
  99         myname = strrchr(argv[0], '/');
 100         myname = myname ? myname+1 : argv[0];
 101         (void) sprintf(typename, "nfs %s", myname);
 102         argv[0] = typename;
 103 
 104         /*
 105          * Set options
 106          */
 107         while ((c = getopt(argc, argv, "f")) != EOF) {
 108                 switch (c) {
 109                 case 'f':
 110                         umnt_flag |= MS_FORCE; /* forced unmount is desired */
 111                         break;
 112                 default:
 113                         usage();
 114                         exit(RET_ERR);
 115                 }
 116         }
 117         if (argc - optind != 1) {
 118                 usage();
 119                 exit(RET_ERR);
 120         }
 121 
 122         if (!priv_ineffect(PRIV_SYS_MOUNT) ||
 123             !priv_ineffect(PRIV_NET_PRIVADDR)) {
 124                 pr_err(gettext("insufficient privileges\n"));
 125                 exit(RET_ERR);
 126         }
 127 
 128         /*
 129          * On a labeled system, a MAC flag may be needed if the mount is
 130          * read-down, so attempt to assert it but ignore errors in any case.
 131          */
 132         if (is_system_labeled())
 133                 (void) setpflags(NET_MAC_AWARE, 1);
 134 
 135         /*
 136          * exit, really
 137          */
 138         return (nfs_unmount(argv[optind], umnt_flag));
 139 }
 140 
 141 static void
 142 pr_err(const char *fmt, ...)
 143 {
 144         va_list ap;
 145 
 146         va_start(ap, fmt);
 147         (void) fprintf(stderr, "%s: ", typename);
 148         (void) vfprintf(stderr, fmt, ap);
 149         (void) fflush(stderr);
 150         va_end(ap);
 151 }
 152 
 153 static void
 154 usage()
 155 {
 156         (void) fprintf(stderr,
 157             gettext("Usage: nfs umount [-o opts] {server:path | dir}\n"));
 158         exit(RET_ERR);
 159 }
 160 
 161 static int
 162 nfs_unmount(char *pathname, int umnt_flag)
 163 {
 164         struct extmnttab *mntp;
 165         bool_t quick = FALSE;
 166         int is_v4 = FALSE;
 167 
 168         mntp = mnttab_find(pathname);
 169         if (mntp) {
 170                 pathname = mntp->mnt_mountp;
 171         }
 172 
 173         if (mntp)
 174                 is_v4 = is_v4_mount(mntp);
 175 
 176         /* Forced unmount will almost always be successful */
 177         if (umount2(pathname, umnt_flag) < 0) {
 178                 if (errno == EBUSY) {
 179                         pr_err(gettext("%s: is busy\n"), pathname);
 180                 } else {
 181                         pr_err(gettext("%s: not mounted\n"), pathname);
 182                 }
 183                 return (RET_ERR);
 184         }
 185 
 186         /* All done if it's v4 */
 187         if (is_v4 == TRUE)
 188                 return (RET_OK);
 189 
 190         /* Inform server quickly in case of forced unmount */
 191         if (umnt_flag & MS_FORCE)
 192                 quick = TRUE;
 193 
 194         if (mntp) {
 195                 inform_server(mntp->mnt_special, mntp->mnt_mntopts, quick);
 196         }
 197 
 198         return (RET_OK);
 199 }
 200 
 201 /*
 202  *  Find the mnttab entry that corresponds to "name".
 203  *  We're not sure what the name represents: either
 204  *  a mountpoint name, or a special name (server:/path).
 205  *  Return the last entry in the file that matches.
 206  */
 207 static struct extmnttab *
 208 mnttab_find(dirname)
 209         char *dirname;
 210 {
 211         FILE *fp;
 212         struct extmnttab mnt;
 213         struct extmnttab *res = NULL;
 214 
 215         fp = fopen(MNTTAB, "r");
 216         if (fp == NULL) {
 217                 pr_err("%s: %s\n", MNTTAB, strerror(errno));
 218                 return (NULL);
 219         }
 220         while (getextmntent(fp, &mnt, sizeof (struct extmnttab)) == 0) {
 221                 if (strcmp(mnt.mnt_mountp, dirname) == 0 ||
 222                     strcmp(mnt.mnt_special, dirname) == 0) {
 223                         if (res)
 224                                 fsfreemnttab(res);
 225                         res = fsdupmnttab(&mnt);
 226                 }
 227         }
 228 
 229         fclose(fp);
 230         return (res);
 231 }
 232 
 233 /*
 234  * If quick is TRUE, it will try to inform server quickly
 235  * as possible.
 236  */
 237 static void
 238 inform_server(char *string, char *opts, bool_t quick)
 239 {
 240         struct timeval timeout;
 241         CLIENT *cl;
 242         enum clnt_stat rpc_stat;
 243         struct replica *list;
 244         int i, n;
 245         char *p = NULL;
 246         static struct timeval create_timeout = {5, 0};
 247         static struct timeval *timep;
 248 
 249         list = parse_replica(string, &n);
 250 
 251         if (list == NULL) {
 252                 if (n < 0)
 253                         pr_err(gettext("%s is not hostname:path format\n"),
 254                                 string);
 255                 else
 256                         pr_err(gettext("no memory\n"));
 257                 return;
 258         }
 259 
 260         /*
 261          * If mounted with -o public, then no need to contact server
 262          * because mount protocol was not used.
 263          */
 264         if (opts != NULL)
 265                 p = strstr(opts, MNTOPT_PUBLIC);
 266 
 267         if (p != NULL) {
 268                 i = strlen(MNTOPT_PUBLIC);
 269                 /*
 270                  * Now make sure the match of "public" isn't a substring
 271                  * of another option.
 272                  */
 273                 if (((p == opts) || (*(p-1) == ',')) &&
 274                     ((p[i] == ',') || (p[i] == '\0')))
 275                         return;
 276         }
 277 
 278         for (i = 0; i < n; i++) {
 279                 int vers;
 280 
 281                 /*
 282                  * Skip file systems mounted using WebNFS, because mount
 283                  * protocol was not used.
 284                  */
 285                 if (strcmp(list[i].host, "nfs") == 0 && strncmp(list[i].path,
 286                     "//", 2) == 0)
 287                         continue;
 288 
 289                 vers =  MOUNTVERS;
 290 retry:
 291                 /*
 292                  * Use 5 sec. timeout if file system is forced unmounted,
 293                  * otherwise use default timeout to create a client handle.
 294                  * This would minimize the time to force unmount a file
 295                  * system reside on a server that is down.
 296                  */
 297                 timep = (quick ? &create_timeout : NULL);
 298                 cl = clnt_create_timed(list[i].host, MOUNTPROG, vers,
 299                                 "datagram_n", timep);
 300                 /*
 301                  * Do not print any error messages in case of forced
 302                  * unmount.
 303                  */
 304                 if (cl == NULL) {
 305                         if (!quick)
 306                                 pr_err("%s:%s %s\n", list[i].host, list[i].path,
 307                                     clnt_spcreateerror(
 308                                         "server not responding"));
 309                         continue;
 310                 }
 311                 /*
 312                  * Now it is most likely that the NFS client will be able
 313                  * to contact the server since rpcbind is running on
 314                  * the server. There is still a small window in which
 315                  * server can be unreachable.
 316                  */
 317 
 318                 if (__clnt_bindresvport(cl) < 0) {
 319                         if (!quick)
 320                                 pr_err(gettext(
 321                                     "couldn't bind to reserved port\n"));
 322                         clnt_destroy(cl);
 323                         return;
 324                 }
 325                 if ((cl->cl_auth = authsys_create_default()) == NULL) {
 326                         if (!quick)
 327                                 pr_err(gettext(
 328                                     "couldn't create authsys structure\n"));
 329                         clnt_destroy(cl);
 330                         return;
 331                 }
 332                 timeout.tv_usec = 0;
 333                 timeout.tv_sec = 5;
 334                 clnt_control(cl, CLSET_RETRY_TIMEOUT, (char *)&timeout);
 335                 timeout.tv_sec = 25;
 336                 rpc_stat = clnt_call(cl, MOUNTPROC_UMNT, xdr_dirpath,
 337                         (char *)&list[i].path, xdr_void, (char *)NULL,
 338                         timeout);
 339                 AUTH_DESTROY(cl->cl_auth);
 340                 clnt_destroy(cl);
 341                 if (rpc_stat == RPC_PROGVERSMISMATCH && vers == MOUNTVERS) {
 342                         /*
 343                          * The rare case of a v3-only server
 344                          */
 345                         vers = MOUNTVERS3;
 346                         goto retry;
 347                 }
 348                 if (rpc_stat != RPC_SUCCESS)
 349                         pr_err("%s\n", clnt_sperror(cl, "unmount"));
 350         }
 351 
 352         free_replica(list, n);
 353 }
 354 
 355 /*
 356  * This function's behavior is taken from nfsstat.
 357  * Trying to determine what NFS version was used for the mount.
 358  */
 359 int
 360 is_v4_mount(struct extmnttab *mntp)
 361 {
 362         kstat_ctl_t *kc = NULL;         /* libkstat cookie */
 363         kstat_t *ksp;
 364         ulong_t fsid;
 365         struct mntinfo_kstat mik;
 366 
 367         if (mntp == NULL)
 368                 return (FALSE);
 369 
 370         if ((kc = kstat_open()) == NULL)
 371                 return (FALSE);
 372 
 373         for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
 374                 if (ksp->ks_type != KSTAT_TYPE_RAW)
 375                         continue;
 376                 if (strcmp(ksp->ks_module, "nfs") != 0)
 377                         continue;
 378                 if (strcmp(ksp->ks_name, "mntinfo") != 0)
 379                         continue;
 380                 if (mntp->mnt_minor != ksp->ks_instance)
 381                         continue;
 382 
 383                 if (kstat_read(kc, ksp, &mik) == -1)
 384                         continue;
 385 
 386                 if (mik.mik_vers == 4)
 387                         return (TRUE);
 388                 else
 389                         return (FALSE);
 390         }
 391         return (FALSE);
 392 }