#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/vnode.h>
#include <vfs/vfs_support.h>
#include <libkern/libkern.h>
#include "hfs.h"
#include "hfs_catalog.h"
#include "hfs_format.h"
#include "hfs_endian.h"
static int cur_link_id = 0;
const char *hfs_private_names[] = {
HFSPLUSMETADATAFOLDER,
HFSPLUS_DIR_METADATA_FOLDER
};
static int setfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t firstlink);
static int getfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t *firstlink);
static int
createindirectlink(struct hfsmount *hfsmp, u_int32_t linknum, struct cat_desc *descp,
cnid_t nextcnid, cnid_t *linkcnid, int is_inode_linkchain_set)
{
struct FndrFileInfo *fip;
struct cat_attr attr;
if (linknum == 0) {
printf("createindirectlink: linknum is zero!\n");
return (EINVAL);
}
bzero(&attr, sizeof(attr));
attr.ca_linkref = linknum;
attr.ca_itime = hfsmp->hfs_itime;
attr.ca_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
attr.ca_recflags = kHFSHasLinkChainMask | kHFSThreadExistsMask;
attr.ca_flags = UF_IMMUTABLE;
fip = (struct FndrFileInfo *)&attr.ca_finderinfo;
if (descp->cd_flags & CD_ISDIR) {
fip->fdType = SWAP_BE32 (kHFSAliasType);
fip->fdCreator = SWAP_BE32 (kHFSAliasCreator);
fip->fdFlags = SWAP_BE16 (kIsAlias);
} else {
fip->fdType = SWAP_BE32 (kHardLinkFileType);
fip->fdCreator = SWAP_BE32 (kHFSPlusCreator);
fip->fdFlags = SWAP_BE16 (kHasBeenInited);
if ((is_inode_linkchain_set == 0) && (nextcnid == 0)) {
attr.ca_recflags &= ~kHFSHasLinkChainMask;
}
}
return cat_createlink(hfsmp, descp, &attr, nextcnid, linkcnid);
}
static int
hfs_makelink(struct hfsmount *hfsmp, struct cnode *cp, struct cnode *dcp,
struct componentname *cnp)
{
vfs_context_t ctx = cnp->cn_context;
struct proc *p = vfs_context_proc(ctx);
u_int32_t indnodeno = 0;
char inodename[32];
struct cat_desc to_desc;
struct cat_desc link_desc;
int newlink = 0;
int lockflags;
int retval = 0;
cat_cookie_t cookie;
cnid_t orig_cnid;
cnid_t linkcnid;
cnid_t orig_firstlink;
enum privdirtype type;
type = S_ISDIR(cp->c_mode) ? DIR_HARDLINKS : FILE_HARDLINKS;
if (cur_link_id == 0) {
cur_link_id = ((random() & 0x3fffffff) + 100);
}
if (dcp->c_fileid == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid ||
dcp->c_fileid == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
return (EPERM);
}
bzero(&cookie, sizeof(cat_cookie_t));
if ((retval = cat_preflight(hfsmp, (2 * CAT_CREATE)+ CAT_RENAME, &cookie, p))) {
return (retval);
}
lockflags = SFL_CATALOG | SFL_ATTRIBUTE;
if (type == DIR_HARDLINKS) {
lockflags |= SFL_BITMAP;
}
lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
orig_cnid = cp->c_desc.cd_cnid;
if ((cp->c_linkcount == 2) && !(cp->c_flag & C_HARDLINK)) {
newlink = 1;
bzero(&to_desc, sizeof(to_desc));
to_desc.cd_parentcnid = hfsmp->hfs_private_desc[type].cd_cnid;
to_desc.cd_cnid = cp->c_fileid;
to_desc.cd_flags = (type == DIR_HARDLINKS) ? CD_ISDIR : 0;
do {
if (type == DIR_HARDLINKS) {
indnodeno = cp->c_fileid;
MAKE_DIRINODE_NAME(inodename, sizeof(inodename),
indnodeno);
} else {
if (retval == 0) {
indnodeno = cp->c_fileid;
} else {
indnodeno = cur_link_id++;
}
MAKE_INODE_NAME(inodename, sizeof(inodename),
indnodeno);
}
to_desc.cd_nameptr = (const u_int8_t *)inodename;
to_desc.cd_namelen = strlen(inodename);
retval = cat_rename(hfsmp, &cp->c_desc, &hfsmp->hfs_private_desc[type],
&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);
}
} while ((retval == EEXIST) && (type == FILE_HARDLINKS));
if (retval)
goto out;
bzero(&link_desc, sizeof(link_desc));
link_desc.cd_nameptr = cp->c_desc.cd_nameptr;
link_desc.cd_namelen = cp->c_desc.cd_namelen;
link_desc.cd_parentcnid = cp->c_parentcnid;
link_desc.cd_flags = S_ISDIR(cp->c_mode) ? CD_ISDIR : 0;
retval = createindirectlink(hfsmp, indnodeno, &link_desc, 0, &linkcnid, true);
if (retval) {
int err;
cp->c_desc.cd_cnid = orig_cnid;
err = cat_rename(hfsmp, &to_desc, &dcp->c_desc, &cp->c_desc, NULL);
if (err && err != EIO && err != ENXIO)
panic("hfs_makelink: error %d from cat_rename backout 1", err);
goto out;
}
cp->c_attr.ca_linkref = indnodeno;
cp->c_desc.cd_cnid = linkcnid;
if (type == DIR_HARDLINKS) {
if (setfirstlink(hfsmp, cp->c_fileid, linkcnid) == 0)
cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
} else {
cp->c_attr.ca_firstlink = linkcnid;
}
cp->c_attr.ca_recflags |= kHFSHasLinkChainMask;
} else {
indnodeno = cp->c_attr.ca_linkref;
}
bzero(&link_desc, sizeof(link_desc));
link_desc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr;
link_desc.cd_namelen = strlen(cnp->cn_nameptr);
link_desc.cd_parentcnid = dcp->c_fileid;
link_desc.cd_flags = S_ISDIR(cp->c_mode) ? CD_ISDIR : 0;
if (type == DIR_HARDLINKS) {
retval = getfirstlink(hfsmp, cp->c_fileid, &orig_firstlink);
} else {
orig_firstlink = cp->c_attr.ca_firstlink;
}
if (retval == 0)
retval = createindirectlink(hfsmp, indnodeno, &link_desc,
orig_firstlink, &linkcnid,
(cp->c_attr.ca_recflags & kHFSHasLinkChainMask));
if (retval && newlink) {
int err;
(void) cat_delete(hfsmp, &cp->c_desc, &cp->c_attr);
cp->c_desc.cd_cnid = orig_cnid;
err = cat_rename(hfsmp, &to_desc, &dcp->c_desc, &cp->c_desc, NULL);
if (err && err != EIO && err != ENXIO)
panic("hfs_makelink: error %d from cat_rename backout 2", err);
cp->c_attr.ca_linkref = 0;
goto out;
} else if (retval == 0) {
if (cp->c_attr.ca_recflags & kHFSHasLinkChainMask) {
(void) cat_updatelink(hfsmp, orig_firstlink, linkcnid, HFS_IGNORABLE_LINK);
if (type == DIR_HARDLINKS) {
if (setfirstlink(hfsmp, cp->c_fileid, linkcnid) == 0)
cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
} else {
cp->c_attr.ca_firstlink = linkcnid;
}
}
if (newlink) {
vnode_t vp;
if (retval != 0) {
panic("hfs_makelink: retval %d but newlink = 1!\n", retval);
}
hfsmp->hfs_private_attr[type].ca_entries++;
if (type == DIR_HARDLINKS) {
INC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[type]);
}
retval = cat_update(hfsmp, &hfsmp->hfs_private_desc[type],
&hfsmp->hfs_private_attr[type], NULL, NULL);
if (retval != 0 && retval != EIO && retval != ENXIO) {
panic("hfs_makelink: cat_update of privdir failed! (%d)\n", retval);
}
cp->c_flag |= C_HARDLINK;
if ((vp = cp->c_vp) != NULLVP) {
if (vnode_get(vp) == 0) {
vnode_setmultipath(vp);
vnode_put(vp);
}
}
if ((vp = cp->c_rsrc_vp) != NULLVP) {
if (vnode_get(vp) == 0) {
vnode_setmultipath(vp);
vnode_put(vp);
}
}
cp->c_touch_chgtime = TRUE;
cp->c_flag |= C_FORCEUPDATE;
}
dcp->c_flag |= C_FORCEUPDATE;
}
out:
hfs_systemfile_unlock(hfsmp, lockflags);
cat_postflight(hfsmp, &cookie, p);
if (retval == 0 && newlink) {
hfs_volupdate(hfsmp, VOL_MKFILE, 0);
}
return (retval);
}
__private_extern__
int
hfs_vnop_link(struct vnop_link_args *ap)
{
struct hfsmount *hfsmp;
struct vnode *vp = ap->a_vp;
struct vnode *tdvp = ap->a_tdvp;
struct vnode *fdvp = NULLVP;
struct componentname *cnp = ap->a_cnp;
struct cnode *cp;
struct cnode *tdcp;
struct cnode *fdcp = NULL;
struct cat_desc todesc;
int lockflags = 0;
int intrans = 0;
enum vtype v_type;
int error, ret;
hfsmp = VTOHFS(vp);
v_type = vnode_vtype(vp);
if (hfsmp->hfs_flags & HFS_STANDARD) {
return (ENOTSUP);
}
if (v_type == VBLK || v_type == VCHR) {
return (EPERM);
}
if (v_type == VDIR) {
if (hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid == 0) {
return (EPERM);
}
if (hfsmp->jnl == NULL) {
return (EPERM);
}
if ((error = hfs_vget(hfsmp, hfs_currentparent(VTOC(vp)), &fdvp, 1))) {
return (error);
}
} else {
if (hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid == 0) {
return (ENOTSUP);
}
}
if (hfs_freeblks(hfsmp, 0) == 0) {
if (fdvp) {
vnode_put(fdvp);
}
return (ENOSPC);
}
if (fdvp) {
if ((error = hfs_lockfour(VTOC(tdvp), VTOC(vp), VTOC(fdvp), NULL, HFS_EXCLUSIVE_LOCK))) {
if (fdvp) {
vnode_put(fdvp);
}
return (error);
}
fdcp = VTOC(fdvp);
} else {
if ((error = hfs_lockpair(VTOC(tdvp), VTOC(vp), HFS_EXCLUSIVE_LOCK))) {
return (error);
}
}
tdcp = VTOC(tdvp);
cp = VTOC(vp);
if (cp->c_linkcount >= HFS_LINK_MAX) {
error = EMLINK;
goto out;
}
if (cp->c_flags & (IMMUTABLE | APPEND)) {
error = EPERM;
goto out;
}
if (cp->c_flag & (C_NOEXISTS | C_DELETED)) {
error = ENOENT;
goto out;
}
tdcp->c_flag |= C_DIR_MODIFICATION;
if (hfs_start_transaction(hfsmp) != 0) {
error = EINVAL;
goto out;
}
intrans = 1;
todesc.cd_flags = (v_type == VDIR) ? CD_ISDIR : 0;
todesc.cd_encoding = 0;
todesc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr;
todesc.cd_namelen = cnp->cn_namelen;
todesc.cd_parentcnid = tdcp->c_fileid;
todesc.cd_hint = 0;
todesc.cd_cnid = 0;
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
if (cat_lookup(hfsmp, &todesc, 0, NULL, NULL, NULL, NULL) == 0) {
error = EEXIST;
goto out;
}
if (cp->c_flag & C_HARDLINK) {
struct cat_attr cattr;
if ((cat_idlookup(hfsmp, cp->c_fileid, 0, NULL, &cattr, NULL) != 0) ||
(cattr.ca_fileid != cp->c_fileid)) {
error = ENOENT;
goto out;
}
} else {
cnid_t fileid;
if ((cat_lookup(hfsmp, &cp->c_desc, 0, NULL, NULL, NULL, &fileid) != 0) ||
(fileid != cp->c_fileid)) {
error = ENOENT;
goto out;
}
}
if (v_type == VDIR) {
if ((cp->c_parentcnid == tdcp->c_fileid) ||
(tdcp->c_fileid == kHFSRootFolderID) ||
(cp->c_parentcnid == kHFSRootFolderID) ||
cat_check_link_ancestry(hfsmp, tdcp->c_fileid, cp->c_fileid)) {
error = EPERM;
goto out;
}
}
hfs_systemfile_unlock(hfsmp, lockflags);
lockflags = 0;
cp->c_linkcount++;
cp->c_touch_chgtime = TRUE;
error = hfs_makelink(hfsmp, cp, tdcp, cnp);
if (error) {
cp->c_linkcount--;
hfs_volupdate(hfsmp, VOL_UPDATE, 0);
} else {
if (tdcp->c_flag & C_NEG_ENTRIES) {
cache_purge_negatives(tdvp);
tdcp->c_flag &= ~C_NEG_ENTRIES;
}
tdcp->c_entries++;
if (v_type == VDIR) {
INC_FOLDERCOUNT(hfsmp, tdcp->c_attr);
tdcp->c_attr.ca_recflags |= kHFSHasChildLinkMask;
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);
error = 0;
}
}
tdcp->c_dirchangecnt++;
tdcp->c_touch_chgtime = TRUE;
tdcp->c_touch_modtime = TRUE;
tdcp->c_flag |= C_FORCEUPDATE;
error = hfs_update(tdvp, 0);
if (error && error != EIO && error != ENXIO) {
panic("hfs_vnop_link: error updating tdvp %p\n", tdvp);
}
if ((v_type == VDIR) &&
(fdcp != NULL) &&
((fdcp->c_attr.ca_recflags & kHFSHasChildLinkMask) == 0)) {
fdcp->c_attr.ca_recflags |= kHFSHasChildLinkMask;
fdcp->c_touch_chgtime = TRUE;
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);
}
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);
error = 0;
}
}
hfs_volupdate(hfsmp, VOL_MKFILE,
(tdcp->c_cnid == kHFSRootFolderID));
}
cp->c_flag |= C_FORCEUPDATE;
if ((error == 0) && (ret = hfs_update(vp, TRUE)) != 0 && ret != EIO && ret != ENXIO) {
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);
}
if (intrans) {
hfs_end_transaction(hfsmp);
}
tdcp->c_flag &= ~C_DIR_MODIFICATION;
wakeup((caddr_t)&tdcp->c_flag);
if (fdcp) {
hfs_unlockfour(tdcp, cp, fdcp, NULL);
} else {
hfs_unlockpair(tdcp, cp);
}
if (fdvp) {
vnode_put(fdvp);
}
return (error);
}
__private_extern__
int
hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct componentname *cnp, int skip_reserve)
{
struct cnode *cp;
struct cnode *dcp;
struct cat_desc cndesc;
struct timeval tv;
char inodename[32];
cnid_t prevlinkid;
cnid_t nextlinkid;
int lockflags = 0;
int started_tr;
int rm_priv_file = 0;
int error;
if (hfsmp->hfs_flags & HFS_STANDARD) {
return (EPERM);
}
cp = VTOC(vp);
dcp = VTOC(dvp);
dcp->c_flag |= C_DIR_MODIFICATION;
cache_purge(vp);
if ((error = hfs_start_transaction(hfsmp)) != 0) {
started_tr = 0;
goto out;
}
started_tr = 1;
cndesc.cd_flags = vnode_isdir(vp) ? CD_ISDIR : 0;
cndesc.cd_encoding = cp->c_desc.cd_encoding;
cndesc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr;
cndesc.cd_namelen = cnp->cn_namelen;
cndesc.cd_parentcnid = dcp->c_fileid;
cndesc.cd_hint = dcp->c_childhint;
lockflags = SFL_CATALOG | SFL_ATTRIBUTE;
if (cndesc.cd_flags & CD_ISDIR) {
lockflags |= SFL_BITMAP;
}
lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
if ((error = cat_lookuplink(hfsmp, &cndesc, &cndesc.cd_cnid, &prevlinkid, &nextlinkid))) {
goto out;
}
if (!skip_reserve && (error = cat_preflight(hfsmp, 2 * CAT_DELETE, NULL, 0))) {
goto out;
}
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);
}
}
if ((error = cat_deletelink(hfsmp, &cndesc))) {
goto out;
}
if (dcp->c_entries > 0) {
dcp->c_entries--;
}
if (cndesc.cd_flags & CD_ISDIR) {
DEC_FOLDERCOUNT(hfsmp, dcp->c_attr);
}
dcp->c_dirchangecnt++;
microtime(&tv);
dcp->c_ctime = tv.tv_sec;
dcp->c_mtime = tv.tv_sec;
(void ) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL);
--cp->c_linkcount;
if (cp->c_linkcount < 1) {
char delname[32];
struct cat_desc to_desc;
struct cat_desc from_desc;
bzero(&from_desc, sizeof(from_desc));
bzero(&to_desc, sizeof(to_desc));
if (vnode_isdir(vp)) {
if (cp->c_entries != 0) {
panic("hfs_unlink: dir not empty (id %d, %d entries)", cp->c_fileid, cp->c_entries);
}
MAKE_DIRINODE_NAME(inodename, sizeof(inodename),
cp->c_attr.ca_linkref);
from_desc.cd_parentcnid = hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid;
from_desc.cd_flags = CD_ISDIR;
to_desc.cd_flags = CD_ISDIR;
} else {
MAKE_INODE_NAME(inodename, sizeof(inodename),
cp->c_attr.ca_linkref);
from_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
from_desc.cd_flags = 0;
to_desc.cd_flags = 0;
}
from_desc.cd_nameptr = (const u_int8_t *)inodename;
from_desc.cd_namelen = strlen(inodename);
from_desc.cd_cnid = cp->c_fileid;
MAKE_DELETED_NAME(delname, sizeof(delname), cp->c_fileid);
to_desc.cd_nameptr = (const u_int8_t *)delname;
to_desc.cd_namelen = strlen(delname);
to_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
to_desc.cd_cnid = cp->c_fileid;
error = cat_rename(hfsmp, &from_desc, &hfsmp->hfs_private_desc[FILE_HARDLINKS],
&to_desc, (struct cat_desc *)NULL);
if (error == 0) {
cp->c_flag |= C_DELETED;
cp->c_attr.ca_recflags &= ~kHFSHasLinkChainMask;
cp->c_attr.ca_firstlink = 0;
if (vnode_isdir(vp)) {
hfsmp->hfs_private_attr[DIR_HARDLINKS].ca_entries--;
DEC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[DIR_HARDLINKS]);
hfsmp->hfs_private_attr[FILE_HARDLINKS].ca_entries++;
INC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[FILE_HARDLINKS]);
(void)cat_update(hfsmp, &hfsmp->hfs_private_desc[DIR_HARDLINKS],
&hfsmp->hfs_private_attr[DIR_HARDLINKS], NULL, NULL);
(void)cat_update(hfsmp, &hfsmp->hfs_private_desc[FILE_HARDLINKS],
&hfsmp->hfs_private_attr[FILE_HARDLINKS], NULL, NULL);
}
} else {
error = 0;
}
} else {
cnid_t firstlink;
if (vnode_isdir(vp) &&
getfirstlink(hfsmp, cp->c_fileid, &firstlink) == 0 &&
firstlink == cndesc.cd_cnid) {
if (setfirstlink(hfsmp, cp->c_fileid, nextlinkid) == 0)
cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
} else if (vnode_isreg(vp) && cp->c_attr.ca_firstlink == cndesc.cd_cnid) {
cp->c_attr.ca_firstlink = nextlinkid;
}
if (prevlinkid) {
(void) cat_updatelink(hfsmp, prevlinkid, HFS_IGNORABLE_LINK, nextlinkid);
}
if (nextlinkid) {
(void) cat_updatelink(hfsmp, nextlinkid, prevlinkid, HFS_IGNORABLE_LINK);
}
}
cp->c_ctime = tv.tv_sec;
(void) cat_update(hfsmp, &cp->c_desc, &cp->c_attr, NULL, NULL);
hfs_systemfile_unlock(hfsmp, lockflags);
lockflags = 0;
hfs_volupdate(hfsmp, VOL_RMFILE, (dcp->c_cnid == kHFSRootFolderID));
if (rm_priv_file) {
hfs_volupdate(hfsmp, VOL_RMFILE, 0);
}
cat_releasedesc(&cp->c_desc);
HFS_KNOTE(dvp, NOTE_WRITE);
HFS_KNOTE(vp, NOTE_DELETE);
out:
if (lockflags) {
hfs_systemfile_unlock(hfsmp, lockflags);
}
if (started_tr) {
hfs_end_transaction(hfsmp);
}
dcp->c_flag &= ~C_DIR_MODIFICATION;
wakeup((caddr_t)&dcp->c_flag);
return (error);
}
__private_extern__
void
hfs_privatedir_init(struct hfsmount * hfsmp, enum privdirtype type)
{
struct vnode * dvp = NULLVP;
struct cnode * dcp = NULL;
struct cat_desc *priv_descp;
struct cat_attr *priv_attrp;
struct FndrDirInfo * fndrinfo;
struct timeval tv;
int lockflags;
int trans = 0;
int error;
if (hfsmp->hfs_flags & HFS_STANDARD) {
return;
}
priv_descp = &hfsmp->hfs_private_desc[type];
priv_attrp = &hfsmp->hfs_private_attr[type];
if (priv_descp->cd_cnid != 0) {
return;
}
priv_descp->cd_parentcnid = kRootDirID;
priv_descp->cd_nameptr = (const u_int8_t *)hfs_private_names[type];
priv_descp->cd_namelen = strlen((const char *)priv_descp->cd_nameptr);
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);
hfs_systemfile_unlock(hfsmp, lockflags);
if (error == 0) {
if (type == FILE_HARDLINKS) {
hfsmp->hfs_metadata_createdate = priv_attrp->ca_itime;
}
priv_descp->cd_cnid = priv_attrp->ca_fileid;
goto exit;
}
if (hfsmp->hfs_flags & HFS_READ_ONLY) {
goto exit;
}
if (hfs_vget(hfsmp, kRootDirID, &dvp, 0) != 0) {
goto exit;
}
dcp = VTOC(dvp);
bzero(priv_attrp, sizeof(struct cat_attr));
priv_attrp->ca_flags = UF_IMMUTABLE | UF_HIDDEN;
priv_attrp->ca_mode = S_IFDIR;
if (type == DIR_HARDLINKS) {
priv_attrp->ca_mode |= S_ISVTX | S_IRUSR | S_IXUSR | S_IRGRP |
S_IXGRP | S_IROTH | S_IXOTH;
}
priv_attrp->ca_linkcount = 1;
priv_attrp->ca_itime = hfsmp->hfs_itime;
priv_attrp->ca_recflags = kHFSHasFolderCountMask;
fndrinfo = (struct FndrDirInfo *)&priv_attrp->ca_finderinfo;
fndrinfo->frLocation.v = SWAP_BE16(16384);
fndrinfo->frLocation.h = SWAP_BE16(16384);
fndrinfo->frFlags = SWAP_BE16(kIsInvisible + kNameLocked);
if (hfs_start_transaction(hfsmp) != 0) {
goto exit;
}
trans = 1;
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
if (cat_preflight(hfsmp, CAT_CREATE, NULL, 0) != 0) {
hfs_systemfile_unlock(hfsmp, lockflags);
goto exit;
}
error = cat_create(hfsmp, priv_descp, priv_attrp, NULL);
if (error == 0) {
priv_descp->cd_cnid = priv_attrp->ca_fileid;
dcp->c_entries++;
INC_FOLDERCOUNT(hfsmp, dcp->c_attr);
dcp->c_dirchangecnt++;
microtime(&tv);
dcp->c_ctime = tv.tv_sec;
dcp->c_mtime = tv.tv_sec;
(void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL);
}
hfs_systemfile_unlock(hfsmp, lockflags);
if (error) {
goto exit;
}
if (type == FILE_HARDLINKS) {
hfsmp->hfs_metadata_createdate = hfsmp->hfs_itime;
}
hfs_volupdate(hfsmp, VOL_MKDIR, 1);
exit:
if (trans) {
hfs_end_transaction(hfsmp);
}
if (dvp) {
hfs_unlock(dcp);
vnode_put(dvp);
}
if ((error == 0) && (type == DIR_HARDLINKS)) {
hfs_xattr_init(hfsmp);
}
}
__private_extern__
int
hfs_lookuplink(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid, cnid_t *nextlinkid)
{
int lockflags;
int error;
*prevlinkid = 0;
*nextlinkid = 0;
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
error = cat_lookuplinkbyid(hfsmp, linkfileid, prevlinkid, nextlinkid);
if (error == ENOLINK) {
hfs_systemfile_unlock(hfsmp, lockflags);
lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
error = getfirstlink(hfsmp, linkfileid, nextlinkid);
}
hfs_systemfile_unlock(hfsmp, lockflags);
return (error);
}
__private_extern__
void
hfs_savelinkorigin(cnode_t *cp, cnid_t parentcnid)
{
linkorigin_t *origin = NULL;
void * thread = current_thread();
int count = 0;
TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
++count;
if (origin->lo_thread == thread) {
TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
break;
}
}
if (origin == NULL) {
if (count > MAX_CACHED_ORIGINS) {
origin = TAILQ_LAST(&cp->c_originlist, hfs_originhead);
TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
} else {
MALLOC(origin, linkorigin_t *, sizeof(linkorigin_t), M_TEMP, M_WAITOK);
}
origin->lo_thread = thread;
}
origin->lo_cnid = cp->c_cnid;
origin->lo_parentcnid = parentcnid;
TAILQ_INSERT_HEAD(&cp->c_originlist, origin, lo_link);
}
__private_extern__
void
hfs_relorigins(struct cnode *cp)
{
linkorigin_t *origin, *prev;
TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev) {
FREE(origin, M_TEMP);
}
TAILQ_INIT(&cp->c_originlist);
}
__private_extern__
void
hfs_relorigin(struct cnode *cp, cnid_t parentcnid)
{
linkorigin_t *origin = NULL;
void * thread = current_thread();
TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
if ((origin->lo_thread == thread) ||
(origin->lo_parentcnid == parentcnid)) {
TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
break;
}
}
}
__private_extern__
int
hfs_haslinkorigin(cnode_t *cp)
{
if (cp->c_flag & C_HARDLINK) {
linkorigin_t *origin;
void * thread = current_thread();
TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
if (origin->lo_thread == thread) {
return (1);
}
}
}
return (0);
}
__private_extern__
cnid_t
hfs_currentparent(cnode_t *cp)
{
if (cp->c_flag & C_HARDLINK) {
linkorigin_t *origin;
void * thread = current_thread();
TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
if (origin->lo_thread == thread) {
return (origin->lo_parentcnid);
}
}
}
return (cp->c_parentcnid);
}
__private_extern__
cnid_t
hfs_currentcnid(cnode_t *cp)
{
if (cp->c_flag & C_HARDLINK) {
linkorigin_t *origin;
void * thread = current_thread();
TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
if (origin->lo_thread == thread) {
return (origin->lo_cnid);
}
}
}
return (cp->c_cnid);
}
static int
setfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t firstlink)
{
FCB * btfile;
BTreeIterator * iterator;
FSBufferDescriptor btdata;
u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
HFSPlusAttrData *dataptr;
int result;
u_int16_t datasize;
if (hfsmp->hfs_attribute_cp == NULL) {
return (EPERM);
}
MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
bzero(iterator, sizeof(*iterator));
result = hfs_buildattrkey(fileid, FIRST_LINK_XATTR_NAME, (HFSPlusAttrKey *)&iterator->key);
if (result) {
goto out;
}
dataptr = (HFSPlusAttrData *)&attrdata[0];
dataptr->recordType = kHFSPlusAttrInlineData;
dataptr->reserved[0] = 0;
dataptr->reserved[1] = 0;
(void)snprintf((char *)&dataptr->attrData[0],
sizeof(dataptr) - (4 * sizeof(uint32_t)),
"%lu", (unsigned long)firstlink);
dataptr->attrSize = 1 + strlen((char *)&dataptr->attrData[0]);
datasize = sizeof(HFSPlusAttrData) - 2 + dataptr->attrSize + ((dataptr->attrSize & 1) ? 1 : 0);
btdata.bufferAddress = dataptr;
btdata.itemSize = datasize;
btdata.itemCount = 1;
btfile = hfsmp->hfs_attribute_cp->c_datafork;
result = BTInsertRecord(btfile, iterator, &btdata, datasize);
if (result == btExists) {
result = BTReplaceRecord(btfile, iterator, &btdata, datasize);
}
(void) BTFlushPath(btfile);
out:
FREE(iterator, M_TEMP);
return MacToVFSError(result);
}
static int
getfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t *firstlink)
{
FCB * btfile;
BTreeIterator * iterator;
FSBufferDescriptor btdata;
u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
HFSPlusAttrData *dataptr;
int result;
u_int16_t datasize;
if (hfsmp->hfs_attribute_cp == NULL) {
return (EPERM);
}
MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
bzero(iterator, sizeof(*iterator));
result = hfs_buildattrkey(fileid, FIRST_LINK_XATTR_NAME, (HFSPlusAttrKey *)&iterator->key);
if (result)
goto out;
dataptr = (HFSPlusAttrData *)&attrdata[0];
datasize = sizeof(attrdata);
btdata.bufferAddress = dataptr;
btdata.itemSize = sizeof(attrdata);
btdata.itemCount = 1;
btfile = hfsmp->hfs_attribute_cp->c_datafork;
result = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL);
if (result)
goto out;
if (dataptr->attrSize < 3) {
result = ENOENT;
goto out;
}
*firstlink = strtoul((char*)&dataptr->attrData[0], NULL, 10);
out:
FREE(iterator, M_TEMP);
return MacToVFSError(result);
}