5 // Created by Yakov Ben Zaken on 22/03/2018.
9 #include "lf_hfs_catalog.h"
10 #include "lf_hfs_utils.h"
11 #include "lf_hfs_vfsutils.h"
12 #include "lf_hfs_vfsops.h"
13 #include "lf_hfs_unicode_wrappers.h"
14 #include "lf_hfs_sbunicode.h"
15 #include "lf_hfs_btrees_internal.h"
17 #include "lf_hfs_vnops.h"
18 #include <UserFS/UserVFS.h>
19 #include "lf_hfs_dirops_handler.h"
20 #include "lf_hfs_endian.h"
21 #include "lf_hfs_btree.h"
22 #include "lf_hfs_xattr.h"
23 #include "lf_hfs_chash.h"
24 #include <sys/types.h>
25 #include <sys/mount.h>
26 #include "lf_hfs_chash.h"
27 #include "lf_hfs_generic_buf.h"
28 #include "lf_hfs_journal.h"
30 #define HFS_LOOKUP_SYSFILE 0x1 /* If set, allow lookup of system files */
31 #define HFS_LOOKUP_HARDLINK 0x2 /* If set, allow lookup of hard link records and not resolve the hard links */
32 #define HFS_LOOKUP_CASESENSITIVE 0x4 /* If set, verify results of a file/directory record match input case */
34 #define SMALL_DIRENTRY_SIZE (UVFS_DIRENTRY_RECLEN(1))
36 /* Map file mode type to directory entry types */
37 u_char modetodirtype
[16] = {
38 UVFS_FA_TYPE_FILE
, 0, 0, 0,
39 UVFS_FA_TYPE_DIR
, 0, 0, 0,
40 UVFS_FA_TYPE_FILE
, 0, UVFS_FA_TYPE_SYMLINK
, 0,
43 #define MODE_TO_TYPE(mode) (modetodirtype[((mode) & S_IFMT) >> 12])
46 * Initialization of an FSBufferDescriptor structure.
48 #define BDINIT(bd, addr) { \
49 (bd).bufferAddress = (addr); \
50 (bd).itemSize = sizeof(*(addr)); \
54 /* HFS ID Hashtable Functions */
55 #define IDHASH(hfsmp, inum) (&hfsmp->hfs_idhashtbl[(inum) & hfsmp->hfs_idhash])
57 static int isadir(const CatalogRecord
*crp
);
58 static int builddesc(const HFSPlusCatalogKey
*key
, cnid_t cnid
,
59 u_int32_t hint
, u_int32_t encoding
, int isdir
, struct cat_desc
*descp
);
60 static int buildkey(struct cat_desc
*descp
, HFSPlusCatalogKey
*key
);
62 // --------------------------------------- Hard Link Support ---------------------------------------------
63 static int resolvelinkid(struct hfsmount
*hfsmp
, u_int32_t linkref
, ino_t
*ino
);
64 static int cat_makealias(struct hfsmount
*hfsmp
, u_int32_t inode_num
, struct HFSPlusCatalogFile
*crp
);
65 /* Hard link information collected during cat_getdirentries. */
70 typedef struct linkinfo linkinfo_t
;
73 BTreeIterator iterator
;
74 HFSPlusCatalogKey key
;
78 /* Constants for directory hard link alias */
80 /* Size of resource fork data array for directory hard link alias */
81 kHFSAliasSize
= 0x1d0,
83 /* Volume type for ejectable devices like disk image */
84 kHFSAliasVolTypeEjectable
= 0x5,
86 /* Offset for volume create date, in Mac OS local time */
87 kHFSAliasVolCreateDateOffset
= 0x12a,
89 /* Offset for the type of volume */
90 kHFSAliasVolTypeOffset
= 0x130,
92 /* Offset for folder ID of the parent directory of the directory inode */
93 kHFSAliasParentIDOffset
= 0x132,
95 /* Offset for folder ID of the directory inode */
96 kHFSAliasTargetIDOffset
= 0x176,
99 /* Directory hard links are visible as aliases on pre-Leopard systems and
100 * as normal directories on Leopard or later. All directory hard link aliases
101 * have the same resource fork content except for the three uniquely
102 * identifying values that are updated in the resource fork data when the alias
103 * is created. The following array is the constant resource fork data used
104 * only for creating directory hard link aliases.
106 static const char hfs_dirlink_alias_rsrc
[] = {
107 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x9e, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x32,
108 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
109 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
110 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
111 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
112 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
113 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
114 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
115 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
116 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
117 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
118 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
119 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
120 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
121 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
122 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
123 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00,
124 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
125 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x2b,
126 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
127 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
128 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
129 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
130 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
131 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
132 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
133 0x01, 0x00, 0x00, 0x00, 0x01, 0x9e, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00,
134 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x32, 0x00, 0x00, 0x61, 0x6c, 0x69, 0x73,
135 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
137 // -------------------------------------------------------------------------------------------------------
139 /* State information for the getdirentries_callback function. */
140 struct packdirentry_state
{
141 int cbs_flags
; /* VNODE_READDIR_* flags */
142 u_int32_t cbs_parentID
;
144 ReadDirBuff_t cbs_psReadDirBuffer
;
145 ExtendedVCB
* cbs_hfsmp
;
148 int32_t cbs_maxlinks
;
149 linkinfo_t
* cbs_linkinfo
;
150 struct cat_desc
* cbs_desc
;
151 u_int8_t
* cbs_namebuf
;
153 * The following fields are only used for NFS readdir, which
154 * uses the next file id as the seek offset of each entry.
156 UVFSDirEntry
* cbs_direntry
;
157 UVFSDirEntry
* cbs_prevdirentry
;
158 UVFSDirEntry
* cbs_lastinsertedentry
;
159 u_int32_t cbs_previlinkref
;
160 Boolean cbs_hasprevdirentry
;
161 Boolean cbs_haslastinsertedentry
;
165 struct position_state
{
170 struct hfsmount
*hfsmp
;
172 /* Initialize the HFS ID hash table */
174 hfs_idhash_init (struct hfsmount
*hfsmp
) {
175 /* secured by catalog lock so no lock init needed */
176 hfsmp
->hfs_idhashtbl
= hashinit(HFS_IDHASH_DEFAULT
, &hfsmp
->hfs_idhash
);
179 /* Free the HFS ID hash table */
181 hfs_idhash_destroy (struct hfsmount
*hfsmp
) {
182 /* during failed mounts & unmounts */
183 hashDeinit(hfsmp
->hfs_idhashtbl
);
187 * Compare two HFS+ catalog keys
189 * Result: +n search key > trial key
190 * 0 search key = trial key
191 * -n search key < trial key
194 CompareExtendedCatalogKeys(HFSPlusCatalogKey
*searchKey
, HFSPlusCatalogKey
*trialKey
)
196 cnid_t searchParentID
, trialParentID
;
199 searchParentID
= searchKey
->parentID
;
200 trialParentID
= trialKey
->parentID
;
202 if (searchParentID
> trialParentID
) {
205 else if (searchParentID
< trialParentID
) {
208 /* parent node ID's are equal, compare names */
209 if ( searchKey
->nodeName
.length
== 0 || trialKey
->nodeName
.length
== 0 )
210 result
= searchKey
->nodeName
.length
- trialKey
->nodeName
.length
;
212 result
= FastUnicodeCompare(&searchKey
->nodeName
.unicode
[0],
213 searchKey
->nodeName
.length
,
214 &trialKey
->nodeName
.unicode
[0],
215 trialKey
->nodeName
.length
);
222 * cat_binarykeycompare - compare two HFS Plus catalog keys.
224 * The name portion of the key is compared using a 16-bit binary comparison.
225 * This is called from the b-tree code.
228 cat_binarykeycompare(HFSPlusCatalogKey
*searchKey
, HFSPlusCatalogKey
*trialKey
)
230 u_int32_t searchParentID
, trialParentID
;
233 searchParentID
= searchKey
->parentID
;
234 trialParentID
= trialKey
->parentID
;
237 if (searchParentID
> trialParentID
) {
239 } else if (searchParentID
< trialParentID
) {
242 u_int16_t
* str1
= &searchKey
->nodeName
.unicode
[0];
243 u_int16_t
* str2
= &trialKey
->nodeName
.unicode
[0];
244 int length1
= searchKey
->nodeName
.length
;
245 int length2
= trialKey
->nodeName
.length
;
247 result
= UnicodeBinaryCompare (str1
, length1
, str2
, length2
);
257 cat_releasedesc(struct cat_desc
*descp
)
262 if ((descp
->cd_flags
& CD_HASBUF
) && (descp
->cd_nameptr
!= NULL
)) {
263 hfs_free( (void*)descp
->cd_nameptr
);
265 descp
->cd_nameptr
= NULL
;
266 descp
->cd_namelen
= 0;
267 descp
->cd_flags
&= ~CD_HASBUF
;
271 * Extract the CNID from a catalog node record.
274 getcnid(const CatalogRecord
*crp
)
278 switch (crp
->recordType
) {
279 case kHFSPlusFolderRecord
:
280 cnid
= crp
->hfsPlusFolder
.folderID
;
282 case kHFSPlusFileRecord
:
283 cnid
= crp
->hfsPlusFile
.fileID
;
286 LFHFS_LOG(LEVEL_ERROR
, "getcnid: unknown recordType=%d\n", crp
->recordType
);
294 * Extract the text encoding from a catalog node record.
297 getencoding(const CatalogRecord
*crp
)
301 if (crp
->recordType
== kHFSPlusFolderRecord
)
302 encoding
= crp
->hfsPlusFolder
.textEncoding
;
303 else if (crp
->recordType
== kHFSPlusFileRecord
)
304 encoding
= crp
->hfsPlusFile
.textEncoding
;
312 * getbsdattr - get attributes in bsd format
316 getbsdattr(struct hfsmount
*hfsmp
, const struct HFSPlusCatalogFile
*crp
, struct cat_attr
* attrp
)
318 int isDirectory
= (crp
->recordType
== kHFSPlusFolderRecord
);
319 const struct HFSPlusBSDInfo
*bsd
= &crp
->bsdInfo
;
321 attrp
->ca_recflags
= crp
->flags
;
322 attrp
->ca_atime
= to_bsd_time(crp
->accessDate
);
323 attrp
->ca_atimeondisk
= attrp
->ca_atime
;
324 attrp
->ca_mtime
= to_bsd_time(crp
->contentModDate
);
325 attrp
->ca_ctime
= to_bsd_time(crp
->attributeModDate
);
326 attrp
->ca_itime
= to_bsd_time(crp
->createDate
);
327 attrp
->ca_btime
= to_bsd_time(crp
->backupDate
);
329 if ((bsd
->fileMode
& S_IFMT
) == 0) {
331 attrp
->ca_uid
= hfsmp
->hfs_uid
;
332 attrp
->ca_gid
= hfsmp
->hfs_gid
;
334 attrp
->ca_mode
= S_IFDIR
| (hfsmp
->hfs_dir_mask
& (S_IRWXU
|S_IRWXG
|S_IRWXO
));
336 attrp
->ca_mode
= S_IFREG
| (hfsmp
->hfs_file_mask
& (S_IRWXU
|S_IRWXG
|S_IRWXO
));
338 attrp
->ca_linkcount
= 1;
341 attrp
->ca_linkcount
= 1; /* may be overridden below */
343 attrp
->ca_uid
= bsd
->ownerID
;
344 attrp
->ca_gid
= bsd
->groupID
;
345 attrp
->ca_flags
= bsd
->ownerFlags
| (bsd
->adminFlags
<< 16);
346 attrp
->ca_mode
= (mode_t
)bsd
->fileMode
;
347 switch (attrp
->ca_mode
& S_IFMT
) {
348 case S_IFCHR
: /* fall through */
350 attrp
->ca_rdev
= bsd
->special
.rawDevice
;
356 /* Pick up the hard link count */
357 if (bsd
->special
.linkCount
> 0)
358 attrp
->ca_linkcount
= bsd
->special
.linkCount
;
363 * Override the permissions as determined by the mount auguments
364 * in ALMOST the same way unset permissions are treated but keep
365 * track of whether or not the file or folder is hfs locked
366 * by leaving the h_pflags field unchanged from what was unpacked
367 * out of the catalog.
370 * This code was used to do UID translation with MNT_IGNORE_OWNERS
371 * (aka MNT_UNKNOWNPERMISSIONS) at the HFS layer. It's largely done
372 * at the VFS layer, so there is no need to do it here now; this also
373 * allows VFS to let root see the real UIDs.
375 * if (((unsigned int)vfs_flags(HFSTOVFS(hfsmp))) & MNT_UNKNOWNPERMISSIONS) {
376 * attrp->ca_uid = hfsmp->hfs_uid;
377 * attrp->ca_gid = hfsmp->hfs_gid;
383 if (!S_ISDIR(attrp
->ca_mode
)) {
384 attrp
->ca_mode
&= ~S_IFMT
;
385 attrp
->ca_mode
|= S_IFDIR
;
387 attrp
->ca_entries
= ((const HFSPlusCatalogFolder
*)crp
)->valence
;
388 attrp
->ca_dircount
= ((hfsmp
->hfs_flags
& HFS_FOLDERCOUNT
) && (attrp
->ca_recflags
& kHFSHasFolderCountMask
)) ?
389 ((const HFSPlusCatalogFolder
*)crp
)->folderCount
: 0;
391 /* Keep UF_HIDDEN bit in sync with Finder Info's invisible bit */
392 if (((const HFSPlusCatalogFolder
*)crp
)->userInfo
.frFlags
& OSSwapHostToBigConstInt16(kFinderInvisibleMask
))
393 attrp
->ca_flags
|= UF_HIDDEN
;
395 /* Keep IMMUTABLE bits in sync with HFS locked flag */
396 if (crp
->flags
& kHFSFileLockedMask
) {
397 /* The file's supposed to be locked:
398 Make sure at least one of the IMMUTABLE bits is set: */
399 if ((attrp
->ca_flags
& (SF_IMMUTABLE
| UF_IMMUTABLE
)) == 0)
400 attrp
->ca_flags
|= UF_IMMUTABLE
;
402 /* The file's supposed to be unlocked: */
403 attrp
->ca_flags
&= ~(SF_IMMUTABLE
| UF_IMMUTABLE
);
405 /* Keep UF_HIDDEN bit in sync with Finder Info's invisible bit */
406 if (crp
->userInfo
.fdFlags
& OSSwapHostToBigConstInt16(kFinderInvisibleMask
))
407 attrp
->ca_flags
|= UF_HIDDEN
;
408 /* get total blocks (both forks) */
409 attrp
->ca_blocks
= crp
->dataFork
.totalBlocks
+ crp
->resourceFork
.totalBlocks
;
411 /* On HFS+ the ThreadExists flag must always be set. */
412 attrp
->ca_recflags
|= kHFSThreadExistsMask
;
414 /* Pick up the hardlink first link, if any. */
415 attrp
->ca_firstlink
= (attrp
->ca_recflags
& kHFSHasLinkChainMask
) ? crp
->hl_firstLinkID
: 0;
418 attrp
->ca_fileid
= crp
->fileID
;
420 bcopy(&crp
->userInfo
, attrp
->ca_finderinfo
, 32);
424 * builddesc - build a cnode descriptor from an HFS+ key
427 builddesc(const HFSPlusCatalogKey
*key
, cnid_t cnid
, u_int32_t hint
, u_int32_t encoding
,
428 int isdir
, struct cat_desc
*descp
)
431 unsigned char * nameptr
;
435 /* guess a size... */
436 bufsize
= (3 * key
->nodeName
.length
) + 1;
437 nameptr
= hfs_malloc(bufsize
);
441 memset(nameptr
,0,bufsize
);
442 result
= utf8_encodestr(key
->nodeName
.unicode
,
443 key
->nodeName
.length
* sizeof(UniChar
),
444 nameptr
, (size_t *)&utf8len
,
445 bufsize
, ':', UTF_ADD_NULL_TERM
);
447 if (result
== ENAMETOOLONG
) {
449 bufsize
= 1 + utf8_encodelen(key
->nodeName
.unicode
,
450 key
->nodeName
.length
* sizeof(UniChar
),
451 ':', UTF_ADD_NULL_TERM
);
452 nameptr
= hfs_malloc(bufsize
);
454 result
= utf8_encodestr(key
->nodeName
.unicode
,
455 key
->nodeName
.length
* sizeof(UniChar
),
456 nameptr
, (size_t *)&utf8len
,
457 bufsize
, ':', UTF_ADD_NULL_TERM
);
459 descp
->cd_parentcnid
= key
->parentID
;
460 descp
->cd_nameptr
= nameptr
;
461 descp
->cd_namelen
= utf8len
;
462 descp
->cd_cnid
= cnid
;
463 descp
->cd_hint
= hint
;
464 descp
->cd_flags
= CD_DECOMPOSED
| CD_HASBUF
;
466 descp
->cd_flags
|= CD_ISDIR
;
467 descp
->cd_encoding
= encoding
;
472 * cat_lookupbykey - lookup a catalog node using a cnode key
475 cat_lookupbykey(struct hfsmount
*hfsmp
, CatalogKey
*keyp
, int flags
, u_int32_t hint
, int wantrsrc
,
476 struct cat_desc
*descp
, struct cat_attr
*attrp
, struct cat_fork
*forkp
, cnid_t
*desc_cnid
)
478 BTreeIterator
* iterator
= NULL
;
479 FSBufferDescriptor btdata
= {0};
480 CatalogRecord
* recp
= NULL
;
481 u_int16_t datasize
= 0;
485 u_int32_t encoding
= 0;
488 recp
= hfs_malloc(sizeof(CatalogRecord
));
489 BDINIT(btdata
, recp
);
490 iterator
= hfs_mallocz(sizeof(*iterator
));
491 iterator
->hint
.nodeNum
= hint
;
492 bcopy(keyp
, &iterator
->key
, sizeof(CatalogKey
));
494 FCB
*filePtr
= VTOF(HFSTOVCB(hfsmp
)->catalogRefNum
);
495 result
= BTSearchRecord(filePtr
, iterator
,
496 &btdata
, &datasize
, iterator
);
500 /* Save the cnid, parentid, and encoding now in case there's a hard link or inode */
501 cnid
= getcnid(recp
);
503 /* CNID of 0 is invalid. Mark as corrupt */
504 hfs_mark_inconsistent (hfsmp
, HFS_INCONSISTENCY_DETECTED
);
509 parentid
= keyp
->hfsPlus
.parentID
;
511 encoding
= getencoding(recp
);
512 hint
= iterator
->hint
.nodeNum
;
514 /* Hide the journal files (if any) */
515 if ( IsEntryAJnlFile(hfsmp
, cnid
) && !(flags
& HFS_LOOKUP_SYSFILE
))
517 result
= HFS_ERESERVEDNAME
;
522 * When a hardlink link is encountered, auto resolve it.
524 * The catalog record will change, and possibly its type.
526 if ( (attrp
|| forkp
)
527 && (recp
->recordType
== kHFSPlusFileRecord
)
528 && ((to_bsd_time(recp
->hfsPlusFile
.createDate
) == (time_t)hfsmp
->hfs_itime
) ||
529 (to_bsd_time(recp
->hfsPlusFile
.createDate
) == (time_t)hfsmp
->hfs_metadata_createdate
))) {
534 if ((SWAP_BE32(recp
->hfsPlusFile
.userInfo
.fdType
) == kHardLinkFileType
) &&
535 (SWAP_BE32(recp
->hfsPlusFile
.userInfo
.fdCreator
) == kHFSPlusCreator
)) {
537 } else if ((recp
->hfsPlusFile
.flags
& kHFSHasLinkChainMask
) &&
538 (SWAP_BE32(recp
->hfsPlusFile
.userInfo
.fdType
) == kHFSAliasType
) &&
539 (SWAP_BE32(recp
->hfsPlusFile
.userInfo
.fdCreator
) == kHFSAliasCreator
)) {
542 if ((isfilelink
|| isdirlink
) && !(flags
& HFS_LOOKUP_HARDLINK
)) {
543 ilink
= recp
->hfsPlusFile
.hl_linkReference
;
544 (void) cat_resolvelink(hfsmp
, ilink
, isdirlink
, (struct HFSPlusCatalogFile
*)recp
);
549 getbsdattr(hfsmp
, (struct HFSPlusCatalogFile
*)recp
, attrp
);
551 /* Update the inode number for this hard link */
552 attrp
->ca_linkref
= ilink
;
556 * Set kHFSHasLinkChainBit for hard links, and reset it for all
557 * other items. Also set linkCount to 1 for regular files.
559 * Due to some bug (rdar://8505977), some regular files can have
560 * kHFSHasLinkChainBit set and linkCount more than 1 even if they
561 * are not really hard links. The runtime code should not consider
562 * these files has hard links. Therefore we reset the kHFSHasLinkChainBit
563 * and linkCount for regular file before we vend it out. This might
564 * also result in repairing the bad files on disk, if the corresponding
565 * file is modified and updated on disk.
569 /* This is a hard link and the link count bit was not set */
570 if (!(attrp
->ca_recflags
& kHFSHasLinkChainMask
))
572 LFHFS_LOG(LEVEL_DEBUG
, "cat_lookupbykey: set hardlink bit on vol=%s cnid=%u inoid=%u\n", hfsmp
->vcbVN
, cnid
, ilink
);
573 attrp
->ca_recflags
|= kHFSHasLinkChainMask
;
578 /* Make sure that this non-hard link (regular) record is not
579 * an inode record that was looked up and we do not end up
580 * reseting the hard link bit on it.
582 if ((parentid
!= hfsmp
->hfs_private_desc
[FILE_HARDLINKS
].cd_cnid
) &&
583 (parentid
!= hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
))
585 /* This is not a hard link or inode and the link count bit was set */
586 if (attrp
->ca_recflags
& kHFSHasLinkChainMask
)
588 LFHFS_LOG(LEVEL_DEBUG
, "cat_lookupbykey: clear hardlink bit on vol=%s cnid=%u\n", hfsmp
->vcbVN
, cnid
);
589 attrp
->ca_recflags
&= ~kHFSHasLinkChainMask
;
591 /* This is a regular file and the link count was more than 1 */
592 if (S_ISREG(attrp
->ca_mode
) && (attrp
->ca_linkcount
> 1))
594 LFHFS_LOG(LEVEL_DEBUG
, "cat_lookupbykey: set linkcount=1 on vol=%s cnid=%u old=%u\n", hfsmp
->vcbVN
, cnid
, attrp
->ca_linkcount
);
595 attrp
->ca_linkcount
= 1;
602 bzero(forkp
, sizeof(*forkp
));
605 /* Convert the resource fork. */
606 forkp
->cf_size
= recp
->hfsPlusFile
.resourceFork
.logicalSize
;
607 forkp
->cf_new_size
= 0;
608 forkp
->cf_blocks
= recp
->hfsPlusFile
.resourceFork
.totalBlocks
;
609 forkp
->cf_bytesread
= 0;
611 forkp
->cf_vblocks
= 0;
612 bcopy(&recp
->hfsPlusFile
.resourceFork
.extents
[0],
613 &forkp
->cf_extents
[0], sizeof(HFSPlusExtentRecord
));
618 /* Convert the data fork. */
619 forkp
->cf_size
= recp
->hfsPlusFile
.dataFork
.logicalSize
;
620 forkp
->cf_new_size
= 0;
621 forkp
->cf_blocks
= recp
->hfsPlusFile
.dataFork
.totalBlocks
;
622 forkp
->cf_bytesread
= 0;
623 forkp
->cf_vblocks
= 0;
624 bcopy(&recp
->hfsPlusFile
.dataFork
.extents
[0],
625 &forkp
->cf_extents
[0], sizeof(HFSPlusExtentRecord
));
627 /* Validate the fork's resident extents. */
629 for (i
= 0; i
< kHFSPlusExtentDensity
; ++i
) {
630 if (forkp
->cf_extents
[i
].startBlock
+ forkp
->cf_extents
[i
].blockCount
>= hfsmp
->totalBlocks
) {
631 /* Suppress any bad extents so a remove can succeed. */
632 forkp
->cf_extents
[i
].startBlock
= 0;
633 forkp
->cf_extents
[i
].blockCount
= 0;
636 attrp
->ca_mode
&= S_IFMT
| S_IRUSR
| S_IRGRP
| S_IROTH
;
639 validblks
+= forkp
->cf_extents
[i
].blockCount
;
642 /* Adjust for any missing blocks. */
643 if ((validblks
< forkp
->cf_blocks
) && (forkp
->cf_extents
[7].blockCount
== 0)) {
647 * This is technically a volume corruption.
648 * If the total number of blocks calculated by iterating + summing
649 * the extents in the resident extent records, is less than that
650 * which is reported in the catalog entry, we should force a fsck.
651 * Only modifying ca_blocks here is not guaranteed to make it out
652 * to disk; it is a runtime-only field.
654 * Note that we could have gotten into this state if we had invalid ranges
655 * that existed in borrowed blocks that somehow made it out to disk.
656 * The cnode's on disk block count should never be greater
657 * than that which is in its extent records.
660 (void) hfs_mark_inconsistent (hfsmp
, HFS_INCONSISTENCY_DETECTED
);
662 forkp
->cf_blocks
= validblks
;
664 attrp
->ca_blocks
= validblks
+ recp
->hfsPlusFile
.resourceFork
.totalBlocks
;
666 psize
= (off_t
)validblks
* (off_t
)hfsmp
->blockSize
;
667 if (psize
< forkp
->cf_size
) {
668 forkp
->cf_size
= psize
;
675 HFSPlusCatalogKey
* pluskey
= NULL
;
676 pluskey
= (HFSPlusCatalogKey
*)&iterator
->key
;
677 builddesc(pluskey
, cnid
, hint
, encoding
, isadir(recp
), descp
);
681 if (desc_cnid
!= NULL
) {
688 return MacToVFSError(result
);
692 * Determine if a catalog node record is a directory.
695 isadir(const CatalogRecord
*crp
)
697 if (crp
->recordType
== kHFSPlusFolderRecord
)
706 buildthread(void *keyp
, void *recp
, int directory
)
710 HFSPlusCatalogKey
*key
= (HFSPlusCatalogKey
*)keyp
;
711 HFSPlusCatalogThread
*rec
= (HFSPlusCatalogThread
*)recp
;
713 size
= sizeof(HFSPlusCatalogThread
);
715 rec
->recordType
= kHFSPlusFolderThreadRecord
;
717 rec
->recordType
= kHFSPlusFileThreadRecord
;
719 rec
->parentID
= key
->parentID
;
720 bcopy(&key
->nodeName
, &rec
->nodeName
,
721 sizeof(UniChar
) * (key
->nodeName
.length
+ 1));
723 /* HFS Plus has variable sized thread records */
724 size
-= (sizeof(rec
->nodeName
.unicode
) -
725 (rec
->nodeName
.length
* sizeof(UniChar
)));
731 * Build a catalog node thread key.
734 buildthreadkey(HFSCatalogNodeID parentID
, CatalogKey
*key
)
736 key
->hfsPlus
.keyLength
= kHFSPlusCatalogKeyMinimumLength
;
737 key
->hfsPlus
.parentID
= parentID
;
738 key
->hfsPlus
.nodeName
.length
= 0;
742 * cat_findname - obtain a descriptor from cnid
744 * Only a thread lookup is performed.
746 * Note: The caller is responsible for releasing the output
747 * catalog descriptor (when supplied outdescp is non-null).
751 cat_findname(struct hfsmount
*hfsmp
, cnid_t cnid
, struct cat_desc
*outdescp
)
753 BTreeIterator
*iterator
= NULL
;
754 CatalogRecord
* recp
= NULL
;
755 FSBufferDescriptor btdata
;
761 iterator
= hfs_mallocz(sizeof(BTreeIterator
));
762 if (iterator
== NULL
)
768 buildthreadkey(cnid
, (CatalogKey
*)&iterator
->key
);
769 iterator
->hint
.nodeNum
= 0;
771 recp
= hfs_malloc(sizeof(CatalogRecord
));
777 memset(recp
,0,sizeof(CatalogRecord
));
778 BDINIT(btdata
, recp
);
780 result
= BTSearchRecord(VTOF(hfsmp
->hfs_catalog_vp
), iterator
, &btdata
, NULL
, NULL
);
784 /* Turn thread record into a cnode key (in place). */
785 switch (recp
->recordType
)
787 case kHFSPlusFolderThreadRecord
:
790 case kHFSPlusFileThreadRecord
:
791 keyp
= (CatalogKey
*)&recp
->hfsPlusThread
.reserved
;
792 keyp
->hfsPlus
.keyLength
= kHFSPlusCatalogKeyMinimumLength
+
793 (keyp
->hfsPlus
.nodeName
.length
* 2);
801 builddesc((HFSPlusCatalogKey
*)keyp
, cnid
, 0, 0, isdir
, outdescp
);
812 bool IsEntryAJnlFile(struct hfsmount
*hfsmp
, cnid_t cnid
)
814 return (((hfsmp
->jnl
|| ((HFSTOVCB(hfsmp
)->vcbAtrb
& kHFSVolumeJournaledMask
) && (hfsmp
->hfs_flags
& HFS_READ_ONLY
)))) &&
815 ((cnid
== hfsmp
->hfs_jnlfileid
) || (cnid
== hfsmp
->hfs_jnlinfoblkid
)));
818 static bool IsEntryADirectoryLink(struct hfsmount
*hfsmp
, const CatalogRecord
*crp
,time_t itime
)
820 return ((SWAP_BE32(crp
->hfsPlusFile
.userInfo
.fdType
) == kHFSAliasType
) &&
821 (SWAP_BE32(crp
->hfsPlusFile
.userInfo
.fdCreator
) == kHFSAliasCreator
) &&
822 (crp
->hfsPlusFile
.flags
& kHFSHasLinkChainMask
) &&
823 (crp
->hfsPlusFile
.bsdInfo
.special
.iNodeNum
>= kHFSFirstUserCatalogNodeID
) &&
824 ((itime
== (time_t)hfsmp
->hfs_itime
) || (itime
== (time_t)hfsmp
->hfs_metadata_createdate
)));
827 static bool IsEntryAHardLink(struct hfsmount
*hfsmp
, const CatalogRecord
*crp
,time_t itime
)
829 return((SWAP_BE32(crp
->hfsPlusFile
.userInfo
.fdType
) == kHardLinkFileType
) && (SWAP_BE32(crp
->hfsPlusFile
.userInfo
.fdCreator
) == kHFSPlusCreator
) &&
830 ((itime
== (time_t)hfsmp
->hfs_itime
) || (itime
== (time_t)hfsmp
->hfs_metadata_createdate
)));
834 * getdirentries callback for HFS Plus directories.
837 getdirentries_callback(const CatalogKey
*ckp
, const CatalogRecord
*crp
, struct packdirentry_state
*state
)
840 UVFSDirEntry
* entry
= NULL
;
841 const CatalogName
*cnp
;
844 u_int32_t ilinkref
= 0;
845 u_int32_t curlinkref
= 0;
850 caddr_t uiobase
= NULL
;
856 Boolean bIsLastRecordInDir
= false;
857 Boolean bToHide
= false;
858 Boolean bIsLink
= false;
859 Boolean bIsMangled
= false;
861 struct hfsmount
*hfsmp
= state
->cbs_hfsmp
;
862 cnid_t curID
= ckp
->hfsPlus
.parentID
;
864 /* We're done when parent directory changes */
865 if (state
->cbs_parentID
!= curID
)
868 * If the parent ID is different from curID this means we've hit
869 * the EOF for the directory. To help future callers, we mark
870 * the cbs_eof boolean. However, we should only mark the EOF
871 * boolean if we're about to return from this function.
873 * This is because this callback function does its own uiomove
874 * to get the data to userspace. If we set the boolean before determining
875 * whether or not the current entry has enough room to write its
876 * data to userland, we could fool the callers of this catalog function
877 * into thinking they've hit EOF earlier than they really would have.
878 * In that case, we'd know that we have more entries to process and
879 * send to userland, but we didn't have enough room.
881 * To be safe, we mark cbs_eof here ONLY for the cases where we know we're
882 * about to return and won't write any new data back
883 * to userland. In the stop_after_pack case, we'll set this boolean
884 * regardless, so it's slightly safer to let that logic mark the boolean,
885 * especially since it's closer to the return of this function.
889 /* The last record has not been returned yet, so we
890 * want to stop after packing the last item
892 if (state
->cbs_hasprevdirentry
)
894 bIsLastRecordInDir
= true;
898 state
->cbs_eof
= true;
899 state
->cbs_result
= ENOENT
;
900 return (0); /* stop */
905 entry
= state
->cbs_direntry
;
906 u_int8_t
* nameptr
= (u_int8_t
*)&entry
->de_name
;
907 if (state
->cbs_flags
& VNODE_READDIR_NAMEMAX
)
910 * The NFS server sometimes needs to make filenames fit in
911 * NAME_MAX bytes (since its client may not be able to
912 * handle a longer name). In that case, NFS will ask us
913 * to mangle the name to keep it short enough.
915 maxnamelen
= NAME_MAX
+ 1;
919 maxnamelen
= UVFS_DIRENTRY_RECLEN(MAX_UTF8_NAME_LENGTH
);
922 if (bIsLastRecordInDir
)
924 /* The last item returns a non-zero invalid cookie */
932 switch(crp
->recordType
)
934 case kHFSPlusFolderRecord
:
935 type
= UVFS_FA_TYPE_DIR
;
936 cnid
= crp
->hfsPlusFolder
.folderID
;
937 /* Hide our private system directories. */
938 if (curID
== kHFSRootFolderID
)
940 if (cnid
== hfsmp
->hfs_private_desc
[FILE_HARDLINKS
].cd_cnid
|| cnid
== hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
)
946 case kHFSPlusFileRecord
:
947 itime
= to_bsd_time(crp
->hfsPlusFile
.createDate
);
948 type
= MODE_TO_TYPE(crp
->hfsPlusFile
.bsdInfo
.fileMode
);
949 cnid
= crp
->hfsPlusFile
.fileID
;
951 * When a hardlink link is encountered save its link ref.
953 if (IsEntryAHardLink(hfsmp
, crp
, itime
))
955 /* If link ref is inode's file id then use it directly. */
956 if (crp
->hfsPlusFile
.flags
& kHFSHasLinkChainMask
)
958 cnid
= crp
->hfsPlusFile
.bsdInfo
.special
.iNodeNum
;
962 ilinkref
= crp
->hfsPlusFile
.bsdInfo
.special
.iNodeNum
;
966 else if (IsEntryADirectoryLink(hfsmp
, crp
,itime
))
968 /* A directory's link resolves to a directory. */
969 type
= UVFS_FA_TYPE_DIR
;
970 /* A directory's link ref is always inode's file id. */
971 cnid
= crp
->hfsPlusFile
.bsdInfo
.special
.iNodeNum
;
975 /* Hide the journal files */
976 if ((curID
== kHFSRootFolderID
) && IsEntryAJnlFile(hfsmp
, cnid
))
983 return (0); /* stop */
986 cnp
= (const CatalogName
*) &ckp
->hfsPlus
.nodeName
;
988 namelen
= cnp
->ustr
.length
;
990 * For MacRoman encoded names (textEncoding == 0), assume that it's ascii
991 * and convert it directly in an attempt to avoid the more
992 * expensive utf8_encodestr conversion.
994 if ((namelen
< maxnamelen
) && (crp
->hfsPlusFile
.textEncoding
== 0)) {
997 const u_int16_t
*chp
;
999 chp
= &cnp
->ustr
.unicode
[0];
1000 for (i
= 0; i
< (int)namelen
; ++i
) {
1002 if (ch
> 0x007f || ch
== 0x0000) {
1003 /* Perform expensive utf8_encodestr conversion */
1006 nameptr
[i
] = (ch
== '/') ? ':' : (u_int8_t
)ch
;
1008 nameptr
[namelen
] = '\0';
1014 result
= utf8_encodestr(cnp
->ustr
.unicode
, namelen
* sizeof(UniChar
), nameptr
, &namelen
, maxnamelen
, ':', UTF_ADD_NULL_TERM
);
1017 /* Check result returned from encoding the filename to utf8 */
1018 if (result
== ENAMETOOLONG
)
1021 * If we were looking at a catalog record for a hardlink (not the inode),
1022 * then we want to use its link ID as opposed to the inode ID for
1023 * a mangled name. For all other cases, they are the same. Note that
1024 * due to the way directory hardlinks are implemented, the actual link
1025 * is going to be counted as a file record, so we can catch both
1028 cnid_t linkid
= cnid
;
1031 linkid
= crp
->hfsPlusFile
.fileID
;
1034 result
= ConvertUnicodeToUTF8Mangled(cnp
->ustr
.length
* sizeof(UniChar
), cnp
->ustr
.unicode
, maxnamelen
, (ByteCount
*)&namelen
, nameptr
, linkid
);
1035 if (result
) return (0); /* stop */
1041 * The index is 1 relative and includes "." and ".."
1043 * Also stuff the cnid in the upper 32 bits of the cookie.
1044 * The cookie is stored to the previous entry, which will
1045 * be packed and copied this time
1047 state
->cbs_prevdirentry
->de_nextcookie
= (state
->cbs_index
+ 3) | ((u_int64_t
)cnid
<< 32);
1048 uiosize
= state
->cbs_prevdirentry
->de_reclen
;
1050 //Check if this will be the last entry to be inserted in the buffer
1051 //If this is the last entry need to change the d_reclen to 0.
1052 //If this is the last file in the dir, need to change the d_seekoff to UVFS_DIRCOOKIE_EOF
1053 if ((UVFS_DIRENTRY_RECLEN(namelen
) + uiosize
) > state
->cbs_psReadDirBuffer
->uBufferResid
)
1055 state
->cbs_prevdirentry
->de_reclen
= 0;
1058 if (bIsLastRecordInDir
)
1060 state
->cbs_prevdirentry
->de_reclen
= 0;
1061 state
->cbs_prevdirentry
->de_nextcookie
= UVFS_DIRCOOKIE_EOF
;
1064 uioaddr
= (caddr_t
) state
->cbs_prevdirentry
;
1066 /* Save current base address for post processing of hard-links. */
1067 if (ilinkref
|| state
->cbs_previlinkref
)
1072 /* If this entry won't fit then we're done */
1073 if ((uiosize
> (user_size_t
)state
->cbs_psReadDirBuffer
->uBufferResid
) || (ilinkref
!= 0 && state
->cbs_nlinks
== state
->cbs_maxlinks
))
1075 return (0); /* stop */
1078 if (state
->cbs_hasprevdirentry
)
1080 // Skip entries marked as "bToHide" on the previous iteration!
1081 if (state
->cbs_prevdirentry
->de_fileid
!= 0)
1083 memcpy(state
->cbs_psReadDirBuffer
->pvBuffer
+ READDIR_BUF_OFFSET(state
->cbs_psReadDirBuffer
), uioaddr
, uiosize
);
1084 state
->cbs_lastinsertedentry
= state
->cbs_psReadDirBuffer
->pvBuffer
+ READDIR_BUF_OFFSET(state
->cbs_psReadDirBuffer
);
1085 state
->cbs_haslastinsertedentry
= true;
1086 state
->cbs_psReadDirBuffer
->uBufferResid
-= uiosize
;
1088 else if (state
->cbs_haslastinsertedentry
&& bIsLastRecordInDir
)
1090 state
->cbs_lastinsertedentry
->de_reclen
= 0;
1091 state
->cbs_lastinsertedentry
->de_nextcookie
= UVFS_DIRCOOKIE_EOF
;
1096 /* Remember previous entry */
1097 state
->cbs_desc
->cd_cnid
= cnid
;
1098 if (type
== UVFS_FA_TYPE_DIR
)
1100 state
->cbs_desc
->cd_flags
|= CD_ISDIR
;
1104 state
->cbs_desc
->cd_flags
&= ~CD_ISDIR
;
1107 if (state
->cbs_desc
->cd_nameptr
!= NULL
)
1109 state
->cbs_desc
->cd_namelen
= 0;
1114 state
->cbs_desc
->cd_namelen
= namelen
;
1115 bcopy(nameptr
, state
->cbs_namebuf
, namelen
+ 1);
1119 /* Store unmangled name for the directory hint else it will
1120 * restart readdir at the last location again
1122 u_int8_t
*new_nameptr
;
1124 size_t tmp_namelen
= 0;
1126 cnp
= (const CatalogName
*)&ckp
->hfsPlus
.nodeName
;
1127 bufsize
= 1 + utf8_encodelen(cnp
->ustr
.unicode
, cnp
->ustr
.length
* sizeof(UniChar
), ':', 0);
1128 new_nameptr
= hfs_mallocz(bufsize
);
1129 result
= utf8_encodestr(cnp
->ustr
.unicode
, cnp
->ustr
.length
* sizeof(UniChar
), new_nameptr
, &tmp_namelen
, bufsize
, ':', UTF_ADD_NULL_TERM
);
1132 hfs_free(new_nameptr
);
1133 return (0); /* stop */
1137 state
->cbs_desc
->cd_namelen
= tmp_namelen
;
1138 bcopy(new_nameptr
, state
->cbs_namebuf
, tmp_namelen
+ 1);
1140 hfs_free(new_nameptr
);
1143 if (state
->cbs_hasprevdirentry
)
1145 curlinkref
= ilinkref
; /* save current */
1146 ilinkref
= state
->cbs_previlinkref
; /* use previous */
1149 * Record any hard links for post processing.
1151 if ((ilinkref
!= 0) && (state
->cbs_result
== 0) && (state
->cbs_nlinks
< state
->cbs_maxlinks
))
1153 state
->cbs_linkinfo
[state
->cbs_nlinks
].dirent_addr
= uiobase
;
1154 state
->cbs_linkinfo
[state
->cbs_nlinks
].link_ref
= ilinkref
;
1155 state
->cbs_nlinks
++;
1158 if (state
->cbs_hasprevdirentry
)
1160 ilinkref
= curlinkref
; /* restore current */
1164 /* Fill the direntry to be used the next time */
1165 if (bIsLastRecordInDir
)
1167 state
->cbs_eof
= true;
1168 return (0); /* stop */
1171 entry
->de_filetype
= type
;
1172 entry
->de_namelen
= namelen
;
1173 entry
->de_reclen
= UVFS_DIRENTRY_RECLEN(namelen
);
1174 entry
->de_fileid
= bToHide
? 0 : cnid
;
1176 /* swap the current and previous entry */
1177 UVFSDirEntry
* tmp
= state
->cbs_direntry
;
1178 state
->cbs_direntry
= state
->cbs_prevdirentry
;
1179 state
->cbs_prevdirentry
= tmp
;
1180 state
->cbs_hasprevdirentry
= true;
1181 state
->cbs_previlinkref
= ilinkref
;
1183 /* Continue iteration if there's room */
1184 return (state
->cbs_result
== 0 && state
->cbs_psReadDirBuffer
->uBufferResid
>= SMALL_DIRENTRY_SIZE
);
1188 * Callback to establish directory position.
1189 * Called with position_state for each item in a directory.
1192 cat_findposition(const CatalogKey
*ckp
, const CatalogRecord
*crp
, struct position_state
*state
)
1195 curID
= ckp
->hfsPlus
.parentID
;
1197 /* Make sure parent directory didn't change */
1198 if (state
->parentID
!= curID
) {
1200 * The parent ID is different from curID this means we've hit
1201 * the EOF for the directory.
1203 state
->error
= ENOENT
;
1204 return (0); /* stop */
1207 /* Count this entry */
1208 switch(crp
->recordType
)
1210 case kHFSPlusFolderRecord
:
1211 case kHFSPlusFileRecord
:
1215 LFHFS_LOG(LEVEL_ERROR
, "cat_findposition: invalid record type %d in dir %d\n", crp
->recordType
, curID
);
1216 state
->error
= EINVAL
;
1217 return (0); /* stop */
1220 return (state
->count
< state
->index
);
1224 * Pack a uio buffer with directory entries from the catalog
1227 cat_getdirentries(struct hfsmount
*hfsmp
, u_int32_t entrycnt
, directoryhint_t
*dirhint
, ReadDirBuff_s
* psReadDirBuffer
, int flags
, int *items
, bool *eofflag
, UVFSDirEntry
* psDotDotEntry
)
1230 BTreeIterator
* iterator
= NULL
;
1232 struct packdirentry_state state
;
1238 extended
= flags
& VNODE_READDIR_EXTENDED
;
1240 fcb
= hfsmp
->hfs_catalog_cp
->c_datafork
;
1242 #define MAX_LINKINFO_ENTRIES 275
1244 * Get a buffer for link info array, btree iterator and a direntry.
1246 * We impose an cap of 275 link entries when trying to compute
1247 * the total number of hardlink entries that we'll allow in the
1248 * linkinfo array, as this has been shown to noticeably impact performance.
1250 * Note that in the case where there are very few hardlinks,
1251 * this does not restrict or prevent us from vending out as many entries
1252 * as we can to the uio_resid, because the getdirentries callback
1253 * uiomoves the directory entries to the uio itself and does not use
1254 * this MALLOC'd array. It also limits itself to maxlinks of hardlinks.
1257 // This value cannot underflow: both entrycnt and the rhs are unsigned 32-bit
1258 // ints, so the worst-case MIN of them is 0.
1259 int maxlinks
= min (entrycnt
, (u_int32_t
)(psReadDirBuffer
->uBufferResid
/ SMALL_DIRENTRY_SIZE
));
1260 // Prevent overflow.
1261 maxlinks
= MIN (maxlinks
, MAX_LINKINFO_ENTRIES
);
1262 int bufsize
= MAXPATHLEN
+ (maxlinks
* sizeof(linkinfo_t
)) + sizeof(*iterator
);
1266 bufsize
+= 2 * (sizeof(UVFSDirEntry
) + sizeof(char)*MAX_UTF8_NAME_LENGTH
);
1268 void* buffer
= hfs_mallocz(bufsize
);
1270 state
.cbs_flags
= flags
;
1271 state
.cbs_hasprevdirentry
= false;
1272 state
.cbs_haslastinsertedentry
= (psDotDotEntry
!= NULL
);
1273 state
.cbs_lastinsertedentry
= psDotDotEntry
;
1274 state
.cbs_previlinkref
= 0;
1275 state
.cbs_nlinks
= 0;
1276 state
.cbs_maxlinks
= maxlinks
;
1277 state
.cbs_linkinfo
= (linkinfo_t
*)((char *)buffer
+ MAXPATHLEN
);
1279 * We need to set cbs_eof to false regardless of whether or not the
1280 * control flow is actually in the extended case, since we use this
1281 * field to track whether or not we've returned EOF from the iterator function.
1283 state
.cbs_eof
= false;
1285 iterator
= (BTreeIterator
*) ((char *)state
.cbs_linkinfo
+ (maxlinks
* sizeof(linkinfo_t
)));
1286 key
= (CatalogKey
*)&iterator
->key
;
1288 index
= dirhint
->dh_index
+ 1;
1291 state
.cbs_direntry
= (UVFSDirEntry
*)((char *)iterator
+ sizeof(BTreeIterator
));
1292 state
.cbs_prevdirentry
= (UVFSDirEntry
*) ((uint8_t*) state
.cbs_direntry
+ sizeof(UVFSDirEntry
) + sizeof(char)*MAX_UTF8_NAME_LENGTH
);
1295 * Attempt to build a key from cached filename
1297 if (dirhint
->dh_desc
.cd_namelen
!= 0)
1299 if (buildkey(&dirhint
->dh_desc
, (HFSPlusCatalogKey
*)key
) == 0)
1301 iterator
->hint
.nodeNum
= dirhint
->dh_desc
.cd_hint
;
1306 if (index
== 0 && dirhint
->dh_threadhint
!= 0)
1309 * Position the iterator at the directory's thread record.
1310 * (i.e. just before the first entry)
1312 buildthreadkey(dirhint
->dh_desc
.cd_parentcnid
, key
);
1313 iterator
->hint
.nodeNum
= dirhint
->dh_threadhint
;
1314 iterator
->hint
.index
= 0;
1319 * If the last entry wasn't cached then position the btree iterator
1324 * Position the iterator at the directory's thread record.
1325 * (i.e. just before the first entry)
1327 buildthreadkey(dirhint
->dh_desc
.cd_parentcnid
, key
);
1328 result
= BTSearchRecord(fcb
, iterator
, NULL
, NULL
, iterator
);
1331 result
= MacToVFSError(result
);
1336 dirhint
->dh_threadhint
= iterator
->hint
.nodeNum
;
1339 * Iterate until we reach the entry just
1340 * before the one we want to start with.
1344 struct position_state ps
;
1349 ps
.parentID
= dirhint
->dh_desc
.cd_parentcnid
;
1352 result
= BTIterateRecords(fcb
, kBTreeNextRecord
, iterator
, (IterateCallBackProcPtr
)cat_findposition
, &ps
);
1356 result
= MacToVFSError(result
);
1358 result
= MacToVFSError(result
);
1359 if (result
== ENOENT
) {
1361 * ENOENT means we've hit the EOF.
1362 * suppress the error, and set the eof flag.
1365 dirhint
->dh_desc
.cd_flags
|= CD_EOF
;
1373 state
.cbs_index
= index
;
1374 state
.cbs_hfsmp
= hfsmp
;
1375 state
.cbs_psReadDirBuffer
= psReadDirBuffer
;
1376 state
.cbs_desc
= &dirhint
->dh_desc
;
1377 state
.cbs_namebuf
= (u_int8_t
*)buffer
;
1378 state
.cbs_result
= 0;
1379 state
.cbs_parentID
= dirhint
->dh_desc
.cd_parentcnid
;
1381 /* Use a temporary buffer to hold intermediate descriptor names. */
1382 if (dirhint
->dh_desc
.cd_namelen
> 0 && dirhint
->dh_desc
.cd_nameptr
!= NULL
)
1384 bcopy(dirhint
->dh_desc
.cd_nameptr
, buffer
, dirhint
->dh_desc
.cd_namelen
+1);
1385 if (dirhint
->dh_desc
.cd_flags
& CD_HASBUF
)
1387 dirhint
->dh_desc
.cd_flags
&= ~CD_HASBUF
;
1388 hfs_free((void*) dirhint
->dh_desc
.cd_nameptr
);
1391 dirhint
->dh_desc
.cd_nameptr
= (u_int8_t
*)buffer
;
1393 enum BTreeIterationOperations op
;
1394 if (extended
&& index
!= 0 && have_key
)
1395 op
= kBTreeCurrentRecord
;
1397 op
= kBTreeNextRecord
;
1400 * Process as many entries as possible starting at iterator->key.
1402 result
= BTIterateRecords(fcb
, op
, iterator
, (IterateCallBackProcPtr
)getdirentries_callback
, &state
);
1404 /* For extended calls, every call to getdirentries_callback()
1405 * transfers the previous directory entry found to the user
1406 * buffer. Therefore when BTIterateRecords reaches the end of
1407 * Catalog BTree, call getdirentries_callback() again with
1408 * dummy values to copy the last directory entry stored in
1409 * packdirentry_state
1411 if (extended
&& (result
== fsBTRecordNotFoundErr
))
1414 bzero(&ckp
, sizeof(ckp
));
1415 result
= getdirentries_callback(&ckp
, NULL
, &state
);
1418 /* Note that state.cbs_index is still valid on errors */
1419 *items
= state
.cbs_index
- index
;
1420 index
= state
.cbs_index
;
1423 * Also note that cbs_eof is set in all cases if we ever hit EOF
1424 * during the enumeration by the catalog callback. Mark the directory's hint
1425 * descriptor as having hit EOF.
1429 dirhint
->dh_desc
.cd_flags
|= CD_EOF
;
1433 //If we went out without any entries.
1434 //Need to check if the last updated entry is dotx2 and update accordingly.
1435 if (*items
== 0 && psDotDotEntry
!= NULL
)
1439 //This is an empty dir
1440 psDotDotEntry
->de_nextcookie
= UVFS_DIRCOOKIE_EOF
;
1441 psDotDotEntry
->de_nextrec
= 0;
1445 //Buffer is too small to add more entries after ".." entry
1446 psDotDotEntry
->de_nextrec
= 0;
1450 /* Finish updating the catalog iterator. */
1451 dirhint
->dh_desc
.cd_hint
= iterator
->hint
.nodeNum
;
1452 dirhint
->dh_desc
.cd_flags
|= CD_DECOMPOSED
;
1453 dirhint
->dh_index
= index
- 1;
1455 /* Fix up the name. */
1456 if (dirhint
->dh_desc
.cd_namelen
> 0)
1458 dirhint
->dh_desc
.cd_nameptr
= lf_hfs_utils_allocate_and_copy_string( (char *)buffer
, dirhint
->dh_desc
.cd_namelen
);
1459 dirhint
->dh_desc
.cd_flags
|= CD_HASBUF
;
1463 dirhint
->dh_desc
.cd_nameptr
= NULL
;
1464 dirhint
->dh_desc
.cd_namelen
= 0;
1468 * Post process any hard links to get the real file id.
1470 if (state
.cbs_nlinks
> 0)
1476 for (i
= 0; i
< state
.cbs_nlinks
; ++i
)
1478 if (resolvelinkid(hfsmp
, state
.cbs_linkinfo
[i
].link_ref
, &fileid
) != 0)
1480 /* This assumes that d_ino is always first field. */
1481 address
= state
.cbs_linkinfo
[i
].dirent_addr
;
1482 if (address
== (user_addr_t
)0)
1487 ino64_t fileid_64
= (ino64_t
)fileid
;
1488 memcpy(&fileid_64
, (void*) address
, sizeof(fileid_64
));
1492 memcpy(&fileid
, (void*) address
, sizeof(fileid
));
1498 if (state
.cbs_result
)
1499 result
= state
.cbs_result
;
1501 result
= MacToVFSError(result
);
1503 if (result
== ENOENT
)
1515 * cat_idlookup - lookup a catalog node using a cnode id
1517 * Note: The caller is responsible for releasing the output
1518 * catalog descriptor (when supplied outdescp is non-null).
1521 cat_idlookup(struct hfsmount
*hfsmp
, cnid_t cnid
, int allow_system_files
, int wantrsrc
,
1522 struct cat_desc
*outdescp
, struct cat_attr
*attrp
, struct cat_fork
*forkp
)
1524 BTreeIterator
* iterator
= NULL
;
1525 FSBufferDescriptor btdata
= {0};
1526 u_int16_t datasize
= 0;
1527 CatalogKey
* keyp
= NULL
;
1528 CatalogRecord
* recp
= NULL
;
1531 iterator
= hfs_mallocz(sizeof(*iterator
));
1532 if (iterator
== NULL
)
1533 return MacToVFSError(ENOMEM
);
1535 buildthreadkey(cnid
, (CatalogKey
*)&iterator
->key
);
1537 recp
= hfs_malloc(sizeof(CatalogRecord
));
1538 BDINIT(btdata
, recp
);
1540 result
= BTSearchRecord(VTOF(HFSTOVCB(hfsmp
)->catalogRefNum
), iterator
,
1541 &btdata
, &datasize
, iterator
);
1545 /* Turn thread record into a cnode key (in place) */
1546 switch (recp
->recordType
) {
1548 case kHFSPlusFileThreadRecord
:
1549 case kHFSPlusFolderThreadRecord
:
1550 keyp
= (CatalogKey
*)&recp
->hfsPlusThread
.reserved
;
1552 /* check for NULL name */
1553 if (keyp
->hfsPlus
.nodeName
.length
== 0) {
1558 keyp
->hfsPlus
.keyLength
= kHFSPlusCatalogKeyMinimumLength
+
1559 (keyp
->hfsPlus
.nodeName
.length
* 2);
1567 result
= cat_lookupbykey(hfsmp
, keyp
,
1568 ((allow_system_files
!= 0) ? HFS_LOOKUP_SYSFILE
: 0),
1569 0, wantrsrc
, outdescp
, attrp
, forkp
, NULL
);
1570 /* No corresponding file/folder record found for a thread record,
1571 * mark the volume inconsistent.
1573 if (result
== 0 && outdescp
) {
1574 cnid_t dcnid
= outdescp
->cd_cnid
;
1576 * Just for sanity's case, let's make sure that
1577 * the key in the thread matches the key in the record.
1581 LFHFS_LOG(LEVEL_ERROR
, "cat_idlookup: Requested cnid (%d / %08x) != dcnid (%d / %08x)\n", cnid
, cnid
, dcnid
, dcnid
);
1589 return MacToVFSError(result
);
1593 * buildkey - build a Catalog b-tree key from a cnode descriptor
1596 buildkey(struct cat_desc
*descp
, HFSPlusCatalogKey
*key
)
1598 int utf8_flags
= UTF_ESCAPE_ILLEGAL
;
1600 size_t unicodeBytes
= 0;
1602 if (descp
->cd_namelen
== 0 || descp
->cd_nameptr
[0] == '\0')
1603 return (EINVAL
); /* invalid name */
1605 key
->parentID
= descp
->cd_parentcnid
;
1606 key
->nodeName
.length
= 0;
1608 * Convert filename from UTF-8 into Unicode
1611 if ((descp
->cd_flags
& CD_DECOMPOSED
) == 0)
1613 utf8_flags
|= UTF_DECOMPOSED
;
1615 result
= utf8_decodestr(descp
->cd_nameptr
, descp
->cd_namelen
, key
->nodeName
.unicode
, &unicodeBytes
, sizeof(key
->nodeName
.unicode
), ':', utf8_flags
);
1616 key
->nodeName
.length
= unicodeBytes
/ sizeof(UniChar
);
1617 key
->keyLength
= kHFSPlusCatalogKeyMinimumLength
+ unicodeBytes
;
1620 if (result
!= ENAMETOOLONG
)
1621 result
= EINVAL
; /* name has invalid characters */
1629 * These Catalog functions allow access to the HFS Catalog (database).
1630 * The catalog b-tree lock must be acquired before calling any of these routines.
1634 * cat_lookup - lookup a catalog node using a cnode descriptor
1636 * Note: The caller is responsible for releasing the output
1637 * catalog descriptor (when supplied outdescp is non-null).
1640 cat_lookup(struct hfsmount
*hfsmp
, struct cat_desc
*descp
, int wantrsrc
,
1641 struct cat_desc
*outdescp
, struct cat_attr
*attrp
,
1642 struct cat_fork
*forkp
, cnid_t
*desc_cnid
)
1644 CatalogKey
* keyp
= NULL
;
1648 keyp
= hfs_malloc(sizeof(CatalogKey
));
1655 result
= buildkey(descp
, (HFSPlusCatalogKey
*)keyp
);
1659 result
= cat_lookupbykey(hfsmp
, keyp
, flags
, descp
->cd_hint
, wantrsrc
, outdescp
, attrp
, forkp
, desc_cnid
);
1661 if (result
== ENOENT
) {
1662 struct cat_desc temp_desc
;
1663 if (outdescp
== NULL
) {
1664 bzero(&temp_desc
, sizeof(temp_desc
));
1665 outdescp
= &temp_desc
;
1667 result
= cat_lookupmangled(hfsmp
, descp
, wantrsrc
, outdescp
, attrp
, forkp
);
1669 *desc_cnid
= outdescp
->cd_cnid
;
1671 if (outdescp
== &temp_desc
) {
1672 /* Release the local copy of desc */
1673 cat_releasedesc(outdescp
);
1684 * cat_lookupmangled - lookup a catalog node using a mangled name
1687 cat_lookupmangled(struct hfsmount
*hfsmp
, struct cat_desc
*descp
, int wantrsrc
,
1688 struct cat_desc
*outdescp
, struct cat_attr
*attrp
, struct cat_fork
*forkp
)
1691 u_int32_t prefixlen
;
1693 u_int8_t utf8
[NAME_MAX
+ 1];
1695 u_int16_t unicode
[kHFSPlusMaxFileNameChars
+ 1];
1701 fileID
= GetEmbeddedFileID(descp
->cd_nameptr
, descp
->cd_namelen
, &prefixlen
);
1702 if (fileID
< (cnid_t
)kHFSFirstUserCatalogNodeID
)
1705 if (fileID
== hfsmp
->hfs_private_desc
[FILE_HARDLINKS
].cd_cnid
||
1706 fileID
== hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
||
1707 fileID
== hfsmp
->hfs_jnlfileid
||
1708 fileID
== hfsmp
->hfs_jnlinfoblkid
)
1713 result
= cat_idlookup(hfsmp
, fileID
, 0, 0, outdescp
, attrp
, forkp
);
1716 /* It must be in the correct directory */
1717 if (descp
->cd_parentcnid
!= outdescp
->cd_parentcnid
)
1721 * Compare the mangled version of file name looked up from the
1722 * disk with the mangled name provided by the user. Note that
1723 * this comparison is case-sensitive, which should be fine
1724 * since we're trying to prevent user space from constructing
1725 * a mangled name that differs from the one they'd get from the
1728 result
= utf8_decodestr(outdescp
->cd_nameptr
, outdescp
->cd_namelen
,
1729 unicode
, &unicodelen
, sizeof(unicode
), ':', 0);
1733 result
= ConvertUnicodeToUTF8Mangled(unicodelen
, unicode
,
1734 sizeof(utf8
), &utf8len
, utf8
, fileID
);
1735 if ((result
!= 0) ||
1736 ((u_int16_t
)descp
->cd_namelen
!= utf8len
) ||
1737 (bcmp(descp
->cd_nameptr
, utf8
, utf8len
) != 0)) {
1744 cat_releasedesc(outdescp
);
1749 * Callback to collect directory entries.
1750 * Called with readattr_state for each item in a directory.
1752 struct readattr_state
{
1753 struct hfsmount
*hfsmp
;
1754 struct cat_entrylist
*list
;
1761 getentriesattr_callback(const CatalogKey
*key
, const CatalogRecord
*rec
, struct readattr_state
*state
)
1763 struct cat_entrylist
*list
= state
->list
;
1764 struct hfsmount
*hfsmp
= state
->hfsmp
;
1765 struct cat_entry
*cep
;
1768 if (list
->realentries
>= list
->maxentries
)
1769 return (0); /* stop */
1771 parentcnid
= key
->hfsPlus
.parentID
;
1773 switch(rec
->recordType
)
1775 case kHFSPlusFolderRecord
:
1776 case kHFSPlusFileRecord
:
1777 if (parentcnid
!= state
->dir_cnid
)
1779 state
->error
= btNotFound
;
1780 state
->reached_eof
= 1;
1781 return (0); /* stop */
1784 case kHFSPlusFolderThreadRecord
:
1785 case kHFSPlusFileThreadRecord
:
1786 list
->skipentries
++;
1787 if (parentcnid
!= state
->dir_cnid
)
1789 state
->error
= btNotFound
;
1790 state
->reached_eof
= 1;
1791 return (0); /* stop */
1794 return (1); /*continue */
1797 state
->error
= btNotFound
;
1798 return (0); /* stop */
1801 /* Hide the private system directories and journal files */
1802 if (parentcnid
== kHFSRootFolderID
)
1804 if (rec
->recordType
== kHFSPlusFolderRecord
)
1806 if (rec
->hfsPlusFolder
.folderID
== hfsmp
->hfs_private_desc
[FILE_HARDLINKS
].cd_cnid
|| rec
->hfsPlusFolder
.folderID
== hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
)
1808 list
->skipentries
++;
1809 return (1); /* continue */
1813 if ((rec
->recordType
== kHFSPlusFileRecord
) && IsEntryAJnlFile(hfsmp
, rec
->hfsPlusFile
.fileID
))
1815 list
->skipentries
++;
1816 return (1); /* continue */
1820 cep
= &list
->entry
[list
->realentries
++];
1822 getbsdattr(hfsmp
, (const struct HFSPlusCatalogFile
*)rec
, &cep
->ce_attr
);
1823 builddesc((const HFSPlusCatalogKey
*)key
, getcnid(rec
), 0, getencoding(rec
),
1824 isadir(rec
), &cep
->ce_desc
);
1826 if (rec
->recordType
== kHFSPlusFileRecord
)
1828 cep
->ce_datasize
= rec
->hfsPlusFile
.dataFork
.logicalSize
;
1829 cep
->ce_datablks
= rec
->hfsPlusFile
.dataFork
.totalBlocks
;
1830 cep
->ce_rsrcsize
= rec
->hfsPlusFile
.resourceFork
.logicalSize
;
1831 cep
->ce_rsrcblks
= rec
->hfsPlusFile
.resourceFork
.totalBlocks
;
1833 /* Save link reference for later processing. */
1834 if ((SWAP_BE32(rec
->hfsPlusFile
.userInfo
.fdType
) == kHardLinkFileType
) &&
1835 (SWAP_BE32(rec
->hfsPlusFile
.userInfo
.fdCreator
) == kHFSPlusCreator
))
1837 cep
->ce_attr
.ca_linkref
= rec
->hfsPlusFile
.bsdInfo
.special
.iNodeNum
;
1839 else if ((rec
->hfsPlusFile
.flags
& kHFSHasLinkChainMask
) &&
1840 (SWAP_BE32(rec
->hfsPlusFile
.userInfo
.fdType
) == kHFSAliasType
) &&
1841 (SWAP_BE32(rec
->hfsPlusFile
.userInfo
.fdCreator
) == kHFSAliasCreator
))
1843 cep
->ce_attr
.ca_linkref
= rec
->hfsPlusFile
.bsdInfo
.special
.iNodeNum
;
1848 return (list
->realentries
< list
->maxentries
);
1852 * Pack a cat_entrylist buffer with attributes from the catalog
1854 * Note: index is zero relative
1857 cat_getentriesattr(struct hfsmount
*hfsmp
, directoryhint_t
*dirhint
, struct cat_entrylist
*ce_list
, int *reachedeof
)
1861 BTreeIterator
* iterator
= NULL
;
1862 struct readattr_state state
;
1866 bool bHaveKey
= false;
1868 int reached_eof
= 0;
1870 ce_list
->realentries
= 0;
1872 fcb
= GetFileControlBlock(HFSTOVCB(hfsmp
)->catalogRefNum
);
1873 parentcnid
= dirhint
->dh_desc
.cd_parentcnid
;
1875 bzero (&state
, sizeof(struct readattr_state
));
1877 state
.hfsmp
= hfsmp
;
1878 state
.list
= ce_list
;
1879 state
.dir_cnid
= parentcnid
;
1882 iterator
= hfs_mallocz(sizeof(*iterator
));
1883 key
= (CatalogKey
*)&iterator
->key
;
1884 iterator
->hint
.nodeNum
= dirhint
->dh_desc
.cd_hint
;
1885 index
= dirhint
->dh_index
+ 1;
1888 * Attempt to build a key from cached filename
1890 if (dirhint
->dh_desc
.cd_namelen
!= 0)
1892 if (buildkey(&dirhint
->dh_desc
, (HFSPlusCatalogKey
*)key
) == 0)
1899 * If the last entry wasn't cached then position the btree iterator
1901 if ((index
== 0) || !bHaveKey
)
1904 * Position the iterator at the directory's thread record.
1905 * (i.e. just before the first entry)
1907 buildthreadkey(dirhint
->dh_desc
.cd_parentcnid
, key
);
1908 result
= BTSearchRecord(fcb
, iterator
, NULL
, NULL
, iterator
);
1911 result
= MacToVFSError(result
);
1916 * Iterate until we reach the entry just
1917 * before the one we want to start with.
1922 struct position_state ps
;
1927 ps
.parentID
= dirhint
->dh_desc
.cd_parentcnid
;
1930 result
= BTIterateRecords(fcb
, kBTreeNextRecord
, iterator
,
1931 (IterateCallBackProcPtr
)cat_findposition
, &ps
);
1935 result
= MacToVFSError(result
);
1940 * Note: the index may now point to EOF if the directory
1941 * was modified in between system calls. We will return
1942 * ENOENT from cat_findposition if this is the case, and
1943 * when we bail out with an error, our caller (hfs_readdirattr_internal)
1944 * will suppress the error and indicate EOF to its caller.
1946 result
= MacToVFSError(result
);
1952 /* Fill list with entries starting at iterator->key. */
1953 result
= BTIterateRecords(fcb
, kBTreeNextRecord
, iterator
,
1954 (IterateCallBackProcPtr
)getentriesattr_callback
, &state
);
1958 result
= state
.error
;
1959 reached_eof
= state
.reached_eof
;
1961 else if (ce_list
->realentries
== 0)
1963 result
= btNotFound
;
1968 result
= MacToVFSError(result
);
1972 * Resolve any hard links.
1974 for (i
= 0; i
< (int)ce_list
->realentries
; ++i
)
1976 struct FndrFileInfo
*fip
;
1977 struct cat_entry
*cep
;
1981 cep
= &ce_list
->entry
[i
];
1982 if (cep
->ce_attr
.ca_linkref
== 0)
1985 /* Note: Finder info is still in Big Endian */
1986 fip
= (struct FndrFileInfo
*)&cep
->ce_attr
.ca_finderinfo
;
1988 if (S_ISREG(cep
->ce_attr
.ca_mode
) &&
1989 (SWAP_BE32(fip
->fdType
) == kHardLinkFileType
) &&
1990 (SWAP_BE32(fip
->fdCreator
) == kHFSPlusCreator
)) {
1993 if (S_ISREG(cep
->ce_attr
.ca_mode
) &&
1994 (SWAP_BE32(fip
->fdType
) == kHFSAliasType
) &&
1995 (SWAP_BE32(fip
->fdCreator
) == kHFSAliasCreator
) &&
1996 (cep
->ce_attr
.ca_recflags
& kHFSHasLinkChainMask
)) {
2000 if (isfilelink
|| isdirlink
) {
2001 struct HFSPlusCatalogFile filerec
;
2003 if (cat_resolvelink(hfsmp
, cep
->ce_attr
.ca_linkref
, isdirlink
, &filerec
) != 0)
2005 /* Repack entry from inode record. */
2006 getbsdattr(hfsmp
, &filerec
, &cep
->ce_attr
);
2007 cep
->ce_datasize
= filerec
.dataFork
.logicalSize
;
2008 cep
->ce_datablks
= filerec
.dataFork
.totalBlocks
;
2009 cep
->ce_rsrcsize
= filerec
.resourceFork
.logicalSize
;
2010 cep
->ce_rsrcblks
= filerec
.resourceFork
.totalBlocks
;
2017 *reachedeof
= reached_eof
;
2018 return MacToVFSError(result
);
2022 * Check the run-time ID hashtable.
2024 * The catalog lock must be held (like other functions in this file).
2027 * 1 if the ID is in the hash table.
2028 * 0 if the ID is not in the hash table
2030 int cat_check_idhash (struct hfsmount
*hfsmp
, cnid_t test_fileid
) {
2032 cat_preflightid_t
*preflight
;
2035 for (preflight
= IDHASH(hfsmp
, test_fileid
)->lh_first
; preflight
; preflight
= preflight
->id_hash
.le_next
)
2037 if (preflight
->fileid
== test_fileid
)
2048 cat_acquire_cnid (struct hfsmount
*hfsmp
, cnid_t
*new_cnid
)
2051 BTreeIterator
*iterator
;
2052 FSBufferDescriptor btdata
;
2054 CatalogRecord
*recp
;
2058 * Get the next CNID. We can change it since we hold the catalog lock.
2061 nextCNID
= hfsmp
->vcbNxtCNID
;
2062 if (nextCNID
== 0xFFFFFFFF) {
2065 /* don't allow more than one wrap-around */
2068 hfs_lock_mount (hfsmp
);
2069 hfsmp
->vcbNxtCNID
= kHFSFirstUserCatalogNodeID
;
2070 hfsmp
->vcbAtrb
|= kHFSCatalogNodeIDsReusedMask
;
2071 hfs_unlock_mount (hfsmp
);
2073 hfsmp
->vcbNxtCNID
++;
2075 hfs_note_header_minor_change(hfsmp
);
2077 /* First check that there are not any entries pending in the hash table with this ID */
2078 if (cat_check_idhash (hfsmp
, nextCNID
))
2080 /* Someone wants to insert this into the catalog but hasn't done so yet. Skip it */
2084 /* Check to see if a thread record exists for the target ID we just got */
2085 iterator
= hfs_mallocz(sizeof(BTreeIterator
));
2086 if (iterator
== NULL
)
2089 buildthreadkey(nextCNID
, (CatalogKey
*)&iterator
->key
);
2091 recp
= hfs_malloc(sizeof(CatalogRecord
));
2092 BDINIT(btdata
, recp
);
2094 result
= BTSearchRecord(hfsmp
->hfs_catalog_cp
->c_datafork
, iterator
, &btdata
, &datasize
, iterator
);
2098 if (result
== btNotFound
) {
2099 /* Good. File ID was not in use. Move on to checking EA B-Tree */
2100 result
= file_attribute_exist (hfsmp
, nextCNID
);
2101 if (result
== EEXIST
) {
2102 /* This CNID has orphaned EAs. Skip it and move on to the next one */
2106 /* For any other error, return the result */
2111 * Now validate that there are no lingering cnodes with this ID. If a cnode
2112 * has been removed on-disk (marked C_NOEXISTS), but has not yet been reclaimed,
2113 * then it will still have an entry in the cnode hash table. This means that
2114 * a subsequent lookup will find THAT entry and believe this one has been deleted
2115 * prematurely. If there is a lingering cnode, then just skip this entry and move on.
2117 * Note that we pass (existence_only == 1) argument to hfs_chash_snoop.
2119 if ((hfsmp
->vcbAtrb
& kHFSCatalogNodeIDsReusedMask
))
2121 if (hfs_chash_snoop (hfsmp
, nextCNID
, 1, NULL
, NULL
) == 0)
2128 * If we get here, then we didn't see any thread records, orphaned EAs,
2129 * or stale cnodes. This ID is safe to vend out.
2131 *new_cnid
= nextCNID
;
2133 else if (result
== noErr
) {
2134 /* move on to the next ID */
2138 /* For any other situation, just bail out */
2147 cat_preflight(struct hfsmount
*hfsmp
, uint32_t ops
, cat_cookie_t
*cookie
)
2152 if (hfsmp
->hfs_catalog_cp
->c_lockowner
!= pthread_self())
2153 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_CATALOG
, HFS_EXCLUSIVE_LOCK
);
2155 result
= BTReserveSpace(hfsmp
->hfs_catalog_cp
->c_datafork
, ops
, (void*)cookie
);
2158 hfs_systemfile_unlock(hfsmp
, lockflags
);
2160 return MacToVFSError(result
);
2164 cat_postflight(struct hfsmount
*hfsmp
, cat_cookie_t
*cookie
)
2168 if (hfsmp
->hfs_catalog_cp
->c_lockowner
!= pthread_self())
2169 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_CATALOG
, HFS_EXCLUSIVE_LOCK
);
2171 (void) BTReleaseReserve(hfsmp
->hfs_catalog_cp
->c_datafork
, (void*)cookie
);
2174 hfs_systemfile_unlock(hfsmp
, lockflags
);
2177 * Extract the parent ID from a catalog node record.
2180 getparentcnid(const CatalogRecord
*recp
)
2184 switch (recp
->recordType
)
2186 case kHFSPlusFileThreadRecord
:
2187 case kHFSPlusFolderThreadRecord
:
2188 cnid
= recp
->hfsPlusThread
.parentID
;
2191 LFHFS_LOG(LEVEL_ERROR
, "getparentcnid: unknown recordType (crp @ %p)\n", recp
);
2201 struct hfsmount
* hfsmp
,
2202 struct cat_desc
* from_cdp
,
2203 struct cat_desc
* todir_cdp
,
2204 struct cat_desc
* to_cdp
,
2205 struct cat_desc
* out_cdp
)
2210 FSBufferDescriptor btdata
;
2211 ExtendedVCB
* vcb
= HFSTOVCB(hfsmp
);
2212 FCB
* fcb
= GetFileControlBlock(vcb
->catalogRefNum
);
2216 int directory
= from_cdp
->cd_flags
& CD_ISDIR
;
2218 u_int32_t encoding
= 0;
2220 if (from_cdp
->cd_namelen
== 0 || to_cdp
->cd_namelen
== 0)
2225 CatalogRecord
* recp
= NULL
;
2226 BTreeIterator
* to_iterator
= NULL
;
2227 BTreeIterator
* from_iterator
= (BTreeIterator
*) hfs_mallocz(sizeof(BTreeIterator
));
2228 if (from_iterator
== NULL
)
2233 if ((result
= buildkey(from_cdp
, (HFSPlusCatalogKey
*) &from_iterator
->key
)))
2238 to_iterator
= hfs_mallocz(sizeof(*to_iterator
));
2239 if (to_iterator
== NULL
)
2245 if ((result
= buildkey(to_cdp
, (HFSPlusCatalogKey
*) &to_iterator
->key
)))
2250 recp
= hfs_malloc(sizeof(CatalogRecord
));
2256 BDINIT(btdata
, recp
);
2259 * When moving a directory, make sure its a valid move.
2261 if (directory
&& (from_cdp
->cd_parentcnid
!= to_cdp
->cd_parentcnid
))
2263 cnid_t cnid
= from_cdp
->cd_cnid
;
2264 cnid_t pathcnid
= todir_cdp
->cd_parentcnid
;
2266 /* First check the obvious ones */
2267 if (cnid
== fsRtDirID
|| cnid
== to_cdp
->cd_parentcnid
|| cnid
== pathcnid
)
2272 /* now allocate the dir_iterator */
2273 BTreeIterator
* dir_iterator
= hfs_mallocz(sizeof(BTreeIterator
));
2274 if (dir_iterator
== NULL
)
2281 * Traverse destination path all the way back to the root
2282 * making sure that source directory is not encountered.
2285 while (pathcnid
> fsRtDirID
)
2287 buildthreadkey(pathcnid
, (CatalogKey
*)&dir_iterator
->key
);
2288 result
= BTSearchRecord(fcb
, dir_iterator
, &btdata
, &datasize
, NULL
);
2291 hfs_free(dir_iterator
);
2294 pathcnid
= getparentcnid(recp
);
2295 if (pathcnid
== cnid
|| pathcnid
== 0)
2298 hfs_free(dir_iterator
);
2302 hfs_free(dir_iterator
);
2306 * Step 1: Find cnode data at old location
2308 result
= BTSearchRecord(fcb
, from_iterator
, &btdata
,
2309 &datasize
, from_iterator
);
2312 if (result
!= btNotFound
)
2315 struct cat_desc temp_desc
;
2317 /* Probably the node has mangled name */
2318 result
= cat_lookupmangled(hfsmp
, from_cdp
, 0, &temp_desc
, NULL
, NULL
);
2322 /* The file has mangled name. Search the cnode data using full name */
2323 bzero(from_iterator
, sizeof(*from_iterator
));
2324 result
= buildkey(&temp_desc
, (HFSPlusCatalogKey
*)&from_iterator
->key
);
2327 cat_releasedesc(&temp_desc
);
2331 result
= BTSearchRecord(fcb
, from_iterator
, &btdata
, &datasize
, from_iterator
);
2334 cat_releasedesc(&temp_desc
);
2338 cat_releasedesc(&temp_desc
);
2341 /* Check if the source is directory hard link. We do not change
2342 * directory flag because it is later used to initialize result descp
2344 if ((directory
) && (recp
->recordType
== kHFSPlusFileRecord
) && (recp
->hfsPlusFile
.flags
& kHFSHasLinkChainMask
))
2350 * Update the text encoding (on disk and in descriptor),
2351 * using hfs_pickencoding to get the new encoding when available.
2353 * Note that hardlink inodes don't require a text encoding hint.
2355 if (todir_cdp
->cd_parentcnid
!= hfsmp
->hfs_private_desc
[FILE_HARDLINKS
].cd_cnid
&&
2356 todir_cdp
->cd_parentcnid
!= hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
)
2358 encoding
= kTextEncodingMacRoman
;
2360 hfs_setencodingbits(hfsmp
, encoding
);
2361 recp
->hfsPlusFile
.textEncoding
= encoding
;
2363 out_cdp
->cd_encoding
= encoding
;
2367 /* Step 2: Insert cnode at new location */
2368 result
= BTInsertRecord(fcb
, to_iterator
, &btdata
, datasize
);
2369 if (result
== btExists
)
2371 int fromtype
= recp
->recordType
;
2374 if (from_cdp
->cd_parentcnid
!= to_cdp
->cd_parentcnid
)
2375 goto exit
; /* EEXIST */
2377 /* Find cnode data at new location */
2378 result
= BTSearchRecord(fcb
, to_iterator
, &btdata
, &datasize
, NULL
);
2382 /* Get the CNID after calling searchrecord */
2383 cnid
= getcnid (recp
);
2386 hfs_mark_inconsistent(hfsmp
, HFS_INCONSISTENCY_DETECTED
);
2391 if ((fromtype
!= recp
->recordType
) || (from_cdp
->cd_cnid
!= cnid
))
2394 goto exit
; /* EEXIST */
2396 /* The old name is a case variant and must be removed */
2397 result
= BTDeleteRecord(fcb
, from_iterator
);
2401 /* Insert cnode (now that case duplicate is gone) */
2402 result
= BTInsertRecord(fcb
, to_iterator
, &btdata
, datasize
);
2405 /* Try and restore original before leaving */
2409 err
= BTInsertRecord(fcb
, from_iterator
, &btdata
, datasize
);
2412 LFHFS_LOG(LEVEL_ERROR
, "cat_create: could not undo (BTInsert = %d)\n", err
);
2413 hfs_mark_inconsistent(hfsmp
, HFS_ROLLBACK_FAILED
);
2426 /* Step 3: Remove cnode from old location */
2429 result
= BTDeleteRecord(fcb
, from_iterator
);
2432 /* Try and delete new record before leaving */
2436 err
= BTDeleteRecord(fcb
, to_iterator
);
2439 LFHFS_LOG(LEVEL_ERROR
, "cat_create: could not undo (BTDelete = %d)\n", err
);
2440 hfs_mark_inconsistent(hfsmp
, HFS_ROLLBACK_FAILED
);
2450 /* #### POINT OF NO RETURN #### */
2453 * Step 4: Remove cnode's old thread record
2455 buildthreadkey(from_cdp
->cd_cnid
, (CatalogKey
*)&from_iterator
->key
);
2456 (void) BTDeleteRecord(fcb
, from_iterator
);
2459 * Step 5: Insert cnode's new thread record
2460 * (optional for HFS files)
2464 /* For directory hard links, always create a file thread
2465 * record. For everything else, use the directory flag.
2469 datasize
= buildthread(&to_iterator
->key
, recp
, false);
2473 datasize
= buildthread(&to_iterator
->key
, recp
, directory
);
2475 btdata
.itemSize
= datasize
;
2476 buildthreadkey(from_cdp
->cd_cnid
, (CatalogKey
*)&from_iterator
->key
);
2477 result
= BTInsertRecord(fcb
, from_iterator
, &btdata
, datasize
);
2482 HFSPlusCatalogKey
* pluskey
= NULL
;
2483 pluskey
= (HFSPlusCatalogKey
*)&to_iterator
->key
;
2484 builddesc(pluskey
, from_cdp
->cd_cnid
, to_iterator
->hint
.nodeNum
, encoding
, directory
, out_cdp
);
2488 (void) BTFlushPath(fcb
);
2490 hfs_free(from_iterator
);
2491 hfs_free(to_iterator
);
2494 return MacToVFSError(result
);
2497 struct update_state
{
2498 struct cat_desc
* s_desc
;
2499 struct cat_attr
* s_attr
;
2500 const struct cat_fork
* s_datafork
;
2501 const struct cat_fork
* s_rsrcfork
;
2502 struct hfsmount
* s_hfsmp
;
2506 * catrec_update - Update the fields of a catalog record
2507 * This is called from within BTUpdateRecord.
2510 catrec_update(const CatalogKey
*ckp
, CatalogRecord
*crp
, struct update_state
*state
)
2512 struct cat_desc
*descp
= state
->s_desc
;
2513 struct cat_attr
*attrp
= state
->s_attr
;
2514 const struct cat_fork
*forkp
;
2515 struct hfsmount
*hfsmp
= state
->s_hfsmp
;
2516 long blksize
= HFSTOVCB(hfsmp
)->blockSize
;
2518 switch (crp
->recordType
)
2520 case kHFSPlusFolderRecord
:
2522 HFSPlusCatalogFolder
*dir
;
2524 dir
= (struct HFSPlusCatalogFolder
*)crp
;
2525 /* Do a quick sanity check */
2526 if (dir
->folderID
!= attrp
->ca_fileid
)
2528 LFHFS_LOG(LEVEL_DEBUG
, "catrec_update: id %d != %d, vol=%s\n", dir
->folderID
, attrp
->ca_fileid
, hfsmp
->vcbVN
);
2529 return (btNotFound
);
2531 dir
->flags
= attrp
->ca_recflags
;
2532 dir
->valence
= attrp
->ca_entries
;
2533 dir
->createDate
= to_hfs_time(attrp
->ca_itime
);
2534 dir
->contentModDate
= to_hfs_time(attrp
->ca_mtime
);
2535 dir
->backupDate
= to_hfs_time(attrp
->ca_btime
);
2536 dir
->accessDate
= to_hfs_time(attrp
->ca_atime
);
2537 attrp
->ca_atimeondisk
= attrp
->ca_atime
;
2538 dir
->attributeModDate
= to_hfs_time(attrp
->ca_ctime
);
2539 /* Note: directory hardlink inodes don't require a text encoding hint. */
2540 if (ckp
->hfsPlus
.parentID
!= hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
) {
2541 dir
->textEncoding
= descp
->cd_encoding
;
2543 dir
->folderCount
= attrp
->ca_dircount
;
2544 bcopy(&attrp
->ca_finderinfo
[0], &dir
->userInfo
, 32);
2546 * Update the BSD Info if it was already initialized on
2547 * disk or if the runtime values have been modified.
2549 * If the BSD info was already initialized, but
2550 * MNT_UNKNOWNPERMISSIONS is set, then the runtime IDs are
2551 * probably different than what was on disk. We don't want
2552 * to overwrite the on-disk values (so if we turn off
2553 * MNT_UNKNOWNPERMISSIONS, the old IDs get used again).
2554 * This way, we can still change fields like the mode or
2555 * dates even when MNT_UNKNOWNPERMISSIONS is set.
2557 * Note that if MNT_UNKNOWNPERMISSIONS is set, hfs_chown
2558 * won't change the uid or gid from their defaults. So, if
2559 * the BSD info wasn't set, and the runtime values are not
2560 * default, then what changed was the mode or flags. We
2561 * have to set the uid and gid to something, so use the
2562 * supplied values (which will be default), which has the
2563 * same effect as creating a new file while
2564 * MNT_UNKNOWNPERMISSIONS is set.
2566 if ((dir
->bsdInfo
.fileMode
!= 0) ||
2567 (attrp
->ca_flags
!= 0) ||
2568 (attrp
->ca_uid
!= hfsmp
->hfs_uid
) ||
2569 (attrp
->ca_gid
!= hfsmp
->hfs_gid
) ||
2570 ((attrp
->ca_mode
& ALLPERMS
) !=
2571 (hfsmp
->hfs_dir_mask
& ACCESSPERMS
))) {
2572 if ((dir
->bsdInfo
.fileMode
== 0) || ((HFSTOVFS(hfsmp
)->mnt_flag
) & MNT_UNKNOWNPERMISSIONS
) == 0)
2574 dir
->bsdInfo
.ownerID
= attrp
->ca_uid
;
2575 dir
->bsdInfo
.groupID
= attrp
->ca_gid
;
2577 dir
->bsdInfo
.ownerFlags
= attrp
->ca_flags
& 0x000000FF;
2578 dir
->bsdInfo
.adminFlags
= attrp
->ca_flags
>> 16;
2579 dir
->bsdInfo
.fileMode
= attrp
->ca_mode
;
2580 /* A directory hardlink has a link count. */
2581 if (attrp
->ca_linkcount
> 1 || dir
->hl_linkCount
> 1)
2583 dir
->hl_linkCount
= attrp
->ca_linkcount
;
2588 case kHFSPlusFileRecord
: {
2589 HFSPlusCatalogFile
*file
;
2592 file
= (struct HFSPlusCatalogFile
*)crp
;
2593 /* Do a quick sanity check */
2594 if (file
->fileID
!= attrp
->ca_fileid
)
2595 return (btNotFound
);
2596 file
->flags
= attrp
->ca_recflags
;
2597 file
->createDate
= to_hfs_time(attrp
->ca_itime
);
2598 file
->contentModDate
= to_hfs_time(attrp
->ca_mtime
);
2599 file
->backupDate
= to_hfs_time(attrp
->ca_btime
);
2600 file
->accessDate
= to_hfs_time(attrp
->ca_atime
);
2601 attrp
->ca_atimeondisk
= attrp
->ca_atime
;
2602 file
->attributeModDate
= to_hfs_time(attrp
->ca_ctime
);
2604 * Note: file hardlink inodes don't require a text encoding
2605 * hint, but they do have a first link value.
2607 if (ckp
->hfsPlus
.parentID
== hfsmp
->hfs_private_desc
[FILE_HARDLINKS
].cd_cnid
) {
2608 file
->hl_firstLinkID
= attrp
->ca_firstlink
;
2610 file
->textEncoding
= descp
->cd_encoding
;
2612 bcopy(&attrp
->ca_finderinfo
[0], &file
->userInfo
, 32);
2614 * Update the BSD Info if it was already initialized on
2615 * disk or if the runtime values have been modified.
2617 * If the BSD info was already initialized, but
2618 * MNT_UNKNOWNPERMISSIONS is set, then the runtime IDs are
2619 * probably different than what was on disk. We don't want
2620 * to overwrite the on-disk values (so if we turn off
2621 * MNT_UNKNOWNPERMISSIONS, the old IDs get used again).
2622 * This way, we can still change fields like the mode or
2623 * dates even when MNT_UNKNOWNPERMISSIONS is set.
2625 * Note that if MNT_UNKNOWNPERMISSIONS is set, hfs_chown
2626 * won't change the uid or gid from their defaults. So, if
2627 * the BSD info wasn't set, and the runtime values are not
2628 * default, then what changed was the mode or flags. We
2629 * have to set the uid and gid to something, so use the
2630 * supplied values (which will be default), which has the
2631 * same effect as creating a new file while
2632 * MNT_UNKNOWNPERMISSIONS is set.
2634 * Do not modify bsdInfo for directory hard link records.
2635 * They are set during creation and are not modifiable, so just
2638 is_dirlink
= (file
->flags
& kHFSHasLinkChainMask
) &&
2639 (SWAP_BE32(file
->userInfo
.fdType
) == kHFSAliasType
) &&
2640 (SWAP_BE32(file
->userInfo
.fdCreator
) == kHFSAliasCreator
);
2642 if (!is_dirlink
&& ((file
->bsdInfo
.fileMode
!= 0) || (attrp
->ca_flags
!= 0) || (attrp
->ca_uid
!= hfsmp
->hfs_uid
) ||(attrp
->ca_gid
!= hfsmp
->hfs_gid
) ||
2643 ((attrp
->ca_mode
& ALLPERMS
) != (hfsmp
->hfs_file_mask
& ACCESSPERMS
))))
2645 if ((file
->bsdInfo
.fileMode
== 0) || (((HFSTOVFS(hfsmp
)->mnt_flag
) & MNT_UNKNOWNPERMISSIONS
) == 0))
2647 file
->bsdInfo
.ownerID
= attrp
->ca_uid
;
2648 file
->bsdInfo
.groupID
= attrp
->ca_gid
;
2650 file
->bsdInfo
.ownerFlags
= attrp
->ca_flags
& 0x000000FF;
2651 file
->bsdInfo
.adminFlags
= attrp
->ca_flags
>> 16;
2652 file
->bsdInfo
.fileMode
= attrp
->ca_mode
;
2654 if (state
->s_rsrcfork
) {
2655 forkp
= state
->s_rsrcfork
;
2656 file
->resourceFork
.logicalSize
= forkp
->cf_size
;
2657 file
->resourceFork
.totalBlocks
= forkp
->cf_blocks
;
2658 bcopy(&forkp
->cf_extents
[0], &file
->resourceFork
.extents
,
2659 sizeof(HFSPlusExtentRecord
));
2660 /* Push blocks read to disk */
2661 file
->resourceFork
.clumpSize
= (u_int32_t
) howmany(forkp
->cf_bytesread
, blksize
);
2663 if (state
->s_datafork
) {
2664 forkp
= state
->s_datafork
;
2665 file
->dataFork
.logicalSize
= forkp
->cf_size
;
2666 file
->dataFork
.totalBlocks
= forkp
->cf_blocks
;
2667 bcopy(&forkp
->cf_extents
[0], &file
->dataFork
.extents
,
2668 sizeof(HFSPlusExtentRecord
));
2669 /* Push blocks read to disk */
2670 file
->dataFork
.clumpSize
= (u_int32_t
) howmany(forkp
->cf_bytesread
, blksize
);
2673 if ((file
->resourceFork
.extents
[0].startBlock
!= 0) &&
2674 (file
->resourceFork
.extents
[0].startBlock
== file
->dataFork
.extents
[0].startBlock
))
2676 LFHFS_LOG(LEVEL_ERROR
, "catrec_update: rsrc fork == data fork");
2680 /* Synchronize the lock state */
2681 if (attrp
->ca_flags
& (SF_IMMUTABLE
| UF_IMMUTABLE
))
2682 file
->flags
|= kHFSFileLockedMask
;
2684 file
->flags
&= ~kHFSFileLockedMask
;
2686 /* Push out special field if necessary */
2687 if (S_ISBLK(attrp
->ca_mode
) || S_ISCHR(attrp
->ca_mode
))
2689 file
->bsdInfo
.special
.rawDevice
= attrp
->ca_rdev
;
2694 * Protect against the degenerate case where the descriptor contains the
2695 * raw inode ID in its CNID field. If the HFSPlusCatalogFile record indicates
2696 * the linkcount was greater than 1 (the default value), then it must have become
2697 * a hardlink. In this case, update the linkcount from the cat_attr passed in.
2699 if ((descp
->cd_cnid
!= attrp
->ca_fileid
) || (attrp
->ca_linkcount
> 1 ) || (file
->hl_linkCount
> 1))
2701 file
->hl_linkCount
= attrp
->ca_linkcount
;
2707 return (btNotFound
);
2713 * getkey - get a key from id by doing a thread lookup
2716 getkey(struct hfsmount
*hfsmp
, cnid_t cnid
, CatalogKey
* key
)
2718 FSBufferDescriptor btdata
;
2720 CatalogKey
* keyp
= NULL
;
2721 CatalogRecord
* recp
= NULL
;
2725 BTreeIterator
* iterator
= hfs_mallocz(sizeof(BTreeIterator
));
2726 if (iterator
== NULL
)
2728 result
= memFullErr
;
2731 buildthreadkey(cnid
, (CatalogKey
*)&iterator
->key
);
2733 recp
= hfs_mallocz(sizeof(CatalogRecord
));
2736 result
= memFullErr
;
2739 BDINIT(btdata
, recp
);
2741 result
= BTSearchRecord(VTOF(HFSTOVCB(hfsmp
)->catalogRefNum
), iterator
, &btdata
, &datasize
, iterator
);
2745 /* Turn thread record into a cnode key (in place) */
2746 switch (recp
->recordType
)
2748 case kHFSPlusFileThreadRecord
:
2749 case kHFSPlusFolderThreadRecord
:
2750 keyp
= (CatalogKey
*)&recp
->hfsPlusThread
.reserved
;
2751 keyp
->hfsPlus
.keyLength
= kHFSPlusCatalogKeyMinimumLength
+
2752 (keyp
->hfsPlus
.nodeName
.length
* 2);
2753 bcopy(keyp
, key
, keyp
->hfsPlus
.keyLength
+ 2);
2757 result
= cmNotFound
;
2765 return MacToVFSError(result
);
2769 * cat_update_internal - update the catalog node described by descp
2770 * using the data from attrp and forkp.
2771 * If update_hardlink is true, the hard link catalog record is updated
2772 * and not the inode catalog record.
2775 cat_update_internal(struct hfsmount
*hfsmp
, int update_hardlink
, struct cat_desc
*descp
, struct cat_attr
*attrp
,
2776 const struct cat_fork
*dataforkp
, const struct cat_fork
*rsrcforkp
)
2778 FCB
* fcb
= hfsmp
->hfs_catalog_cp
->c_datafork
;
2779 BTreeIterator
* iterator
;
2782 struct update_state state
;
2783 state
.s_desc
= descp
;
2784 state
.s_attr
= attrp
;
2785 state
.s_datafork
= dataforkp
;
2786 state
.s_rsrcfork
= rsrcforkp
;
2787 state
.s_hfsmp
= hfsmp
;
2789 /* Borrow the btcb iterator since we have an exclusive catalog lock. */
2790 iterator
= &((BTreeControlBlockPtr
)(fcb
->ff_sysfileinfo
))->iterator
;
2793 * For open-deleted files we need to do a lookup by cnid
2794 * (using thread rec).
2796 * For hard links and if not requested by caller, the target
2797 * of the update is the inode itself (not the link record)
2798 * so a lookup by fileid (i.e. thread rec) is needed.
2800 if ((update_hardlink
== false) &&
2801 ((descp
->cd_cnid
!= attrp
->ca_fileid
) ||
2802 (descp
->cd_namelen
== 0) ||
2803 (attrp
->ca_recflags
& kHFSHasLinkChainMask
)))
2805 result
= getkey(hfsmp
, attrp
->ca_fileid
, (CatalogKey
*)&iterator
->key
);
2809 result
= buildkey(descp
, (HFSPlusCatalogKey
*)&iterator
->key
);
2814 /* Pass a node hint */
2815 iterator
->hint
.nodeNum
= descp
->cd_hint
;
2817 result
= BTUpdateRecord(fcb
, iterator
, (IterateCallBackProcPtr
)catrec_update
, &state
);
2821 /* Update the node hint. */
2822 descp
->cd_hint
= iterator
->hint
.nodeNum
;
2825 (void) BTFlushPath(fcb
);
2827 return MacToVFSError(result
);
2831 * cat_update - update the catalog node described by descp
2832 * using the data from attrp and forkp.
2835 cat_update(struct hfsmount
*hfsmp
, struct cat_desc
*descp
, struct cat_attr
*attrp
,
2836 const struct cat_fork
*dataforkp
, const struct cat_fork
*rsrcforkp
)
2838 return cat_update_internal(hfsmp
, false, descp
, attrp
, dataforkp
, rsrcforkp
);
2842 * cat_delete - delete a node from the catalog
2844 * Order of B-tree operations:
2845 * 1. BTDeleteRecord(cnode);
2846 * 2. BTDeleteRecord(thread);
2847 * 3. BTUpdateRecord(parent);
2850 cat_delete(struct hfsmount
*hfsmp
, struct cat_desc
*descp
, struct cat_attr
*attrp
)
2852 FCB
* fcb
= hfsmp
->hfs_catalog_cp
->c_datafork
;
2853 BTreeIterator
*iterator
;
2859 * The root directory cannot be deleted
2860 * A directory must be empty
2861 * A file must be zero length (no blocks)
2863 if (descp
->cd_cnid
< kHFSFirstUserCatalogNodeID
|| descp
->cd_parentcnid
== kHFSRootParentID
)
2866 /* XXX Preflight Missing */
2868 /* Borrow the btcb iterator since we have an exclusive catalog lock. */
2869 iterator
= &((BTreeControlBlockPtr
)(fcb
->ff_sysfileinfo
))->iterator
;
2870 iterator
->hint
.nodeNum
= 0;
2873 * Derive a key from either the file ID (for a virtual inode)
2874 * or the descriptor.
2876 if (descp
->cd_namelen
== 0)
2878 result
= getkey(hfsmp
, attrp
->ca_fileid
, (CatalogKey
*)&iterator
->key
);
2879 cnid
= attrp
->ca_fileid
;
2883 result
= buildkey(descp
, (HFSPlusCatalogKey
*)&iterator
->key
);
2884 cnid
= descp
->cd_cnid
;
2890 result
= BTDeleteRecord(fcb
, iterator
);
2893 if (result
!= btNotFound
)
2896 struct cat_desc temp_desc
;
2898 /* Probably the node has mangled name */
2899 result
= cat_lookupmangled(hfsmp
, descp
, 0, &temp_desc
, attrp
, NULL
);
2903 /* The file has mangled name. Delete the file using full name */
2904 bzero(iterator
, sizeof(*iterator
));
2905 result
= buildkey(&temp_desc
, (HFSPlusCatalogKey
*)&iterator
->key
);
2906 cnid
= temp_desc
.cd_cnid
;
2909 cat_releasedesc(&temp_desc
);
2913 result
= BTDeleteRecord(fcb
, iterator
);
2916 cat_releasedesc(&temp_desc
);
2920 cat_releasedesc(&temp_desc
);
2923 /* Delete thread record. On error, mark volume inconsistent */
2924 buildthreadkey(cnid
, (CatalogKey
*)&iterator
->key
);
2925 if (BTDeleteRecord(fcb
, iterator
))
2927 LFHFS_LOG(LEVEL_ERROR
, "cat_delete: failed to delete thread record id=%u on vol=%s\n", cnid
, hfsmp
->vcbVN
);
2928 hfs_mark_inconsistent(hfsmp
, HFS_OP_INCOMPLETE
);
2932 (void) BTFlushPath(fcb
);
2934 return MacToVFSError(result
);
2938 * buildrecord - build a default catalog directory or file record
2941 buildrecord(struct cat_attr
*attrp
, cnid_t cnid
, u_int32_t encoding
, CatalogRecord
*crp
, u_int32_t
*recordSize
)
2943 int type
= attrp
->ca_mode
& S_IFMT
;
2944 u_int32_t createtime
= to_hfs_time(attrp
->ca_itime
);
2946 struct HFSPlusBSDInfo
* bsdp
= NULL
;
2948 if (type
== S_IFDIR
)
2950 crp
->recordType
= kHFSPlusFolderRecord
;
2951 crp
->hfsPlusFolder
.flags
= attrp
->ca_recflags
;
2952 crp
->hfsPlusFolder
.valence
= 0;
2953 crp
->hfsPlusFolder
.folderID
= cnid
;
2954 crp
->hfsPlusFolder
.createDate
= createtime
;
2955 crp
->hfsPlusFolder
.contentModDate
= createtime
;
2956 crp
->hfsPlusFolder
.attributeModDate
= createtime
;
2957 crp
->hfsPlusFolder
.accessDate
= createtime
;
2958 crp
->hfsPlusFolder
.backupDate
= 0;
2959 crp
->hfsPlusFolder
.textEncoding
= encoding
;
2960 crp
->hfsPlusFolder
.folderCount
= 0;
2961 bcopy(attrp
->ca_finderinfo
, &crp
->hfsPlusFolder
.userInfo
, 32);
2962 bsdp
= &crp
->hfsPlusFolder
.bsdInfo
;
2963 bsdp
->special
.linkCount
= 1;
2964 *recordSize
= sizeof(HFSPlusCatalogFolder
);
2968 crp
->recordType
= kHFSPlusFileRecord
;
2969 crp
->hfsPlusFile
.flags
= attrp
->ca_recflags
;
2970 crp
->hfsPlusFile
.reserved1
= 0;
2971 crp
->hfsPlusFile
.fileID
= cnid
;
2972 crp
->hfsPlusFile
.createDate
= createtime
;
2973 crp
->hfsPlusFile
.contentModDate
= createtime
;
2974 crp
->hfsPlusFile
.accessDate
= createtime
;
2975 crp
->hfsPlusFile
.attributeModDate
= createtime
;
2976 crp
->hfsPlusFile
.backupDate
= 0;
2977 crp
->hfsPlusFile
.textEncoding
= encoding
;
2978 crp
->hfsPlusFile
.reserved2
= 0;
2979 bcopy(attrp
->ca_finderinfo
, &crp
->hfsPlusFile
.userInfo
, 32);
2980 bsdp
= &crp
->hfsPlusFile
.bsdInfo
;
2981 /* BLK/CHR need to save the device info */
2982 if (type
== S_IFBLK
|| type
== S_IFCHR
)
2984 bsdp
->special
.rawDevice
= attrp
->ca_rdev
;
2986 bsdp
->special
.linkCount
= 1;
2988 bzero(&crp
->hfsPlusFile
.dataFork
, 2*sizeof(HFSPlusForkData
));
2989 *recordSize
= sizeof(HFSPlusCatalogFile
);
2991 bsdp
->ownerID
= attrp
->ca_uid
;
2992 bsdp
->groupID
= attrp
->ca_gid
;
2993 bsdp
->fileMode
= attrp
->ca_mode
;
2994 bsdp
->adminFlags
= attrp
->ca_flags
>> 16;
2995 bsdp
->ownerFlags
= attrp
->ca_flags
& 0x000000FF;
3000 * cat_create - create a node in the catalog
3001 * using MacRoman encoding
3003 * NOTE: both the catalog file and attribute file locks must
3004 * be held before calling this function.
3006 * The caller is responsible for releasing the output
3007 * catalog descriptor (when supplied outdescp is non-null).
3010 cat_create(struct hfsmount
*hfsmp
, cnid_t new_fileid
, struct cat_desc
*descp
, struct cat_attr
*attrp
, struct cat_desc
*out_descp
)
3014 FCB
* fcb
= hfsmp
->hfs_catalog_cp
->c_datafork
;
3015 BTreeIterator
* iterator
= NULL
;
3016 HFSPlusCatalogKey
* key
= NULL
;
3017 CatalogRecord
* data
= NULL
;
3018 FSBufferDescriptor btdata
= {0};
3020 u_int32_t encoding
= kTextEncodingMacRoman
;
3022 /* The caller is expected to reserve a CNID before calling this-> function! */
3024 /* Get space for iterator, key and data */
3025 iterator
= hfs_mallocz(sizeof(BTreeIterator
));
3026 key
= hfs_mallocz(sizeof(HFSPlusCatalogKey
));
3027 data
= hfs_mallocz(sizeof(CatalogRecord
));
3029 if ( (iterator
== NULL
) || (key
== NULL
) || (data
== NULL
) )
3035 result
= buildkey(descp
, key
);
3040 * Insert the thread record first
3042 datalen
= buildthread((void*)key
, data
, S_ISDIR(attrp
->ca_mode
));
3043 btdata
.bufferAddress
= data
;
3044 btdata
.itemSize
= datalen
;
3045 btdata
.itemCount
= 1;
3047 /* Caller asserts the following:
3048 * 1) this CNID is not in use by any orphaned EAs
3049 * 2) There are no lingering cnodes (removed on-disk but still in-core) with this CNID
3050 * 3) There are no thread or catalog records for this ID
3052 buildthreadkey(new_fileid
, (CatalogKey
*) &iterator
->key
);
3053 result
= BTInsertRecord(fcb
, iterator
, &btdata
, datalen
);
3060 * Now insert the file/directory record
3062 buildrecord(attrp
, new_fileid
, encoding
, data
, &datalen
);
3063 btdata
.bufferAddress
= data
;
3064 btdata
.itemSize
= datalen
;
3065 btdata
.itemCount
= 1;
3067 bcopy(key
, &iterator
->key
, sizeof(HFSPlusCatalogKey
));
3069 result
= BTInsertRecord(fcb
, iterator
, &btdata
, datalen
);
3072 if (result
== btExists
)
3075 /* Back out the thread record */
3076 buildthreadkey(new_fileid
, (CatalogKey
*)&iterator
->key
);
3077 if (BTDeleteRecord(fcb
, iterator
))
3079 /* Error on deleting extra thread record, mark
3080 * volume inconsistent
3082 LFHFS_LOG(LEVEL_ERROR
, "cat_create() failed to delete thread record id=%u on vol=%s\n", new_fileid
, hfsmp
->vcbVN
);
3083 hfs_mark_inconsistent(hfsmp
, HFS_ROLLBACK_FAILED
);
3090 * Insert was successful, update name, parent and volume
3092 if (out_descp
!= NULL
)
3094 HFSPlusCatalogKey
* pluskey
= NULL
;
3096 pluskey
= (HFSPlusCatalogKey
*)&iterator
->key
;
3098 builddesc(pluskey
, new_fileid
, iterator
->hint
.nodeNum
, encoding
, S_ISDIR(attrp
->ca_mode
), out_descp
);
3100 attrp
->ca_fileid
= new_fileid
;
3103 (void) BTFlushPath(fcb
);
3111 return MacToVFSError(result
);
3114 /* This function sets kHFSHasChildLinkBit in a directory hierarchy in the
3115 * catalog btree of given cnid by walking up the parent chain till it reaches
3116 * either the root folder, or the private metadata directory for storing
3117 * directory hard links. This function updates the corresponding in-core
3118 * cnode, if any, and the directory record in the catalog btree.
3119 * On success, returns zero. On failure, returns non-zero value.
3122 cat_set_childlinkbit(struct hfsmount
*hfsmp
, cnid_t cnid
)
3126 struct cat_desc desc
;
3127 struct cat_attr attr
= {0};
3129 while ((cnid
!= kHFSRootFolderID
) && (cnid
!= kHFSRootParentID
) &&
3130 (cnid
!= hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
)) {
3131 /* Update the bit in corresponding cnode, if any, in the hash.
3132 * If the cnode has the bit already set, stop the traversal.
3134 retval
= hfs_chash_set_childlinkbit(hfsmp
, cnid
);
3139 /* Update the catalog record on disk if either cnode was not
3140 * found in the hash, or if a cnode was found and the cnode
3141 * did not have the bit set previously.
3143 retval
= hfs_start_transaction(hfsmp
);
3147 lockflags
= hfs_systemfile_lock(hfsmp
, SFL_CATALOG
, HFS_EXCLUSIVE_LOCK
);
3149 /* Look up our catalog folder record */
3150 retval
= cat_idlookup(hfsmp
, cnid
, 0, 0, &desc
, &attr
, NULL
);
3152 hfs_systemfile_unlock(hfsmp
, lockflags
);
3153 hfs_end_transaction(hfsmp
);
3157 /* Update the bit in the catalog record */
3158 attr
.ca_recflags
|= kHFSHasChildLinkMask
;
3159 retval
= cat_update(hfsmp
, &desc
, &attr
, NULL
, NULL
);
3161 hfs_systemfile_unlock(hfsmp
, lockflags
);
3162 hfs_end_transaction(hfsmp
);
3163 cat_releasedesc(&desc
);
3167 hfs_systemfile_unlock(hfsmp
, lockflags
);
3168 hfs_end_transaction(hfsmp
);
3170 cnid
= desc
.cd_parentcnid
;
3171 cat_releasedesc(&desc
);
3177 /* This function traverses the parent directory hierarchy from the given
3178 * directory to one level below root directory and checks if any of its
3180 * 1. A directory hard link.
3181 * 2. The 'pointed at' directory.
3182 * If any of these conditions fail or an internal error is encountered
3183 * during look up of the catalog record, this function returns non-zero value.
3186 cat_check_link_ancestry(struct hfsmount
*hfsmp
, cnid_t cnid
, cnid_t pointed_at_cnid
)
3188 FSBufferDescriptor btdata
;
3189 HFSPlusCatalogFolder folder
;
3193 BDINIT(btdata
, &folder
);
3194 BTreeIterator
* ip
= hfs_mallocz(sizeof(BTreeIterator
));
3198 HFSPlusCatalogKey
* keyp
= (HFSPlusCatalogKey
*)&ip
->key
;
3199 FCB
*fcb
= hfsmp
->hfs_catalog_cp
->c_datafork
;
3201 while (cnid
!= kHFSRootParentID
)
3203 /* Check if the 'pointed at' directory is an ancestor */
3204 if (pointed_at_cnid
== cnid
)
3209 if ((result
= getkey(hfsmp
, cnid
, (CatalogKey
*)keyp
))) {
3210 LFHFS_LOG(LEVEL_ERROR
, "cat_check_link_ancestry: getkey failed id=%u, vol=%s\n", cnid
, hfsmp
->vcbVN
);
3211 invalid
= 1; /* On errors, assume an invalid parent */
3214 if ((result
= BTSearchRecord(fcb
, ip
, &btdata
, NULL
, NULL
))) {
3215 LFHFS_LOG(LEVEL_ERROR
, "cat_check_link_ancestry: cannot find id=%u, vol=%s\n", cnid
, hfsmp
->vcbVN
);
3216 invalid
= 1; /* On errors, assume an invalid parent */
3219 /* Check if this ancestor is a directory hard link */
3220 if (folder
.flags
& kHFSHasLinkChainMask
) {
3224 cnid
= keyp
->parentID
;
3232 // --------------------------------------- Hard Link Support ---------------------------------------------
3236 * Resolve hard link reference to obtain the inode record.
3239 cat_resolvelink(struct hfsmount
*hfsmp
, u_int32_t linkref
, int isdirlink
, struct HFSPlusCatalogFile
*recp
)
3241 FSBufferDescriptor btdata
;
3242 BTreeIterator
*iterator
;
3243 struct cat_desc idesc
;
3248 BDINIT(btdata
, recp
);
3251 MAKE_DIRINODE_NAME(inodename
, sizeof(inodename
), (unsigned int)linkref
);
3252 parentcnid
= hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
;
3254 MAKE_INODE_NAME(inodename
, sizeof(inodename
), (unsigned int)linkref
);
3255 parentcnid
= hfsmp
->hfs_private_desc
[FILE_HARDLINKS
].cd_cnid
;
3258 /* Get space for iterator */
3259 iterator
= hfs_mallocz(sizeof(BTreeIterator
));
3260 if (iterator
== NULL
)
3265 /* Build a descriptor for private dir. */
3266 idesc
.cd_parentcnid
= parentcnid
;
3267 idesc
.cd_nameptr
= (const u_int8_t
*)inodename
;
3268 idesc
.cd_namelen
= strlen(inodename
);
3271 idesc
.cd_encoding
= 0;
3272 (void) buildkey(&idesc
, (HFSPlusCatalogKey
*)&iterator
->key
);
3274 result
= BTSearchRecord(VTOF(HFSTOVCB(hfsmp
)->catalogRefNum
), iterator
,&btdata
, NULL
, NULL
);
3277 /* Make sure there's a reference */
3278 if (recp
->hl_linkCount
== 0)
3279 recp
->hl_linkCount
= 2;
3281 LFHFS_LOG(LEVEL_ERROR
, "cat_resolvelink: can't find inode=%s on vol=%s\n", inodename
, hfsmp
->vcbVN
);
3286 return (result
? ENOENT
: 0);
3290 * Resolve hard link reference to obtain the inode number.
3293 resolvelinkid(struct hfsmount
*hfsmp
, u_int32_t linkref
, ino_t
*ino
)
3295 struct HFSPlusCatalogFile record
;
3299 * Since we know resolvelinkid is only called from
3300 * cat_getdirentries, we can assume that only file
3301 * hardlinks need to be resolved (cat_getdirentries
3302 * can resolve directory hardlinks in place).
3304 error
= cat_resolvelink(hfsmp
, linkref
, 0, &record
);
3306 if (record
.fileID
== 0)
3309 *ino
= record
.fileID
;
3316 * cat_lookup_lastlink - find the last sibling link in the chain (no "next" ptr)
3319 cat_lookup_lastlink(struct hfsmount
*hfsmp
, cnid_t linkfileid
, cnid_t
*lastlink
, struct cat_desc
*cdesc
)
3322 BTreeIterator
* iterator
;
3323 FSBufferDescriptor btdata
= {0};
3324 struct HFSPlusCatalogFile file
;
3328 cnid_t currentlink
= linkfileid
;
3330 fcb
= hfsmp
->hfs_catalog_cp
->c_datafork
;
3332 /* Create an iterator for use by us temporarily */
3333 iterator
= hfs_mallocz(sizeof(*iterator
));
3334 if (iterator
== NULL
)
3337 while ((foundlast
== 0) && (itercount
< HFS_LINK_MAX
)) {
3339 bzero(iterator
, sizeof(*iterator
));
3341 if ((result
= getkey(hfsmp
, currentlink
, (CatalogKey
*)&iterator
->key
))) {
3344 BDINIT(btdata
, &file
);
3346 if ((result
= BTSearchRecord(fcb
, iterator
, &btdata
, NULL
, NULL
))) {
3350 /* The prev/next chain is only valid when kHFSHasLinkChainMask is set. */
3351 if (file
.flags
& kHFSHasLinkChainMask
) {
3354 parent
= ((HFSPlusCatalogKey
*)&iterator
->key
)->parentID
;
3356 * The raw inode for a directory hardlink doesn't have a chain.
3357 * Its link information lives in an EA.
3359 if (parent
== hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
) {
3360 /* We don't iterate to find the oldest directory hardlink. */
3364 else if (parent
== hfsmp
->hfs_private_desc
[FILE_HARDLINKS
].cd_cnid
) {
3365 /* Raw inode for file hardlink (the base inode) */
3366 currentlink
= file
.hl_firstLinkID
;
3369 * One minor special-casing here is necessary.
3370 * If our ID brought us to the raw hardlink inode, and it does
3371 * not have any siblings, then it's an open-unlinked file, and we
3372 * should not proceed any further.
3374 if (currentlink
== 0) {
3380 /* Otherwise, this item's parent is a legitimate directory in the namespace */
3381 if (file
.hl_nextLinkID
== 0) {
3382 /* If nextLinkID is 0, then we found the end; no more hardlinks */
3384 *lastlink
= currentlink
;
3386 * Since we had to construct a catalog key to do this lookup
3387 * we still hold it in-hand. We might as well use it to build
3388 * the descriptor that the caller asked for.
3390 builddesc ((HFSPlusCatalogKey
*)&iterator
->key
, currentlink
, 0, 0, 0, cdesc
);
3394 currentlink
= file
.hl_nextLinkID
;
3398 /* Sorry, can't help you without a link chain */
3404 /* If we didn't find what we were looking for, zero out the args */
3405 if (foundlast
== 0) {
3407 bzero (cdesc
, sizeof(struct cat_desc
));
3415 return MacToVFSError(result
);
3419 * cat_lookuplink - lookup a link by it's name
3422 cat_lookuplink(struct hfsmount
*hfsmp
, struct cat_desc
*descp
, cnid_t
*linkfileid
, cnid_t
*prevlinkid
, cnid_t
*nextlinkid
)
3425 BTreeIterator
* iterator
;
3426 FSBufferDescriptor btdata
;
3427 struct HFSPlusCatalogFile file
;
3430 fcb
= hfsmp
->hfs_catalog_cp
->c_datafork
;
3432 /* Create an iterator for use by us temporarily */
3433 iterator
= hfs_mallocz(sizeof(*iterator
));
3434 if (iterator
== NULL
)
3437 if ((result
= buildkey(descp
, (HFSPlusCatalogKey
*)&iterator
->key
))) {
3440 BDINIT(btdata
, &file
);
3442 if ((result
= BTSearchRecord(fcb
, iterator
, &btdata
, NULL
, NULL
))) {
3445 if (file
.recordType
!= kHFSPlusFileRecord
) {
3449 *linkfileid
= file
.fileID
;
3451 if (file
.flags
& kHFSHasLinkChainMask
) {
3452 *prevlinkid
= file
.hl_prevLinkID
;
3453 *nextlinkid
= file
.hl_nextLinkID
;
3460 return MacToVFSError(result
);
3464 * cat_deletelink - delete a link from the catalog
3467 cat_deletelink(struct hfsmount
*hfsmp
, struct cat_desc
*descp
)
3469 struct HFSPlusCatalogFile file
= {0};
3470 struct cat_attr cattr
= {0};
3471 uint32_t totalBlocks
;
3474 cattr
.ca_fileid
= descp
->cd_cnid
;
3476 /* Directory links have alias content to remove. */
3477 if (descp
->cd_flags
& CD_ISDIR
) {
3479 BTreeIterator
* iterator
;
3480 FSBufferDescriptor btdata
;
3482 fcb
= hfsmp
->hfs_catalog_cp
->c_datafork
;
3484 /* Borrow the btcb iterator since we have an exclusive catalog lock. */
3485 iterator
= &((BTreeControlBlockPtr
)(fcb
->ff_sysfileinfo
))->iterator
;
3486 iterator
->hint
.nodeNum
= 0;
3488 if ((result
= buildkey(descp
, (HFSPlusCatalogKey
*)&iterator
->key
))) {
3491 BDINIT(btdata
, &file
);
3493 if ((result
= BTSearchRecord(fcb
, iterator
, &btdata
, NULL
, NULL
))) {
3498 result
= cat_delete(hfsmp
, descp
, &cattr
);
3500 if ((result
== 0) &&
3501 (descp
->cd_flags
& CD_ISDIR
) &&
3502 (file
.recordType
== kHFSPlusFileRecord
)) {
3504 totalBlocks
= file
.resourceFork
.totalBlocks
;
3506 for (int i
= 0; (i
< 8) && (totalBlocks
> 0); i
++) {
3507 if ((file
.resourceFork
.extents
[i
].blockCount
== 0) &&
3508 (file
.resourceFork
.extents
[i
].startBlock
== 0)) {
3512 (void) BlockDeallocate(hfsmp
,file
.resourceFork
.extents
[i
].startBlock
,file
.resourceFork
.extents
[i
].blockCount
, 0);
3514 totalBlocks
-= file
.resourceFork
.extents
[i
].blockCount
;
3515 file
.resourceFork
.extents
[i
].startBlock
= 0;
3516 file
.resourceFork
.extents
[i
].blockCount
= 0;
3524 * update_siblinglinks_callback - update a link's chain
3527 struct linkupdate_state
{
3534 update_siblinglinks_callback(__unused
const CatalogKey
*ckp
, CatalogRecord
*crp
, struct linkupdate_state
*state
)
3536 HFSPlusCatalogFile
*file
;
3538 if (crp
->recordType
!= kHFSPlusFileRecord
) {
3539 LFHFS_LOG(LEVEL_ERROR
, "update_siblinglinks_callback: unexpected rec type %d\n", crp
->recordType
);
3540 return (btNotFound
);
3543 file
= (struct HFSPlusCatalogFile
*)crp
;
3544 if (file
->flags
& kHFSHasLinkChainMask
) {
3545 if (state
->prevlinkid
!= HFS_IGNORABLE_LINK
) {
3546 file
->hl_prevLinkID
= state
->prevlinkid
;
3548 if (state
->nextlinkid
!= HFS_IGNORABLE_LINK
) {
3549 file
->hl_nextLinkID
= state
->nextlinkid
;
3552 LFHFS_LOG(LEVEL_ERROR
, "update_siblinglinks_callback: file %d isn't a chain\n", file
->fileID
);
3558 * cat_update_siblinglinks - update a link's chain
3561 cat_update_siblinglinks(struct hfsmount
*hfsmp
, cnid_t linkfileid
, cnid_t prevlinkid
, cnid_t nextlinkid
)
3564 BTreeIterator
* iterator
;
3565 struct linkupdate_state state
;
3568 fcb
= hfsmp
->hfs_catalog_cp
->c_datafork
;
3569 state
.filelinkid
= linkfileid
;
3570 state
.prevlinkid
= prevlinkid
;
3571 state
.nextlinkid
= nextlinkid
;
3573 /* Create an iterator for use by us temporarily */
3574 iterator
= hfs_mallocz(sizeof(*iterator
));
3575 if (iterator
== NULL
)
3578 result
= getkey(hfsmp
, linkfileid
, (CatalogKey
*)&iterator
->key
);
3580 result
= BTUpdateRecord(fcb
, iterator
, (IterateCallBackProcPtr
)update_siblinglinks_callback
, &state
);
3581 (void) BTFlushPath(fcb
);
3583 LFHFS_LOG(LEVEL_ERROR
, "cat_update_siblinglinks: couldn't resolve cnid=%d, vol=%s\n", linkfileid
, hfsmp
->vcbVN
);
3587 return MacToVFSError(result
);
3592 struct hfsmount
*hfsmp
,
3593 CatalogRecord
* recp
,
3594 struct cat_attr
*attrp
,
3595 struct cat_fork
*datafp
,
3596 struct cat_fork
*rsrcfp
)
3598 getbsdattr(hfsmp
, (struct HFSPlusCatalogFile
*)recp
, attrp
);
3602 bzero(datafp
, sizeof(*datafp
));
3604 /* Convert the data fork. */
3605 datafp
->cf_size
= recp
->hfsPlusFile
.dataFork
.logicalSize
;
3606 datafp
->cf_new_size
= 0;
3607 datafp
->cf_blocks
= recp
->hfsPlusFile
.dataFork
.totalBlocks
;
3608 datafp
->cf_bytesread
= 0;
3609 datafp
->cf_vblocks
= 0;
3610 bcopy(&recp
->hfsPlusFile
.dataFork
.extents
[0],
3611 &datafp
->cf_extents
[0], sizeof(HFSPlusExtentRecord
));
3613 /* Convert the resource fork. */
3614 rsrcfp
->cf_size
= recp
->hfsPlusFile
.resourceFork
.logicalSize
;
3615 rsrcfp
->cf_new_size
= 0;
3616 rsrcfp
->cf_blocks
= recp
->hfsPlusFile
.resourceFork
.totalBlocks
;
3617 datafp
->cf_bytesread
= 0;
3618 rsrcfp
->cf_vblocks
= 0;
3619 bcopy(&recp
->hfsPlusFile
.resourceFork
.extents
[0],
3620 &rsrcfp
->cf_extents
[0], sizeof(HFSPlusExtentRecord
));
3624 /* Create and write an alias that points at the directory represented by given
3625 * inode number on the same volume. Directory hard links are visible as
3626 * aliases in pre-Leopard systems and this function creates these aliases.
3628 * Note: This code is very specific to creating alias for the purpose
3629 * of directory hard links only, and should not be generalized.
3632 cat_makealias(struct hfsmount
*hfsmp
, u_int32_t inode_num
, struct HFSPlusCatalogFile
*crp
)
3634 GenericLFBufPtr bp
= NULL
;
3640 HFSPlusForkData
*rsrcforkp
;
3644 rsrcforkp
= &(crp
->resourceFork
);
3646 blksize
= hfsmp
->blockSize
;
3647 blkcount
= howmany(kHFSAliasSize
, blksize
);
3648 sectorsize
= hfsmp
->hfs_logical_block_size
;
3649 bzero(rsrcforkp
, sizeof(HFSPlusForkData
));
3651 /* Allocate some disk space for the alias content. */
3652 result
= BlockAllocate(hfsmp
, 0, blkcount
, blkcount
,
3653 HFS_ALLOC_FORCECONTIG
| HFS_ALLOC_METAZONE
,
3654 &rsrcforkp
->extents
[0].startBlock
,
3655 &rsrcforkp
->extents
[0].blockCount
);
3656 /* Did it fail with an out of space error? If so, re-try and allow journal flushing. */
3657 if (result
== dskFulErr
) {
3658 result
= BlockAllocate(hfsmp
, 0, blkcount
, blkcount
,
3659 HFS_ALLOC_FORCECONTIG
| HFS_ALLOC_METAZONE
| HFS_ALLOC_FLUSHTXN
,
3660 &rsrcforkp
->extents
[0].startBlock
,
3661 &rsrcforkp
->extents
[0].blockCount
);
3665 rsrcforkp
->extents
[0].startBlock
= 0;
3669 /* Acquire a buffer cache block for our block. */
3670 blkno
= ((u_int64_t
)rsrcforkp
->extents
[0].startBlock
* (u_int64_t
)blksize
) / sectorsize
;
3671 blkno
+= hfsmp
->hfsPlusIOPosOffset
/ sectorsize
;
3673 bp
= lf_hfs_generic_buf_allocate( hfsmp
->hfs_devvp
, blkno
, roundup(kHFSAliasSize
, hfsmp
->hfs_logical_block_size
), 0);
3674 result
= lf_hfs_generic_buf_read(bp
);
3680 journal_modify_block_start(hfsmp
->jnl
, bp
);
3683 /* Generate alias content */
3684 alias
= (char *)bp
->pvData
;
3685 bzero(alias
, bp
->uDataSize
);
3686 bcopy(hfs_dirlink_alias_rsrc
, alias
, kHFSAliasSize
);
3688 /* Set the volume create date, local time in Mac OS format */
3689 valptr
= (uint32_t *)(alias
+ kHFSAliasVolCreateDateOffset
);
3690 *valptr
= OSSwapHostToBigInt32(hfsmp
->localCreateDate
);
3692 /* Set id of the parent of the target directory */
3693 valptr
= (uint32_t *)(alias
+ kHFSAliasParentIDOffset
);
3694 *valptr
= OSSwapHostToBigInt32(hfsmp
->hfs_private_desc
[DIR_HARDLINKS
].cd_cnid
);
3696 /* Set id of the target directory */
3697 valptr
= (uint32_t *)(alias
+ kHFSAliasTargetIDOffset
);
3698 *valptr
= OSSwapHostToBigInt32(inode_num
);
3700 /* Write alias content to disk. */
3702 journal_modify_block_end(hfsmp
->jnl
, bp
, NULL
, NULL
);
3705 if ((result
= lf_hfs_generic_buf_write(bp
))) {
3709 /* Finish initializing the fork data. */
3710 rsrcforkp
->logicalSize
= kHFSAliasSize
;
3711 rsrcforkp
->totalBlocks
= rsrcforkp
->extents
[0].blockCount
;
3715 lf_hfs_generic_buf_release(bp
);
3718 if (result
&& rsrcforkp
->extents
[0].startBlock
!= 0) {
3719 (void) BlockDeallocate(hfsmp
, rsrcforkp
->extents
[0].startBlock
, rsrcforkp
->extents
[0].blockCount
, 0);
3720 rsrcforkp
->extents
[0].startBlock
= 0;
3721 rsrcforkp
->extents
[0].blockCount
= 0;
3722 rsrcforkp
->logicalSize
= 0;
3723 rsrcforkp
->totalBlocks
= 0;
3729 * cat_createlink - create a link in the catalog
3731 * The following cat_attr fields are expected to be set:
3737 * ca_finderinfo (type and creator)
3740 cat_createlink(struct hfsmount
*hfsmp
, struct cat_desc
*descp
, struct cat_attr
*attrp
, cnid_t nextlinkid
, cnid_t
*linkfileid
)
3744 FSBufferDescriptor btdata
;
3745 HFSPlusForkData
*rsrcforkp
;
3748 int thread_inserted
= 0;
3749 int alias_allocated
= 0;
3752 fcb
= hfsmp
->hfs_catalog_cp
->c_datafork
;
3755 * Get the next CNID. Note that we are currently holding catalog lock.
3757 result
= cat_acquire_cnid(hfsmp
, &nextCNID
);
3762 /* Get space for iterator, key and data */
3763 bto
= hfs_malloc(sizeof(struct btobj
));
3764 bto
->iterator
.hint
.nodeNum
= 0;
3765 rsrcforkp
= &bto
->data
.hfsPlusFile
.resourceFork
;
3767 result
= buildkey(descp
, &bto
->key
);
3769 LFHFS_LOG(LEVEL_ERROR
, "cat_createlink: err %d from buildkey\n", result
);
3774 * Insert the thread record first.
3776 datalen
= buildthread((void*)&bto
->key
, &bto
->data
, 0);
3777 btdata
.bufferAddress
= &bto
->data
;
3778 btdata
.itemSize
= datalen
;
3779 btdata
.itemCount
= 1;
3781 buildthreadkey(nextCNID
, (CatalogKey
*) &bto
->iterator
.key
);
3782 result
= BTInsertRecord(fcb
, &bto
->iterator
, &btdata
, datalen
);
3786 thread_inserted
= 1;
3789 * Now insert the link record.
3791 buildrecord(attrp
, nextCNID
, kTextEncodingMacUnicode
, &bto
->data
, &datalen
);
3793 bto
->data
.hfsPlusFile
.hl_prevLinkID
= 0;
3794 bto
->data
.hfsPlusFile
.hl_nextLinkID
= nextlinkid
;
3795 bto
->data
.hfsPlusFile
.hl_linkReference
= attrp
->ca_linkref
;
3797 /* For directory hard links, create alias in resource fork */
3798 if (descp
->cd_flags
& CD_ISDIR
) {
3799 if ((result
= cat_makealias(hfsmp
, attrp
->ca_linkref
, &bto
->data
.hfsPlusFile
))) {
3802 alias_allocated
= 1;
3804 btdata
.bufferAddress
= &bto
->data
;
3805 btdata
.itemSize
= datalen
;
3806 btdata
.itemCount
= 1;
3808 bcopy(&bto
->key
, &bto
->iterator
.key
, sizeof(bto
->key
));
3810 result
= BTInsertRecord(fcb
, &bto
->iterator
, &btdata
, datalen
);
3812 if (result
== btExists
)
3816 if (linkfileid
!= NULL
) {
3817 *linkfileid
= nextCNID
;
3821 if (thread_inserted
) {
3822 LFHFS_LOG(LEVEL_ERROR
, "cat_createlink: BTInsertRecord err=%d, vol=%s\n", MacToVFSError(result
), hfsmp
->vcbVN
);
3824 buildthreadkey(nextCNID
, (CatalogKey
*)&bto
->iterator
.key
);
3825 if (BTDeleteRecord(fcb
, &bto
->iterator
)) {
3826 LFHFS_LOG(LEVEL_ERROR
, "cat_createlink: failed to delete thread record on volume %s\n", hfsmp
->vcbVN
);
3827 hfs_mark_inconsistent(hfsmp
, HFS_ROLLBACK_FAILED
);
3830 if (alias_allocated
&& rsrcforkp
->extents
[0].startBlock
!= 0) {
3831 (void) BlockDeallocate(hfsmp
, rsrcforkp
->extents
[0].startBlock
,
3832 rsrcforkp
->extents
[0].blockCount
, 0);
3833 rsrcforkp
->extents
[0].startBlock
= 0;
3834 rsrcforkp
->extents
[0].blockCount
= 0;
3837 (void) BTFlushPath(fcb
);
3840 return MacToVFSError(result
);