#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/namei.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
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);
hfs_global_shared_lock_acquire(hfsmp);
if (hfsmp->jnl) {
if (journal_start_transaction(hfsmp->jnl) != 0) {
hfs_global_shared_lock_release(hfsmp);
return EINVAL;
}
}
result = cat_create(hfsmp, &desc, &attr, NULL);
if (result == 0 && linkcnid != NULL)
*linkcnid = attr.ca_fileid;
if (hfsmp->jnl) {
journal_end_transaction(hfsmp->jnl);
}
hfs_global_shared_lock_release(hfsmp);
return (result);
}
static int
hfs_makelink(struct hfsmount *hfsmp, struct cnode *cp, struct cnode *dcp,
struct componentname *cnp)
{
struct proc *p = cnp->cn_proc;
u_int32_t indnodeno = 0;
char inodename[32];
struct cat_desc to_desc;
int newlink = 0;
int retval;
if (dcp->c_fileid == hfsmp->hfs_privdir_desc.cd_cnid)
return (EPERM);
if (hfs_freeblks(hfsmp, 0) == 0)
return (ENOSPC);
retval = hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_EXCLUSIVE, p);
if (retval) {
return retval;
}
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 {
indnodeno = ((random() & 0x3fffffff) + 100);
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);
} 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) {
#if 1
{
int err;
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);
}
#else
(void) cat_rename(hfsmp, &to_desc, &dcp->c_desc, &cp->c_desc, NULL);
#endif
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) {
(void) cat_delete(hfsmp, &cp->c_desc, &cp->c_attr);
#if 1
{
int err;
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);
}
#else
(void) cat_rename(hfsmp, &to_desc, &dcp->c_desc, &cp->c_desc, NULL);
#endif
goto out;
}
if (newlink) {
hfsmp->hfs_privdir_attr.ca_entries++;
(void)cat_update(hfsmp, &hfsmp->hfs_privdir_desc,
&hfsmp->hfs_privdir_attr, NULL, NULL);
hfs_volupdate(hfsmp, VOL_MKFILE, 0);
cp->c_flag |= (C_CHANGE | C_HARDLINK);
}
out:
(void) hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_RELEASE, p);
return (retval);
}
int
hfs_link(ap)
struct vop_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 proc *p = cnp->cn_proc;
struct cnode *cp;
struct cnode *tdcp;
struct timeval tv;
int error;
hfsmp = VTOHFS(vp);
#if HFS_DIAGNOSTIC
if ((cnp->cn_flags & HASBUF) == 0)
panic("hfs_link: no name");
#endif
if (tdvp->v_mount != vp->v_mount) {
VOP_ABORTOP(tdvp, cnp);
error = EXDEV;
goto out2;
}
if (VTOVCB(tdvp)->vcbSigWord != kHFSPlusSigWord)
return err_link(ap);
if (hfsmp->hfs_private_metadata_dir == 0)
return err_link(ap);
if (tdvp != vp && (error = vn_lock(vp, LK_EXCLUSIVE, p))) {
VOP_ABORTOP(tdvp, cnp);
goto out2;
}
cp = VTOC(vp);
tdcp = VTOC(tdvp);
if (cp->c_nlink >= HFS_LINK_MAX) {
VOP_ABORTOP(tdvp, cnp);
error = EMLINK;
goto out1;
}
if (cp->c_flags & (IMMUTABLE | APPEND)) {
VOP_ABORTOP(tdvp, cnp);
error = EPERM;
goto out1;
}
if (vp->v_type == VBLK || vp->v_type == VCHR) {
VOP_ABORTOP(tdvp, cnp);
error = EINVAL;
goto out1;
}
hfs_global_shared_lock_acquire(hfsmp);
if (hfsmp->jnl) {
if (journal_start_transaction(hfsmp->jnl) != 0) {
hfs_global_shared_lock_release(hfsmp);
VOP_ABORTOP(tdvp, cnp);
error = EINVAL;
goto out1;
}
}
cp->c_nlink++;
cp->c_flag |= C_CHANGE;
tv = time;
error = VOP_UPDATE(vp, &tv, &tv, 1);
if (!error) {
error = hfs_makelink(hfsmp, cp, tdcp, cnp);
}
if (error) {
cp->c_nlink--;
cp->c_flag |= C_CHANGE;
} else {
tdcp->c_nlink++;
tdcp->c_entries++;
tdcp->c_flag |= C_CHANGE | C_UPDATE;
tv = time;
(void) VOP_UPDATE(tdvp, &tv, &tv, 0);
hfs_volupdate(hfsmp, VOL_MKFILE,
(tdcp->c_cnid == kHFSRootFolderID));
}
error = VOP_UPDATE(vp, &tv, &tv, 1);
FREE_ZONE(cnp->cn_pnbuf, cnp->cn_pnlen, M_NAMEI);
if (hfsmp->jnl) {
journal_end_transaction(hfsmp->jnl);
}
hfs_global_shared_lock_release(hfsmp);
out1:
if (tdvp != vp)
VOP_UNLOCK(vp, 0, p);
out2:
vput(tdvp);
return (error);
}