/* * Async unmount callback function. * After the base umount() succeeds, we may want to take extra actions, * such as informing remote mount daemons that we've unmounted them. * See amfs_auto_umounted(), host_umounted(), nfs_umounted(). */ static void amfs_nfsl_umounted(am_node *mp) { mntfs *mf = mp->am_mnt; /* * If a link, do nothing (same as type:=link) * If non-link, do nfs_fumount (same as type:=nfs). */ if (mf->mf_flags & MFF_NFSLINK) { return; } else { nfs_umounted(mp); /* * MUST remove mount point directories, because if they remain * behind, the next nfsl access will think they are a link * type file system, and not NFS! (when it performs link target * existence test) */ if (mf->mf_flags & MFF_MKMNT) rmdirs(mf->mf_mount); return; } }
/* * Unmount a mount tree */ static int amfs_host_umount(am_node *am, mntfs *mf) { mntlist *ml, *mprev; int unmount_flags = (mf->mf_flags & MFF_ON_AUTOFS) ? AMU_UMOUNT_AUTOFS : 0; int xerror = 0; /* * Read the mount list */ mntlist *mlist = read_mtab(mf->mf_mount, mnttab_file_name); #ifdef MOUNT_TABLE_ON_FILE /* * Unlock the mount list */ unlock_mntlist(); #endif /* MOUNT_TABLE_ON_FILE */ /* * Reverse list... */ ml = mlist; mprev = NULL; while (ml) { mntlist *ml2 = ml->mnext; ml->mnext = mprev; mprev = ml; ml = ml2; } mlist = mprev; /* * Unmount all filesystems... */ for (ml = mlist; ml && !xerror; ml = ml->mnext) { char *dir = ml->mnt->mnt_dir; if (directory_prefix(mf->mf_mount, dir)) { int error; dlog("amfs_host: unmounts %s", dir); /* * Unmount "dir" */ error = UMOUNT_FS(dir, mnttab_file_name, unmount_flags); /* * Keep track of errors */ if (error) { /* * If we have not already set xerror and error is not ENOENT, * then set xerror equal to error and log it. * 'xerror' is the return value for this function. * * We do not want to pass ENOENT as an error because if the * directory does not exists our work is done anyway. */ if (!xerror && error != ENOENT) xerror = error; if (error != EBUSY) { errno = error; plog(XLOG_ERROR, "Tree unmount of %s failed: %m", ml->mnt->mnt_dir); } } else { (void) rmdirs(dir); } } } /* * Throw away mount list */ discard_mntlist(mlist); /* * Try to remount, except when we are shutting down. */ if (xerror && amd_state != Finishing) { xerror = amfs_host_mount(am, mf); if (!xerror) { /* * Don't log this - it's usually too verbose plog(XLOG_INFO, "Remounted host %s", mf->mf_info); */ xerror = EBUSY; } } return xerror; }
/* * Pick a file system to try mounting and * do that in the background if necessary * For each location: discard previous mount location if required fetch next mount location if the filesystem failed to be mounted then this_error = error from filesystem goto failed if the filesystem is mounting or unmounting then goto retry; if the fileserver is down then this_error = EIO continue; if the filesystem is already mounted break fi this_error = initialize mount point if no error on this mount and mount is delayed then this_error = -1 fi if this_error < 0 then retry = true fi if no error on this mount then if mount in background then run mount in background return -1 else this_error = mount in foreground fi fi if an error occurred on this mount then update stats save error in mount point fi endfor */ static int amfs_bgmount(struct continuation *cp) { am_node *mp = cp->mp; am_loc *loc; mntfs *mf; int this_error = -1; /* Per-mount error */ int hard_error = -1; /* Cumulative per-node error */ if (mp->am_al) free_loc(mp->am_al); /* * Try to mount each location. * At the end: * hard_error == 0 indicates something was mounted. * hard_error > 0 indicates everything failed with a hard error * hard_error < 0 indicates nothing could be mounted now */ for (mp->am_al = *cp->al; *cp->al; cp->al++, mp->am_al = *cp->al) { am_ops *p; loc = dup_loc(mp->am_al); mf = loc->al_mnt; p = mf->mf_ops; if (hard_error < 0) hard_error = this_error; this_error = 0; if (mf->mf_error > 0) { this_error = mf->mf_error; goto failed; } if (mf->mf_flags & (MFF_MOUNTING | MFF_UNMOUNTING)) { /* * Still mounting - retry later */ dlog("mount of \"%s\" already pending", mf->mf_info); goto retry; } if (FSRV_ISDOWN(mf->mf_server)) { /* * Would just mount from the same place * as a hung mount - so give up */ dlog("%s is already hung - giving up", mf->mf_server->fs_host); this_error = EIO; goto failed; } XFREE(mp->am_link); mp->am_link = NULL; if (loc->al_fo && loc->al_fo->opt_sublink && loc->al_fo->opt_sublink[0]) mp->am_link = xstrdup(loc->al_fo->opt_sublink); /* * Will usually need to play around with the mount nodes * file attribute structure. This must be done here. * Try and get things initialized, even if the fileserver * is not known to be up. In the common case this will * progress things faster. */ /* * Fill in attribute fields. */ if (mf->mf_fsflags & FS_DIRECTORY) mk_fattr(&mp->am_fattr, NFDIR); else mk_fattr(&mp->am_fattr, NFLNK); if (mf->mf_flags & MFF_MOUNTED) { dlog("duplicate mount of \"%s\" ...", mf->mf_info); /* * Skip initial processing of the mountpoint if already mounted. * This could happen if we have multiple sublinks into the same f/s, * or if we are restarting an already-mounted filesystem. */ goto already_mounted; } if (mf->mf_fo && mf->mf_fo->fs_mtab) { plog(XLOG_MAP, "Trying mount of %s on %s fstype %s mount_type %s", mf->mf_fo->fs_mtab, mf->mf_mount, p->fs_type, mp->am_flags & AMF_AUTOFS ? "autofs" : "non-autofs"); } if (p->fs_init && !(mf->mf_flags & MFF_RESTART)) this_error = p->fs_init(mf); if (this_error > 0) goto failed; if (this_error < 0) goto retry; if (loc->al_fo && loc->al_fo->opt_delay) { /* * If there is a delay timer on the location * then don't try to mount if the timer * has not expired. */ int i = atoi(loc->al_fo->opt_delay); time_t now = clocktime(NULL); if (i > 0 && now < (cp->start + i)) { dlog("Mount of %s delayed by %lds", mf->mf_mount, (long) (i - now + cp->start)); goto retry; } } /* * If the directory is not yet made and it needs to be made, then make it! */ if (!(mf->mf_flags & MFF_MKMNT) && mf->mf_fsflags & FS_MKMNT) { plog(XLOG_INFO, "creating mountpoint directory '%s'", mf->mf_mount); this_error = mkdirs(mf->mf_mount, 0555); if (this_error) { plog(XLOG_ERROR, "mkdirs failed: %s", strerror(this_error)); goto failed; } mf->mf_flags |= MFF_MKMNT; } #ifdef HAVE_FS_AUTOFS if (mf->mf_flags & MFF_IS_AUTOFS) if ((this_error = autofs_get_fh(mp))) goto failed; #endif /* HAVE_FS_AUTOFS */ already_mounted: mf->mf_flags |= MFF_MOUNTING; if (mf->mf_fsflags & FS_MBACKGROUND) { dlog("backgrounding mount of \"%s\"", mf->mf_mount); if (cp->callout) { untimeout(cp->callout); cp->callout = 0; } /* actually run the task, backgrounding as necessary */ run_task(mount_node, (opaque_t) mp, amfs_cont, (opaque_t) cp); return -1; } else { dlog("foreground mount of \"%s\" ...", mf->mf_mount); this_error = mount_node((opaque_t) mp); } mf->mf_flags &= ~MFF_MOUNTING; if (this_error > 0) goto failed; if (this_error == 0) { am_mounted(mp); break; /* Success */ } retry: if (!cp->retry) continue; dlog("will retry ...\n"); /* * Arrange that amfs_bgmount is called * after anything else happens. */ dlog("Arranging to retry mount of %s", mp->am_path); sched_task(amfs_retry, (opaque_t) cp, get_mntfs_wchan(mf)); if (cp->callout) untimeout(cp->callout); cp->callout = timeout(RETRY_INTERVAL, wakeup, (opaque_t) get_mntfs_wchan(mf)); mp->am_ttl = clocktime(NULL) + RETRY_INTERVAL; /* * Not done yet - so don't return anything */ return -1; failed: if (!FSRV_ISDOWN(mf->mf_server)) { /* mark the mount as failed unless the server is down */ amd_stats.d_merr++; mf->mf_error = this_error; mf->mf_flags |= MFF_ERROR; #ifdef HAVE_FS_AUTOFS if (mp->am_autofs_fh) autofs_release_fh(mp); #endif /* HAVE_FS_AUTOFS */ if (mf->mf_flags & MFF_MKMNT) { rmdirs(mf->mf_mount); mf->mf_flags &= ~MFF_MKMNT; } } /* * Wakeup anything waiting for this mount */ wakeup(get_mntfs_wchan(mf)); free_loc(loc); /* continue */ } /* * If we get here, then either the mount succeeded or * there is no more mount information available. */ if (this_error) { if (mp->am_al) free_loc(mp->am_al); mp->am_al = loc = new_loc(); mf = loc->al_mnt; #ifdef HAVE_FS_AUTOFS if (mp->am_flags & AMF_AUTOFS) autofs_mount_failed(mp); else #endif /* HAVE_FS_AUTOFS */ nfs_quick_reply(mp, this_error); if (hard_error <= 0) hard_error = this_error; if (hard_error < 0) hard_error = ETIMEDOUT; /* * Set a small(ish) timeout on an error node if * the error was not a time out. */ switch (hard_error) { case ETIMEDOUT: case EWOULDBLOCK: case EIO: mp->am_timeo = 17; break; default: mp->am_timeo = 5; break; } new_ttl(mp); } else { mf = loc->al_mnt; /* * Wakeup anything waiting for this mount */ wakeup(get_mntfs_wchan(mf)); hard_error = 0; } /* * Make sure that the error value in the mntfs has a * reasonable value. */ if (mf->mf_error < 0) { mf->mf_error = hard_error; if (hard_error) mf->mf_flags |= MFF_ERROR; } /* * In any case we don't need the continuation any more */ free_continuation(cp); return hard_error; }
void am_unmounted(am_node *mp) { mntfs *mf = mp->am_mnt; if (!foreground) /* firewall - should never happen */ return; /* * Do unmounted callback */ if (mf->mf_ops->umounted) mf->mf_ops->umounted(mf); /* * This is ugly, but essentially unavoidable. * Sublinks must be treated separately as type==link * when the base type is different. */ if (mp->am_link && mp->am_mnt->mf_ops != &amfs_link_ops) amfs_link_ops.umount_fs(mp, mp->am_mnt); #ifdef HAVE_FS_AUTOFS if (mp->am_flags & AMF_AUTOFS) autofs_umount_succeeded(mp); #endif /* HAVE_FS_AUTOFS */ /* * Clean up any directories that were made * * If we remove the mount point of a pending mount, any queued access * to it will fail. So don't do it in that case. */ if (mf->mf_flags & MFF_MKMNT && !(mp->am_flags & AMF_REMOUNT)) { plog(XLOG_INFO, "removing mountpoint directory '%s'", mf->mf_real_mount); rmdirs(mf->mf_real_mount); } /* * If this is a pseudo-directory then adjust the link count * in the parent */ if (mp->am_parent && mp->am_fattr.na_type == NFDIR) --mp->am_parent->am_fattr.na_nlink; /* * Update mtime of parent node */ if (mp->am_parent && mp->am_parent->am_mnt) mp->am_parent->am_fattr.na_mtime.nt_seconds = clocktime(); if (mp->am_flags & AMF_REMOUNT) { char *fname = strdup(mp->am_name); am_node *mp_parent = mp->am_parent; int error = 0; free_map(mp); plog(XLOG_INFO, "am_unmounted: remounting %s", fname); mp = amfs_auto_lookup_child(mp_parent, fname, &error, VLOOK_CREATE); if (mp && error < 0) mp = amfs_auto_mount_child(mp, &error); if (error > 0) { errno = error; plog(XLOG_ERROR, "am_unmounted: could not remount %s: %m", fname); } XFREE(fname); } else free_map(mp); }
/* * Unmount an NFS hierarchy. * Note that this is called in the foreground * and so may hang under extremely rare conditions. */ static int nfsx_fumount(mntfs *mf) { struct nfsx *nx = (struct nfsx *) mf->mf_private; nfsx_mnt *n; int glob_error = 0; /* * Iterate in reverse through the mntfs's and unmount each filesystem * which is mounted. */ for (n = nx->nx_v + nx->nx_c - 1; n >= nx->nx_v; --n) { mntfs *m = n->n_mnt; /* * If this node has not been messed with * and there has been no error so far * then try and unmount. * If an error had occured then zero * the error code so that the remount * only tries to unmount those nodes * which had been successfully unmounted. */ if (n->n_error == 0) { #ifdef DEBUG dlog("calling underlying fumount on %s", m->mf_mount); #endif n->n_error = (*m->mf_ops->fumount_fs)(m); if (n->n_error) { glob_error = n->n_error; n->n_error = 0; } else { /* * Make sure remount gets this node */ n->n_error = -1; } } } /* * If any unmounts failed then remount the * whole lot... */ if (glob_error) { glob_error = nfsx_remount(mf, TRUE); if (glob_error) { errno = glob_error; /* XXX */ plog(XLOG_USER, "nfsx: remount of %s failed: %m", mf->mf_mount); } glob_error = EBUSY; } else { /* * Remove all the mount points */ for (n = nx->nx_v; n < nx->nx_v + nx->nx_c; n++) { mntfs *m = n->n_mnt; if (n->n_error < 0) { if (m->mf_ops->fs_flags & FS_MKMNT) { (void) rmdirs(m->mf_mount); m->mf_flags &= ~MFF_MKMNT; } } free_mntfs(m); n->n_mnt = 0; n->n_error = -1; } } return glob_error; }
void am_unmounted(am_node *mp) { mntfs *mf = mp->am_al->al_mnt; if (!foreground) { /* firewall - should never happen */ /* * This is a coding error. Make sure we hear about it! */ plog(XLOG_FATAL, "am_unmounted: illegal use in background (%s)", mp->am_name); notify_child(mp, AMQ_UMNT_OK, 0, 0); /* XXX - be safe? */ return; } /* * Do unmounted callback */ if (mf->mf_ops->umounted) mf->mf_ops->umounted(mf); /* * This is ugly, but essentially unavoidable. * Sublinks must be treated separately as type==link * when the base type is different. */ if (mp->am_link && mf->mf_ops != &amfs_link_ops) amfs_link_ops.umount_fs(mp, mf); #ifdef HAVE_FS_AUTOFS if (mf->mf_flags & MFF_IS_AUTOFS) autofs_release_fh(mp); if (mp->am_flags & AMF_AUTOFS) autofs_umount_succeeded(mp); #endif /* HAVE_FS_AUTOFS */ /* * Clean up any directories that were made * * If we remove the mount point of a pending mount, any queued access * to it will fail. So don't do it in that case. * Also don't do it if the refcount is > 1. */ if (mf->mf_flags & MFF_MKMNT && mf->mf_refc == 1 && !(mp->am_flags & AMF_REMOUNT)) { plog(XLOG_INFO, "removing mountpoint directory '%s'", mf->mf_mount); rmdirs(mf->mf_mount); mf->mf_flags &= ~MFF_MKMNT; } /* * If this is a pseudo-directory then adjust the link count * in the parent */ if (mp->am_parent && mp->am_fattr.na_type == NFDIR) --mp->am_parent->am_fattr.na_nlink; /* * Update mtime of parent node */ if (mp->am_parent && mp->am_parent->am_al->al_mnt) clocktime(&mp->am_parent->am_fattr.na_mtime); if (mp->am_parent && (mp->am_flags & AMF_REMOUNT)) { char *fname = xstrdup(mp->am_name); am_node *mp_parent = mp->am_parent; mntfs *mf_parent = mp_parent->am_al->al_mnt; am_node fake_mp; int error = 0; /* * We need to use notify_child() after free_map(), so save enough * to do that in fake_mp. */ fake_mp.am_fd[1] = mp->am_fd[1]; mp->am_fd[1] = -1; free_map(mp); plog(XLOG_INFO, "am_unmounted: remounting %s", fname); mp = mf_parent->mf_ops->lookup_child(mp_parent, fname, &error, VLOOK_CREATE); if (mp && error < 0) (void)mf_parent->mf_ops->mount_child(mp, &error); if (error > 0) { errno = error; plog(XLOG_ERROR, "am_unmounted: could not remount %s: %m", fname); notify_child(&fake_mp, AMQ_UMNT_OK, 0, 0); } else { notify_child(&fake_mp, AMQ_UMNT_FAILED, EBUSY, 0); } XFREE(fname); } else { /* * We have a race here. * If this node has a pending mount and amd is going down (unmounting * everything in the process), then we could potentially free it here * while a struct continuation still has a reference to it. So when * amfs_cont is called, it blows up. * We avoid the race by refusing to free any nodes that have * pending mounts (defined as having a non-NULL am_alarray). */ notify_child(mp, AMQ_UMNT_OK, 0, 0); /* do this regardless */ if (!mp->am_alarray) free_map(mp); } }