]> git.saurik.com Git - apple/xnu.git/blame - bsd/hfs/hfs_link.c
xnu-1228.5.20.tar.gz
[apple/xnu.git] / bsd / hfs / hfs_link.c
CommitLineData
1c79356b 1/*
2d21ac55 2 * Copyright (c) 1999-2007 Apple Inc. All rights reserved.
1c79356b 3 *
2d21ac55 4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
1c79356b 5 *
2d21ac55
A
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
8f6c56a5 14 *
2d21ac55
A
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
8f6c56a5
A
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
2d21ac55
A
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
8f6c56a5 25 *
2d21ac55 26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
1c79356b
A
27 */
28
1c79356b
A
29
30#include <sys/systm.h>
31#include <sys/kernel.h>
32#include <sys/malloc.h>
9bccf70c 33#include <sys/mount.h>
1c79356b
A
34#include <sys/stat.h>
35#include <sys/vnode.h>
36#include <vfs/vfs_support.h>
37#include <libkern/libkern.h>
38
39#include "hfs.h"
9bccf70c
A
40#include "hfs_catalog.h"
41#include "hfs_format.h"
42#include "hfs_endian.h"
1c79356b
A
43
44
91447636
A
45static int cur_link_id = 0;
46
2d21ac55
A
47/*
48 * Private directories where hardlink inodes reside.
49 */
50const char *hfs_private_names[] = {
51 HFSPLUSMETADATAFOLDER, /* FILE HARDLINKS */
52 HFSPLUS_DIR_METADATA_FOLDER /* DIRECTORY HARDLINKS */
53};
54
55
56/*
57 * Hardlink inodes save the head of their link chain in a
58 * private extended attribute. The following calls are
59 * used to access this attribute.
60 */
61static int setfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t firstlink);
62static int getfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t *firstlink);
91447636 63
1c79356b 64/*
2d21ac55
A
65 * Create a new catalog link record
66 *
67 * An indirect link is a reference to an inode (the real
68 * file or directory record).
69 *
70 * All the indirect links for a given inode are chained
71 * together in a doubly linked list.
1c79356b 72 *
2d21ac55
A
73 * Pre-Leopard file hard links do not have kHFSHasLinkChainBit
74 * set and do not have first/prev/next link IDs i.e. the values
75 * are zero. If a new link is being added to an existing
76 * pre-Leopard file hard link chain, do not set kHFSHasLinkChainBit.
1c79356b
A
77 */
78static int
2d21ac55
A
79createindirectlink(struct hfsmount *hfsmp, u_int32_t linknum, struct cat_desc *descp,
80 cnid_t nextcnid, cnid_t *linkcnid, int is_inode_linkchain_set)
1c79356b 81{
9bccf70c 82 struct FndrFileInfo *fip;
9bccf70c 83 struct cat_attr attr;
1c79356b 84
2d21ac55
A
85 if (linknum == 0) {
86 printf("createindirectlink: linknum is zero!\n");
87 return (EINVAL);
88 }
1c79356b 89
9bccf70c
A
90 /* Setup the default attributes */
91 bzero(&attr, sizeof(attr));
92
2d21ac55
A
93 /* Links are matched to inodes by link ID and to volumes by create date */
94 attr.ca_linkref = linknum;
95 attr.ca_itime = hfsmp->hfs_itime;
96 attr.ca_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
97 attr.ca_recflags = kHFSHasLinkChainMask | kHFSThreadExistsMask;
98 attr.ca_flags = UF_IMMUTABLE;
9bccf70c 99 fip = (struct FndrFileInfo *)&attr.ca_finderinfo;
1c79356b 100
2d21ac55
A
101 if (descp->cd_flags & CD_ISDIR) {
102 fip->fdType = SWAP_BE32 (kHFSAliasType);
103 fip->fdCreator = SWAP_BE32 (kHFSAliasCreator);
104 fip->fdFlags = SWAP_BE16 (kIsAlias);
105 } else /* file */ {
106 fip->fdType = SWAP_BE32 (kHardLinkFileType);
107 fip->fdCreator = SWAP_BE32 (kHFSPlusCreator);
108 fip->fdFlags = SWAP_BE16 (kHasBeenInited);
109 /* If the file inode does not have kHFSHasLinkChainBit set
110 * and the next link chain ID is zero, assume that this
111 * is pre-Leopard file inode. Therefore clear the bit.
112 */
113 if ((is_inode_linkchain_set == 0) && (nextcnid == 0)) {
114 attr.ca_recflags &= ~kHFSHasLinkChainMask;
115 }
116 }
9bccf70c 117 /* Create the indirect link directly in the catalog */
2d21ac55 118 return cat_createlink(hfsmp, descp, &attr, nextcnid, linkcnid);
1c79356b
A
119}
120
121
122/*
2d21ac55
A
123 * Make a link to the cnode cp in the directory dp
124 * using the name in cnp.
1c79356b 125 *
2d21ac55 126 * The cnodes cp and dcp must be locked.
1c79356b
A
127 */
128static int
9bccf70c
A
129hfs_makelink(struct hfsmount *hfsmp, struct cnode *cp, struct cnode *dcp,
130 struct componentname *cnp)
1c79356b 131{
91447636
A
132 vfs_context_t ctx = cnp->cn_context;
133 struct proc *p = vfs_context_proc(ctx);
1c79356b 134 u_int32_t indnodeno = 0;
91447636 135 char inodename[32];
9bccf70c 136 struct cat_desc to_desc;
2d21ac55 137 struct cat_desc link_desc;
9bccf70c 138 int newlink = 0;
91447636 139 int lockflags;
2d21ac55 140 int retval = 0;
91447636
A
141 cat_cookie_t cookie;
142 cnid_t orig_cnid;
2d21ac55
A
143 cnid_t linkcnid;
144 cnid_t orig_firstlink;
145 enum privdirtype type;
146
147 type = S_ISDIR(cp->c_mode) ? DIR_HARDLINKS : FILE_HARDLINKS;
1c79356b 148
91447636 149 if (cur_link_id == 0) {
2d21ac55 150 cur_link_id = ((random() & 0x3fffffff) + 100);
91447636
A
151 }
152
2d21ac55
A
153 /* We don't allow link nodes in our private system directories. */
154 if (dcp->c_fileid == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid ||
155 dcp->c_fileid == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
1c79356b 156 return (EPERM);
2d21ac55 157 }
1c79356b 158
91447636 159 bzero(&cookie, sizeof(cat_cookie_t));
55e303ae
A
160 /* Reserve some space in the Catalog file. */
161 if ((retval = cat_preflight(hfsmp, (2 * CAT_CREATE)+ CAT_RENAME, &cookie, p))) {
162 return (retval);
163 }
164
2d21ac55
A
165 lockflags = SFL_CATALOG | SFL_ATTRIBUTE;
166 /* Directory hard links allocate space for a symlink. */
167 if (type == DIR_HARDLINKS) {
168 lockflags |= SFL_BITMAP;
169 }
170 lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
91447636 171
2d21ac55 172 /* Save the current cnid value so we restore it if an error occurs. */
91447636 173 orig_cnid = cp->c_desc.cd_cnid;
1c79356b
A
174
175 /*
2d21ac55
A
176 * If this is a new hardlink then we need to create the inode
177 * and replace the original file/dir object with a link node.
1c79356b 178 */
2d21ac55 179 if ((cp->c_linkcount == 2) && !(cp->c_flag & C_HARDLINK)) {
9bccf70c
A
180 newlink = 1;
181 bzero(&to_desc, sizeof(to_desc));
2d21ac55 182 to_desc.cd_parentcnid = hfsmp->hfs_private_desc[type].cd_cnid;
9bccf70c 183 to_desc.cd_cnid = cp->c_fileid;
2d21ac55 184 to_desc.cd_flags = (type == DIR_HARDLINKS) ? CD_ISDIR : 0;
b4c24cb9 185
1c79356b 186 do {
2d21ac55
A
187 if (type == DIR_HARDLINKS) {
188 /* Directory hardlinks always use the cnid. */
189 indnodeno = cp->c_fileid;
190 MAKE_DIRINODE_NAME(inodename, sizeof(inodename),
191 indnodeno);
91447636 192 } else {
2d21ac55
A
193 /* Get a unique indirect node number */
194 if (retval == 0) {
195 indnodeno = cp->c_fileid;
196 } else {
197 indnodeno = cur_link_id++;
198 }
199 MAKE_INODE_NAME(inodename, sizeof(inodename),
200 indnodeno);
91447636 201 }
2d21ac55
A
202 /* Move original file/dir to data node directory */
203 to_desc.cd_nameptr = (const u_int8_t *)inodename;
9bccf70c
A
204 to_desc.cd_namelen = strlen(inodename);
205
2d21ac55 206 retval = cat_rename(hfsmp, &cp->c_desc, &hfsmp->hfs_private_desc[type],
9bccf70c 207 &to_desc, NULL);
1c79356b 208
91447636
A
209 if (retval != 0 && retval != EEXIST) {
210 printf("hfs_makelink: cat_rename to %s failed (%d). fileid %d\n",
211 inodename, retval, cp->c_fileid);
212 }
2d21ac55 213 } while ((retval == EEXIST) && (type == FILE_HARDLINKS));
9bccf70c
A
214 if (retval)
215 goto out;
1c79356b 216
2d21ac55
A
217 /*
218 * Replace original file/dir with a link record.
219 */
220
221 bzero(&link_desc, sizeof(link_desc));
222 link_desc.cd_nameptr = cp->c_desc.cd_nameptr;
223 link_desc.cd_namelen = cp->c_desc.cd_namelen;
224 link_desc.cd_parentcnid = cp->c_parentcnid;
225 link_desc.cd_flags = S_ISDIR(cp->c_mode) ? CD_ISDIR : 0;
226
227 retval = createindirectlink(hfsmp, indnodeno, &link_desc, 0, &linkcnid, true);
1c79356b 228 if (retval) {
2d21ac55 229 int err;
91447636 230
2d21ac55
A
231 /* Restore the cnode's cnid. */
232 cp->c_desc.cd_cnid = orig_cnid;
91447636 233
2d21ac55
A
234 /* Put the original file back. */
235 err = cat_rename(hfsmp, &to_desc, &dcp->c_desc, &cp->c_desc, NULL);
236 if (err && err != EIO && err != ENXIO)
237 panic("hfs_makelink: error %d from cat_rename backout 1", err);
238 goto out;
239 }
240 cp->c_attr.ca_linkref = indnodeno;
241 cp->c_desc.cd_cnid = linkcnid;
242 /* Directory hard links store the first link in an attribute. */
243 if (type == DIR_HARDLINKS) {
244 if (setfirstlink(hfsmp, cp->c_fileid, linkcnid) == 0)
245 cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
246 } else /* FILE_HARDLINKS */ {
247 cp->c_attr.ca_firstlink = linkcnid;
1c79356b 248 }
2d21ac55 249 cp->c_attr.ca_recflags |= kHFSHasLinkChainMask;
9bccf70c 250 } else {
2d21ac55 251 indnodeno = cp->c_attr.ca_linkref;
9bccf70c 252 }
1c79356b
A
253
254 /*
255 * Create a catalog entry for the new link (parentID + name).
256 */
2d21ac55
A
257
258 bzero(&link_desc, sizeof(link_desc));
259 link_desc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr;
260 link_desc.cd_namelen = strlen(cnp->cn_nameptr);
261 link_desc.cd_parentcnid = dcp->c_fileid;
262 link_desc.cd_flags = S_ISDIR(cp->c_mode) ? CD_ISDIR : 0;
263
264 /* Directory hard links store the first link in an attribute. */
265 if (type == DIR_HARDLINKS) {
266 retval = getfirstlink(hfsmp, cp->c_fileid, &orig_firstlink);
267 } else /* FILE_HARDLINKS */ {
268 orig_firstlink = cp->c_attr.ca_firstlink;
269 }
270 if (retval == 0)
271 retval = createindirectlink(hfsmp, indnodeno, &link_desc,
272 orig_firstlink, &linkcnid,
273 (cp->c_attr.ca_recflags & kHFSHasLinkChainMask));
9bccf70c 274 if (retval && newlink) {
2d21ac55 275 int err;
91447636 276
2d21ac55
A
277 /* Get rid of new link */
278 (void) cat_delete(hfsmp, &cp->c_desc, &cp->c_attr);
279
280 /* Restore the cnode's cnid. */
281 cp->c_desc.cd_cnid = orig_cnid;
282
283 /* Put the original file back. */
284 err = cat_rename(hfsmp, &to_desc, &dcp->c_desc, &cp->c_desc, NULL);
285 if (err && err != EIO && err != ENXIO)
286 panic("hfs_makelink: error %d from cat_rename backout 2", err);
91447636 287
2d21ac55
A
288 cp->c_attr.ca_linkref = 0;
289 goto out;
290 } else if (retval == 0) {
91447636 291
2d21ac55
A
292 /* Update the original first link to point back to the new first link. */
293 if (cp->c_attr.ca_recflags & kHFSHasLinkChainMask) {
294 (void) cat_updatelink(hfsmp, orig_firstlink, linkcnid, HFS_IGNORABLE_LINK);
1c79356b 295
2d21ac55
A
296 /* Update the inode's first link value. */
297 if (type == DIR_HARDLINKS) {
298 if (setfirstlink(hfsmp, cp->c_fileid, linkcnid) == 0)
299 cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
300 } else {
301 cp->c_attr.ca_firstlink = linkcnid;
302 }
303 }
304 /*
305 * Finally, if this is a new hardlink then:
306 * - update the private system directory
307 * - mark the cnode as a hard link
308 */
309 if (newlink) {
91447636
A
310 vnode_t vp;
311
91447636 312 if (retval != 0) {
2d21ac55
A
313 panic("hfs_makelink: retval %d but newlink = 1!\n", retval);
314 }
315
316 hfsmp->hfs_private_attr[type].ca_entries++;
317 /* From application perspective, directory hard link is a
318 * normal directory. Therefore count the new directory
319 * hard link for folder count calculation.
320 */
321 if (type == DIR_HARDLINKS) {
322 INC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[type]);
323 }
324 retval = cat_update(hfsmp, &hfsmp->hfs_private_desc[type],
325 &hfsmp->hfs_private_attr[type], NULL, NULL);
326 if (retval != 0 && retval != EIO && retval != ENXIO) {
327 panic("hfs_makelink: cat_update of privdir failed! (%d)\n", retval);
91447636 328 }
91447636
A
329 cp->c_flag |= C_HARDLINK;
330 if ((vp = cp->c_vp) != NULLVP) {
2d21ac55
A
331 if (vnode_get(vp) == 0) {
332 vnode_setmultipath(vp);
333 vnode_put(vp);
334 }
91447636
A
335 }
336 if ((vp = cp->c_rsrc_vp) != NULLVP) {
2d21ac55
A
337 if (vnode_get(vp) == 0) {
338 vnode_setmultipath(vp);
339 vnode_put(vp);
340 }
91447636
A
341 }
342 cp->c_touch_chgtime = TRUE;
343 cp->c_flag |= C_FORCEUPDATE;
2d21ac55
A
344 }
345 dcp->c_flag |= C_FORCEUPDATE;
1c79356b 346 }
1c79356b 347out:
91447636
A
348 hfs_systemfile_unlock(hfsmp, lockflags);
349
55e303ae 350 cat_postflight(hfsmp, &cookie, p);
2d21ac55
A
351
352 if (retval == 0 && newlink) {
353 hfs_volupdate(hfsmp, VOL_MKFILE, 0);
354 }
1c79356b
A
355 return (retval);
356}
357
358
359/*
2d21ac55
A
360 * link vnode operation
361 *
362 * IN vnode_t a_vp;
363 * IN vnode_t a_tdvp;
364 * IN struct componentname *a_cnp;
365 * IN vfs_context_t a_context;
366 */
55e303ae 367__private_extern__
1c79356b 368int
91447636 369hfs_vnop_link(struct vnop_link_args *ap)
1c79356b 370{
b4c24cb9 371 struct hfsmount *hfsmp;
1c79356b
A
372 struct vnode *vp = ap->a_vp;
373 struct vnode *tdvp = ap->a_tdvp;
2d21ac55 374 struct vnode *fdvp = NULLVP;
1c79356b 375 struct componentname *cnp = ap->a_cnp;
9bccf70c
A
376 struct cnode *cp;
377 struct cnode *tdcp;
2d21ac55
A
378 struct cnode *fdcp = NULL;
379 struct cat_desc todesc;
380 int lockflags = 0;
381 int intrans = 0;
91447636 382 enum vtype v_type;
2d21ac55
A
383 int error, ret;
384
385 hfsmp = VTOHFS(vp);
386 v_type = vnode_vtype(vp);
1c79356b 387
2d21ac55
A
388 /* No hard links in HFS standard file systems. */
389 if (hfsmp->hfs_flags & HFS_STANDARD) {
390 return (ENOTSUP);
91447636 391 }
2d21ac55
A
392 /* Linking to a special file is not permitted. */
393 if (v_type == VBLK || v_type == VCHR) {
394 return (EPERM);
1c79356b 395 }
2d21ac55
A
396 if (v_type == VDIR) {
397 /* Make sure our private directory exists. */
398 if (hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid == 0) {
399 return (EPERM);
400 }
401 /*
402 * Directory hardlinks (ADLs) have only been qualified on
403 * journaled HFS+. If/when they are tested on non-journaled
404 * file systems then this test can be removed.
405 */
406 if (hfsmp->jnl == NULL) {
407 return (EPERM);
408 }
409 /* Directory hardlinks also need the parent of the original directory. */
410 if ((error = hfs_vget(hfsmp, hfs_currentparent(VTOC(vp)), &fdvp, 1))) {
411 return (error);
412 }
413 } else {
414 /* Make sure our private directory exists. */
415 if (hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid == 0) {
416 return (ENOTSUP);
417 }
91447636 418 }
2d21ac55
A
419 if (hfs_freeblks(hfsmp, 0) == 0) {
420 if (fdvp) {
421 vnode_put(fdvp);
422 }
423 return (ENOSPC);
424 }
425 /* Lock the cnodes. */
426 if (fdvp) {
427 if ((error = hfs_lockfour(VTOC(tdvp), VTOC(vp), VTOC(fdvp), NULL, HFS_EXCLUSIVE_LOCK))) {
428 if (fdvp) {
429 vnode_put(fdvp);
430 }
431 return (error);
432 }
433 fdcp = VTOC(fdvp);
434 } else {
435 if ((error = hfs_lockpair(VTOC(tdvp), VTOC(vp), HFS_EXCLUSIVE_LOCK))) {
436 return (error);
437 }
91447636 438 }
9bccf70c 439 tdcp = VTOC(tdvp);
91447636 440 cp = VTOC(vp);
2d21ac55 441 if (cp->c_linkcount >= HFS_LINK_MAX) {
1c79356b 442 error = EMLINK;
91447636 443 goto out;
1c79356b 444 }
9bccf70c 445 if (cp->c_flags & (IMMUTABLE | APPEND)) {
1c79356b 446 error = EPERM;
91447636 447 goto out;
1c79356b 448 }
91447636 449 if (cp->c_flag & (C_NOEXISTS | C_DELETED)) {
2d21ac55 450 error = ENOENT;
91447636 451 goto out;
1c79356b
A
452 }
453
2d21ac55
A
454 tdcp->c_flag |= C_DIR_MODIFICATION;
455
91447636 456 if (hfs_start_transaction(hfsmp) != 0) {
2d21ac55
A
457 error = EINVAL;
458 goto out;
b4c24cb9 459 }
2d21ac55 460 intrans = 1;
b4c24cb9 461
2d21ac55
A
462 todesc.cd_flags = (v_type == VDIR) ? CD_ISDIR : 0;
463 todesc.cd_encoding = 0;
464 todesc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr;
465 todesc.cd_namelen = cnp->cn_namelen;
466 todesc.cd_parentcnid = tdcp->c_fileid;
467 todesc.cd_hint = 0;
468 todesc.cd_cnid = 0;
b4c24cb9 469
2d21ac55
A
470 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
471
472 /* If destination exists then we lost a race with create. */
473 if (cat_lookup(hfsmp, &todesc, 0, NULL, NULL, NULL, NULL) == 0) {
474 error = EEXIST;
475 goto out;
476 }
477 if (cp->c_flag & C_HARDLINK) {
478 struct cat_attr cattr;
479
480 /* If inode is missing then we lost a race with unlink. */
481 if ((cat_idlookup(hfsmp, cp->c_fileid, 0, NULL, &cattr, NULL) != 0) ||
482 (cattr.ca_fileid != cp->c_fileid)) {
483 error = ENOENT;
484 goto out;
485 }
486 } else {
487 cnid_t fileid;
488
489 /* If source is missing then we lost a race with unlink. */
490 if ((cat_lookup(hfsmp, &cp->c_desc, 0, NULL, NULL, NULL, &fileid) != 0) ||
491 (fileid != cp->c_fileid)) {
492 error = ENOENT;
493 goto out;
494 }
495 }
496 /*
497 * All directory links must reside in an non-ARCHIVED hierarchy.
498 */
499 if (v_type == VDIR) {
500 /*
501 * - Source parent and destination parent cannot match
502 * - A link is not permitted in the root directory
503 * - Parent of 'pointed at' directory is not the root directory
504 * - The 'pointed at' directory (source) is not an ancestor
505 * of the new directory hard link (destination).
506 * - No ancestor of the new directory hard link (destination)
507 * is a directory hard link.
508 */
509 if ((cp->c_parentcnid == tdcp->c_fileid) ||
510 (tdcp->c_fileid == kHFSRootFolderID) ||
511 (cp->c_parentcnid == kHFSRootFolderID) ||
512 cat_check_link_ancestry(hfsmp, tdcp->c_fileid, cp->c_fileid)) {
513 error = EPERM; /* abide by the rules, you did not */
514 goto out;
515 }
516 }
517 hfs_systemfile_unlock(hfsmp, lockflags);
518 lockflags = 0;
519
520 cp->c_linkcount++;
521 cp->c_touch_chgtime = TRUE;
91447636 522 error = hfs_makelink(hfsmp, cp, tdcp, cnp);
1c79356b 523 if (error) {
2d21ac55 524 cp->c_linkcount--;
91447636 525 hfs_volupdate(hfsmp, VOL_UPDATE, 0);
9bccf70c 526 } else {
91447636 527 /* Invalidate negative cache entries in the destination directory */
2d21ac55 528 if (tdcp->c_flag & C_NEG_ENTRIES) {
91447636 529 cache_purge_negatives(tdvp);
2d21ac55
A
530 tdcp->c_flag &= ~C_NEG_ENTRIES;
531 }
91447636 532
9bccf70c 533 /* Update the target directory and volume stats */
9bccf70c 534 tdcp->c_entries++;
2d21ac55
A
535 if (v_type == VDIR) {
536 INC_FOLDERCOUNT(hfsmp, tdcp->c_attr);
537 tdcp->c_attr.ca_recflags |= kHFSHasChildLinkMask;
538
539 /* Set kHFSHasChildLinkBit in the destination hierarchy */
540 error = cat_set_childlinkbit(hfsmp, tdcp->c_parentcnid);
541 if (error) {
542 printf ("hfs_vnop_link: error updating destination parent chain for %u\n", tdcp->c_cnid);
543 error = 0;
544 }
545 }
546 tdcp->c_dirchangecnt++;
91447636
A
547 tdcp->c_touch_chgtime = TRUE;
548 tdcp->c_touch_modtime = TRUE;
549 tdcp->c_flag |= C_FORCEUPDATE;
550
551 error = hfs_update(tdvp, 0);
2d21ac55
A
552 if (error && error != EIO && error != ENXIO) {
553 panic("hfs_vnop_link: error updating tdvp %p\n", tdvp);
91447636 554 }
2d21ac55
A
555
556 if ((v_type == VDIR) &&
557 (fdcp != NULL) &&
558 ((fdcp->c_attr.ca_recflags & kHFSHasChildLinkMask) == 0)) {
b4c24cb9 559
2d21ac55
A
560 fdcp->c_attr.ca_recflags |= kHFSHasChildLinkMask;
561 fdcp->c_touch_chgtime = TRUE;
562 fdcp->c_flag |= C_FORCEUPDATE;
563 error = hfs_update(fdvp, 0);
564 if (error && error != EIO && error != ENXIO) {
565 panic("hfs_vnop_link: error updating fdvp %p\n", fdvp);
566 }
567
568 /* Set kHFSHasChildLinkBit in the source hierarchy */
569 error = cat_set_childlinkbit(hfsmp, fdcp->c_parentcnid);
570 if (error) {
571 printf ("hfs_vnop_link: error updating source parent chain for %u\n", fdcp->c_cnid);
572 error = 0;
573 }
574 }
b4c24cb9 575 hfs_volupdate(hfsmp, VOL_MKFILE,
9bccf70c 576 (tdcp->c_cnid == kHFSRootFolderID));
1c79356b 577 }
2d21ac55
A
578 /* Make sure update occurs inside transaction */
579 cp->c_flag |= C_FORCEUPDATE;
b4c24cb9 580
2d21ac55
A
581 if ((error == 0) && (ret = hfs_update(vp, TRUE)) != 0 && ret != EIO && ret != ENXIO) {
582 panic("hfs_vnop_link: error %d updating vp @ %p\n", ret, vp);
55e303ae 583 }
91447636 584
55e303ae
A
585 HFS_KNOTE(vp, NOTE_LINK);
586 HFS_KNOTE(tdvp, NOTE_WRITE);
91447636 587out:
2d21ac55
A
588 if (lockflags) {
589 hfs_systemfile_unlock(hfsmp, lockflags);
590 }
591 if (intrans) {
592 hfs_end_transaction(hfsmp);
593 }
594
595 tdcp->c_flag &= ~C_DIR_MODIFICATION;
596 wakeup((caddr_t)&tdcp->c_flag);
597
598 if (fdcp) {
599 hfs_unlockfour(tdcp, cp, fdcp, NULL);
600 } else {
601 hfs_unlockpair(tdcp, cp);
602 }
603 if (fdvp) {
604 vnode_put(fdvp);
605 }
1c79356b
A
606 return (error);
607}
2d21ac55
A
608
609
610/*
611 * Remove a link to a hardlink file/dir.
612 *
613 * Note: dvp and vp cnodes are already locked.
614 */
615__private_extern__
616int
617hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct componentname *cnp, int skip_reserve)
618{
619 struct cnode *cp;
620 struct cnode *dcp;
621 struct cat_desc cndesc;
622 struct timeval tv;
623 char inodename[32];
624 cnid_t prevlinkid;
625 cnid_t nextlinkid;
626 int lockflags = 0;
627 int started_tr;
628 int rm_priv_file = 0;
629 int error;
630
631 if (hfsmp->hfs_flags & HFS_STANDARD) {
632 return (EPERM);
633 }
634 cp = VTOC(vp);
635 dcp = VTOC(dvp);
636
637 dcp->c_flag |= C_DIR_MODIFICATION;
638
639 /* Remove the entry from the namei cache: */
640 cache_purge(vp);
641
642 if ((error = hfs_start_transaction(hfsmp)) != 0) {
643 started_tr = 0;
644 goto out;
645 }
646 started_tr = 1;
647
648 /*
649 * Protect against a race with rename by using the component
650 * name passed in and parent id from dvp (instead of using
651 * the cp->c_desc which may have changed).
652 *
653 * Re-lookup the component name so we get the correct cnid
654 * for the name (as opposed to the c_cnid in the cnode which
655 * could have changed before the cnode was locked).
656 */
657 cndesc.cd_flags = vnode_isdir(vp) ? CD_ISDIR : 0;
658 cndesc.cd_encoding = cp->c_desc.cd_encoding;
659 cndesc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr;
660 cndesc.cd_namelen = cnp->cn_namelen;
661 cndesc.cd_parentcnid = dcp->c_fileid;
662 cndesc.cd_hint = dcp->c_childhint;
663
664 lockflags = SFL_CATALOG | SFL_ATTRIBUTE;
665 if (cndesc.cd_flags & CD_ISDIR) {
666 /* We'll be removing the alias resource allocation blocks. */
667 lockflags |= SFL_BITMAP;
668 }
669 lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
670
671 if ((error = cat_lookuplink(hfsmp, &cndesc, &cndesc.cd_cnid, &prevlinkid, &nextlinkid))) {
672 goto out;
673 }
674
675 /* Reserve some space in the catalog file. */
676 if (!skip_reserve && (error = cat_preflight(hfsmp, 2 * CAT_DELETE, NULL, 0))) {
677 goto out;
678 }
679
680 /* Purge any cached origin entries for a directory hard link. */
681 if (cndesc.cd_flags & CD_ISDIR) {
682 hfs_relorigin(cp, dcp->c_fileid);
683 if (dcp->c_fileid != dcp->c_cnid) {
684 hfs_relorigin(cp, dcp->c_cnid);
685 }
686 }
687
688 /* Delete the link record. */
689 if ((error = cat_deletelink(hfsmp, &cndesc))) {
690 goto out;
691 }
692
693 /* Update the parent directory. */
694 if (dcp->c_entries > 0) {
695 dcp->c_entries--;
696 }
697 if (cndesc.cd_flags & CD_ISDIR) {
698 DEC_FOLDERCOUNT(hfsmp, dcp->c_attr);
699 }
700 dcp->c_dirchangecnt++;
701 microtime(&tv);
702 dcp->c_ctime = tv.tv_sec;
703 dcp->c_mtime = tv.tv_sec;
704 (void ) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL);
705
706 /*
707 * If this is the last link then we need to process the inode.
708 * Otherwise we need to fix up the link chain.
709 */
710 --cp->c_linkcount;
711 if (cp->c_linkcount < 1) {
712 char delname[32];
713 struct cat_desc to_desc;
714 struct cat_desc from_desc;
715
716 /*
717 * If a file inode or directory inode is being deleted, rename
718 * it to an open deleted file. This ensures that deletion
719 * of inode and its corresponding extended attributes does
720 * not overflow the journal. This inode will be deleted
721 * either in hfs_vnop_inactive() or in hfs_remove_orphans().
722 * Note: a rename failure here is not fatal.
723 */
724 bzero(&from_desc, sizeof(from_desc));
725 bzero(&to_desc, sizeof(to_desc));
726 if (vnode_isdir(vp)) {
727 if (cp->c_entries != 0) {
728 panic("hfs_unlink: dir not empty (id %d, %d entries)", cp->c_fileid, cp->c_entries);
729 }
730 MAKE_DIRINODE_NAME(inodename, sizeof(inodename),
731 cp->c_attr.ca_linkref);
732 from_desc.cd_parentcnid = hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid;
733 from_desc.cd_flags = CD_ISDIR;
734 to_desc.cd_flags = CD_ISDIR;
735 } else {
736 MAKE_INODE_NAME(inodename, sizeof(inodename),
737 cp->c_attr.ca_linkref);
738 from_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
739 from_desc.cd_flags = 0;
740 to_desc.cd_flags = 0;
741 }
742 from_desc.cd_nameptr = (const u_int8_t *)inodename;
743 from_desc.cd_namelen = strlen(inodename);
744 from_desc.cd_cnid = cp->c_fileid;
745
746 MAKE_DELETED_NAME(delname, sizeof(delname), cp->c_fileid);
747 to_desc.cd_nameptr = (const u_int8_t *)delname;
748 to_desc.cd_namelen = strlen(delname);
749 to_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
750 to_desc.cd_cnid = cp->c_fileid;
751
752 error = cat_rename(hfsmp, &from_desc, &hfsmp->hfs_private_desc[FILE_HARDLINKS],
753 &to_desc, (struct cat_desc *)NULL);
754 if (error == 0) {
755 cp->c_flag |= C_DELETED;
756 cp->c_attr.ca_recflags &= ~kHFSHasLinkChainMask;
757 cp->c_attr.ca_firstlink = 0;
758 if (vnode_isdir(vp)) {
759 hfsmp->hfs_private_attr[DIR_HARDLINKS].ca_entries--;
760 DEC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[DIR_HARDLINKS]);
761
762 hfsmp->hfs_private_attr[FILE_HARDLINKS].ca_entries++;
763 INC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[FILE_HARDLINKS]);
764
765 (void)cat_update(hfsmp, &hfsmp->hfs_private_desc[DIR_HARDLINKS],
766 &hfsmp->hfs_private_attr[DIR_HARDLINKS], NULL, NULL);
767 (void)cat_update(hfsmp, &hfsmp->hfs_private_desc[FILE_HARDLINKS],
768 &hfsmp->hfs_private_attr[FILE_HARDLINKS], NULL, NULL);
769 }
770 } else {
771 error = 0; /* rename failure here is not fatal */
772 }
773 } else /* Still some links left */ {
774 cnid_t firstlink;
775
776 /*
777 * Update the start of the link chain.
778 * Note: Directory hard links store the first link in an attribute.
779 */
780 if (vnode_isdir(vp) &&
781 getfirstlink(hfsmp, cp->c_fileid, &firstlink) == 0 &&
782 firstlink == cndesc.cd_cnid) {
783 if (setfirstlink(hfsmp, cp->c_fileid, nextlinkid) == 0)
784 cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
785 } else if (vnode_isreg(vp) && cp->c_attr.ca_firstlink == cndesc.cd_cnid) {
786 cp->c_attr.ca_firstlink = nextlinkid;
787 }
788 /* Update previous link. */
789 if (prevlinkid) {
790 (void) cat_updatelink(hfsmp, prevlinkid, HFS_IGNORABLE_LINK, nextlinkid);
791 }
792 /* Update next link. */
793 if (nextlinkid) {
794 (void) cat_updatelink(hfsmp, nextlinkid, prevlinkid, HFS_IGNORABLE_LINK);
795 }
796 }
797
798 /* Push new link count to disk. */
799 cp->c_ctime = tv.tv_sec;
800 (void) cat_update(hfsmp, &cp->c_desc, &cp->c_attr, NULL, NULL);
801
802 /* All done with the system files. */
803 hfs_systemfile_unlock(hfsmp, lockflags);
804 lockflags = 0;
805
806 /* Update file system stats. */
807 hfs_volupdate(hfsmp, VOL_RMFILE, (dcp->c_cnid == kHFSRootFolderID));
808 /* The last link of a directory removed the inode. */
809 if (rm_priv_file) {
810 hfs_volupdate(hfsmp, VOL_RMFILE, 0);
811 }
812 /*
813 * All done with this cnode's descriptor...
814 *
815 * Note: all future catalog calls for this cnode may be
816 * by fileid only. This is OK for HFS (which doesn't have
817 * file thread records) since HFS doesn't support hard links.
818 */
819 cat_releasedesc(&cp->c_desc);
820
821 HFS_KNOTE(dvp, NOTE_WRITE);
822 HFS_KNOTE(vp, NOTE_DELETE);
823out:
824 if (lockflags) {
825 hfs_systemfile_unlock(hfsmp, lockflags);
826 }
827 if (started_tr) {
828 hfs_end_transaction(hfsmp);
829 }
830
831 dcp->c_flag &= ~C_DIR_MODIFICATION;
832 wakeup((caddr_t)&dcp->c_flag);
833
834 return (error);
835}
836
837
838/*
839 * Initialize the HFS+ private system directories.
840 *
841 * These directories are used to hold the inodes
842 * for file and directory hardlinks as well as
843 * open-unlinked files.
844 *
845 * If they don't yet exist they will get created.
846 *
847 * This call is assumed to be made during mount.
848 */
849__private_extern__
850void
851hfs_privatedir_init(struct hfsmount * hfsmp, enum privdirtype type)
852{
853 struct vnode * dvp = NULLVP;
854 struct cnode * dcp = NULL;
855 struct cat_desc *priv_descp;
856 struct cat_attr *priv_attrp;
857 struct FndrDirInfo * fndrinfo;
858 struct timeval tv;
859 int lockflags;
860 int trans = 0;
861 int error;
862
863 if (hfsmp->hfs_flags & HFS_STANDARD) {
864 return;
865 }
866
867 priv_descp = &hfsmp->hfs_private_desc[type];
868 priv_attrp = &hfsmp->hfs_private_attr[type];
869
870 /* Check if directory already exists. */
871 if (priv_descp->cd_cnid != 0) {
872 return;
873 }
874
875 priv_descp->cd_parentcnid = kRootDirID;
876 priv_descp->cd_nameptr = (const u_int8_t *)hfs_private_names[type];
877 priv_descp->cd_namelen = strlen((const char *)priv_descp->cd_nameptr);
878 priv_descp->cd_flags = CD_ISDIR | CD_DECOMPOSED;
879
880 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
881 error = cat_lookup(hfsmp, priv_descp, 0, NULL, priv_attrp, NULL, NULL);
882 hfs_systemfile_unlock(hfsmp, lockflags);
883
884 if (error == 0) {
885 if (type == FILE_HARDLINKS) {
886 hfsmp->hfs_metadata_createdate = priv_attrp->ca_itime;
887 }
888 priv_descp->cd_cnid = priv_attrp->ca_fileid;
889 goto exit;
890 }
891
892 /* Directory is missing, if this is read-only then we're done. */
893 if (hfsmp->hfs_flags & HFS_READ_ONLY) {
894 goto exit;
895 }
896
897 /* Grab the root directory so we can update it later. */
898 if (hfs_vget(hfsmp, kRootDirID, &dvp, 0) != 0) {
899 goto exit;
900 }
901 dcp = VTOC(dvp);
902
903 /* Setup the default attributes */
904 bzero(priv_attrp, sizeof(struct cat_attr));
905 priv_attrp->ca_flags = UF_IMMUTABLE | UF_HIDDEN;
906 priv_attrp->ca_mode = S_IFDIR;
907 if (type == DIR_HARDLINKS) {
908 priv_attrp->ca_mode |= S_ISVTX | S_IRUSR | S_IXUSR | S_IRGRP |
909 S_IXGRP | S_IROTH | S_IXOTH;
910 }
911 priv_attrp->ca_linkcount = 1;
912 priv_attrp->ca_itime = hfsmp->hfs_itime;
913 priv_attrp->ca_recflags = kHFSHasFolderCountMask;
914
915 fndrinfo = (struct FndrDirInfo *)&priv_attrp->ca_finderinfo;
916 fndrinfo->frLocation.v = SWAP_BE16(16384);
917 fndrinfo->frLocation.h = SWAP_BE16(16384);
918 fndrinfo->frFlags = SWAP_BE16(kIsInvisible + kNameLocked);
919
920 if (hfs_start_transaction(hfsmp) != 0) {
921 goto exit;
922 }
923 trans = 1;
924
925 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
926
927 /* Make sure there's space in the Catalog file. */
928 if (cat_preflight(hfsmp, CAT_CREATE, NULL, 0) != 0) {
929 hfs_systemfile_unlock(hfsmp, lockflags);
930 goto exit;
931 }
932
933 /* Create the private directory on disk. */
934 error = cat_create(hfsmp, priv_descp, priv_attrp, NULL);
935 if (error == 0) {
936 priv_descp->cd_cnid = priv_attrp->ca_fileid;
937
938 /* Update the parent directory */
939 dcp->c_entries++;
940 INC_FOLDERCOUNT(hfsmp, dcp->c_attr);
941 dcp->c_dirchangecnt++;
942 microtime(&tv);
943 dcp->c_ctime = tv.tv_sec;
944 dcp->c_mtime = tv.tv_sec;
945 (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL);
946 }
947
948 hfs_systemfile_unlock(hfsmp, lockflags);
949
950 if (error) {
951 goto exit;
952 }
953 if (type == FILE_HARDLINKS) {
954 hfsmp->hfs_metadata_createdate = hfsmp->hfs_itime;
955 }
956 hfs_volupdate(hfsmp, VOL_MKDIR, 1);
957exit:
958 if (trans) {
959 hfs_end_transaction(hfsmp);
960 }
961 if (dvp) {
962 hfs_unlock(dcp);
963 vnode_put(dvp);
964 }
965 if ((error == 0) && (type == DIR_HARDLINKS)) {
966 hfs_xattr_init(hfsmp);
967 }
968}
969
970
971/*
972 * Lookup a hardlink link (from chain)
973 */
974__private_extern__
975int
976hfs_lookuplink(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid, cnid_t *nextlinkid)
977{
978 int lockflags;
979 int error;
980
981 *prevlinkid = 0;
982 *nextlinkid = 0;
983
984 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
985
986 error = cat_lookuplinkbyid(hfsmp, linkfileid, prevlinkid, nextlinkid);
987 if (error == ENOLINK) {
988 hfs_systemfile_unlock(hfsmp, lockflags);
989 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
990
991 error = getfirstlink(hfsmp, linkfileid, nextlinkid);
992 }
993 hfs_systemfile_unlock(hfsmp, lockflags);
994
995 return (error);
996}
997
998/*
999 * Cache the orgin of a directory hard link
1000 *
1001 * cnode must be lock on entry
1002 */
1003__private_extern__
1004void
1005hfs_savelinkorigin(cnode_t *cp, cnid_t parentcnid)
1006{
1007 linkorigin_t *origin = NULL;
1008 void * thread = current_thread();
1009 int count = 0;
1010
1011 /*
1012 * Look for an existing origin first. If not found, create/steal one.
1013 */
1014 TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
1015 ++count;
1016 if (origin->lo_thread == thread) {
1017 TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
1018 break;
1019 }
1020 }
1021 if (origin == NULL) {
1022 /* Recycle the last (i.e., the oldest) if we have too many. */
1023 if (count > MAX_CACHED_ORIGINS) {
1024 origin = TAILQ_LAST(&cp->c_originlist, hfs_originhead);
1025 TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
1026 } else {
1027 MALLOC(origin, linkorigin_t *, sizeof(linkorigin_t), M_TEMP, M_WAITOK);
1028 }
1029 origin->lo_thread = thread;
1030 }
1031 origin->lo_cnid = cp->c_cnid;
1032 origin->lo_parentcnid = parentcnid;
1033 TAILQ_INSERT_HEAD(&cp->c_originlist, origin, lo_link);
1034}
1035
1036/*
1037 * Release any cached origins for a directory hard link
1038 *
1039 * cnode must be lock on entry
1040 */
1041__private_extern__
1042void
1043hfs_relorigins(struct cnode *cp)
1044{
1045 linkorigin_t *origin, *prev;
1046
1047 TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev) {
1048 FREE(origin, M_TEMP);
1049 }
1050 TAILQ_INIT(&cp->c_originlist);
1051}
1052
1053/*
1054 * Release a specific origin for a directory hard link
1055 *
1056 * cnode must be lock on entry
1057 */
1058__private_extern__
1059void
1060hfs_relorigin(struct cnode *cp, cnid_t parentcnid)
1061{
4a3eedf9 1062 linkorigin_t *origin, *prev;
2d21ac55
A
1063 void * thread = current_thread();
1064
4a3eedf9 1065 TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev) {
2d21ac55
A
1066 if ((origin->lo_thread == thread) ||
1067 (origin->lo_parentcnid == parentcnid)) {
1068 TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
4a3eedf9 1069 FREE(origin, M_TEMP);
2d21ac55
A
1070 break;
1071 }
1072 }
1073}
1074
1075/*
1076 * Test if a directory hard link has a cached origin
1077 *
1078 * cnode must be lock on entry
1079 */
1080__private_extern__
1081int
1082hfs_haslinkorigin(cnode_t *cp)
1083{
1084 if (cp->c_flag & C_HARDLINK) {
1085 linkorigin_t *origin;
1086 void * thread = current_thread();
1087
1088 TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
1089 if (origin->lo_thread == thread) {
1090 return (1);
1091 }
1092 }
1093 }
1094 return (0);
1095}
1096
1097/*
1098 * Obtain the current parent cnid of a directory hard link
1099 *
1100 * cnode must be lock on entry
1101 */
1102__private_extern__
1103cnid_t
1104hfs_currentparent(cnode_t *cp)
1105{
1106 if (cp->c_flag & C_HARDLINK) {
1107 linkorigin_t *origin;
1108 void * thread = current_thread();
1109
1110 TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
1111 if (origin->lo_thread == thread) {
1112 return (origin->lo_parentcnid);
1113 }
1114 }
1115 }
1116 return (cp->c_parentcnid);
1117}
1118
1119/*
1120 * Obtain the current cnid of a directory hard link
1121 *
1122 * cnode must be lock on entry
1123 */
1124__private_extern__
1125cnid_t
1126hfs_currentcnid(cnode_t *cp)
1127{
1128 if (cp->c_flag & C_HARDLINK) {
1129 linkorigin_t *origin;
1130 void * thread = current_thread();
1131
1132 TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
1133 if (origin->lo_thread == thread) {
1134 return (origin->lo_cnid);
1135 }
1136 }
1137 }
1138 return (cp->c_cnid);
1139}
1140
1141
1142/*
1143 * Set the first link attribute for a given file id.
1144 *
1145 * The attributes b-tree must already be locked.
1146 * If journaling is enabled, a transaction must already be started.
1147 */
1148static int
1149setfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t firstlink)
1150{
1151 FCB * btfile;
1152 BTreeIterator * iterator;
1153 FSBufferDescriptor btdata;
1154 u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
1155 HFSPlusAttrData *dataptr;
1156 int result;
1157 u_int16_t datasize;
1158
1159 if (hfsmp->hfs_attribute_cp == NULL) {
1160 return (EPERM);
1161 }
1162 MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
1163 bzero(iterator, sizeof(*iterator));
1164
1165 result = hfs_buildattrkey(fileid, FIRST_LINK_XATTR_NAME, (HFSPlusAttrKey *)&iterator->key);
1166 if (result) {
1167 goto out;
1168 }
1169 dataptr = (HFSPlusAttrData *)&attrdata[0];
1170 dataptr->recordType = kHFSPlusAttrInlineData;
1171 dataptr->reserved[0] = 0;
1172 dataptr->reserved[1] = 0;
1173
1174 /*
1175 * Since attrData is variable length, we calculate the size of
1176 * attrData by subtracting the size of all other members of
1177 * structure HFSPlusAttData from the size of attrdata.
1178 */
1179 (void)snprintf((char *)&dataptr->attrData[0],
1180 sizeof(dataptr) - (4 * sizeof(uint32_t)),
1181 "%lu", (unsigned long)firstlink);
1182 dataptr->attrSize = 1 + strlen((char *)&dataptr->attrData[0]);
1183
1184 /* Calculate size of record rounded up to multiple of 2 bytes. */
1185 datasize = sizeof(HFSPlusAttrData) - 2 + dataptr->attrSize + ((dataptr->attrSize & 1) ? 1 : 0);
1186
1187 btdata.bufferAddress = dataptr;
1188 btdata.itemSize = datasize;
1189 btdata.itemCount = 1;
1190
1191 btfile = hfsmp->hfs_attribute_cp->c_datafork;
1192
1193 /* Insert the attribute. */
1194 result = BTInsertRecord(btfile, iterator, &btdata, datasize);
1195 if (result == btExists) {
1196 result = BTReplaceRecord(btfile, iterator, &btdata, datasize);
1197 }
1198 (void) BTFlushPath(btfile);
1199out:
1200 FREE(iterator, M_TEMP);
1201
1202 return MacToVFSError(result);
1203}
1204
1205/*
1206 * Get the first link attribute for a given file id.
1207 *
1208 * The attributes b-tree must already be locked.
1209 */
1210static int
1211getfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t *firstlink)
1212{
1213 FCB * btfile;
1214 BTreeIterator * iterator;
1215 FSBufferDescriptor btdata;
1216 u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
1217 HFSPlusAttrData *dataptr;
1218 int result;
1219 u_int16_t datasize;
1220
1221 if (hfsmp->hfs_attribute_cp == NULL) {
1222 return (EPERM);
1223 }
1224 MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
1225 bzero(iterator, sizeof(*iterator));
1226
1227 result = hfs_buildattrkey(fileid, FIRST_LINK_XATTR_NAME, (HFSPlusAttrKey *)&iterator->key);
1228 if (result)
1229 goto out;
1230
1231 dataptr = (HFSPlusAttrData *)&attrdata[0];
1232 datasize = sizeof(attrdata);
1233
1234 btdata.bufferAddress = dataptr;
1235 btdata.itemSize = sizeof(attrdata);
1236 btdata.itemCount = 1;
1237
1238 btfile = hfsmp->hfs_attribute_cp->c_datafork;
1239
1240 result = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL);
1241 if (result)
1242 goto out;
1243
1244 if (dataptr->attrSize < 3) {
1245 result = ENOENT;
1246 goto out;
1247 }
1248 *firstlink = strtoul((char*)&dataptr->attrData[0], NULL, 10);
1249out:
1250 FREE(iterator, M_TEMP);
1251
1252 return MacToVFSError(result);
1253}
1254