]> git.saurik.com Git - apple/xnu.git/blob - bsd/hfs/hfs_link.c
f6c5e8409a82dd48ffe799fdbbf3a03f217cde2e
[apple/xnu.git] / bsd / hfs / hfs_link.c
1 /*
2 * Copyright (c) 1999-2007 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("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 int lockflags = 0;
381 int intrans = 0;
382 enum vtype v_type;
383 int error, ret;
384
385 hfsmp = VTOHFS(vp);
386 v_type = vnode_vtype(vp);
387
388 /* No hard links in HFS standard file systems. */
389 if (hfsmp->hfs_flags & HFS_STANDARD) {
390 return (ENOTSUP);
391 }
392 /* Linking to a special file is not permitted. */
393 if (v_type == VBLK || v_type == VCHR) {
394 return (EPERM);
395 }
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 }
418 }
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 }
438 }
439 tdcp = VTOC(tdvp);
440 cp = VTOC(vp);
441 if (cp->c_linkcount >= HFS_LINK_MAX) {
442 error = EMLINK;
443 goto out;
444 }
445 if (cp->c_flags & (IMMUTABLE | APPEND)) {
446 error = EPERM;
447 goto out;
448 }
449 if (cp->c_flag & (C_NOEXISTS | C_DELETED)) {
450 error = ENOENT;
451 goto out;
452 }
453
454 tdcp->c_flag |= C_DIR_MODIFICATION;
455
456 if (hfs_start_transaction(hfsmp) != 0) {
457 error = EINVAL;
458 goto out;
459 }
460 intrans = 1;
461
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;
469
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;
522 error = hfs_makelink(hfsmp, cp, tdcp, cnp);
523 if (error) {
524 cp->c_linkcount--;
525 hfs_volupdate(hfsmp, VOL_UPDATE, 0);
526 } else {
527 /* Invalidate negative cache entries in the destination directory */
528 if (tdcp->c_flag & C_NEG_ENTRIES) {
529 cache_purge_negatives(tdvp);
530 tdcp->c_flag &= ~C_NEG_ENTRIES;
531 }
532
533 /* Update the target directory and volume stats */
534 tdcp->c_entries++;
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++;
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);
552 if (error && error != EIO && error != ENXIO) {
553 panic("hfs_vnop_link: error updating tdvp %p\n", tdvp);
554 }
555
556 if ((v_type == VDIR) &&
557 (fdcp != NULL) &&
558 ((fdcp->c_attr.ca_recflags & kHFSHasChildLinkMask) == 0)) {
559
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 }
575 hfs_volupdate(hfsmp, VOL_MKFILE,
576 (tdcp->c_cnid == kHFSRootFolderID));
577 }
578 /* Make sure update occurs inside transaction */
579 cp->c_flag |= C_FORCEUPDATE;
580
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);
583 }
584
585 HFS_KNOTE(vp, NOTE_LINK);
586 HFS_KNOTE(tdvp, NOTE_WRITE);
587 out:
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 }
606 return (error);
607 }
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__
616 int
617 hfs_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 or file hard link. */
681 hfs_relorigin(cp, dcp->c_fileid);
682 if (dcp->c_fileid != dcp->c_cnid) {
683 hfs_relorigin(cp, dcp->c_cnid);
684 }
685
686 /* Delete the link record. */
687 if ((error = cat_deletelink(hfsmp, &cndesc))) {
688 goto out;
689 }
690
691 /* Update the parent directory. */
692 if (dcp->c_entries > 0) {
693 dcp->c_entries--;
694 }
695 if (cndesc.cd_flags & CD_ISDIR) {
696 DEC_FOLDERCOUNT(hfsmp, dcp->c_attr);
697 }
698 dcp->c_dirchangecnt++;
699 microtime(&tv);
700 dcp->c_ctime = tv.tv_sec;
701 dcp->c_mtime = tv.tv_sec;
702 (void ) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL);
703
704 /*
705 * If this is the last link then we need to process the inode.
706 * Otherwise we need to fix up the link chain.
707 */
708 --cp->c_linkcount;
709 if (cp->c_linkcount < 1) {
710 char delname[32];
711 struct cat_desc to_desc;
712 struct cat_desc from_desc;
713
714 /*
715 * If a file inode or directory inode is being deleted, rename
716 * it to an open deleted file. This ensures that deletion
717 * of inode and its corresponding extended attributes does
718 * not overflow the journal. This inode will be deleted
719 * either in hfs_vnop_inactive() or in hfs_remove_orphans().
720 * Note: a rename failure here is not fatal.
721 */
722 bzero(&from_desc, sizeof(from_desc));
723 bzero(&to_desc, sizeof(to_desc));
724 if (vnode_isdir(vp)) {
725 if (cp->c_entries != 0) {
726 panic("hfs_unlink: dir not empty (id %d, %d entries)", cp->c_fileid, cp->c_entries);
727 }
728 MAKE_DIRINODE_NAME(inodename, sizeof(inodename),
729 cp->c_attr.ca_linkref);
730 from_desc.cd_parentcnid = hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid;
731 from_desc.cd_flags = CD_ISDIR;
732 to_desc.cd_flags = CD_ISDIR;
733 } else {
734 MAKE_INODE_NAME(inodename, sizeof(inodename),
735 cp->c_attr.ca_linkref);
736 from_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
737 from_desc.cd_flags = 0;
738 to_desc.cd_flags = 0;
739 }
740 from_desc.cd_nameptr = (const u_int8_t *)inodename;
741 from_desc.cd_namelen = strlen(inodename);
742 from_desc.cd_cnid = cp->c_fileid;
743
744 MAKE_DELETED_NAME(delname, sizeof(delname), cp->c_fileid);
745 to_desc.cd_nameptr = (const u_int8_t *)delname;
746 to_desc.cd_namelen = strlen(delname);
747 to_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
748 to_desc.cd_cnid = cp->c_fileid;
749
750 error = cat_rename(hfsmp, &from_desc, &hfsmp->hfs_private_desc[FILE_HARDLINKS],
751 &to_desc, (struct cat_desc *)NULL);
752 if (error == 0) {
753 cp->c_flag |= C_DELETED;
754 cp->c_attr.ca_recflags &= ~kHFSHasLinkChainMask;
755 cp->c_attr.ca_firstlink = 0;
756 if (vnode_isdir(vp)) {
757 hfsmp->hfs_private_attr[DIR_HARDLINKS].ca_entries--;
758 DEC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[DIR_HARDLINKS]);
759
760 hfsmp->hfs_private_attr[FILE_HARDLINKS].ca_entries++;
761 INC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[FILE_HARDLINKS]);
762
763 (void)cat_update(hfsmp, &hfsmp->hfs_private_desc[DIR_HARDLINKS],
764 &hfsmp->hfs_private_attr[DIR_HARDLINKS], NULL, NULL);
765 (void)cat_update(hfsmp, &hfsmp->hfs_private_desc[FILE_HARDLINKS],
766 &hfsmp->hfs_private_attr[FILE_HARDLINKS], NULL, NULL);
767 }
768 } else {
769 error = 0; /* rename failure here is not fatal */
770 }
771 } else /* Still some links left */ {
772 cnid_t firstlink;
773
774 /*
775 * Update the start of the link chain.
776 * Note: Directory hard links store the first link in an attribute.
777 */
778 if (vnode_isdir(vp) &&
779 getfirstlink(hfsmp, cp->c_fileid, &firstlink) == 0 &&
780 firstlink == cndesc.cd_cnid) {
781 if (setfirstlink(hfsmp, cp->c_fileid, nextlinkid) == 0)
782 cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
783 } else if (vnode_isreg(vp) && cp->c_attr.ca_firstlink == cndesc.cd_cnid) {
784 cp->c_attr.ca_firstlink = nextlinkid;
785 }
786 /* Update previous link. */
787 if (prevlinkid) {
788 (void) cat_updatelink(hfsmp, prevlinkid, HFS_IGNORABLE_LINK, nextlinkid);
789 }
790 /* Update next link. */
791 if (nextlinkid) {
792 (void) cat_updatelink(hfsmp, nextlinkid, prevlinkid, HFS_IGNORABLE_LINK);
793 }
794 }
795
796 /* Push new link count to disk. */
797 cp->c_ctime = tv.tv_sec;
798 (void) cat_update(hfsmp, &cp->c_desc, &cp->c_attr, NULL, NULL);
799
800 /* All done with the system files. */
801 hfs_systemfile_unlock(hfsmp, lockflags);
802 lockflags = 0;
803
804 /* Update file system stats. */
805 hfs_volupdate(hfsmp, VOL_RMFILE, (dcp->c_cnid == kHFSRootFolderID));
806 /* The last link of a directory removed the inode. */
807 if (rm_priv_file) {
808 hfs_volupdate(hfsmp, VOL_RMFILE, 0);
809 }
810 /*
811 * All done with this cnode's descriptor...
812 *
813 * Note: all future catalog calls for this cnode may be
814 * by fileid only. This is OK for HFS (which doesn't have
815 * file thread records) since HFS doesn't support hard links.
816 */
817 cat_releasedesc(&cp->c_desc);
818
819 HFS_KNOTE(dvp, NOTE_WRITE);
820 HFS_KNOTE(vp, NOTE_DELETE);
821 out:
822 if (lockflags) {
823 hfs_systemfile_unlock(hfsmp, lockflags);
824 }
825 if (started_tr) {
826 hfs_end_transaction(hfsmp);
827 }
828
829 dcp->c_flag &= ~C_DIR_MODIFICATION;
830 wakeup((caddr_t)&dcp->c_flag);
831
832 return (error);
833 }
834
835
836 /*
837 * Initialize the HFS+ private system directories.
838 *
839 * These directories are used to hold the inodes
840 * for file and directory hardlinks as well as
841 * open-unlinked files.
842 *
843 * If they don't yet exist they will get created.
844 *
845 * This call is assumed to be made during mount.
846 */
847 __private_extern__
848 void
849 hfs_privatedir_init(struct hfsmount * hfsmp, enum privdirtype type)
850 {
851 struct vnode * dvp = NULLVP;
852 struct cnode * dcp = NULL;
853 struct cat_desc *priv_descp;
854 struct cat_attr *priv_attrp;
855 struct FndrDirInfo * fndrinfo;
856 struct timeval tv;
857 int lockflags;
858 int trans = 0;
859 int error;
860
861 if (hfsmp->hfs_flags & HFS_STANDARD) {
862 return;
863 }
864
865 priv_descp = &hfsmp->hfs_private_desc[type];
866 priv_attrp = &hfsmp->hfs_private_attr[type];
867
868 /* Check if directory already exists. */
869 if (priv_descp->cd_cnid != 0) {
870 return;
871 }
872
873 priv_descp->cd_parentcnid = kRootDirID;
874 priv_descp->cd_nameptr = (const u_int8_t *)hfs_private_names[type];
875 priv_descp->cd_namelen = strlen((const char *)priv_descp->cd_nameptr);
876 priv_descp->cd_flags = CD_ISDIR | CD_DECOMPOSED;
877
878 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
879 error = cat_lookup(hfsmp, priv_descp, 0, NULL, priv_attrp, NULL, NULL);
880 hfs_systemfile_unlock(hfsmp, lockflags);
881
882 if (error == 0) {
883 if (type == FILE_HARDLINKS) {
884 hfsmp->hfs_metadata_createdate = priv_attrp->ca_itime;
885 }
886 priv_descp->cd_cnid = priv_attrp->ca_fileid;
887 goto exit;
888 }
889
890 /* Directory is missing, if this is read-only then we're done. */
891 if (hfsmp->hfs_flags & HFS_READ_ONLY) {
892 goto exit;
893 }
894
895 /* Grab the root directory so we can update it later. */
896 if (hfs_vget(hfsmp, kRootDirID, &dvp, 0) != 0) {
897 goto exit;
898 }
899 dcp = VTOC(dvp);
900
901 /* Setup the default attributes */
902 bzero(priv_attrp, sizeof(struct cat_attr));
903 priv_attrp->ca_flags = UF_IMMUTABLE | UF_HIDDEN;
904 priv_attrp->ca_mode = S_IFDIR;
905 if (type == DIR_HARDLINKS) {
906 priv_attrp->ca_mode |= S_ISVTX | S_IRUSR | S_IXUSR | S_IRGRP |
907 S_IXGRP | S_IROTH | S_IXOTH;
908 }
909 priv_attrp->ca_linkcount = 1;
910 priv_attrp->ca_itime = hfsmp->hfs_itime;
911 priv_attrp->ca_recflags = kHFSHasFolderCountMask;
912
913 fndrinfo = (struct FndrDirInfo *)&priv_attrp->ca_finderinfo;
914 fndrinfo->frLocation.v = SWAP_BE16(16384);
915 fndrinfo->frLocation.h = SWAP_BE16(16384);
916 fndrinfo->frFlags = SWAP_BE16(kIsInvisible + kNameLocked);
917
918 if (hfs_start_transaction(hfsmp) != 0) {
919 goto exit;
920 }
921 trans = 1;
922
923 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
924
925 /* Make sure there's space in the Catalog file. */
926 if (cat_preflight(hfsmp, CAT_CREATE, NULL, 0) != 0) {
927 hfs_systemfile_unlock(hfsmp, lockflags);
928 goto exit;
929 }
930
931 /* Create the private directory on disk. */
932 error = cat_create(hfsmp, priv_descp, priv_attrp, NULL);
933 if (error == 0) {
934 priv_descp->cd_cnid = priv_attrp->ca_fileid;
935
936 /* Update the parent directory */
937 dcp->c_entries++;
938 INC_FOLDERCOUNT(hfsmp, dcp->c_attr);
939 dcp->c_dirchangecnt++;
940 microtime(&tv);
941 dcp->c_ctime = tv.tv_sec;
942 dcp->c_mtime = tv.tv_sec;
943 (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL);
944 }
945
946 hfs_systemfile_unlock(hfsmp, lockflags);
947
948 if (error) {
949 goto exit;
950 }
951 if (type == FILE_HARDLINKS) {
952 hfsmp->hfs_metadata_createdate = hfsmp->hfs_itime;
953 }
954 hfs_volupdate(hfsmp, VOL_MKDIR, 1);
955 exit:
956 if (trans) {
957 hfs_end_transaction(hfsmp);
958 }
959 if (dvp) {
960 hfs_unlock(dcp);
961 vnode_put(dvp);
962 }
963 if ((error == 0) && (type == DIR_HARDLINKS)) {
964 hfs_xattr_init(hfsmp);
965 }
966 }
967
968
969 /*
970 * Lookup a hardlink link (from chain)
971 */
972 __private_extern__
973 int
974 hfs_lookuplink(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid, cnid_t *nextlinkid)
975 {
976 int lockflags;
977 int error;
978
979 *prevlinkid = 0;
980 *nextlinkid = 0;
981
982 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
983
984 error = cat_lookuplinkbyid(hfsmp, linkfileid, prevlinkid, nextlinkid);
985 if (error == ENOLINK) {
986 hfs_systemfile_unlock(hfsmp, lockflags);
987 lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
988
989 error = getfirstlink(hfsmp, linkfileid, nextlinkid);
990 }
991 hfs_systemfile_unlock(hfsmp, lockflags);
992
993 return (error);
994 }
995
996 /*
997 * Cache the origin of a directory or file hard link
998 *
999 * cnode must be lock on entry
1000 */
1001 __private_extern__
1002 void
1003 hfs_savelinkorigin(cnode_t *cp, cnid_t parentcnid)
1004 {
1005 linkorigin_t *origin = NULL;
1006 void * thread = current_thread();
1007 int count = 0;
1008 int maxorigins = (S_ISDIR(cp->c_mode)) ? MAX_CACHED_ORIGINS : MAX_CACHED_FILE_ORIGINS;
1009
1010 /*
1011 * Look for an existing origin first. If not found, create/steal one.
1012 */
1013 TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
1014 ++count;
1015 if (origin->lo_thread == thread) {
1016 TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
1017 break;
1018 }
1019 }
1020 if (origin == NULL) {
1021 /* Recycle the last (i.e., the oldest) if we have too many. */
1022 if (count > maxorigins) {
1023 origin = TAILQ_LAST(&cp->c_originlist, hfs_originhead);
1024 TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
1025 } else {
1026 MALLOC(origin, linkorigin_t *, sizeof(linkorigin_t), M_TEMP, M_WAITOK);
1027 }
1028 origin->lo_thread = thread;
1029 }
1030 origin->lo_cnid = cp->c_cnid;
1031 origin->lo_parentcnid = parentcnid;
1032 TAILQ_INSERT_HEAD(&cp->c_originlist, origin, lo_link);
1033 }
1034
1035 /*
1036 * Release any cached origins for a directory or file hard link
1037 *
1038 * cnode must be lock on entry
1039 */
1040 __private_extern__
1041 void
1042 hfs_relorigins(struct cnode *cp)
1043 {
1044 linkorigin_t *origin, *prev;
1045
1046 TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev) {
1047 FREE(origin, M_TEMP);
1048 }
1049 TAILQ_INIT(&cp->c_originlist);
1050 }
1051
1052 /*
1053 * Release a specific origin for a directory or file hard link
1054 *
1055 * cnode must be lock on entry
1056 */
1057 __private_extern__
1058 void
1059 hfs_relorigin(struct cnode *cp, cnid_t parentcnid)
1060 {
1061 linkorigin_t *origin, *prev;
1062 void * thread = current_thread();
1063
1064 TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev) {
1065 if ((origin->lo_thread == thread) ||
1066 (origin->lo_parentcnid == parentcnid)) {
1067 TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
1068 FREE(origin, M_TEMP);
1069 break;
1070 }
1071 }
1072 }
1073
1074 /*
1075 * Test if a directory or file hard link has a cached origin
1076 *
1077 * cnode must be lock on entry
1078 */
1079 __private_extern__
1080 int
1081 hfs_haslinkorigin(cnode_t *cp)
1082 {
1083 if (cp->c_flag & C_HARDLINK) {
1084 linkorigin_t *origin;
1085 void * thread = current_thread();
1086
1087 TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
1088 if (origin->lo_thread == thread) {
1089 return (1);
1090 }
1091 }
1092 }
1093 return (0);
1094 }
1095
1096 /*
1097 * Obtain the current parent cnid of a directory or file hard link
1098 *
1099 * cnode must be lock on entry
1100 */
1101 __private_extern__
1102 cnid_t
1103 hfs_currentparent(cnode_t *cp)
1104 {
1105 if (cp->c_flag & C_HARDLINK) {
1106 linkorigin_t *origin;
1107 void * thread = current_thread();
1108
1109 TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
1110 if (origin->lo_thread == thread) {
1111 return (origin->lo_parentcnid);
1112 }
1113 }
1114 }
1115 return (cp->c_parentcnid);
1116 }
1117
1118 /*
1119 * Obtain the current cnid of a directory or file hard link
1120 *
1121 * cnode must be lock on entry
1122 */
1123 __private_extern__
1124 cnid_t
1125 hfs_currentcnid(cnode_t *cp)
1126 {
1127 if (cp->c_flag & C_HARDLINK) {
1128 linkorigin_t *origin;
1129 void * thread = current_thread();
1130
1131 TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
1132 if (origin->lo_thread == thread) {
1133 return (origin->lo_cnid);
1134 }
1135 }
1136 }
1137 return (cp->c_cnid);
1138 }
1139
1140
1141 /*
1142 * Set the first link attribute for a given file id.
1143 *
1144 * The attributes b-tree must already be locked.
1145 * If journaling is enabled, a transaction must already be started.
1146 */
1147 static int
1148 setfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t firstlink)
1149 {
1150 FCB * btfile;
1151 BTreeIterator * iterator;
1152 FSBufferDescriptor btdata;
1153 u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
1154 HFSPlusAttrData *dataptr;
1155 int result;
1156 u_int16_t datasize;
1157
1158 if (hfsmp->hfs_attribute_cp == NULL) {
1159 return (EPERM);
1160 }
1161 MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
1162 bzero(iterator, sizeof(*iterator));
1163
1164 result = hfs_buildattrkey(fileid, FIRST_LINK_XATTR_NAME, (HFSPlusAttrKey *)&iterator->key);
1165 if (result) {
1166 goto out;
1167 }
1168 dataptr = (HFSPlusAttrData *)&attrdata[0];
1169 dataptr->recordType = kHFSPlusAttrInlineData;
1170 dataptr->reserved[0] = 0;
1171 dataptr->reserved[1] = 0;
1172
1173 /*
1174 * Since attrData is variable length, we calculate the size of
1175 * attrData by subtracting the size of all other members of
1176 * structure HFSPlusAttData from the size of attrdata.
1177 */
1178 (void)snprintf((char *)&dataptr->attrData[0],
1179 sizeof(dataptr) - (4 * sizeof(uint32_t)),
1180 "%lu", (unsigned long)firstlink);
1181 dataptr->attrSize = 1 + strlen((char *)&dataptr->attrData[0]);
1182
1183 /* Calculate size of record rounded up to multiple of 2 bytes. */
1184 datasize = sizeof(HFSPlusAttrData) - 2 + dataptr->attrSize + ((dataptr->attrSize & 1) ? 1 : 0);
1185
1186 btdata.bufferAddress = dataptr;
1187 btdata.itemSize = datasize;
1188 btdata.itemCount = 1;
1189
1190 btfile = hfsmp->hfs_attribute_cp->c_datafork;
1191
1192 /* Insert the attribute. */
1193 result = BTInsertRecord(btfile, iterator, &btdata, datasize);
1194 if (result == btExists) {
1195 result = BTReplaceRecord(btfile, iterator, &btdata, datasize);
1196 }
1197 (void) BTFlushPath(btfile);
1198 out:
1199 FREE(iterator, M_TEMP);
1200
1201 return MacToVFSError(result);
1202 }
1203
1204 /*
1205 * Get the first link attribute for a given file id.
1206 *
1207 * The attributes b-tree must already be locked.
1208 */
1209 static int
1210 getfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t *firstlink)
1211 {
1212 FCB * btfile;
1213 BTreeIterator * iterator;
1214 FSBufferDescriptor btdata;
1215 u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
1216 HFSPlusAttrData *dataptr;
1217 int result;
1218 u_int16_t datasize;
1219
1220 if (hfsmp->hfs_attribute_cp == NULL) {
1221 return (EPERM);
1222 }
1223 MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
1224 bzero(iterator, sizeof(*iterator));
1225
1226 result = hfs_buildattrkey(fileid, FIRST_LINK_XATTR_NAME, (HFSPlusAttrKey *)&iterator->key);
1227 if (result)
1228 goto out;
1229
1230 dataptr = (HFSPlusAttrData *)&attrdata[0];
1231 datasize = sizeof(attrdata);
1232
1233 btdata.bufferAddress = dataptr;
1234 btdata.itemSize = sizeof(attrdata);
1235 btdata.itemCount = 1;
1236
1237 btfile = hfsmp->hfs_attribute_cp->c_datafork;
1238
1239 result = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL);
1240 if (result)
1241 goto out;
1242
1243 if (dataptr->attrSize < 3) {
1244 result = ENOENT;
1245 goto out;
1246 }
1247 *firstlink = strtoul((char*)&dataptr->attrData[0], NULL, 10);
1248 out:
1249 FREE(iterator, M_TEMP);
1250
1251 return MacToVFSError(result);
1252 }
1253