/* * Copyright (c) 2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int prtactive; extern void hfs_relnamehints(struct cnode *dcp); /* * Last reference to an cnode. If necessary, write or delete it. */ __private_extern__ int hfs_inactive(ap) struct vop_inactive_args /* { struct vnode *a_vp; } */ *ap; { struct vnode *vp = ap->a_vp; struct cnode *cp = VTOC(vp); struct hfsmount *hfsmp = VTOHFS(vp); struct proc *p = ap->a_p; struct timeval tv; int error = 0; int recycle = 0; int forkcount = 0; int truncated = 0; int started_tr = 0, grabbed_lock = 0; if (prtactive && vp->v_usecount != 0) vprint("hfs_inactive: pushing active", vp); /* * Ignore nodes related to stale file handles. */ if (cp->c_mode == 0) goto out; if (vp->v_mount->mnt_flag & MNT_RDONLY) goto out; if (cp->c_datafork) ++forkcount; if (cp->c_rsrcfork) ++forkcount; /* If needed, get rid of any fork's data for a deleted file */ if ((cp->c_flag & C_DELETED) && vp->v_type == VREG && (VTOF(vp)->ff_blocks != 0)) { error = VOP_TRUNCATE(vp, (off_t)0, IO_NDELAY, NOCRED, p); truncated = 1; // have to do this to prevent the lost ubc_info panic SET(cp->c_flag, C_TRANSIT); recycle = 1; if (error) goto out; } /* * Check for a postponed deletion. * (only delete cnode when the last fork goes inactive) */ if ((cp->c_flag & C_DELETED) && (forkcount <= 1)) { /* * Mark cnode in transit so that one can get this * cnode from cnode hash. */ SET(cp->c_flag, C_TRANSIT); cp->c_flag &= ~C_DELETED; cp->c_rdev = 0; // XXXdbg hfs_global_shared_lock_acquire(hfsmp); grabbed_lock = 1; if (hfsmp->jnl) { if (journal_start_transaction(hfsmp->jnl) != 0) { error = EINVAL; goto out; } started_tr = 1; } /* Lock catalog b-tree */ error = hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_EXCLUSIVE, p); if (error) goto out; if (cp->c_blocks > 0) printf("hfs_inactive: attempting to delete a non-empty file!"); /* * The descriptor name may be zero, * in which case the fileid is used. */ error = cat_delete(hfsmp, &cp->c_desc, &cp->c_attr); if (error && truncated && (error != ENXIO)) printf("hfs_inactive: couldn't delete a truncated file!"); /* Update HFS Private Data dir */ if (error == 0) { hfsmp->hfs_privdir_attr.ca_entries--; (void)cat_update(hfsmp, &hfsmp->hfs_privdir_desc, &hfsmp->hfs_privdir_attr, NULL, NULL); } /* Unlock catalog b-tree */ (void) hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_RELEASE, p); if (error) goto out; #if QUOTA (void)hfs_chkiq(cp, -1, NOCRED, 0); #endif /* QUOTA */ cp->c_mode = 0; cp->c_flag |= C_NOEXISTS | C_CHANGE | C_UPDATE; if (error == 0) hfs_volupdate(hfsmp, VOL_RMFILE, 0); } /* Push any defered access times to disk */ if (cp->c_flag & C_ATIMEMOD) { cp->c_flag &= ~C_ATIMEMOD; if (HFSTOVCB(hfsmp)->vcbSigWord == kHFSPlusSigWord) cp->c_flag |= C_MODIFIED; } if (cp->c_flag & (C_ACCESS | C_CHANGE | C_MODIFIED | C_UPDATE)) { tv = time; VOP_UPDATE(vp, &tv, &tv, 0); } out: // XXXdbg - have to do this because a goto could have come here if (started_tr) { journal_end_transaction(hfsmp->jnl); started_tr = 0; } if (grabbed_lock) { hfs_global_shared_lock_release(hfsmp); } VOP_UNLOCK(vp, 0, p); /* * If we are done with the vnode, reclaim it * so that it can be reused immediately. */ if (cp->c_mode == 0 || recycle) vrecycle(vp, (struct slock *)0, p); return (error); } /* * Reclaim a cnode so that it can be used for other purposes. */ __private_extern__ int hfs_reclaim(ap) struct vop_reclaim_args /* { struct vnode *a_vp; } */ *ap; { struct vnode *vp = ap->a_vp; struct cnode *cp = VTOC(vp); struct vnode *devvp = NULL; struct filefork *fp = NULL; struct filefork *altfp = NULL; int i; if (prtactive && vp->v_usecount != 0) vprint("hfs_reclaim(): pushing active", vp); devvp = cp->c_devvp; /* For later releasing */ /* * Find file fork for this vnode (if any) * Also check if another fork is active */ if ((fp = cp->c_datafork) && (cp->c_vp == vp)) { cp->c_datafork = NULL; cp->c_vp = NULL; altfp = cp->c_rsrcfork; } else if ((fp = cp->c_rsrcfork) && (cp->c_rsrc_vp == vp)) { cp->c_rsrcfork = NULL; cp->c_rsrc_vp = NULL; altfp = cp->c_datafork; } else { cp->c_vp = NULL; fp = NULL; altfp = NULL; } /* * On the last fork, remove the cnode from its hash chain. */ if (altfp == NULL) hfs_chashremove(cp); /* Release the file fork and related data (can block) */ if (fp) { fp->ff_cp = NULL; /* Dump cached symlink data */ if ((vp->v_type == VLNK) && (fp->ff_symlinkptr != NULL)) { FREE(fp->ff_symlinkptr, M_TEMP); fp->ff_symlinkptr = NULL; } FREE_ZONE(fp, sizeof(struct filefork), M_HFSFORK); fp = NULL; } /* * Purge old data structures associated with the cnode. */ cache_purge(vp); if (devvp && altfp == NULL) { cp->c_devvp = NULL; vrele(devvp); } vp->v_data = NULL; /* * If there was only one active fork then we can release the cnode. */ if (altfp == NULL) { #if QUOTA for (i = 0; i < MAXQUOTAS; i++) { if (cp->c_dquot[i] != NODQUOT) { dqreclaim(vp, cp->c_dquot[i]); cp->c_dquot[i] = NODQUOT; } } #endif /* QUOTA */ /* * Free any left over directory indices */ if (vp->v_type == VDIR) hfs_relnamehints(cp); /* * If the descriptor has a name then release it */ if (cp->c_desc.cd_flags & CD_HASBUF) { char *nameptr; nameptr = cp->c_desc.cd_nameptr; cp->c_desc.cd_nameptr = 0; cp->c_desc.cd_flags &= ~CD_HASBUF; cp->c_desc.cd_namelen = 0; FREE(nameptr, M_TEMP); } CLR(cp->c_flag, (C_ALLOC | C_TRANSIT)); if (ISSET(cp->c_flag, C_WALLOC) || ISSET(cp->c_flag, C_WTRANSIT)) wakeup(cp); FREE_ZONE(cp, sizeof(struct cnode), M_HFSNODE); } return (0); } /* * get a cnode * * called by hfs_lookup and hfs_vget (descp == NULL) * * returns a locked vnode for cnode for given cnid/fileid */ __private_extern__ int hfs_getcnode(struct hfsmount *hfsmp, cnid_t cnid, struct cat_desc *descp, int wantrsrc, struct cat_attr *attrp, struct cat_fork *forkp, struct vnode **vpp) { dev_t dev = hfsmp->hfs_raw_dev; struct vnode *vp = NULL; struct vnode *rvp = NULL; struct vnode *new_vp = NULL; struct cnode *cp = NULL; struct proc *p = current_proc(); int retval = E_NONE; /* Check if unmount in progress */ if (HFSTOVFS(hfsmp)->mnt_kern_flag & MNTK_UNMOUNT) { *vpp = NULL; return (EPERM); } /* * Check the hash for an active cnode */ cp = hfs_chashget(dev, cnid, wantrsrc, &vp, &rvp); if (cp != NULL) { /* hide open files that have been deleted */ if ((hfsmp->hfs_private_metadata_dir != 0) && (cp->c_parentcnid == hfsmp->hfs_private_metadata_dir) && (cp->c_nlink == 0)) { retval = ENOENT; goto exit; } /* Hide private journal files */ if (hfsmp->jnl && (cp->c_parentcnid == kRootDirID) && ((cp->c_cnid == hfsmp->hfs_jnlfileid) || (cp->c_cnid == hfsmp->hfs_jnlinfoblkid))) { retval = ENOENT; goto exit; } if (wantrsrc && rvp != NULL) { vp = rvp; rvp = NULL; goto done; } if (!wantrsrc && vp != NULL) { /* Hardlinks need an updated catalog descriptor */ if (descp && cp->c_flag & C_HARDLINK) { replace_desc(cp, descp); } /* We have a vnode so we're done. */ goto done; } } /* * There was no active vnode so get a new one. * Use the existing cnode (if any). */ if (descp != NULL) { /* * hfs_lookup case, use descp, attrp and forkp */ retval = hfs_getnewvnode(hfsmp, cp, descp, wantrsrc, attrp, forkp, &new_vp); } else { struct cat_desc cndesc = {0}; struct cat_attr cnattr = {0}; struct cat_fork cnfork = {0}; /* * hfs_vget case, need to lookup entry (by file id) */ if (cnid == kRootParID) { static char hfs_rootname[] = "/"; cndesc.cd_nameptr = &hfs_rootname[0]; cndesc.cd_namelen = 1; cndesc.cd_parentcnid = kRootParID; cndesc.cd_cnid = kRootParID; cndesc.cd_flags = CD_ISDIR; cnattr.ca_fileid = kRootParID; cnattr.ca_nlink = 2; cnattr.ca_mode = (S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO); } else { /* Lock catalog b-tree */ retval = hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_SHARED, p); if (retval) goto exit; retval = cat_idlookup(hfsmp, cnid, &cndesc, &cnattr, &cnfork); /* Unlock catalog b-tree */ (void) hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_RELEASE, p); if (retval) goto exit; /* Hide open files that have been deleted */ if ((hfsmp->hfs_private_metadata_dir != 0) && (cndesc.cd_parentcnid == hfsmp->hfs_private_metadata_dir)) { cat_releasedesc(&cndesc); retval = ENOENT; goto exit; } } retval = hfs_getnewvnode(hfsmp, cp, &cndesc, 0, &cnattr, &cnfork, &new_vp); /* Hardlinks may need an updated catalog descriptor */ if (retval == 0 && new_vp && (VTOC(new_vp)->c_flag & C_HARDLINK) && cndesc.cd_nameptr && cndesc.cd_namelen > 0) { replace_desc(VTOC(new_vp), &cndesc); } cat_releasedesc(&cndesc); } exit: /* Release reference taken on opposite vnode (if any). */ if (vp) vput(vp); else if (rvp) vput(rvp); if (retval) { *vpp = NULL; return (retval); } vp = new_vp; done: /* The cnode's vnode should be in vp. */ if (vp == NULL) panic("hfs_getcnode: missing vp!"); UBCINFOCHECK("hfs_getcnode", vp); *vpp = vp; return (0); } /* * hfs_getnewvnode - get new default vnode * * the vnode is returned locked */ extern int (**hfs_vnodeop_p) (void *); extern int (**hfs_specop_p) (void *); extern int (**hfs_fifoop_p) (void *); __private_extern__ int hfs_getnewvnode(struct hfsmount *hfsmp, struct cnode *cp, struct cat_desc *descp, int wantrsrc, struct cat_attr *attrp, struct cat_fork *forkp, struct vnode **vpp) { struct mount *mp = HFSTOVFS(hfsmp); struct vnode *vp = NULL; struct vnode *rvp = NULL; struct vnode *new_vp = NULL; struct cnode *cp2 = NULL; struct filefork *fp = NULL; int allocated = 0; int i; int retval; dev_t dev; struct proc *p = current_proc(); /* Bail when unmount is in progress */ if (mp->mnt_kern_flag & MNTK_UNMOUNT) { *vpp = NULL; return (EPERM); } #if !FIFO if (IFTOVT(attrp->ca_mode) == VFIFO) { *vpp = NULL; return (EOPNOTSUPP); } #endif dev = hfsmp->hfs_raw_dev; /* If no cnode was passed in then create one */ if (cp == NULL) { MALLOC_ZONE(cp2, struct cnode *, sizeof(struct cnode), M_HFSNODE, M_WAITOK); bzero(cp2, sizeof(struct cnode)); allocated = 1; SET(cp2->c_flag, C_ALLOC); cp2->c_cnid = descp->cd_cnid; cp2->c_fileid = attrp->ca_fileid; cp2->c_dev = dev; lockinit(&cp2->c_lock, PINOD, "cnode", 0, 0); (void) lockmgr(&cp2->c_lock, LK_EXCLUSIVE, (struct slock *)0, p); /* * There were several blocking points since we first * checked the hash. Now that we're through blocking, * check the hash again in case we're racing for the * same cnode. */ cp = hfs_chashget(dev, attrp->ca_fileid, wantrsrc, &vp, &rvp); if (cp != NULL) { /* We lost the race - use the winner's cnode */ FREE_ZONE(cp2, sizeof(struct cnode), M_HFSNODE); allocated = 0; if (wantrsrc && rvp != NULL) { *vpp = rvp; return (0); } if (!wantrsrc && vp != NULL) { *vpp = vp; return (0); } } else /* allocated */ { cp = cp2; hfs_chashinsert(cp); } } /* Allocate a new vnode. If unsuccesful, leave after freeing memory */ if ((retval = getnewvnode(VT_HFS, mp, hfs_vnodeop_p, &new_vp))) { if (allocated) { hfs_chashremove(cp); if (ISSET(cp->c_flag, C_WALLOC)) { CLR(cp->c_flag, C_WALLOC); wakeup(cp); } FREE_ZONE(cp2, sizeof(struct cnode), M_HFSNODE); allocated = 0; } else if (rvp) { vput(rvp); } else if (vp) { vput(vp); } *vpp = NULL; return (retval); } if (allocated) { bcopy(attrp, &cp->c_attr, sizeof(struct cat_attr)); bcopy(descp, &cp->c_desc, sizeof(struct cat_desc)); } new_vp->v_data = cp; if (wantrsrc && S_ISREG(cp->c_mode)) cp->c_rsrc_vp = new_vp; else cp->c_vp = new_vp; /* Release reference taken on opposite vnode (if any). */ if (rvp) vput(rvp); if (vp) vput(vp); vp = new_vp; vp->v_ubcinfo = UBC_NOINFO; /* * If this is a new cnode then initialize it using descp and attrp... */ if (allocated) { /* The name was inherited so clear descriptor state... */ descp->cd_namelen = 0; descp->cd_nameptr = NULL; descp->cd_flags &= ~CD_HASBUF; /* Tag hardlinks */ if (IFTOVT(cp->c_mode) == VREG && (descp->cd_cnid != attrp->ca_fileid)) { cp->c_flag |= C_HARDLINK; } /* Take one dev reference for each non-directory cnode */ if (IFTOVT(cp->c_mode) != VDIR) { cp->c_devvp = hfsmp->hfs_devvp; VREF(cp->c_devvp); } #if QUOTA for (i = 0; i < MAXQUOTAS; i++) cp->c_dquot[i] = NODQUOT; #endif /* QUOTA */ } if (IFTOVT(cp->c_mode) != VDIR) { if (forkp && attrp->ca_blocks < forkp->cf_blocks) panic("hfs_getnewvnode: bad ca_blocks (too small)"); /* * Allocate and initialize a file fork... */ MALLOC_ZONE(fp, struct filefork *, sizeof(struct filefork), M_HFSFORK, M_WAITOK); bzero(fp, sizeof(struct filefork)); fp->ff_cp = cp; if (forkp) bcopy(forkp, &fp->ff_data, sizeof(HFSPlusForkData)); if (fp->ff_clumpsize == 0) fp->ff_clumpsize = HFSTOVCB(hfsmp)->vcbClpSiz; rl_init(&fp->ff_invalidranges); if (wantrsrc) { if (cp->c_rsrcfork != NULL) panic("stale rsrc fork"); cp->c_rsrcfork = fp; } else { if (cp->c_datafork != NULL) panic("stale data fork"); cp->c_datafork = fp; } } /* * Finish vnode initialization. * Setting the v_type 'stamps' the vnode as 'complete', * so should be done almost last. * * At this point the vnode should be locked and fully * allocated. And ready to be used or accessed. (though * having it locked prevents most of this, it can still * be accessed through lists and hashes). */ vp->v_type = IFTOVT(cp->c_mode); /* Tag system files */ if ((descp->cd_cnid < kHFSFirstUserCatalogNodeID) && (vp->v_type == VREG)) vp->v_flag |= VSYSTEM; /* Tag root directory */ if (cp->c_cnid == kRootDirID) vp->v_flag |= VROOT; if ((vp->v_type == VREG) && !(vp->v_flag & VSYSTEM) && (UBCINFOMISSING(vp) || UBCINFORECLAIMED(vp))) { ubc_info_init(vp); } else { vp->v_ubcinfo = UBC_NOINFO; } if (vp->v_type == VCHR || vp->v_type == VBLK) { struct vnode *nvp; vp->v_op = hfs_specop_p; if ((nvp = checkalias(vp, cp->c_rdev, mp))) { /* * Discard unneeded vnode, but save its cnode. * Note that the lock is carried over in the * cnode to the replacement vnode. */ nvp->v_data = vp->v_data; vp->v_data = NULL; vp->v_op = spec_vnodeop_p; vrele(vp); vgone(vp); /* * Reinitialize aliased cnode. * Assume its not a resource fork. */ cp->c_vp = nvp; vp = nvp; } } else if (vp->v_type == VFIFO) { #if FIFO vp->v_op = hfs_fifoop_p; #endif } /* Vnode is now initialized - see if anyone was waiting for it. */ CLR(cp->c_flag, C_ALLOC); if (ISSET(cp->c_flag, C_WALLOC)) { CLR(cp->c_flag, C_WALLOC); wakeup((caddr_t)cp); } *vpp = vp; return (0); }