/*
- * Copyright (c) 1999-2007 Apple Inc. All rights reserved.
+ * Copyright (c) 1999-2013 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
#include <sys/vnode.h>
#include <vfs/vfs_support.h>
#include <libkern/libkern.h>
+#include <sys/fsctl.h>
#include "hfs.h"
#include "hfs_catalog.h"
static int setfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t firstlink);
static int getfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t *firstlink);
+int hfs_makelink(struct hfsmount *hfsmp, struct vnode *src_vp, struct cnode *cp,
+ struct cnode *dcp, struct componentname *cnp);
/*
* Create a new catalog link record
*
struct cat_attr attr;
if (linknum == 0) {
- printf("createindirectlink: linknum is zero!\n");
+ printf("hfs: createindirectlink: linknum is zero!\n");
return (EINVAL);
}
/* Links are matched to inodes by link ID and to volumes by create date */
attr.ca_linkref = linknum;
- attr.ca_itime = hfsmp->hfs_itime;
+ attr.ca_itime = hfsmp->hfs_metadata_createdate;
attr.ca_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
attr.ca_recflags = kHFSHasLinkChainMask | kHFSThreadExistsMask;
attr.ca_flags = UF_IMMUTABLE;
/*
* Make a link to the cnode cp in the directory dp
- * using the name in cnp.
+ * using the name in cnp. src_vp is the vnode that
+ * corresponds to 'cp' which was part of the arguments to
+ * hfs_vnop_link.
*
* The cnodes cp and dcp must be locked.
*/
-static int
-hfs_makelink(struct hfsmount *hfsmp, struct cnode *cp, struct cnode *dcp,
- struct componentname *cnp)
+int
+hfs_makelink(struct hfsmount *hfsmp, struct vnode *src_vp, struct cnode *cp,
+ struct cnode *dcp, struct componentname *cnp)
{
vfs_context_t ctx = cnp->cn_context;
struct proc *p = vfs_context_proc(ctx);
&to_desc, NULL);
if (retval != 0 && retval != EEXIST) {
- printf("hfs_makelink: cat_rename to %s failed (%d). fileid %d\n",
- inodename, retval, cp->c_fileid);
+ printf("hfs_makelink: cat_rename to %s failed (%d) fileid=%d, vol=%s\n",
+ inodename, retval, cp->c_fileid, hfsmp->vcbVN);
}
} while ((retval == EEXIST) && (type == FILE_HARDLINKS));
if (retval)
/* Update the original first link to point back to the new first link. */
if (cp->c_attr.ca_recflags & kHFSHasLinkChainMask) {
- (void) cat_updatelink(hfsmp, orig_firstlink, linkcnid, HFS_IGNORABLE_LINK);
+ (void) cat_update_siblinglinks(hfsmp, orig_firstlink, linkcnid, HFS_IGNORABLE_LINK);
/* Update the inode's first link value. */
if (type == DIR_HARDLINKS) {
panic("hfs_makelink: cat_update of privdir failed! (%d)\n", retval);
}
cp->c_flag |= C_HARDLINK;
+
+ /*
+ * Now we need to mark the vnodes as being hardlinks via the vnode_setmultipath call.
+ * Note that we're calling vnode_get here, which should simply add an iocount if possible, without
+ * doing much checking. It's safe to call this because we are protected by the cnode lock, which
+ * ensures that anyone trying to reclaim it will block until we release it. vnode_get will usually
+ * give us an extra iocount, unless the vnode is about to be reclaimed (and has no iocounts).
+ * In that case, we'd error out, but we'd also not care if we added the VISHARDLINK bit to the vnode.
+ *
+ * As for the iocount we're about to add, we can't necessarily always call vnode_put here.
+ * If the one we add is the only iocount on the vnode, and there was
+ * sufficient vnode pressure, it could go through VNOP_INACTIVE immediately, which would
+ * require the cnode lock and cause us to double-lock panic. We can only call vnode_put if we know
+ * that the vnode we're operating on is the one with which we came into hfs_vnop_link, because
+ * that means VFS took an iocount on it for us. If it's *not* the one that we came into the call
+ * with, then mark it as NEED_VNODE_PUT to have hfs_unlock drop it for us. hfs_vnop_link will
+ * unlock the cnode when it is finished.
+ */
if ((vp = cp->c_vp) != NULLVP) {
- if (vnode_get(vp) == 0) {
- vnode_setmultipath(vp);
- vnode_put(vp);
- }
+ if (vnode_get(vp) == 0) {
+ vnode_setmultipath(vp);
+ if (vp == src_vp) {
+ /* we have an iocount on data fork vnode already. */
+ vnode_put(vp);
+ }
+ else {
+ cp->c_flag |= C_NEED_DVNODE_PUT;
+ }
+ }
}
if ((vp = cp->c_rsrc_vp) != NULLVP) {
- if (vnode_get(vp) == 0) {
- vnode_setmultipath(vp);
- vnode_put(vp);
- }
+ if (vnode_get(vp) == 0) {
+ vnode_setmultipath(vp);
+ if (vp == src_vp) {
+ vnode_put(vp);
+ }
+ else {
+ cp->c_flag |= C_NEED_RVNODE_PUT;
+ }
+ }
}
cp->c_touch_chgtime = TRUE;
cp->c_flag |= C_FORCEUPDATE;
* IN struct componentname *a_cnp;
* IN vfs_context_t a_context;
*/
-__private_extern__
int
hfs_vnop_link(struct vnop_link_args *ap)
{
struct cnode *tdcp;
struct cnode *fdcp = NULL;
struct cat_desc todesc;
+ cnid_t parentcnid;
int lockflags = 0;
int intrans = 0;
enum vtype v_type;
return (EPERM);
}
if (v_type == VDIR) {
+#if CONFIG_HFS_DIRLINK
/* Make sure our private directory exists. */
if (hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid == 0) {
return (EPERM);
return (EPERM);
}
/* Directory hardlinks also need the parent of the original directory. */
- if ((error = hfs_vget(hfsmp, hfs_currentparent(VTOC(vp)), &fdvp, 1))) {
+ if ((error = hfs_vget(hfsmp, hfs_currentparent(VTOC(vp)), &fdvp, 1, 0))) {
return (error);
}
+#else
+ /* some platforms don't support directory hardlinks. */
+ return EPERM;
+#endif
} else {
/* Make sure our private directory exists. */
if (hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid == 0) {
}
return (ENOSPC);
}
+
+ check_for_tracked_file(vp, VTOC(vp)->c_ctime, NAMESPACE_HANDLER_LINK_CREATE, NULL);
+
+
/* Lock the cnodes. */
if (fdvp) {
- if ((error = hfs_lockfour(VTOC(tdvp), VTOC(vp), VTOC(fdvp), NULL, HFS_EXCLUSIVE_LOCK))) {
+ if ((error = hfs_lockfour(VTOC(tdvp), VTOC(vp), VTOC(fdvp), NULL, HFS_EXCLUSIVE_LOCK, NULL))) {
if (fdvp) {
vnode_put(fdvp);
}
}
tdcp = VTOC(tdvp);
cp = VTOC(vp);
+ /* grab the parent CNID from originlist after grabbing cnode locks */
+ parentcnid = hfs_currentparent(cp);
+
+ /*
+ * Make sure we didn't race the src or dst parent directories with rmdir.
+ * Note that we should only have a src parent directory cnode lock
+ * if we're dealing with a directory hardlink here.
+ */
+ if (fdcp) {
+ if (fdcp->c_flag & (C_NOEXISTS | C_DELETED)) {
+ error = ENOENT;
+ goto out;
+ }
+ }
+
+ if (tdcp->c_flag & (C_NOEXISTS | C_DELETED)) {
+ error = ENOENT;
+ goto out;
+ }
+
+ /* Check the source for errors:
+ * too many links, immutable, race with unlink
+ */
if (cp->c_linkcount >= HFS_LINK_MAX) {
error = EMLINK;
goto out;
}
- if (cp->c_flags & (IMMUTABLE | APPEND)) {
+ if (cp->c_bsdflags & (IMMUTABLE | APPEND)) {
error = EPERM;
goto out;
}
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
/* If destination exists then we lost a race with create. */
- if (cat_lookup(hfsmp, &todesc, 0, NULL, NULL, NULL, NULL) == 0) {
+ if (cat_lookup(hfsmp, &todesc, 0, 0, NULL, NULL, NULL, NULL) == 0) {
error = EEXIST;
goto out;
}
struct cat_attr cattr;
/* If inode is missing then we lost a race with unlink. */
- if ((cat_idlookup(hfsmp, cp->c_fileid, 0, NULL, &cattr, NULL) != 0) ||
+ if ((cat_idlookup(hfsmp, cp->c_fileid, 0, 0, NULL, &cattr, NULL) != 0) ||
(cattr.ca_fileid != cp->c_fileid)) {
error = ENOENT;
goto out;
cnid_t fileid;
/* If source is missing then we lost a race with unlink. */
- if ((cat_lookup(hfsmp, &cp->c_desc, 0, NULL, NULL, NULL, &fileid) != 0) ||
+ if ((cat_lookup(hfsmp, &cp->c_desc, 0, 0, NULL, NULL, NULL, &fileid) != 0) ||
(fileid != cp->c_fileid)) {
error = ENOENT;
goto out;
* - No ancestor of the new directory hard link (destination)
* is a directory hard link.
*/
- if ((cp->c_parentcnid == tdcp->c_fileid) ||
+ if ((parentcnid == tdcp->c_fileid) ||
(tdcp->c_fileid == kHFSRootFolderID) ||
- (cp->c_parentcnid == kHFSRootFolderID) ||
+ (parentcnid == kHFSRootFolderID) ||
cat_check_link_ancestry(hfsmp, tdcp->c_fileid, cp->c_fileid)) {
error = EPERM; /* abide by the rules, you did not */
goto out;
cp->c_linkcount++;
cp->c_touch_chgtime = TRUE;
- error = hfs_makelink(hfsmp, cp, tdcp, cnp);
+ error = hfs_makelink(hfsmp, vp, cp, tdcp, cnp);
if (error) {
cp->c_linkcount--;
hfs_volupdate(hfsmp, VOL_UPDATE, 0);
/* Set kHFSHasChildLinkBit in the destination hierarchy */
error = cat_set_childlinkbit(hfsmp, tdcp->c_parentcnid);
if (error) {
- printf ("hfs_vnop_link: error updating destination parent chain for %u\n", tdcp->c_cnid);
+ printf ("hfs_vnop_link: error updating destination parent chain for id=%u, vol=%s\n", tdcp->c_cnid, hfsmp->vcbVN);
error = 0;
}
}
error = hfs_update(tdvp, 0);
if (error && error != EIO && error != ENXIO) {
- panic("hfs_vnop_link: error updating tdvp %p\n", tdvp);
+ panic("hfs_vnop_link: error %d updating tdvp %p\n", error, tdvp);
}
if ((v_type == VDIR) &&
fdcp->c_flag |= C_FORCEUPDATE;
error = hfs_update(fdvp, 0);
if (error && error != EIO && error != ENXIO) {
- panic("hfs_vnop_link: error updating fdvp %p\n", fdvp);
+ panic("hfs_vnop_link: error %d updating fdvp %p\n", error, fdvp);
}
/* Set kHFSHasChildLinkBit in the source hierarchy */
error = cat_set_childlinkbit(hfsmp, fdcp->c_parentcnid);
if (error) {
- printf ("hfs_vnop_link: error updating source parent chain for %u\n", fdcp->c_cnid);
+ printf ("hfs_vnop_link: error updating source parent chain for id=%u, vol=%s\n", fdcp->c_cnid, hfsmp->vcbVN);
error = 0;
}
}
panic("hfs_vnop_link: error %d updating vp @ %p\n", ret, vp);
}
- HFS_KNOTE(vp, NOTE_LINK);
- HFS_KNOTE(tdvp, NOTE_WRITE);
out:
if (lockflags) {
hfs_systemfile_unlock(hfsmp, lockflags);
*
* Note: dvp and vp cnodes are already locked.
*/
-__private_extern__
int
hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct componentname *cnp, int skip_reserve)
{
cnid_t nextlinkid;
int lockflags = 0;
int started_tr;
- int rm_priv_file = 0;
int error;
if (hfsmp->hfs_flags & HFS_STANDARD) {
goto out;
}
- /* Purge any cached origin entries for a directory hard link. */
- if (cndesc.cd_flags & CD_ISDIR) {
- hfs_relorigin(cp, dcp->c_fileid);
- if (dcp->c_fileid != dcp->c_cnid) {
- hfs_relorigin(cp, dcp->c_cnid);
- }
+ /* Purge any cached origin entries for a directory or file hard link. */
+ hfs_relorigin(cp, dcp->c_fileid);
+ if (dcp->c_fileid != dcp->c_cnid) {
+ hfs_relorigin(cp, dcp->c_cnid);
}
/* Delete the link record. */
}
/* Update previous link. */
if (prevlinkid) {
- (void) cat_updatelink(hfsmp, prevlinkid, HFS_IGNORABLE_LINK, nextlinkid);
+ (void) cat_update_siblinglinks(hfsmp, prevlinkid, HFS_IGNORABLE_LINK, nextlinkid);
}
/* Update next link. */
if (nextlinkid) {
- (void) cat_updatelink(hfsmp, nextlinkid, prevlinkid, HFS_IGNORABLE_LINK);
+ (void) cat_update_siblinglinks(hfsmp, nextlinkid, prevlinkid, HFS_IGNORABLE_LINK);
}
+
+ /*
+ * The call to cat_releasedesc below will only release the name buffer;
+ * it does not zero out the rest of the fields in the 'cat_desc' data structure.
+ *
+ * As a result, since there are still other links at this point, we need
+ * to make the current cnode descriptor point to the raw inode. If a path-based
+ * system call comes along first, it will replace the descriptor with a valid link
+ * ID. If a userland process already has a file descriptor open, then they will
+ * bypass that lookup, though. Replacing the descriptor CNID with the raw
+ * inode will force it to generate a new full path.
+ */
+ cp->c_cnid = cp->c_fileid;
+
}
/* Push new link count to disk. */
/* Update file system stats. */
hfs_volupdate(hfsmp, VOL_RMFILE, (dcp->c_cnid == kHFSRootFolderID));
- /* The last link of a directory removed the inode. */
- if (rm_priv_file) {
- hfs_volupdate(hfsmp, VOL_RMFILE, 0);
- }
+
/*
* All done with this cnode's descriptor...
*
*/
cat_releasedesc(&cp->c_desc);
- HFS_KNOTE(dvp, NOTE_WRITE);
- HFS_KNOTE(vp, NOTE_DELETE);
out:
if (lockflags) {
hfs_systemfile_unlock(hfsmp, lockflags);
*
* This call is assumed to be made during mount.
*/
-__private_extern__
void
hfs_privatedir_init(struct hfsmount * hfsmp, enum privdirtype type)
{
priv_descp->cd_flags = CD_ISDIR | CD_DECOMPOSED;
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
- error = cat_lookup(hfsmp, priv_descp, 0, NULL, priv_attrp, NULL, NULL);
+ error = cat_lookup(hfsmp, priv_descp, 0, 0, NULL, priv_attrp, NULL, NULL);
hfs_systemfile_unlock(hfsmp, lockflags);
if (error == 0) {
}
/* Grab the root directory so we can update it later. */
- if (hfs_vget(hfsmp, kRootDirID, &dvp, 0) != 0) {
+ if (hfs_vget(hfsmp, kRootDirID, &dvp, 0, 0) != 0) {
goto exit;
}
dcp = VTOC(dvp);
}
trans = 1;
- lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
+ /* Need the catalog and EA b-trees for CNID acquisition */
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
/* Make sure there's space in the Catalog file. */
if (cat_preflight(hfsmp, CAT_CREATE, NULL, 0) != 0) {
goto exit;
}
+ /* Get the CNID for use */
+ cnid_t new_id;
+ if ((error = cat_acquire_cnid(hfsmp, &new_id))) {
+ hfs_systemfile_unlock (hfsmp, lockflags);
+ goto exit;
+ }
+
/* Create the private directory on disk. */
- error = cat_create(hfsmp, priv_descp, priv_attrp, NULL);
+ error = cat_create(hfsmp, new_id, priv_descp, priv_attrp, NULL);
if (error == 0) {
priv_descp->cd_cnid = priv_attrp->ca_fileid;
goto exit;
}
if (type == FILE_HARDLINKS) {
- hfsmp->hfs_metadata_createdate = hfsmp->hfs_itime;
+ hfsmp->hfs_metadata_createdate = priv_attrp->ca_itime;
}
hfs_volupdate(hfsmp, VOL_MKDIR, 1);
exit:
/*
* Lookup a hardlink link (from chain)
*/
-__private_extern__
int
-hfs_lookuplink(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid, cnid_t *nextlinkid)
+hfs_lookup_siblinglinks(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid, cnid_t *nextlinkid)
{
int lockflags;
int error;
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
- error = cat_lookuplinkbyid(hfsmp, linkfileid, prevlinkid, nextlinkid);
+ error = cat_lookup_siblinglinks(hfsmp, linkfileid, prevlinkid, nextlinkid);
if (error == ENOLINK) {
hfs_systemfile_unlock(hfsmp, lockflags);
lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
return (error);
}
+
+/* Find the oldest / last hardlink in the link chain */
+int
+hfs_lookup_lastlink (struct hfsmount *hfsmp, cnid_t linkfileid,
+ cnid_t *lastid, struct cat_desc *cdesc) {
+ int lockflags;
+ int error;
+
+ *lastid = 0;
+
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
+
+ error = cat_lookup_lastlink(hfsmp, linkfileid, lastid, cdesc);
+
+ hfs_systemfile_unlock(hfsmp, lockflags);
+
+ /*
+ * cat_lookup_lastlink will zero out the lastid/cdesc arguments as needed
+ * upon error cases.
+ */
+ return error;
+}
+
+
/*
- * Cache the orgin of a directory hard link
+ * Cache the origin of a directory or file hard link
*
* cnode must be lock on entry
*/
linkorigin_t *origin = NULL;
void * thread = current_thread();
int count = 0;
-
+ int maxorigins = (S_ISDIR(cp->c_mode)) ? MAX_CACHED_ORIGINS : MAX_CACHED_FILE_ORIGINS;
/*
* Look for an existing origin first. If not found, create/steal one.
*/
}
if (origin == NULL) {
/* Recycle the last (i.e., the oldest) if we have too many. */
- if (count > MAX_CACHED_ORIGINS) {
+ if (count > maxorigins) {
origin = TAILQ_LAST(&cp->c_originlist, hfs_originhead);
TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
} else {
}
/*
- * Release any cached origins for a directory hard link
+ * Release any cached origins for a directory or file hard link
*
* cnode must be lock on entry
*/
}
/*
- * Release a specific origin for a directory hard link
+ * Release a specific origin for a directory or file hard link
*
* cnode must be lock on entry
*/
void
hfs_relorigin(struct cnode *cp, cnid_t parentcnid)
{
- linkorigin_t *origin = NULL;
+ linkorigin_t *origin, *prev;
void * thread = current_thread();
- TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
+ TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev) {
if ((origin->lo_thread == thread) ||
(origin->lo_parentcnid == parentcnid)) {
TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
+ FREE(origin, M_TEMP);
break;
}
}
}
/*
- * Test if a directory hard link has a cached origin
+ * Test if a directory or file hard link has a cached origin
*
* cnode must be lock on entry
*/
}
/*
- * Obtain the current parent cnid of a directory hard link
+ * Obtain the current parent cnid of a directory or file hard link
*
* cnode must be lock on entry
*/
}
/*
- * Obtain the current cnid of a directory hard link
+ * Obtain the current cnid of a directory or file hard link
*
* cnode must be lock on entry
*/