1243245Strasz/*- 2243245Strasz * Copyright (c) 2012 The FreeBSD Foundation 3243245Strasz * All rights reserved. 4243245Strasz * 5243245Strasz * This software was developed by Edward Tomasz Napierala under sponsorship 6243245Strasz * from the FreeBSD Foundation. 7243245Strasz * 8243245Strasz * Redistribution and use in source and binary forms, with or without 9243245Strasz * modification, are permitted provided that the following conditions 10243245Strasz * are met: 11243245Strasz * 1. Redistributions of source code must retain the above copyright 12243245Strasz * notice, this list of conditions and the following disclaimer. 13243245Strasz * 2. Redistributions in binary form must reproduce the above copyright 14243245Strasz * notice, this list of conditions and the following disclaimer in the 15243245Strasz * documentation and/or other materials provided with the distribution. 16243245Strasz * 17243245Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18243245Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19243245Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20243245Strasz * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21243245Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22243245Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23243245Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24243245Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25243245Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26243245Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27243245Strasz * SUCH DAMAGE. 28243245Strasz * 29243245Strasz * $FreeBSD$ 30243245Strasz */ 31243245Strasz 32243245Strasz#include <sys/cdefs.h> 33243245Strasz__FBSDID("$FreeBSD$"); 34243245Strasz 35243245Strasz#include <sys/param.h> 36243245Strasz#include <sys/systm.h> 37243245Strasz#include <sys/ioccom.h> 38243245Strasz#include <sys/mount.h> 39243245Strasz#include <sys/vnode.h> 40243245Strasz#include <sys/conf.h> 41243245Strasz#include <sys/jail.h> 42243245Strasz#include <sys/sx.h> 43243245Strasz 44243245Strasz#include <security/mac/mac_framework.h> 45243245Strasz 46243245Strasz#include <ufs/ufs/extattr.h> 47243245Strasz#include <ufs/ufs/quota.h> 48243245Strasz#include <ufs/ufs/ufsmount.h> 49243245Strasz#include <ufs/ufs/inode.h> 50243245Strasz 51243245Strasz#include <ufs/ffs/fs.h> 52243245Strasz#include <ufs/ffs/ffs_extern.h> 53243245Strasz 54243245Straszstatic d_open_t ffs_susp_open; 55243245Straszstatic d_write_t ffs_susp_rdwr; 56243245Straszstatic d_ioctl_t ffs_susp_ioctl; 57243245Strasz 58243245Straszstatic struct cdevsw ffs_susp_cdevsw = { 59243245Strasz .d_version = D_VERSION, 60243245Strasz .d_open = ffs_susp_open, 61243245Strasz .d_read = ffs_susp_rdwr, 62243245Strasz .d_write = ffs_susp_rdwr, 63243245Strasz .d_ioctl = ffs_susp_ioctl, 64243245Strasz .d_name = "ffs_susp", 65243245Strasz}; 66243245Strasz 67243245Straszstatic struct cdev *ffs_susp_dev; 68243245Straszstatic struct sx ffs_susp_lock; 69243245Strasz 70243245Straszstatic int 71243245Straszffs_susp_suspended(struct mount *mp) 72243245Strasz{ 73243245Strasz struct ufsmount *ump; 74243245Strasz 75243245Strasz sx_assert(&ffs_susp_lock, SA_LOCKED); 76243245Strasz 77243245Strasz ump = VFSTOUFS(mp); 78243245Strasz if (ump->um_writesuspended) 79243245Strasz return (1); 80243245Strasz return (0); 81243245Strasz} 82243245Strasz 83243245Straszstatic int 84243245Straszffs_susp_open(struct cdev *dev __unused, int flags __unused, 85243245Strasz int fmt __unused, struct thread *td __unused) 86243245Strasz{ 87243245Strasz 88243245Strasz return (0); 89243245Strasz} 90243245Strasz 91243245Straszstatic int 92243245Straszffs_susp_rdwr(struct cdev *dev, struct uio *uio, int ioflag) 93243245Strasz{ 94243245Strasz int error, i; 95243245Strasz struct vnode *devvp; 96243245Strasz struct mount *mp; 97243245Strasz struct ufsmount *ump; 98243245Strasz struct buf *bp; 99243245Strasz void *base; 100243245Strasz size_t len; 101243245Strasz ssize_t cnt; 102243245Strasz struct fs *fs; 103243245Strasz 104243245Strasz sx_slock(&ffs_susp_lock); 105243245Strasz 106243245Strasz error = devfs_get_cdevpriv((void **)&mp); 107243245Strasz if (error != 0) { 108243245Strasz sx_sunlock(&ffs_susp_lock); 109243245Strasz return (ENXIO); 110243245Strasz } 111243245Strasz 112243245Strasz ump = VFSTOUFS(mp); 113243245Strasz devvp = ump->um_devvp; 114243245Strasz fs = ump->um_fs; 115243245Strasz 116243245Strasz if (ffs_susp_suspended(mp) == 0) { 117243245Strasz sx_sunlock(&ffs_susp_lock); 118243245Strasz return (ENXIO); 119243245Strasz } 120243245Strasz 121243245Strasz KASSERT(uio->uio_rw == UIO_READ || uio->uio_rw == UIO_WRITE, 122243245Strasz ("neither UIO_READ or UIO_WRITE")); 123243245Strasz KASSERT(uio->uio_segflg == UIO_USERSPACE, 124243245Strasz ("uio->uio_segflg != UIO_USERSPACE")); 125243245Strasz 126243245Strasz cnt = uio->uio_resid; 127243245Strasz 128243245Strasz for (i = 0; i < uio->uio_iovcnt; i++) { 129243245Strasz while (uio->uio_iov[i].iov_len) { 130243245Strasz base = uio->uio_iov[i].iov_base; 131243245Strasz len = uio->uio_iov[i].iov_len; 132243245Strasz if (len > fs->fs_bsize) 133243245Strasz len = fs->fs_bsize; 134243245Strasz if (fragoff(fs, uio->uio_offset) != 0 || 135243245Strasz fragoff(fs, len) != 0) { 136243245Strasz error = EINVAL; 137243245Strasz goto out; 138243245Strasz } 139243245Strasz error = bread(devvp, btodb(uio->uio_offset), len, 140243245Strasz NOCRED, &bp); 141243245Strasz if (error != 0) 142243245Strasz goto out; 143243245Strasz if (uio->uio_rw == UIO_WRITE) { 144243245Strasz error = copyin(base, bp->b_data, len); 145243245Strasz if (error != 0) { 146243245Strasz bp->b_flags |= B_INVAL | B_NOCACHE; 147243245Strasz brelse(bp); 148243245Strasz goto out; 149243245Strasz } 150243245Strasz error = bwrite(bp); 151243245Strasz if (error != 0) 152243245Strasz goto out; 153243245Strasz } else { 154243245Strasz error = copyout(bp->b_data, base, len); 155243245Strasz brelse(bp); 156243245Strasz if (error != 0) 157243245Strasz goto out; 158243245Strasz } 159243245Strasz uio->uio_iov[i].iov_base = 160243245Strasz (char *)uio->uio_iov[i].iov_base + len; 161243245Strasz uio->uio_iov[i].iov_len -= len; 162243245Strasz uio->uio_resid -= len; 163243245Strasz uio->uio_offset += len; 164243245Strasz } 165243245Strasz } 166243245Strasz 167243245Straszout: 168243245Strasz sx_sunlock(&ffs_susp_lock); 169243245Strasz 170243245Strasz if (uio->uio_resid < cnt) 171243245Strasz return (0); 172243245Strasz 173243245Strasz return (error); 174243245Strasz} 175243245Strasz 176243245Straszstatic int 177243245Straszffs_susp_suspend(struct mount *mp) 178243245Strasz{ 179243245Strasz struct fs *fs; 180243245Strasz struct ufsmount *ump; 181243245Strasz int error; 182243245Strasz 183243245Strasz sx_assert(&ffs_susp_lock, SA_XLOCKED); 184243245Strasz 185243245Strasz if (!ffs_own_mount(mp)) 186243245Strasz return (EINVAL); 187243245Strasz if (ffs_susp_suspended(mp)) 188243245Strasz return (EBUSY); 189243245Strasz 190243245Strasz ump = VFSTOUFS(mp); 191243245Strasz fs = ump->um_fs; 192243245Strasz 193243245Strasz /* 194243245Strasz * Make sure the calling thread is permitted to access the mounted 195243245Strasz * device. The permissions can change after we unlock the vnode; 196243245Strasz * it's harmless. 197243245Strasz */ 198243245Strasz vn_lock(ump->um_devvp, LK_EXCLUSIVE | LK_RETRY); 199243245Strasz error = VOP_ACCESS(ump->um_devvp, VREAD | VWRITE, 200243245Strasz curthread->td_ucred, curthread); 201243245Strasz VOP_UNLOCK(ump->um_devvp, 0); 202243245Strasz if (error != 0) 203243245Strasz return (error); 204243245Strasz#ifdef MAC 205243245Strasz if (mac_mount_check_stat(curthread->td_ucred, mp) != 0) 206243245Strasz return (EPERM); 207243245Strasz#endif 208243245Strasz 209253106Skib if ((error = vfs_write_suspend(mp, VS_SKIP_UNMOUNT)) != 0) 210243245Strasz return (error); 211243245Strasz 212243245Strasz ump->um_writesuspended = 1; 213243245Strasz 214243245Strasz return (0); 215243245Strasz} 216243245Strasz 217243245Straszstatic void 218243245Straszffs_susp_dtor(void *data) 219243245Strasz{ 220243245Strasz struct fs *fs; 221243245Strasz struct ufsmount *ump; 222243245Strasz struct mount *mp; 223243245Strasz int error; 224243245Strasz 225243245Strasz sx_xlock(&ffs_susp_lock); 226243245Strasz 227243245Strasz mp = (struct mount *)data; 228243245Strasz ump = VFSTOUFS(mp); 229243245Strasz fs = ump->um_fs; 230243245Strasz 231243245Strasz if (ffs_susp_suspended(mp) == 0) { 232243245Strasz sx_xunlock(&ffs_susp_lock); 233243245Strasz return; 234243245Strasz } 235243245Strasz 236243245Strasz KASSERT((mp->mnt_kern_flag & MNTK_SUSPEND) != 0, 237243245Strasz ("MNTK_SUSPEND not set")); 238243245Strasz 239243245Strasz error = ffs_reload(mp, curthread, 1); 240243245Strasz if (error != 0) 241243245Strasz panic("failed to unsuspend writes on %s", fs->fs_fsmnt); 242243245Strasz 243243245Strasz /* 244243245Strasz * XXX: The status is kept per-process; the vfs_write_resume() routine 245243245Strasz * asserts that the resuming thread is the same one that called 246243245Strasz * vfs_write_suspend(). The cdevpriv data, however, is attached 247243245Strasz * to the file descriptor, e.g. is inherited during fork. Thus, 248243245Strasz * it's possible that the resuming process will be different from 249243245Strasz * the one that started the suspension. 250243245Strasz * 251243245Strasz * Work around by fooling the check in vfs_write_resume(). 252243245Strasz */ 253243245Strasz mp->mnt_susp_owner = curthread; 254243245Strasz 255245286Skib vfs_write_resume(mp, 0); 256243245Strasz vfs_unbusy(mp); 257243245Strasz ump->um_writesuspended = 0; 258243245Strasz 259243245Strasz sx_xunlock(&ffs_susp_lock); 260243245Strasz} 261243245Strasz 262243245Straszstatic int 263243245Straszffs_susp_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, 264243245Strasz struct thread *td) 265243245Strasz{ 266243245Strasz struct mount *mp; 267243245Strasz fsid_t *fsidp; 268243245Strasz int error; 269243245Strasz 270243245Strasz /* 271243245Strasz * No suspend inside the jail. Allowing it would require making 272243245Strasz * sure that e.g. the devfs ruleset for that jail permits access 273243245Strasz * to the devvp. 274243245Strasz */ 275243245Strasz if (jailed(td->td_ucred)) 276243245Strasz return (EPERM); 277243245Strasz 278243245Strasz sx_xlock(&ffs_susp_lock); 279243245Strasz 280243245Strasz switch (cmd) { 281243245Strasz case UFSSUSPEND: 282243245Strasz fsidp = (fsid_t *)addr; 283243245Strasz mp = vfs_getvfs(fsidp); 284243245Strasz if (mp == NULL) { 285243245Strasz error = ENOENT; 286243245Strasz break; 287243245Strasz } 288243245Strasz error = vfs_busy(mp, 0); 289243245Strasz vfs_rel(mp); 290243245Strasz if (error != 0) 291243245Strasz break; 292243245Strasz error = ffs_susp_suspend(mp); 293243245Strasz if (error != 0) { 294243245Strasz vfs_unbusy(mp); 295243245Strasz break; 296243245Strasz } 297243245Strasz error = devfs_set_cdevpriv(mp, ffs_susp_dtor); 298243245Strasz KASSERT(error == 0, ("devfs_set_cdevpriv failed")); 299243245Strasz break; 300243245Strasz case UFSRESUME: 301243245Strasz error = devfs_get_cdevpriv((void **)&mp); 302243245Strasz if (error != 0) 303243245Strasz break; 304243245Strasz /* 305243245Strasz * This calls ffs_susp_dtor, which in turn unsuspends the fs. 306243245Strasz * The dtor expects to be called without lock held, because 307243245Strasz * sometimes it's called from here, and sometimes due to the 308243245Strasz * file being closed or process exiting. 309243245Strasz */ 310243245Strasz sx_xunlock(&ffs_susp_lock); 311243245Strasz devfs_clear_cdevpriv(); 312243245Strasz return (0); 313243245Strasz default: 314243245Strasz error = ENXIO; 315243245Strasz break; 316243245Strasz } 317243245Strasz 318243245Strasz sx_xunlock(&ffs_susp_lock); 319243245Strasz 320243245Strasz return (error); 321243245Strasz} 322243245Strasz 323243245Straszvoid 324243245Straszffs_susp_initialize(void) 325243245Strasz{ 326243245Strasz 327243245Strasz sx_init(&ffs_susp_lock, "ffs_susp"); 328243245Strasz ffs_susp_dev = make_dev(&ffs_susp_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, 329243245Strasz "ufssuspend"); 330243245Strasz} 331243245Strasz 332243245Straszvoid 333243245Straszffs_susp_uninitialize(void) 334243245Strasz{ 335243245Strasz 336243245Strasz destroy_dev(ffs_susp_dev); 337243245Strasz sx_destroy(&ffs_susp_lock); 338243245Strasz} 339