ffs_suspend.c revision 306175
1/*- 2 * Copyright (c) 2012 The FreeBSD Foundation 3 * All rights reserved. 4 * 5 * This software was developed by Edward Tomasz Napierala under sponsorship 6 * from the FreeBSD Foundation. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 * $FreeBSD: stable/10/sys/ufs/ffs/ffs_suspend.c 306175 2016-09-22 10:42:40Z kib $ 30 */ 31 32#include <sys/cdefs.h> 33__FBSDID("$FreeBSD: stable/10/sys/ufs/ffs/ffs_suspend.c 306175 2016-09-22 10:42:40Z kib $"); 34 35#include <sys/param.h> 36#include <sys/systm.h> 37#include <sys/ioccom.h> 38#include <sys/mount.h> 39#include <sys/vnode.h> 40#include <sys/conf.h> 41#include <sys/jail.h> 42#include <sys/sx.h> 43 44#include <security/mac/mac_framework.h> 45 46#include <ufs/ufs/extattr.h> 47#include <ufs/ufs/quota.h> 48#include <ufs/ufs/ufsmount.h> 49#include <ufs/ufs/inode.h> 50 51#include <ufs/ffs/fs.h> 52#include <ufs/ffs/ffs_extern.h> 53 54static d_open_t ffs_susp_open; 55static d_write_t ffs_susp_rdwr; 56static d_ioctl_t ffs_susp_ioctl; 57 58static struct cdevsw ffs_susp_cdevsw = { 59 .d_version = D_VERSION, 60 .d_open = ffs_susp_open, 61 .d_read = ffs_susp_rdwr, 62 .d_write = ffs_susp_rdwr, 63 .d_ioctl = ffs_susp_ioctl, 64 .d_name = "ffs_susp", 65}; 66 67static struct cdev *ffs_susp_dev; 68static struct sx ffs_susp_lock; 69 70static int 71ffs_susp_suspended(struct mount *mp) 72{ 73 struct ufsmount *ump; 74 75 sx_assert(&ffs_susp_lock, SA_LOCKED); 76 77 ump = VFSTOUFS(mp); 78 if (ump->um_writesuspended) 79 return (1); 80 return (0); 81} 82 83static int 84ffs_susp_open(struct cdev *dev __unused, int flags __unused, 85 int fmt __unused, struct thread *td __unused) 86{ 87 88 return (0); 89} 90 91static int 92ffs_susp_rdwr(struct cdev *dev, struct uio *uio, int ioflag) 93{ 94 int error, i; 95 struct vnode *devvp; 96 struct mount *mp; 97 struct ufsmount *ump; 98 struct buf *bp; 99 void *base; 100 size_t len; 101 ssize_t cnt; 102 struct fs *fs; 103 104 sx_slock(&ffs_susp_lock); 105 106 error = devfs_get_cdevpriv((void **)&mp); 107 if (error != 0) { 108 sx_sunlock(&ffs_susp_lock); 109 return (ENXIO); 110 } 111 112 ump = VFSTOUFS(mp); 113 devvp = ump->um_devvp; 114 fs = ump->um_fs; 115 116 if (ffs_susp_suspended(mp) == 0) { 117 sx_sunlock(&ffs_susp_lock); 118 return (ENXIO); 119 } 120 121 KASSERT(uio->uio_rw == UIO_READ || uio->uio_rw == UIO_WRITE, 122 ("neither UIO_READ or UIO_WRITE")); 123 KASSERT(uio->uio_segflg == UIO_USERSPACE, 124 ("uio->uio_segflg != UIO_USERSPACE")); 125 126 cnt = uio->uio_resid; 127 128 for (i = 0; i < uio->uio_iovcnt; i++) { 129 while (uio->uio_iov[i].iov_len) { 130 base = uio->uio_iov[i].iov_base; 131 len = uio->uio_iov[i].iov_len; 132 if (len > fs->fs_bsize) 133 len = fs->fs_bsize; 134 if (fragoff(fs, uio->uio_offset) != 0 || 135 fragoff(fs, len) != 0) { 136 error = EINVAL; 137 goto out; 138 } 139 error = bread(devvp, btodb(uio->uio_offset), len, 140 NOCRED, &bp); 141 if (error != 0) 142 goto out; 143 if (uio->uio_rw == UIO_WRITE) { 144 error = copyin(base, bp->b_data, len); 145 if (error != 0) { 146 bp->b_flags |= B_INVAL | B_NOCACHE; 147 brelse(bp); 148 goto out; 149 } 150 error = bwrite(bp); 151 if (error != 0) 152 goto out; 153 } else { 154 error = copyout(bp->b_data, base, len); 155 brelse(bp); 156 if (error != 0) 157 goto out; 158 } 159 uio->uio_iov[i].iov_base = 160 (char *)uio->uio_iov[i].iov_base + len; 161 uio->uio_iov[i].iov_len -= len; 162 uio->uio_resid -= len; 163 uio->uio_offset += len; 164 } 165 } 166 167out: 168 sx_sunlock(&ffs_susp_lock); 169 170 if (uio->uio_resid < cnt) 171 return (0); 172 173 return (error); 174} 175 176static int 177ffs_susp_suspend(struct mount *mp) 178{ 179 struct ufsmount *ump; 180 int error; 181 182 sx_assert(&ffs_susp_lock, SA_XLOCKED); 183 184 if (!ffs_own_mount(mp)) 185 return (EINVAL); 186 if (ffs_susp_suspended(mp)) 187 return (EBUSY); 188 189 ump = VFSTOUFS(mp); 190 191 /* 192 * Make sure the calling thread is permitted to access the mounted 193 * device. The permissions can change after we unlock the vnode; 194 * it's harmless. 195 */ 196 vn_lock(ump->um_devvp, LK_EXCLUSIVE | LK_RETRY); 197 error = VOP_ACCESS(ump->um_devvp, VREAD | VWRITE, 198 curthread->td_ucred, curthread); 199 VOP_UNLOCK(ump->um_devvp, 0); 200 if (error != 0) 201 return (error); 202#ifdef MAC 203 if (mac_mount_check_stat(curthread->td_ucred, mp) != 0) 204 return (EPERM); 205#endif 206 207 if ((error = vfs_write_suspend(mp, VS_SKIP_UNMOUNT)) != 0) 208 return (error); 209 210 ump->um_writesuspended = 1; 211 212 return (0); 213} 214 215static void 216ffs_susp_dtor(void *data) 217{ 218 struct fs *fs; 219 struct ufsmount *ump; 220 struct mount *mp; 221 int error; 222 223 sx_xlock(&ffs_susp_lock); 224 225 mp = (struct mount *)data; 226 ump = VFSTOUFS(mp); 227 fs = ump->um_fs; 228 229 if (ffs_susp_suspended(mp) == 0) { 230 sx_xunlock(&ffs_susp_lock); 231 return; 232 } 233 234 KASSERT((mp->mnt_kern_flag & MNTK_SUSPEND) != 0, 235 ("MNTK_SUSPEND not set")); 236 237 error = ffs_reload(mp, curthread, FFSR_FORCE | FFSR_UNSUSPEND); 238 if (error != 0) 239 panic("failed to unsuspend writes on %s", fs->fs_fsmnt); 240 241 /* 242 * XXX: The status is kept per-process; the vfs_write_resume() routine 243 * asserts that the resuming thread is the same one that called 244 * vfs_write_suspend(). The cdevpriv data, however, is attached 245 * to the file descriptor, e.g. is inherited during fork. Thus, 246 * it's possible that the resuming process will be different from 247 * the one that started the suspension. 248 * 249 * Work around by fooling the check in vfs_write_resume(). 250 */ 251 mp->mnt_susp_owner = curthread; 252 253 vfs_write_resume(mp, 0); 254 vfs_unbusy(mp); 255 ump->um_writesuspended = 0; 256 257 sx_xunlock(&ffs_susp_lock); 258} 259 260static int 261ffs_susp_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, 262 struct thread *td) 263{ 264 struct mount *mp; 265 fsid_t *fsidp; 266 int error; 267 268 /* 269 * No suspend inside the jail. Allowing it would require making 270 * sure that e.g. the devfs ruleset for that jail permits access 271 * to the devvp. 272 */ 273 if (jailed(td->td_ucred)) 274 return (EPERM); 275 276 sx_xlock(&ffs_susp_lock); 277 278 switch (cmd) { 279 case UFSSUSPEND: 280 fsidp = (fsid_t *)addr; 281 mp = vfs_getvfs(fsidp); 282 if (mp == NULL) { 283 error = ENOENT; 284 break; 285 } 286 error = vfs_busy(mp, 0); 287 vfs_rel(mp); 288 if (error != 0) 289 break; 290 error = ffs_susp_suspend(mp); 291 if (error != 0) { 292 vfs_unbusy(mp); 293 break; 294 } 295 error = devfs_set_cdevpriv(mp, ffs_susp_dtor); 296 KASSERT(error == 0, ("devfs_set_cdevpriv failed")); 297 break; 298 case UFSRESUME: 299 error = devfs_get_cdevpriv((void **)&mp); 300 if (error != 0) 301 break; 302 /* 303 * This calls ffs_susp_dtor, which in turn unsuspends the fs. 304 * The dtor expects to be called without lock held, because 305 * sometimes it's called from here, and sometimes due to the 306 * file being closed or process exiting. 307 */ 308 sx_xunlock(&ffs_susp_lock); 309 devfs_clear_cdevpriv(); 310 return (0); 311 default: 312 error = ENXIO; 313 break; 314 } 315 316 sx_xunlock(&ffs_susp_lock); 317 318 return (error); 319} 320 321void 322ffs_susp_initialize(void) 323{ 324 325 sx_init(&ffs_susp_lock, "ffs_susp"); 326 ffs_susp_dev = make_dev(&ffs_susp_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, 327 "ufssuspend"); 328} 329 330void 331ffs_susp_uninitialize(void) 332{ 333 334 destroy_dev(ffs_susp_dev); 335 sx_destroy(&ffs_susp_lock); 336} 337