| 1 | /* |
| 2 | * Copyright (c) 1994 The Regents of the University of California. |
| 3 | * Copyright (c) 1994 Jan-Simon Pendry. |
| 4 | * All rights reserved. |
| 5 | * |
| 6 | * This code is derived from software donated to Berkeley by |
| 7 | * Jan-Simon Pendry. |
| 8 | * |
| 9 | * %sccs.include.redist.c% |
| 10 | * |
| 11 | * @(#)union_vfsops.c 8.8 (Berkeley) %G% |
| 12 | */ |
| 13 | |
| 14 | /* |
| 15 | * Union Layer |
| 16 | */ |
| 17 | |
| 18 | #include <sys/param.h> |
| 19 | #include <sys/systm.h> |
| 20 | #include <sys/time.h> |
| 21 | #include <sys/types.h> |
| 22 | #include <sys/proc.h> |
| 23 | #include <sys/vnode.h> |
| 24 | #include <sys/mount.h> |
| 25 | #include <sys/namei.h> |
| 26 | #include <sys/malloc.h> |
| 27 | #include <sys/filedesc.h> |
| 28 | #include <sys/queue.h> |
| 29 | #include <miscfs/union/union.h> |
| 30 | |
| 31 | /* |
| 32 | * Mount union filesystem |
| 33 | */ |
| 34 | int |
| 35 | union_mount(mp, path, data, ndp, p) |
| 36 | struct mount *mp; |
| 37 | char *path; |
| 38 | caddr_t data; |
| 39 | struct nameidata *ndp; |
| 40 | struct proc *p; |
| 41 | { |
| 42 | int error = 0; |
| 43 | struct union_args args; |
| 44 | struct vnode *lowerrootvp = NULLVP; |
| 45 | struct vnode *upperrootvp = NULLVP; |
| 46 | struct union_mount *um; |
| 47 | struct ucred *cred = 0; |
| 48 | struct ucred *scred; |
| 49 | struct vattr va; |
| 50 | char *cp; |
| 51 | int len; |
| 52 | u_int size; |
| 53 | |
| 54 | #ifdef UNION_DIAGNOSTIC |
| 55 | printf("union_mount(mp = %x)\n", mp); |
| 56 | #endif |
| 57 | |
| 58 | /* |
| 59 | * Update is a no-op |
| 60 | */ |
| 61 | if (mp->mnt_flag & MNT_UPDATE) { |
| 62 | /* |
| 63 | * Need to provide. |
| 64 | * 1. a way to convert between rdonly and rdwr mounts. |
| 65 | * 2. support for nfs exports. |
| 66 | */ |
| 67 | error = EOPNOTSUPP; |
| 68 | goto bad; |
| 69 | } |
| 70 | |
| 71 | /* |
| 72 | * Take a copy of the process's credentials. This isn't |
| 73 | * quite right since the euid will always be zero and we |
| 74 | * want to get the "real" users credentials. So fix up |
| 75 | * the uid field after taking the copy. |
| 76 | */ |
| 77 | cred = crdup(p->p_ucred); |
| 78 | cred->cr_uid = p->p_cred->p_ruid; |
| 79 | |
| 80 | /* |
| 81 | * Ensure the *real* user has write permission on the |
| 82 | * mounted-on directory. This allows the mount_union |
| 83 | * command to be made setuid root so allowing anyone |
| 84 | * to do union mounts onto any directory on which they |
| 85 | * have write permission and which they also own. |
| 86 | */ |
| 87 | error = VOP_GETATTR(mp->mnt_vnodecovered, &va, cred, p); |
| 88 | if (error) |
| 89 | goto bad; |
| 90 | if ((va.va_uid != cred->cr_uid) && |
| 91 | (cred->cr_uid != 0)) { |
| 92 | error = EACCES; |
| 93 | goto bad; |
| 94 | } |
| 95 | error = VOP_ACCESS(mp->mnt_vnodecovered, VWRITE, cred, p); |
| 96 | if (error) |
| 97 | goto bad; |
| 98 | |
| 99 | /* |
| 100 | * Get argument |
| 101 | */ |
| 102 | if (error = copyin(data, (caddr_t)&args, sizeof(struct union_args))) |
| 103 | goto bad; |
| 104 | |
| 105 | lowerrootvp = mp->mnt_vnodecovered; |
| 106 | VREF(lowerrootvp); |
| 107 | |
| 108 | /* |
| 109 | * Find upper node. Use the real process credentials, |
| 110 | * not the effective ones since this will have come |
| 111 | * through a setuid process (mount_union). All this |
| 112 | * messing around with permissions is entirely bogus |
| 113 | * and should be removed by allowing any user straight |
| 114 | * past the mount system call. |
| 115 | */ |
| 116 | scred = p->p_ucred; |
| 117 | p->p_ucred = cred; |
| 118 | NDINIT(ndp, LOOKUP, FOLLOW|WANTPARENT, |
| 119 | UIO_USERSPACE, args.target, p); |
| 120 | p->p_ucred = scred; |
| 121 | |
| 122 | if (error = namei(ndp)) |
| 123 | goto bad; |
| 124 | |
| 125 | upperrootvp = ndp->ni_vp; |
| 126 | vrele(ndp->ni_dvp); |
| 127 | ndp->ni_dvp = NULL; |
| 128 | |
| 129 | if (upperrootvp->v_type != VDIR) { |
| 130 | error = EINVAL; |
| 131 | goto bad; |
| 132 | } |
| 133 | |
| 134 | um = (struct union_mount *) malloc(sizeof(struct union_mount), |
| 135 | M_UFSMNT, M_WAITOK); /* XXX */ |
| 136 | |
| 137 | /* |
| 138 | * Keep a held reference to the target vnodes. |
| 139 | * They are vrele'd in union_unmount. |
| 140 | * |
| 141 | * Depending on the _BELOW flag, the filesystems are |
| 142 | * viewed in a different order. In effect, this is the |
| 143 | * same as providing a mount under option to the mount syscall. |
| 144 | */ |
| 145 | |
| 146 | um->um_op = args.mntflags & UNMNT_OPMASK; |
| 147 | switch (um->um_op) { |
| 148 | case UNMNT_ABOVE: |
| 149 | um->um_lowervp = lowerrootvp; |
| 150 | um->um_uppervp = upperrootvp; |
| 151 | break; |
| 152 | |
| 153 | case UNMNT_BELOW: |
| 154 | um->um_lowervp = upperrootvp; |
| 155 | um->um_uppervp = lowerrootvp; |
| 156 | break; |
| 157 | |
| 158 | case UNMNT_REPLACE: |
| 159 | vrele(lowerrootvp); |
| 160 | lowerrootvp = NULLVP; |
| 161 | um->um_uppervp = upperrootvp; |
| 162 | um->um_lowervp = lowerrootvp; |
| 163 | break; |
| 164 | |
| 165 | default: |
| 166 | error = EINVAL; |
| 167 | goto bad; |
| 168 | } |
| 169 | |
| 170 | um->um_cred = cred; |
| 171 | um->um_cmode = UN_DIRMODE &~ p->p_fd->fd_cmask; |
| 172 | |
| 173 | /* |
| 174 | * Depending on what you think the MNT_LOCAL flag might mean, |
| 175 | * you may want the && to be || on the conditional below. |
| 176 | * At the moment it has been defined that the filesystem is |
| 177 | * only local if it is all local, ie the MNT_LOCAL flag implies |
| 178 | * that the entire namespace is local. If you think the MNT_LOCAL |
| 179 | * flag implies that some of the files might be stored locally |
| 180 | * then you will want to change the conditional. |
| 181 | */ |
| 182 | if (um->um_op == UNMNT_ABOVE) { |
| 183 | if (((um->um_lowervp == NULLVP) || |
| 184 | (um->um_lowervp->v_mount->mnt_flag & MNT_LOCAL)) && |
| 185 | (um->um_uppervp->v_mount->mnt_flag & MNT_LOCAL)) |
| 186 | mp->mnt_flag |= MNT_LOCAL; |
| 187 | } |
| 188 | |
| 189 | /* |
| 190 | * Copy in the upper layer's RDONLY flag. This is for the benefit |
| 191 | * of lookup() which explicitly checks the flag, rather than asking |
| 192 | * the filesystem for it's own opinion. This means, that an update |
| 193 | * mount of the underlying filesystem to go from rdonly to rdwr |
| 194 | * will leave the unioned view as read-only. |
| 195 | */ |
| 196 | mp->mnt_flag |= (um->um_uppervp->v_mount->mnt_flag & MNT_RDONLY); |
| 197 | |
| 198 | /* |
| 199 | * This is a user mount. Privilege check for unmount |
| 200 | * will be done in union_unmount. |
| 201 | */ |
| 202 | mp->mnt_flag |= MNT_USER; |
| 203 | |
| 204 | mp->mnt_data = (qaddr_t) um; |
| 205 | getnewfsid(mp, MOUNT_UNION); |
| 206 | |
| 207 | (void) copyinstr(path, mp->mnt_stat.f_mntonname, MNAMELEN - 1, &size); |
| 208 | bzero(mp->mnt_stat.f_mntonname + size, MNAMELEN - size); |
| 209 | |
| 210 | switch (um->um_op) { |
| 211 | case UNMNT_ABOVE: |
| 212 | cp = "<above>"; |
| 213 | break; |
| 214 | case UNMNT_BELOW: |
| 215 | cp = "<below>"; |
| 216 | break; |
| 217 | case UNMNT_REPLACE: |
| 218 | cp = ""; |
| 219 | break; |
| 220 | } |
| 221 | len = strlen(cp); |
| 222 | bcopy(cp, mp->mnt_stat.f_mntfromname, len); |
| 223 | |
| 224 | cp = mp->mnt_stat.f_mntfromname + len; |
| 225 | len = MNAMELEN - len; |
| 226 | |
| 227 | (void) copyinstr(args.target, cp, len - 1, &size); |
| 228 | bzero(cp + size, len - size); |
| 229 | |
| 230 | #ifdef UNION_DIAGNOSTIC |
| 231 | printf("union_mount: from %s, on %s\n", |
| 232 | mp->mnt_stat.f_mntfromname, mp->mnt_stat.f_mntonname); |
| 233 | #endif |
| 234 | return (0); |
| 235 | |
| 236 | bad: |
| 237 | if (cred) |
| 238 | crfree(cred); |
| 239 | if (upperrootvp) |
| 240 | vrele(upperrootvp); |
| 241 | if (lowerrootvp) |
| 242 | vrele(lowerrootvp); |
| 243 | return (error); |
| 244 | } |
| 245 | |
| 246 | /* |
| 247 | * VFS start. Nothing needed here - the start routine |
| 248 | * on the underlying filesystem(s) will have been called |
| 249 | * when that filesystem was mounted. |
| 250 | */ |
| 251 | int |
| 252 | union_start(mp, flags, p) |
| 253 | struct mount *mp; |
| 254 | int flags; |
| 255 | struct proc *p; |
| 256 | { |
| 257 | |
| 258 | return (0); |
| 259 | } |
| 260 | |
| 261 | /* |
| 262 | * Free reference to union layer |
| 263 | */ |
| 264 | int |
| 265 | union_unmount(mp, mntflags, p) |
| 266 | struct mount *mp; |
| 267 | int mntflags; |
| 268 | struct proc *p; |
| 269 | { |
| 270 | struct union_mount *um = MOUNTTOUNIONMOUNT(mp); |
| 271 | struct vnode *um_rootvp; |
| 272 | int error; |
| 273 | int flags = 0; |
| 274 | extern int doforce; |
| 275 | |
| 276 | #ifdef UNION_DIAGNOSTIC |
| 277 | printf("union_unmount(mp = %x)\n", mp); |
| 278 | #endif |
| 279 | |
| 280 | /* only the mounter, or superuser can unmount */ |
| 281 | if ((p->p_cred->p_ruid != um->um_cred->cr_uid) && |
| 282 | (error = suser(p->p_ucred, &p->p_acflag))) |
| 283 | return (error); |
| 284 | |
| 285 | if (mntflags & MNT_FORCE) { |
| 286 | /* union can never be rootfs so don't check for it */ |
| 287 | if (!doforce) |
| 288 | return (EINVAL); |
| 289 | flags |= FORCECLOSE; |
| 290 | } |
| 291 | |
| 292 | if (error = union_root(mp, &um_rootvp)) |
| 293 | return (error); |
| 294 | if (um_rootvp->v_usecount > 1) { |
| 295 | vput(um_rootvp); |
| 296 | return (EBUSY); |
| 297 | } |
| 298 | if (error = vflush(mp, um_rootvp, flags)) { |
| 299 | vput(um_rootvp); |
| 300 | return (error); |
| 301 | } |
| 302 | |
| 303 | #ifdef UNION_DIAGNOSTIC |
| 304 | vprint("alias root of lower", um_rootvp); |
| 305 | #endif |
| 306 | /* |
| 307 | * Discard references to upper and lower target vnodes. |
| 308 | */ |
| 309 | if (um->um_lowervp) |
| 310 | vrele(um->um_lowervp); |
| 311 | vrele(um->um_uppervp); |
| 312 | crfree(um->um_cred); |
| 313 | /* |
| 314 | * Release reference on underlying root vnode |
| 315 | */ |
| 316 | vput(um_rootvp); |
| 317 | /* |
| 318 | * And blow it away for future re-use |
| 319 | */ |
| 320 | vgone(um_rootvp); |
| 321 | /* |
| 322 | * Finally, throw away the union_mount structure |
| 323 | */ |
| 324 | free(mp->mnt_data, M_UFSMNT); /* XXX */ |
| 325 | mp->mnt_data = 0; |
| 326 | return (0); |
| 327 | } |
| 328 | |
| 329 | int |
| 330 | union_root(mp, vpp) |
| 331 | struct mount *mp; |
| 332 | struct vnode **vpp; |
| 333 | { |
| 334 | struct union_mount *um = MOUNTTOUNIONMOUNT(mp); |
| 335 | int error; |
| 336 | int loselock; |
| 337 | |
| 338 | #ifdef UNION_DIAGNOSTIC |
| 339 | printf("union_root(mp = %x, lvp = %x, uvp = %x)\n", mp, |
| 340 | um->um_lowervp, |
| 341 | um->um_uppervp); |
| 342 | #endif |
| 343 | |
| 344 | /* |
| 345 | * Return locked reference to root. |
| 346 | */ |
| 347 | VREF(um->um_uppervp); |
| 348 | if ((um->um_op == UNMNT_BELOW) && |
| 349 | VOP_ISLOCKED(um->um_uppervp)) { |
| 350 | loselock = 1; |
| 351 | } else { |
| 352 | VOP_LOCK(um->um_uppervp); |
| 353 | loselock = 0; |
| 354 | } |
| 355 | if (um->um_lowervp) |
| 356 | VREF(um->um_lowervp); |
| 357 | error = union_allocvp(vpp, mp, |
| 358 | (struct vnode *) 0, |
| 359 | (struct vnode *) 0, |
| 360 | (struct componentname *) 0, |
| 361 | um->um_uppervp, |
| 362 | um->um_lowervp); |
| 363 | |
| 364 | if (error) { |
| 365 | if (!loselock) |
| 366 | VOP_UNLOCK(um->um_uppervp); |
| 367 | vrele(um->um_uppervp); |
| 368 | if (um->um_lowervp) |
| 369 | vrele(um->um_lowervp); |
| 370 | } else { |
| 371 | if (loselock) |
| 372 | VTOUNION(*vpp)->un_flags &= ~UN_ULOCK; |
| 373 | } |
| 374 | |
| 375 | return (error); |
| 376 | } |
| 377 | |
| 378 | int |
| 379 | union_quotactl(mp, cmd, uid, arg, p) |
| 380 | struct mount *mp; |
| 381 | int cmd; |
| 382 | uid_t uid; |
| 383 | caddr_t arg; |
| 384 | struct proc *p; |
| 385 | { |
| 386 | |
| 387 | return (EOPNOTSUPP); |
| 388 | } |
| 389 | |
| 390 | int |
| 391 | union_statfs(mp, sbp, p) |
| 392 | struct mount *mp; |
| 393 | struct statfs *sbp; |
| 394 | struct proc *p; |
| 395 | { |
| 396 | int error; |
| 397 | struct union_mount *um = MOUNTTOUNIONMOUNT(mp); |
| 398 | struct statfs mstat; |
| 399 | int lbsize; |
| 400 | |
| 401 | #ifdef UNION_DIAGNOSTIC |
| 402 | printf("union_statfs(mp = %x, lvp = %x, uvp = %x)\n", mp, |
| 403 | um->um_lowervp, |
| 404 | um->um_uppervp); |
| 405 | #endif |
| 406 | |
| 407 | bzero(&mstat, sizeof(mstat)); |
| 408 | |
| 409 | if (um->um_lowervp) { |
| 410 | error = VFS_STATFS(um->um_lowervp->v_mount, &mstat, p); |
| 411 | if (error) |
| 412 | return (error); |
| 413 | } |
| 414 | |
| 415 | /* now copy across the "interesting" information and fake the rest */ |
| 416 | #if 0 |
| 417 | sbp->f_type = mstat.f_type; |
| 418 | sbp->f_flags = mstat.f_flags; |
| 419 | sbp->f_bsize = mstat.f_bsize; |
| 420 | sbp->f_iosize = mstat.f_iosize; |
| 421 | #endif |
| 422 | lbsize = mstat.f_bsize; |
| 423 | sbp->f_blocks = mstat.f_blocks; |
| 424 | sbp->f_bfree = mstat.f_bfree; |
| 425 | sbp->f_bavail = mstat.f_bavail; |
| 426 | sbp->f_files = mstat.f_files; |
| 427 | sbp->f_ffree = mstat.f_ffree; |
| 428 | |
| 429 | error = VFS_STATFS(um->um_uppervp->v_mount, &mstat, p); |
| 430 | if (error) |
| 431 | return (error); |
| 432 | |
| 433 | sbp->f_type = MOUNT_UNION; |
| 434 | sbp->f_flags = mstat.f_flags; |
| 435 | sbp->f_bsize = mstat.f_bsize; |
| 436 | sbp->f_iosize = mstat.f_iosize; |
| 437 | |
| 438 | /* |
| 439 | * if the lower and upper blocksizes differ, then frig the |
| 440 | * block counts so that the sizes reported by df make some |
| 441 | * kind of sense. none of this makes sense though. |
| 442 | */ |
| 443 | |
| 444 | if (mstat.f_bsize != lbsize) { |
| 445 | sbp->f_blocks = sbp->f_blocks * lbsize / mstat.f_bsize; |
| 446 | sbp->f_bfree = sbp->f_bfree * lbsize / mstat.f_bsize; |
| 447 | sbp->f_bavail = sbp->f_bavail * lbsize / mstat.f_bsize; |
| 448 | } |
| 449 | sbp->f_blocks += mstat.f_blocks; |
| 450 | sbp->f_bfree += mstat.f_bfree; |
| 451 | sbp->f_bavail += mstat.f_bavail; |
| 452 | sbp->f_files += mstat.f_files; |
| 453 | sbp->f_ffree += mstat.f_ffree; |
| 454 | |
| 455 | if (sbp != &mp->mnt_stat) { |
| 456 | bcopy(&mp->mnt_stat.f_fsid, &sbp->f_fsid, sizeof(sbp->f_fsid)); |
| 457 | bcopy(mp->mnt_stat.f_mntonname, sbp->f_mntonname, MNAMELEN); |
| 458 | bcopy(mp->mnt_stat.f_mntfromname, sbp->f_mntfromname, MNAMELEN); |
| 459 | } |
| 460 | return (0); |
| 461 | } |
| 462 | |
| 463 | int |
| 464 | union_sync(mp, waitfor, cred, p) |
| 465 | struct mount *mp; |
| 466 | int waitfor; |
| 467 | struct ucred *cred; |
| 468 | struct proc *p; |
| 469 | { |
| 470 | |
| 471 | /* |
| 472 | * XXX - Assumes no data cached at union layer. |
| 473 | */ |
| 474 | return (0); |
| 475 | } |
| 476 | |
| 477 | int |
| 478 | union_vget(mp, ino, vpp) |
| 479 | struct mount *mp; |
| 480 | ino_t ino; |
| 481 | struct vnode **vpp; |
| 482 | { |
| 483 | |
| 484 | return (EOPNOTSUPP); |
| 485 | } |
| 486 | |
| 487 | int |
| 488 | union_fhtovp(mp, fidp, nam, vpp, exflagsp, credanonp) |
| 489 | struct mount *mp; |
| 490 | struct fid *fidp; |
| 491 | struct mbuf *nam; |
| 492 | struct vnode **vpp; |
| 493 | int *exflagsp; |
| 494 | struct ucred **credanonp; |
| 495 | { |
| 496 | |
| 497 | return (EOPNOTSUPP); |
| 498 | } |
| 499 | |
| 500 | int |
| 501 | union_vptofh(vp, fhp) |
| 502 | struct vnode *vp; |
| 503 | struct fid *fhp; |
| 504 | { |
| 505 | |
| 506 | return (EOPNOTSUPP); |
| 507 | } |
| 508 | |
| 509 | int union_init __P((void)); |
| 510 | |
| 511 | struct vfsops union_vfsops = { |
| 512 | union_mount, |
| 513 | union_start, |
| 514 | union_unmount, |
| 515 | union_root, |
| 516 | union_quotactl, |
| 517 | union_statfs, |
| 518 | union_sync, |
| 519 | union_vget, |
| 520 | union_fhtovp, |
| 521 | union_vptofh, |
| 522 | union_init, |
| 523 | }; |