1/*	$NetBSD$	*/
2
3/*
4 * Copyright (c) 1997-2009 Erez Zadok
5 * Copyright (c) 1990 Jan-Simon Pendry
6 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
7 * Copyright (c) 1990 The Regents of the University of California.
8 * All rights reserved.
9 *
10 * This code is derived from software contributed to Berkeley by
11 * Jan-Simon Pendry at Imperial College, London.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 *    notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 *    notice, this list of conditions and the following disclaimer in the
20 *    documentation and/or other materials provided with the distribution.
21 * 3. All advertising materials mentioning features or use of this software
22 *    must display the following acknowledgment:
23 *      This product includes software developed by the University of
24 *      California, Berkeley and its contributors.
25 * 4. Neither the name of the University nor the names of its contributors
26 *    may be used to endorse or promote products derived from this software
27 *    without specific prior written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 * SUCH DAMAGE.
40 *
41 *
42 * File: am-utils/conf/umount/umount_linux.c
43 *
44 */
45
46/*
47 * Linux method of unmounting filesystems: if umount(2) failed with EIO or
48 * ESTALE, try umount2(2) with MNT_FORCE+MNT_DETACH.
49 */
50
51#ifdef HAVE_CONFIG_H
52# include <config.h>
53#endif /* HAVE_CONFIG_H */
54#include <am_defs.h>
55#include <amu.h>
56
57
58int
59umount_fs(char *mntdir, const char *mnttabname, u_int unmount_flags)
60{
61  mntlist *mlist, *mp, *mp_save = NULL;
62  int error = 0;
63#ifdef HAVE_LOOP_DEVICE
64  char *opt, *xopts = NULL;
65  char loopstr[] = "loop=";
66  char *loopdev;
67#endif /* HAVE_LOOP_DEVICE */
68
69  mp = mlist = read_mtab(mntdir, mnttabname);
70
71  /*
72   * Search the mount table looking for
73   * the correct (ie last) matching entry
74   */
75  while (mp) {
76    if (STREQ(mp->mnt->mnt_dir, mntdir))
77      mp_save = mp;
78    mp = mp->mnext;
79  }
80
81  if (!mp_save) {
82    plog(XLOG_ERROR, "Couldn't find how to unmount %s", mntdir);
83    /* Assume it is already unmounted */
84    error = 0;
85    goto out;
86  }
87
88  dlog("Trying unmount(%s)", mp_save->mnt->mnt_dir);
89
90#ifdef MOUNT_TABLE_ON_FILE
91  /*
92   * This unmount may hang leaving this process with an exclusive lock on
93   * /etc/mtab. Therefore it is necessary to unlock mtab, do the unmount,
94   * then lock mtab (again) and reread it and finally update it.
95   */
96  unlock_mntlist();
97#endif /* MOUNT_TABLE_ON_FILE */
98
99#if defined(HAVE_UMOUNT2) && defined(MNT2_GEN_OPT_DETACH)
100  /*
101   * If user asked to try forced unmounts, then do a quick check to see if
102   * the mount point is hung badly.  If so, then try to detach it by
103   * force; if the latter works, we're done.
104   */
105  if (unmount_flags & AMU_UMOUNT_DETACH) {
106    /*
107     * Note: we pass both DETACH and FORCE flags, because umount2_fs below
108     * (on Linux), should try FORCE before DETACH (the latter always
109     * succeeds).
110     */
111    error = umount2_fs(mp_save->mnt->mnt_dir,
112		       unmount_flags & (AMU_UMOUNT_DETACH|AMU_UMOUNT_FORCE));
113  } else
114#endif /* defined(HAVE_UMOUNT2) && defined(MNT2_GEN_OPT_DETACH) */
115    error = UNMOUNT_TRAP(mp_save->mnt);
116  if (error < 0) {
117    plog(XLOG_WARNING, "unmount(%s) failed: %m", mp_save->mnt->mnt_dir);
118    switch ((error = errno)) {
119    case EINVAL:
120    case ENOTBLK:
121      plog(XLOG_WARNING, "unmount: %s is not mounted", mp_save->mnt->mnt_dir);
122      error = 0;		/* Not really an error */
123      break;
124
125    case ENOENT:
126      /*
127       * This could happen if the kernel insists on following symlinks
128       * when we try to unmount a direct mountpoint. We need to propagate
129       * the error up so that the top layers know it failed and don't
130       * try to rmdir() the mountpoint or other silly things.
131       */
132      plog(XLOG_ERROR, "mount point %s: %m", mp_save->mnt->mnt_dir);
133      break;
134
135#if defined(HAVE_UMOUNT2) && defined(MNT2_GEN_OPT_FORCE)
136    case EBUSY:
137      /*
138       * Caller determines if forced unmounts should be used now (for
139       * EBUSY).  If caller asked to force an unmount, *and* the above
140       * "trivial" unmount attempt failed with EBUSY, then try to force
141       * the unmount.
142       */
143      if (unmount_flags & AMU_UMOUNT_FORCE) {
144	error = umount2_fs(mp_save->mnt->mnt_dir,
145			   unmount_flags & AMU_UMOUNT_FORCE);
146	if (error < 0) {
147	  plog(XLOG_WARNING, "%s: unmount/force: %m",
148	       mp_save->mnt->mnt_dir);
149	  error = errno;
150	}
151      }
152      break;
153#endif /* defined(HAVE_UMOUNT2) && defined(MNT2_GEN_OPT_FORCE) */
154
155    default:
156      dlog("%s: unmount: %m", mp_save->mnt->mnt_dir);
157      break;
158    }
159  } else {
160    dlog("unmount(%s) succeeded", mp_save->mnt->mnt_dir);
161  }
162  dlog("Finished unmount(%s)", mp_save->mnt->mnt_dir);
163
164  /*
165   * If we are successful or there was an ENOENT, remove
166   * the mount entry from the mtab file.
167   */
168  if (error && error != ENOENT)
169    goto out;
170
171#ifdef HAVE_LOOP_DEVICE
172  /* look for loop=/dev/loopX in mnt_opts */
173  xopts = strdup(mp_save->mnt->mnt_opts); /* b/c strtok is destructive */
174  for (opt = strtok(xopts, ","); opt; opt = strtok(NULL, ","))
175    if (NSTREQ(opt, loopstr, sizeof(loopstr) - 1)) {
176      loopdev = opt + sizeof(loopstr) - 1;
177      if (delete_loop_device(loopdev) < 0)
178	plog(XLOG_WARNING, "unmount() failed to release loop device %s: %m", loopdev);
179      else
180	plog(XLOG_INFO, "unmount() released loop device %s OK", loopdev);
181      break;
182    }
183  if (xopts)
184    XFREE(xopts);
185#endif /* HAVE_LOOP_DEVICE */
186
187#ifdef MOUNT_TABLE_ON_FILE
188  free_mntlist(mlist);
189  mp = mlist = read_mtab(mntdir, mnttabname);
190
191  /*
192   * Search the mount table looking for
193   * the correct (ie last) matching entry
194   */
195  mp_save = NULL;
196  while (mp) {
197    if (STREQ(mp->mnt->mnt_dir, mntdir))
198      mp_save = mp;
199    mp = mp->mnext;
200  }
201
202  if (mp_save) {
203    mnt_free(mp_save->mnt);
204    mp_save->mnt = NULL;
205    rewrite_mtab(mlist, mnttabname);
206  }
207#endif /* MOUNT_TABLE_ON_FILE */
208
209 out:
210  free_mntlist(mlist);
211
212  return error;
213}
214
215
216#if defined(HAVE_UMOUNT2) && (defined(MNT2_GEN_OPT_FORCE) || defined(MNT2_GEN_OPT_DETACH))
217/*
218 * Force unmount, no questions asked, without touching mnttab file.  Try
219 * detach first because it is safer: will remove the hung mnt point without
220 * affecting hung applications.  "Force" is more risky: it will cause the
221 * kernel to return EIO to applications stuck on a stat(2) of Amd.
222 */
223int
224umount2_fs(const char *mntdir, u_int unmount_flags)
225{
226  int error = 0;
227
228#ifdef MNT2_GEN_OPT_DETACH
229  if (unmount_flags & AMU_UMOUNT_DETACH) {
230    error = umount2(mntdir, MNT2_GEN_OPT_DETACH);
231    if (error < 0 && (errno == EINVAL || errno == ENOENT))
232      error = 0;		/* ignore EINVAL/ENOENT */
233    if (error < 0) {		/* don't try FORCE if detach succeeded */
234      plog(XLOG_WARNING, "%s: unmount/detach: %m", mntdir);
235      /* fall through to try "force" (if flag specified) */
236    } else {
237      dlog("%s: unmount/detach: OK", mntdir);
238      return error;
239    }
240  }
241#endif /* MNT2_GEN_OPT_DETACH */
242
243#ifdef MNT2_GEN_OPT_FORCE
244  if (unmount_flags & AMU_UMOUNT_FORCE) {
245    plog(XLOG_INFO, "umount2_fs: trying unmount/forced on %s", mntdir);
246    error = umount2(mntdir, MNT2_GEN_OPT_FORCE);
247    if (error < 0 && (errno == EINVAL || errno == ENOENT))
248      error = 0;		/* ignore EINVAL/ENOENT */
249    if (error < 0)
250      plog(XLOG_WARNING, "%s: unmount/force: %m", mntdir);
251    else
252      dlog("%s: unmount/force: OK", mntdir);
253    /* fall through to return whatever error we got (if any) */
254  }
255#endif /* MNT2_GEN_OPT_FORCE */
256
257  return error;
258}
259#endif /* HAVE_UMOUNT2 && (MNT2_GEN_OPT_FORCE || MNT2_GEN_OPT_DETACH) */
260