2 * Copyright (c) 1999-2007 Apple Inc. All rights reserved.
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
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.
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
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.
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
30 #include <sys/systm.h>
31 #include <sys/kernel.h>
32 #include <sys/malloc.h>
33 #include <sys/mount.h>
35 #include <sys/vnode.h>
36 #include <vfs/vfs_support.h>
37 #include <libkern/libkern.h>
40 #include "hfs_catalog.h"
41 #include "hfs_format.h"
42 #include "hfs_endian.h"
45 static int cur_link_id
= 0;
48 * Private directories where hardlink inodes reside.
50 const char *hfs_private_names
[] = {
51 HFSPLUSMETADATAFOLDER
, /* FILE HARDLINKS */
52 HFSPLUS_DIR_METADATA_FOLDER
/* DIRECTORY HARDLINKS */
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.
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
);
65 * Create a new catalog link record
67 * An indirect link is a reference to an inode (the real
68 * file or directory record).
70 * All the indirect links for a given inode are chained
71 * together in a doubly linked list.
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.
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
)
82 struct FndrFileInfo
*fip
;
86 printf("createindirectlink: linknum is zero!\n");
90 /* Setup the default attributes */
91 bzero(&attr
, sizeof(attr
));
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
;
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
);
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.
113 if ((is_inode_linkchain_set
== 0) && (nextcnid
== 0)) {
114 attr
.ca_recflags
&= ~kHFSHasLinkChainMask
;
117 /* Create the indirect link directly in the catalog */
118 return cat_createlink(hfsmp
, descp
, &attr
, nextcnid
, linkcnid
);
123 * Make a link to the cnode cp in the directory dp
124 * using the name in cnp.
126 * The cnodes cp and dcp must be locked.
129 hfs_makelink(struct hfsmount
*hfsmp
, struct cnode
*cp
, struct cnode
*dcp
,
130 struct componentname
*cnp
)
132 vfs_context_t ctx
= cnp
->cn_context
;
133 struct proc
*p
= vfs_context_proc(ctx
);
134 u_int32_t indnodeno
= 0;
136 struct cat_desc to_desc
;
137 struct cat_desc link_desc
;
144 cnid_t orig_firstlink
;
145 enum privdirtype type
;
147 type
= S_ISDIR(cp
->c_mode
) ? DIR_HARDLINKS
: FILE_HARDLINKS
;
149 if (cur_link_id
== 0) {
150 cur_link_id
= ((random() & 0x3fffffff) + 100);
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
) {
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
))) {
165 lockflags
= SFL_CATALOG
| SFL_ATTRIBUTE
;
166 /* Directory hard links allocate space for a symlink. */
167 if (type
== DIR_HARDLINKS
) {
168 lockflags
|= SFL_BITMAP
;
170 lockflags
= hfs_systemfile_lock(hfsmp
, lockflags
, HFS_EXCLUSIVE_LOCK
);
172 /* Save the current cnid value so we restore it if an error occurs. */
173 orig_cnid
= cp
->c_desc
.cd_cnid
;
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.
179 if ((cp
->c_linkcount
== 2) && !(cp
->c_flag
& C_HARDLINK
)) {
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;
187 if (type
== DIR_HARDLINKS
) {
188 /* Directory hardlinks always use the cnid. */
189 indnodeno
= cp
->c_fileid
;
190 MAKE_DIRINODE_NAME(inodename
, sizeof(inodename
),
193 /* Get a unique indirect node number */
195 indnodeno
= cp
->c_fileid
;
197 indnodeno
= cur_link_id
++;
199 MAKE_INODE_NAME(inodename
, sizeof(inodename
),
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
);
206 retval
= cat_rename(hfsmp
, &cp
->c_desc
, &hfsmp
->hfs_private_desc
[type
],
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
);
213 } while ((retval
== EEXIST
) && (type
== FILE_HARDLINKS
));
218 * Replace original file/dir with a link record.
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;
227 retval
= createindirectlink(hfsmp
, indnodeno
, &link_desc
, 0, &linkcnid
, true);
231 /* Restore the cnode's cnid. */
232 cp
->c_desc
.cd_cnid
= orig_cnid
;
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
);
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
;
249 cp
->c_attr
.ca_recflags
|= kHFSHasLinkChainMask
;
251 indnodeno
= cp
->c_attr
.ca_linkref
;
255 * Create a catalog entry for the new link (parentID + name).
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;
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
;
271 retval
= createindirectlink(hfsmp
, indnodeno
, &link_desc
,
272 orig_firstlink
, &linkcnid
,
273 (cp
->c_attr
.ca_recflags
& kHFSHasLinkChainMask
));
274 if (retval
&& newlink
) {
277 /* Get rid of new link */
278 (void) cat_delete(hfsmp
, &cp
->c_desc
, &cp
->c_attr
);
280 /* Restore the cnode's cnid. */
281 cp
->c_desc
.cd_cnid
= orig_cnid
;
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
);
288 cp
->c_attr
.ca_linkref
= 0;
290 } else if (retval
== 0) {
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
);
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
;
301 cp
->c_attr
.ca_firstlink
= linkcnid
;
305 * Finally, if this is a new hardlink then:
306 * - update the private system directory
307 * - mark the cnode as a hard link
313 panic("hfs_makelink: retval %d but newlink = 1!\n", retval
);
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.
321 if (type
== DIR_HARDLINKS
) {
322 INC_FOLDERCOUNT(hfsmp
, hfsmp
->hfs_private_attr
[type
]);
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
);
329 cp
->c_flag
|= C_HARDLINK
;
330 if ((vp
= cp
->c_vp
) != NULLVP
) {
331 if (vnode_get(vp
) == 0) {
332 vnode_setmultipath(vp
);
336 if ((vp
= cp
->c_rsrc_vp
) != NULLVP
) {
337 if (vnode_get(vp
) == 0) {
338 vnode_setmultipath(vp
);
342 cp
->c_touch_chgtime
= TRUE
;
343 cp
->c_flag
|= C_FORCEUPDATE
;
345 dcp
->c_flag
|= C_FORCEUPDATE
;
348 hfs_systemfile_unlock(hfsmp
, lockflags
);
350 cat_postflight(hfsmp
, &cookie
, p
);
352 if (retval
== 0 && newlink
) {
353 hfs_volupdate(hfsmp
, VOL_MKFILE
, 0);
360 * link vnode operation
364 * IN struct componentname *a_cnp;
365 * IN vfs_context_t a_context;
369 hfs_vnop_link(struct vnop_link_args
*ap
)
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
;
378 struct cnode
*fdcp
= NULL
;
379 struct cat_desc todesc
;
386 v_type
= vnode_vtype(vp
);
388 /* No hard links in HFS standard file systems. */
389 if (hfsmp
->hfs_flags
& HFS_STANDARD
) {
392 /* Linking to a special file is not permitted. */
393 if (v_type
== VBLK
|| v_type
== VCHR
) {
396 if (v_type
== VDIR
) {
397 /* Make sure our private directory exists. */
398 if (hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
== 0) {
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.
406 if (hfsmp
->jnl
== NULL
) {
409 /* Directory hardlinks also need the parent of the original directory. */
410 if ((error
= hfs_vget(hfsmp
, hfs_currentparent(VTOC(vp
)), &fdvp
, 1))) {
414 /* Make sure our private directory exists. */
415 if (hfsmp
->hfs_private_desc
[FILE_HARDLINKS
].cd_cnid
== 0) {
419 if (hfs_freeblks(hfsmp
, 0) == 0) {
425 /* Lock the cnodes. */
427 if ((error
= hfs_lockfour(VTOC(tdvp
), VTOC(vp
), VTOC(fdvp
), NULL
, HFS_EXCLUSIVE_LOCK
))) {
435 if ((error
= hfs_lockpair(VTOC(tdvp
), VTOC(vp
), HFS_EXCLUSIVE_LOCK
))) {
441 if (cp
->c_linkcount
>= HFS_LINK_MAX
) {
445 if (cp
->c_flags
& (IMMUTABLE
| APPEND
)) {
449 if (cp
->c_flag
& (C_NOEXISTS
| C_DELETED
)) {
454 tdcp
->c_flag
|= C_DIR_MODIFICATION
;
456 if (hfs_start_transaction(hfsmp
) != 0) {
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
;
470 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_CATALOG
, HFS_SHARED_LOCK
);
472 /* If destination exists then we lost a race with create. */
473 if (cat_lookup(hfsmp
, &todesc
, 0, NULL
, NULL
, NULL
, NULL
) == 0) {
477 if (cp
->c_flag
& C_HARDLINK
) {
478 struct cat_attr cattr
;
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
)) {
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
)) {
497 * All directory links must reside in an non-ARCHIVED hierarchy.
499 if (v_type
== VDIR
) {
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.
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 */
517 hfs_systemfile_unlock(hfsmp
, lockflags
);
521 cp
->c_touch_chgtime
= TRUE
;
522 error
= hfs_makelink(hfsmp
, cp
, tdcp
, cnp
);
525 hfs_volupdate(hfsmp
, VOL_UPDATE
, 0);
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
;
533 /* Update the target directory and volume stats */
535 if (v_type
== VDIR
) {
536 INC_FOLDERCOUNT(hfsmp
, tdcp
->c_attr
);
537 tdcp
->c_attr
.ca_recflags
|= kHFSHasChildLinkMask
;
539 /* Set kHFSHasChildLinkBit in the destination hierarchy */
540 error
= cat_set_childlinkbit(hfsmp
, tdcp
->c_parentcnid
);
542 printf ("hfs_vnop_link: error updating destination parent chain for %u\n", tdcp
->c_cnid
);
546 tdcp
->c_dirchangecnt
++;
547 tdcp
->c_touch_chgtime
= TRUE
;
548 tdcp
->c_touch_modtime
= TRUE
;
549 tdcp
->c_flag
|= C_FORCEUPDATE
;
551 error
= hfs_update(tdvp
, 0);
552 if (error
&& error
!= EIO
&& error
!= ENXIO
) {
553 panic("hfs_vnop_link: error updating tdvp %p\n", tdvp
);
556 if ((v_type
== VDIR
) &&
558 ((fdcp
->c_attr
.ca_recflags
& kHFSHasChildLinkMask
) == 0)) {
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
);
568 /* Set kHFSHasChildLinkBit in the source hierarchy */
569 error
= cat_set_childlinkbit(hfsmp
, fdcp
->c_parentcnid
);
571 printf ("hfs_vnop_link: error updating source parent chain for %u\n", fdcp
->c_cnid
);
575 hfs_volupdate(hfsmp
, VOL_MKFILE
,
576 (tdcp
->c_cnid
== kHFSRootFolderID
));
578 /* Make sure update occurs inside transaction */
579 cp
->c_flag
|= C_FORCEUPDATE
;
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
);
585 HFS_KNOTE(vp
, NOTE_LINK
);
586 HFS_KNOTE(tdvp
, NOTE_WRITE
);
589 hfs_systemfile_unlock(hfsmp
, lockflags
);
592 hfs_end_transaction(hfsmp
);
595 tdcp
->c_flag
&= ~C_DIR_MODIFICATION
;
596 wakeup((caddr_t
)&tdcp
->c_flag
);
599 hfs_unlockfour(tdcp
, cp
, fdcp
, NULL
);
601 hfs_unlockpair(tdcp
, cp
);
611 * Remove a link to a hardlink file/dir.
613 * Note: dvp and vp cnodes are already locked.
617 hfs_unlink(struct hfsmount
*hfsmp
, struct vnode
*dvp
, struct vnode
*vp
, struct componentname
*cnp
, int skip_reserve
)
621 struct cat_desc cndesc
;
628 int rm_priv_file
= 0;
631 if (hfsmp
->hfs_flags
& HFS_STANDARD
) {
637 dcp
->c_flag
|= C_DIR_MODIFICATION
;
639 /* Remove the entry from the namei cache: */
642 if ((error
= hfs_start_transaction(hfsmp
)) != 0) {
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).
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).
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
;
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
;
669 lockflags
= hfs_systemfile_lock(hfsmp
, lockflags
, HFS_EXCLUSIVE_LOCK
);
671 if ((error
= cat_lookuplink(hfsmp
, &cndesc
, &cndesc
.cd_cnid
, &prevlinkid
, &nextlinkid
))) {
675 /* Reserve some space in the catalog file. */
676 if (!skip_reserve
&& (error
= cat_preflight(hfsmp
, 2 * CAT_DELETE
, NULL
, 0))) {
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
);
686 /* Delete the link record. */
687 if ((error
= cat_deletelink(hfsmp
, &cndesc
))) {
691 /* Update the parent directory. */
692 if (dcp
->c_entries
> 0) {
695 if (cndesc
.cd_flags
& CD_ISDIR
) {
696 DEC_FOLDERCOUNT(hfsmp
, dcp
->c_attr
);
698 dcp
->c_dirchangecnt
++;
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
);
705 * If this is the last link then we need to process the inode.
706 * Otherwise we need to fix up the link chain.
709 if (cp
->c_linkcount
< 1) {
711 struct cat_desc to_desc
;
712 struct cat_desc from_desc
;
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.
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
);
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
;
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;
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
;
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
;
750 error
= cat_rename(hfsmp
, &from_desc
, &hfsmp
->hfs_private_desc
[FILE_HARDLINKS
],
751 &to_desc
, (struct cat_desc
*)NULL
);
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
]);
760 hfsmp
->hfs_private_attr
[FILE_HARDLINKS
].ca_entries
++;
761 INC_FOLDERCOUNT(hfsmp
, hfsmp
->hfs_private_attr
[FILE_HARDLINKS
]);
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
);
769 error
= 0; /* rename failure here is not fatal */
771 } else /* Still some links left */ {
775 * Update the start of the link chain.
776 * Note: Directory hard links store the first link in an attribute.
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
;
786 /* Update previous link. */
788 (void) cat_updatelink(hfsmp
, prevlinkid
, HFS_IGNORABLE_LINK
, nextlinkid
);
790 /* Update next link. */
792 (void) cat_updatelink(hfsmp
, nextlinkid
, prevlinkid
, HFS_IGNORABLE_LINK
);
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
);
800 /* All done with the system files. */
801 hfs_systemfile_unlock(hfsmp
, lockflags
);
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. */
808 hfs_volupdate(hfsmp
, VOL_RMFILE
, 0);
811 * All done with this cnode's descriptor...
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.
817 cat_releasedesc(&cp
->c_desc
);
819 HFS_KNOTE(dvp
, NOTE_WRITE
);
820 HFS_KNOTE(vp
, NOTE_DELETE
);
823 hfs_systemfile_unlock(hfsmp
, lockflags
);
826 hfs_end_transaction(hfsmp
);
829 dcp
->c_flag
&= ~C_DIR_MODIFICATION
;
830 wakeup((caddr_t
)&dcp
->c_flag
);
837 * Initialize the HFS+ private system directories.
839 * These directories are used to hold the inodes
840 * for file and directory hardlinks as well as
841 * open-unlinked files.
843 * If they don't yet exist they will get created.
845 * This call is assumed to be made during mount.
849 hfs_privatedir_init(struct hfsmount
* hfsmp
, enum privdirtype type
)
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
;
861 if (hfsmp
->hfs_flags
& HFS_STANDARD
) {
865 priv_descp
= &hfsmp
->hfs_private_desc
[type
];
866 priv_attrp
= &hfsmp
->hfs_private_attr
[type
];
868 /* Check if directory already exists. */
869 if (priv_descp
->cd_cnid
!= 0) {
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
;
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
);
883 if (type
== FILE_HARDLINKS
) {
884 hfsmp
->hfs_metadata_createdate
= priv_attrp
->ca_itime
;
886 priv_descp
->cd_cnid
= priv_attrp
->ca_fileid
;
890 /* Directory is missing, if this is read-only then we're done. */
891 if (hfsmp
->hfs_flags
& HFS_READ_ONLY
) {
895 /* Grab the root directory so we can update it later. */
896 if (hfs_vget(hfsmp
, kRootDirID
, &dvp
, 0) != 0) {
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
;
909 priv_attrp
->ca_linkcount
= 1;
910 priv_attrp
->ca_itime
= hfsmp
->hfs_itime
;
911 priv_attrp
->ca_recflags
= kHFSHasFolderCountMask
;
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
);
918 if (hfs_start_transaction(hfsmp
) != 0) {
923 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_CATALOG
, HFS_EXCLUSIVE_LOCK
);
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
);
931 /* Create the private directory on disk. */
932 error
= cat_create(hfsmp
, priv_descp
, priv_attrp
, NULL
);
934 priv_descp
->cd_cnid
= priv_attrp
->ca_fileid
;
936 /* Update the parent directory */
938 INC_FOLDERCOUNT(hfsmp
, dcp
->c_attr
);
939 dcp
->c_dirchangecnt
++;
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
);
946 hfs_systemfile_unlock(hfsmp
, lockflags
);
951 if (type
== FILE_HARDLINKS
) {
952 hfsmp
->hfs_metadata_createdate
= hfsmp
->hfs_itime
;
954 hfs_volupdate(hfsmp
, VOL_MKDIR
, 1);
957 hfs_end_transaction(hfsmp
);
963 if ((error
== 0) && (type
== DIR_HARDLINKS
)) {
964 hfs_xattr_init(hfsmp
);
970 * Lookup a hardlink link (from chain)
974 hfs_lookuplink(struct hfsmount
*hfsmp
, cnid_t linkfileid
, cnid_t
*prevlinkid
, cnid_t
*nextlinkid
)
982 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_CATALOG
, HFS_SHARED_LOCK
);
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
);
989 error
= getfirstlink(hfsmp
, linkfileid
, nextlinkid
);
991 hfs_systemfile_unlock(hfsmp
, lockflags
);
997 * Cache the origin of a directory or file hard link
999 * cnode must be lock on entry
1003 hfs_savelinkorigin(cnode_t
*cp
, cnid_t parentcnid
)
1005 linkorigin_t
*origin
= NULL
;
1006 void * thread
= current_thread();
1008 int maxorigins
= (S_ISDIR(cp
->c_mode
)) ? MAX_CACHED_ORIGINS
: MAX_CACHED_FILE_ORIGINS
;
1011 * Look for an existing origin first. If not found, create/steal one.
1013 TAILQ_FOREACH(origin
, &cp
->c_originlist
, lo_link
) {
1015 if (origin
->lo_thread
== thread
) {
1016 TAILQ_REMOVE(&cp
->c_originlist
, origin
, lo_link
);
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
);
1026 MALLOC(origin
, linkorigin_t
*, sizeof(linkorigin_t
), M_TEMP
, M_WAITOK
);
1028 origin
->lo_thread
= thread
;
1030 origin
->lo_cnid
= cp
->c_cnid
;
1031 origin
->lo_parentcnid
= parentcnid
;
1032 TAILQ_INSERT_HEAD(&cp
->c_originlist
, origin
, lo_link
);
1036 * Release any cached origins for a directory or file hard link
1038 * cnode must be lock on entry
1042 hfs_relorigins(struct cnode
*cp
)
1044 linkorigin_t
*origin
, *prev
;
1046 TAILQ_FOREACH_SAFE(origin
, &cp
->c_originlist
, lo_link
, prev
) {
1047 FREE(origin
, M_TEMP
);
1049 TAILQ_INIT(&cp
->c_originlist
);
1053 * Release a specific origin for a directory or file hard link
1055 * cnode must be lock on entry
1059 hfs_relorigin(struct cnode
*cp
, cnid_t parentcnid
)
1061 linkorigin_t
*origin
, *prev
;
1062 void * thread
= current_thread();
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
);
1075 * Test if a directory or file hard link has a cached origin
1077 * cnode must be lock on entry
1081 hfs_haslinkorigin(cnode_t
*cp
)
1083 if (cp
->c_flag
& C_HARDLINK
) {
1084 linkorigin_t
*origin
;
1085 void * thread
= current_thread();
1087 TAILQ_FOREACH(origin
, &cp
->c_originlist
, lo_link
) {
1088 if (origin
->lo_thread
== thread
) {
1097 * Obtain the current parent cnid of a directory or file hard link
1099 * cnode must be lock on entry
1103 hfs_currentparent(cnode_t
*cp
)
1105 if (cp
->c_flag
& C_HARDLINK
) {
1106 linkorigin_t
*origin
;
1107 void * thread
= current_thread();
1109 TAILQ_FOREACH(origin
, &cp
->c_originlist
, lo_link
) {
1110 if (origin
->lo_thread
== thread
) {
1111 return (origin
->lo_parentcnid
);
1115 return (cp
->c_parentcnid
);
1119 * Obtain the current cnid of a directory or file hard link
1121 * cnode must be lock on entry
1125 hfs_currentcnid(cnode_t
*cp
)
1127 if (cp
->c_flag
& C_HARDLINK
) {
1128 linkorigin_t
*origin
;
1129 void * thread
= current_thread();
1131 TAILQ_FOREACH(origin
, &cp
->c_originlist
, lo_link
) {
1132 if (origin
->lo_thread
== thread
) {
1133 return (origin
->lo_cnid
);
1137 return (cp
->c_cnid
);
1142 * Set the first link attribute for a given file id.
1144 * The attributes b-tree must already be locked.
1145 * If journaling is enabled, a transaction must already be started.
1148 setfirstlink(struct hfsmount
* hfsmp
, cnid_t fileid
, cnid_t firstlink
)
1151 BTreeIterator
* iterator
;
1152 FSBufferDescriptor btdata
;
1153 u_int8_t attrdata
[FIRST_LINK_XATTR_REC_SIZE
];
1154 HFSPlusAttrData
*dataptr
;
1158 if (hfsmp
->hfs_attribute_cp
== NULL
) {
1161 MALLOC(iterator
, BTreeIterator
*, sizeof(*iterator
), M_TEMP
, M_WAITOK
);
1162 bzero(iterator
, sizeof(*iterator
));
1164 result
= hfs_buildattrkey(fileid
, FIRST_LINK_XATTR_NAME
, (HFSPlusAttrKey
*)&iterator
->key
);
1168 dataptr
= (HFSPlusAttrData
*)&attrdata
[0];
1169 dataptr
->recordType
= kHFSPlusAttrInlineData
;
1170 dataptr
->reserved
[0] = 0;
1171 dataptr
->reserved
[1] = 0;
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.
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]);
1183 /* Calculate size of record rounded up to multiple of 2 bytes. */
1184 datasize
= sizeof(HFSPlusAttrData
) - 2 + dataptr
->attrSize
+ ((dataptr
->attrSize
& 1) ? 1 : 0);
1186 btdata
.bufferAddress
= dataptr
;
1187 btdata
.itemSize
= datasize
;
1188 btdata
.itemCount
= 1;
1190 btfile
= hfsmp
->hfs_attribute_cp
->c_datafork
;
1192 /* Insert the attribute. */
1193 result
= BTInsertRecord(btfile
, iterator
, &btdata
, datasize
);
1194 if (result
== btExists
) {
1195 result
= BTReplaceRecord(btfile
, iterator
, &btdata
, datasize
);
1197 (void) BTFlushPath(btfile
);
1199 FREE(iterator
, M_TEMP
);
1201 return MacToVFSError(result
);
1205 * Get the first link attribute for a given file id.
1207 * The attributes b-tree must already be locked.
1210 getfirstlink(struct hfsmount
* hfsmp
, cnid_t fileid
, cnid_t
*firstlink
)
1213 BTreeIterator
* iterator
;
1214 FSBufferDescriptor btdata
;
1215 u_int8_t attrdata
[FIRST_LINK_XATTR_REC_SIZE
];
1216 HFSPlusAttrData
*dataptr
;
1220 if (hfsmp
->hfs_attribute_cp
== NULL
) {
1223 MALLOC(iterator
, BTreeIterator
*, sizeof(*iterator
), M_TEMP
, M_WAITOK
);
1224 bzero(iterator
, sizeof(*iterator
));
1226 result
= hfs_buildattrkey(fileid
, FIRST_LINK_XATTR_NAME
, (HFSPlusAttrKey
*)&iterator
->key
);
1230 dataptr
= (HFSPlusAttrData
*)&attrdata
[0];
1231 datasize
= sizeof(attrdata
);
1233 btdata
.bufferAddress
= dataptr
;
1234 btdata
.itemSize
= sizeof(attrdata
);
1235 btdata
.itemCount
= 1;
1237 btfile
= hfsmp
->hfs_attribute_cp
->c_datafork
;
1239 result
= BTSearchRecord(btfile
, iterator
, &btdata
, NULL
, NULL
);
1243 if (dataptr
->attrSize
< 3) {
1247 *firstlink
= strtoul((char*)&dataptr
->attrData
[0], NULL
, 10);
1249 FREE(iterator
, M_TEMP
);
1251 return MacToVFSError(result
);