#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;
static int
createindirectlink(struct hfsmount *hfsmp, u_int32_t linknum,
u_int32_t linkparid, char *linkName, cnid_t *linkcnid)
{
struct FndrFileInfo *fip;
struct cat_desc desc;
struct cat_attr attr;
int result;
bzero(&desc, sizeof(desc));
desc.cd_nameptr = linkName;
desc.cd_namelen = strlen(linkName);
desc.cd_parentcnid = linkparid;
bzero(&attr, sizeof(attr));
attr.ca_rdev = linknum;
attr.ca_itime = HFSTOVCB(hfsmp)->vcbCrDate;
attr.ca_mode = S_IFREG;
fip = (struct FndrFileInfo *)&attr.ca_finderinfo;
fip->fdType = SWAP_BE32 (kHardLinkFileType);
fip->fdCreator = SWAP_BE32 (kHFSPlusCreator);
fip->fdFlags = SWAP_BE16 (kHasBeenInited);
result = cat_create(hfsmp, &desc, &attr, NULL);
if (result == 0 && linkcnid != NULL)
*linkcnid = attr.ca_fileid;
return (result);
}
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;
int newlink = 0;
int lockflags;
int retval;
cat_cookie_t cookie;
cnid_t orig_cnid;
if (cur_link_id == 0) {
cur_link_id = ((random() & 0x3fffffff) + 100);
}
if (dcp->c_fileid == hfsmp->hfs_privdir_desc.cd_cnid)
return (EPERM);
if (hfs_freeblks(hfsmp, 0) == 0)
return (ENOSPC);
bzero(&cookie, sizeof(cat_cookie_t));
if ((retval = cat_preflight(hfsmp, (2 * CAT_CREATE)+ CAT_RENAME, &cookie, p))) {
return (retval);
}
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
orig_cnid = cp->c_desc.cd_cnid;
if (cp->c_nlink == 2 && (cp->c_flag & C_HARDLINK) == 0) {
newlink = 1;
bzero(&to_desc, sizeof(to_desc));
to_desc.cd_parentcnid = hfsmp->hfs_privdir_desc.cd_cnid;
to_desc.cd_cnid = cp->c_fileid;
do {
if (retval == 0) {
indnodeno = cp->c_fileid;
} else {
indnodeno = cur_link_id++;
}
MAKE_INODE_NAME(inodename, indnodeno);
to_desc.cd_nameptr = inodename;
to_desc.cd_namelen = strlen(inodename);
retval = cat_rename(hfsmp, &cp->c_desc, &hfsmp->hfs_privdir_desc,
&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);
if (retval)
goto out;
retval = createindirectlink(hfsmp, indnodeno, cp->c_parentcnid,
cp->c_desc.cd_nameptr, &cp->c_desc.cd_cnid);
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)
panic("hfs_makelink: error %d from cat_rename backout 1", err);
goto out;
}
cp->c_rdev = indnodeno;
} else {
indnodeno = cp->c_rdev;
}
retval = createindirectlink(hfsmp, indnodeno, dcp->c_fileid, cnp->cn_nameptr, NULL);
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)
panic("hfs_makelink: error %d from cat_rename backout 2", err);
goto out;
}
if (newlink) {
vnode_t vp;
if (retval != 0) {
panic("hfs_makelink: retval %d but newlink = 1!\n", retval);
}
hfsmp->hfs_privdir_attr.ca_entries++;
retval = cat_update(hfsmp, &hfsmp->hfs_privdir_desc,
&hfsmp->hfs_privdir_attr, NULL, NULL);
if (retval != 0) {
panic("hfs_makelink: cat_update of privdir failed! (%d)\n",
retval);
}
hfs_volupdate(hfsmp, VOL_MKFILE, 0);
cp->c_flag |= C_HARDLINK;
if ((vp = cp->c_vp) != NULLVP) {
if (vnode_get(vp) == 0) {
vnode_set_hard_link(vp);
vnode_put(vp);
}
}
if ((vp = cp->c_rsrc_vp) != NULLVP) {
if (vnode_get(vp) == 0) {
vnode_set_hard_link(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);
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 componentname *cnp = ap->a_cnp;
struct cnode *cp;
struct cnode *tdcp;
enum vtype v_type;
int error, ret, lockflags;
struct cat_desc cndesc;
if (VTOVCB(tdvp)->vcbSigWord != kHFSPlusSigWord) {
return err_link(ap);
}
if (VTOHFS(vp)->hfs_privdir_desc.cd_cnid == 0) {
return err_link(ap);
}
if (vnode_mount(tdvp) != vnode_mount(vp)) {
return (EXDEV);
}
if ((error = hfs_lockpair(VTOC(tdvp), VTOC(vp), HFS_EXCLUSIVE_LOCK))) {
return (error);
}
tdcp = VTOC(tdvp);
cp = VTOC(vp);
hfsmp = VTOHFS(vp);
if (cp->c_nlink >= 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;
}
v_type = vnode_vtype(vp);
if (v_type == VBLK || v_type == VCHR) {
error = EINVAL;
goto out;
}
if (hfs_start_transaction(hfsmp) != 0) {
error = EINVAL;
goto out;
}
cp->c_nlink++;
cp->c_touch_chgtime = TRUE;
error = hfs_makelink(hfsmp, cp, tdcp, cnp);
if (error) {
cp->c_nlink--;
hfs_volupdate(hfsmp, VOL_UPDATE, 0);
} else {
if (hfsmp->hfs_flags & HFS_CASE_SENSITIVE)
cache_purge_negatives(tdvp);
tdcp->c_nlink++;
tdcp->c_entries++;
tdcp->c_touch_chgtime = TRUE;
tdcp->c_touch_modtime = TRUE;
tdcp->c_flag |= C_FORCEUPDATE;
error = hfs_update(tdvp, 0);
if (error) {
panic("hfs_vnop_link: error updating tdvp 0x%x\n", tdvp);
}
hfs_volupdate(hfsmp, VOL_MKFILE,
(tdcp->c_cnid == kHFSRootFolderID));
}
cp->c_flag |= C_FORCEUPDATE;
if ((ret = hfs_update(vp, TRUE)) != 0) {
panic("hfs_vnop_link: error %d updating vp @ 0x%x\n", ret, vp);
}
hfs_end_transaction(hfsmp);
HFS_KNOTE(vp, NOTE_LINK);
HFS_KNOTE(tdvp, NOTE_WRITE);
out:
hfs_unlockpair(tdcp, cp);
return (error);
}