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