]> git.saurik.com Git - apple/hfs.git/blob - livefiles_hfs_plugin/lf_hfs_catalog.c
hfs-522.0.9.tar.gz
[apple/hfs.git] / livefiles_hfs_plugin / lf_hfs_catalog.c
1 //
2 // lf_hfs_catalog.c
3 // hfs
4 //
5 // Created by Yakov Ben Zaken on 22/03/2018.
6 //
7
8 #include "lf_hfs.h"
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"
16 #include <sys/stat.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"
29
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 */
33
34 #define SMALL_DIRENTRY_SIZE (UVFS_DIRENTRY_RECLEN(1))
35
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,
41 0, 0, 0, 0
42 };
43 #define MODE_TO_TYPE(mode) (modetodirtype[((mode) & S_IFMT) >> 12])
44
45 /*
46 * Initialization of an FSBufferDescriptor structure.
47 */
48 #define BDINIT(bd, addr) { \
49 (bd).bufferAddress = (addr); \
50 (bd).itemSize = sizeof(*(addr)); \
51 (bd).itemCount = 1; \
52 }
53
54 /* HFS ID Hashtable Functions */
55 #define IDHASH(hfsmp, inum) (&hfsmp->hfs_idhashtbl[(inum) & hfsmp->hfs_idhash])
56
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);
61
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. */
66 struct linkinfo {
67 u_int32_t link_ref;
68 caddr_t dirent_addr;
69 };
70 typedef struct linkinfo linkinfo_t;
71
72 struct btobj {
73 BTreeIterator iterator;
74 HFSPlusCatalogKey key;
75 CatalogRecord data;
76 };
77
78 /* Constants for directory hard link alias */
79 enum {
80 /* Size of resource fork data array for directory hard link alias */
81 kHFSAliasSize = 0x1d0,
82
83 /* Volume type for ejectable devices like disk image */
84 kHFSAliasVolTypeEjectable = 0x5,
85
86 /* Offset for volume create date, in Mac OS local time */
87 kHFSAliasVolCreateDateOffset = 0x12a,
88
89 /* Offset for the type of volume */
90 kHFSAliasVolTypeOffset = 0x130,
91
92 /* Offset for folder ID of the parent directory of the directory inode */
93 kHFSAliasParentIDOffset = 0x132,
94
95 /* Offset for folder ID of the directory inode */
96 kHFSAliasTargetIDOffset = 0x176,
97 };
98
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.
105 */
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
136 };
137 // -------------------------------------------------------------------------------------------------------
138
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;
143 u_int32_t cbs_index;
144 ReadDirBuff_t cbs_psReadDirBuffer;
145 ExtendedVCB * cbs_hfsmp;
146 int cbs_result;
147 int32_t cbs_nlinks;
148 int32_t cbs_maxlinks;
149 linkinfo_t * cbs_linkinfo;
150 struct cat_desc * cbs_desc;
151 u_int8_t * cbs_namebuf;
152 /*
153 * The following fields are only used for NFS readdir, which
154 * uses the next file id as the seek offset of each entry.
155 */
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;
162 Boolean cbs_eof;
163 };
164
165 struct position_state {
166 int error;
167 u_int32_t count;
168 u_int32_t index;
169 u_int32_t parentID;
170 struct hfsmount *hfsmp;
171 };
172 /* Initialize the HFS ID hash table */
173 void
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);
177 }
178
179 /* Free the HFS ID hash table */
180 void
181 hfs_idhash_destroy (struct hfsmount *hfsmp) {
182 /* during failed mounts & unmounts */
183 hashDeinit(hfsmp->hfs_idhashtbl);
184 }
185
186 /*
187 * Compare two HFS+ catalog keys
188 *
189 * Result: +n search key > trial key
190 * 0 search key = trial key
191 * -n search key < trial key
192 */
193 int
194 CompareExtendedCatalogKeys(HFSPlusCatalogKey *searchKey, HFSPlusCatalogKey *trialKey)
195 {
196 cnid_t searchParentID, trialParentID;
197 int result;
198
199 searchParentID = searchKey->parentID;
200 trialParentID = trialKey->parentID;
201
202 if (searchParentID > trialParentID) {
203 result = 1;
204 }
205 else if (searchParentID < trialParentID) {
206 result = -1;
207 } else {
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;
211 else
212 result = FastUnicodeCompare(&searchKey->nodeName.unicode[0],
213 searchKey->nodeName.length,
214 &trialKey->nodeName.unicode[0],
215 trialKey->nodeName.length);
216 }
217
218 return result;
219 }
220
221 /*
222 * cat_binarykeycompare - compare two HFS Plus catalog keys.
223
224 * The name portion of the key is compared using a 16-bit binary comparison.
225 * This is called from the b-tree code.
226 */
227 int
228 cat_binarykeycompare(HFSPlusCatalogKey *searchKey, HFSPlusCatalogKey *trialKey)
229 {
230 u_int32_t searchParentID, trialParentID;
231 int result;
232
233 searchParentID = searchKey->parentID;
234 trialParentID = trialKey->parentID;
235 result = 0;
236
237 if (searchParentID > trialParentID) {
238 ++result;
239 } else if (searchParentID < trialParentID) {
240 --result;
241 } else {
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;
246
247 result = UnicodeBinaryCompare (str1, length1, str2, length2);
248 }
249
250 return result;
251 }
252
253 /*
254 * cat_releasedesc
255 */
256 void
257 cat_releasedesc(struct cat_desc *descp)
258 {
259 if (descp == NULL)
260 return;
261
262 if ((descp->cd_flags & CD_HASBUF) && (descp->cd_nameptr != NULL)) {
263 hfs_free( (void*)descp->cd_nameptr );
264 }
265 descp->cd_nameptr = NULL;
266 descp->cd_namelen = 0;
267 descp->cd_flags &= ~CD_HASBUF;
268 }
269
270 /*
271 * Extract the CNID from a catalog node record.
272 */
273 static cnid_t
274 getcnid(const CatalogRecord *crp)
275 {
276 cnid_t cnid = 0;
277
278 switch (crp->recordType) {
279 case kHFSPlusFolderRecord:
280 cnid = crp->hfsPlusFolder.folderID;
281 break;
282 case kHFSPlusFileRecord:
283 cnid = crp->hfsPlusFile.fileID;
284 break;
285 default:
286 LFHFS_LOG(LEVEL_ERROR, "getcnid: unknown recordType=%d\n", crp->recordType);
287 break;
288 }
289
290 return (cnid);
291 }
292
293 /*
294 * Extract the text encoding from a catalog node record.
295 */
296 static u_int32_t
297 getencoding(const CatalogRecord *crp)
298 {
299 u_int32_t encoding;
300
301 if (crp->recordType == kHFSPlusFolderRecord)
302 encoding = crp->hfsPlusFolder.textEncoding;
303 else if (crp->recordType == kHFSPlusFileRecord)
304 encoding = crp->hfsPlusFile.textEncoding;
305 else
306 encoding = 0;
307
308 return (encoding);
309 }
310
311 /*
312 * getbsdattr - get attributes in bsd format
313 *
314 */
315 static void
316 getbsdattr(struct hfsmount *hfsmp, const struct HFSPlusCatalogFile *crp, struct cat_attr * attrp)
317 {
318 int isDirectory = (crp->recordType == kHFSPlusFolderRecord);
319 const struct HFSPlusBSDInfo *bsd = &crp->bsdInfo;
320
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);
328
329 if ((bsd->fileMode & S_IFMT) == 0) {
330 attrp->ca_flags = 0;
331 attrp->ca_uid = hfsmp->hfs_uid;
332 attrp->ca_gid = hfsmp->hfs_gid;
333 if (isDirectory) {
334 attrp->ca_mode = S_IFDIR | (hfsmp->hfs_dir_mask & (S_IRWXU|S_IRWXG|S_IRWXO));
335 } else {
336 attrp->ca_mode = S_IFREG | (hfsmp->hfs_file_mask & (S_IRWXU|S_IRWXG|S_IRWXO));
337 }
338 attrp->ca_linkcount = 1;
339 attrp->ca_rdev = 0;
340 } else {
341 attrp->ca_linkcount = 1; /* may be overridden below */
342 attrp->ca_rdev = 0;
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 */
349 case S_IFBLK:
350 attrp->ca_rdev = bsd->special.rawDevice;
351 break;
352 case S_IFIFO:
353 case S_IFSOCK:
354 case S_IFDIR:
355 case S_IFREG:
356 /* Pick up the hard link count */
357 if (bsd->special.linkCount > 0)
358 attrp->ca_linkcount = bsd->special.linkCount;
359 break;
360 }
361
362 /*
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.
368 */
369 /*
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.
374 *
375 * if (((unsigned int)vfs_flags(HFSTOVFS(hfsmp))) & MNT_UNKNOWNPERMISSIONS) {
376 * attrp->ca_uid = hfsmp->hfs_uid;
377 * attrp->ca_gid = hfsmp->hfs_gid;
378 * }
379 */
380 }
381
382 if (isDirectory) {
383 if (!S_ISDIR(attrp->ca_mode)) {
384 attrp->ca_mode &= ~S_IFMT;
385 attrp->ca_mode |= S_IFDIR;
386 }
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;
390
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;
394 } else {
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;
401 } else {
402 /* The file's supposed to be unlocked: */
403 attrp->ca_flags &= ~(SF_IMMUTABLE | UF_IMMUTABLE);
404 }
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;
410
411 /* On HFS+ the ThreadExists flag must always be set. */
412 attrp->ca_recflags |= kHFSThreadExistsMask;
413
414 /* Pick up the hardlink first link, if any. */
415 attrp->ca_firstlink = (attrp->ca_recflags & kHFSHasLinkChainMask) ? crp->hl_firstLinkID : 0;
416 }
417
418 attrp->ca_fileid = crp->fileID;
419
420 bcopy(&crp->userInfo, attrp->ca_finderinfo, 32);
421 }
422
423 /*
424 * builddesc - build a cnode descriptor from an HFS+ key
425 */
426 static int
427 builddesc(const HFSPlusCatalogKey *key, cnid_t cnid, u_int32_t hint, u_int32_t encoding,
428 int isdir, struct cat_desc *descp)
429 {
430 int result = 0;
431 unsigned char * nameptr;
432 size_t bufsize;
433 size_t utf8len;
434
435 /* guess a size... */
436 bufsize = (3 * key->nodeName.length) + 1;
437 nameptr = hfs_malloc(bufsize);
438 if (nameptr == NULL)
439 return ENOMEM;
440
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);
446
447 if (result == ENAMETOOLONG) {
448 hfs_free(nameptr);
449 bufsize = 1 + utf8_encodelen(key->nodeName.unicode,
450 key->nodeName.length * sizeof(UniChar),
451 ':', UTF_ADD_NULL_TERM);
452 nameptr = hfs_malloc(bufsize);
453
454 result = utf8_encodestr(key->nodeName.unicode,
455 key->nodeName.length * sizeof(UniChar),
456 nameptr, (size_t *)&utf8len,
457 bufsize, ':', UTF_ADD_NULL_TERM);
458 }
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;
465 if (isdir)
466 descp->cd_flags |= CD_ISDIR;
467 descp->cd_encoding = encoding;
468 return result;
469 }
470
471 /*
472 * cat_lookupbykey - lookup a catalog node using a cnode key
473 */
474 static int
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)
477 {
478 BTreeIterator * iterator = NULL;
479 FSBufferDescriptor btdata = {0};
480 CatalogRecord * recp = NULL;
481 u_int16_t datasize = 0;
482 int result = 0;
483 u_int32_t ilink = 0;
484 cnid_t cnid = 0;
485 u_int32_t encoding = 0;
486 cnid_t parentid = 0;
487
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));
493
494 FCB *filePtr = VTOF(HFSTOVCB(hfsmp)->catalogRefNum);
495 result = BTSearchRecord(filePtr, iterator,
496 &btdata, &datasize, iterator);
497 if (result)
498 goto exit;
499
500 /* Save the cnid, parentid, and encoding now in case there's a hard link or inode */
501 cnid = getcnid(recp);
502 if (cnid == 0) {
503 /* CNID of 0 is invalid. Mark as corrupt */
504 hfs_mark_inconsistent (hfsmp, HFS_INCONSISTENCY_DETECTED);
505 result = EINVAL;
506 goto exit;
507 }
508
509 parentid = keyp->hfsPlus.parentID;
510
511 encoding = getencoding(recp);
512 hint = iterator->hint.nodeNum;
513
514 /* Hide the journal files (if any) */
515 if ( IsEntryAJnlFile(hfsmp, cnid) && !(flags & HFS_LOOKUP_SYSFILE))
516 {
517 result = HFS_ERESERVEDNAME;
518 goto exit;
519 }
520
521 /*
522 * When a hardlink link is encountered, auto resolve it.
523 *
524 * The catalog record will change, and possibly its type.
525 */
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))) {
530
531 int isdirlink = 0;
532 int isfilelink = 0;
533
534 if ((SWAP_BE32(recp->hfsPlusFile.userInfo.fdType) == kHardLinkFileType) &&
535 (SWAP_BE32(recp->hfsPlusFile.userInfo.fdCreator) == kHFSPlusCreator)) {
536 isfilelink = 1;
537 } else if ((recp->hfsPlusFile.flags & kHFSHasLinkChainMask) &&
538 (SWAP_BE32(recp->hfsPlusFile.userInfo.fdType) == kHFSAliasType) &&
539 (SWAP_BE32(recp->hfsPlusFile.userInfo.fdCreator) == kHFSAliasCreator)) {
540 isdirlink = 1;
541 }
542 if ((isfilelink || isdirlink) && !(flags & HFS_LOOKUP_HARDLINK)) {
543 ilink = recp->hfsPlusFile.hl_linkReference;
544 (void) cat_resolvelink(hfsmp, ilink, isdirlink, (struct HFSPlusCatalogFile *)recp);
545 }
546 }
547
548 if (attrp != NULL) {
549 getbsdattr(hfsmp, (struct HFSPlusCatalogFile *)recp, attrp);
550 if (ilink) {
551 /* Update the inode number for this hard link */
552 attrp->ca_linkref = ilink;
553 }
554
555 /*
556 * Set kHFSHasLinkChainBit for hard links, and reset it for all
557 * other items. Also set linkCount to 1 for regular files.
558 *
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.
566 */
567 if (ilink)
568 {
569 /* This is a hard link and the link count bit was not set */
570 if (!(attrp->ca_recflags & kHFSHasLinkChainMask))
571 {
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;
574 }
575 }
576 else
577 {
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.
581 */
582 if ((parentid != hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid) &&
583 (parentid != hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid))
584 {
585 /* This is not a hard link or inode and the link count bit was set */
586 if (attrp->ca_recflags & kHFSHasLinkChainMask)
587 {
588 LFHFS_LOG(LEVEL_DEBUG, "cat_lookupbykey: clear hardlink bit on vol=%s cnid=%u\n", hfsmp->vcbVN, cnid);
589 attrp->ca_recflags &= ~kHFSHasLinkChainMask;
590 }
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))
593 {
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;
596 }
597 }
598 }
599 }
600 if (forkp != NULL) {
601 if (isadir(recp)) {
602 bzero(forkp, sizeof(*forkp));
603 }
604 else if (wantrsrc) {
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;
610
611 forkp->cf_vblocks = 0;
612 bcopy(&recp->hfsPlusFile.resourceFork.extents[0],
613 &forkp->cf_extents[0], sizeof(HFSPlusExtentRecord));
614 } else {
615 int i;
616 u_int32_t validblks;
617
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));
626
627 /* Validate the fork's resident extents. */
628 validblks = 0;
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;
634 /* Disable writes */
635 if (attrp != NULL) {
636 attrp->ca_mode &= S_IFMT | S_IRUSR | S_IRGRP | S_IROTH;
637 }
638 } else {
639 validblks += forkp->cf_extents[i].blockCount;
640 }
641 }
642 /* Adjust for any missing blocks. */
643 if ((validblks < forkp->cf_blocks) && (forkp->cf_extents[7].blockCount == 0)) {
644 off_t psize;
645
646 /*
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.
653 *
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.
658 */
659
660 (void) hfs_mark_inconsistent (hfsmp, HFS_INCONSISTENCY_DETECTED);
661
662 forkp->cf_blocks = validblks;
663 if (attrp != NULL) {
664 attrp->ca_blocks = validblks + recp->hfsPlusFile.resourceFork.totalBlocks;
665 }
666 psize = (off_t)validblks * (off_t)hfsmp->blockSize;
667 if (psize < forkp->cf_size) {
668 forkp->cf_size = psize;
669 }
670
671 }
672 }
673 }
674 if (descp != NULL) {
675 HFSPlusCatalogKey * pluskey = NULL;
676 pluskey = (HFSPlusCatalogKey *)&iterator->key;
677 builddesc(pluskey, cnid, hint, encoding, isadir(recp), descp);
678
679 }
680
681 if (desc_cnid != NULL) {
682 *desc_cnid = cnid;
683 }
684 exit:
685 hfs_free(iterator);
686 hfs_free(recp);
687
688 return MacToVFSError(result);
689 }
690
691 /*
692 * Determine if a catalog node record is a directory.
693 */
694 static int
695 isadir(const CatalogRecord *crp)
696 {
697 if (crp->recordType == kHFSPlusFolderRecord)
698 {
699 return 1;
700 }
701
702 return 0;
703 }
704
705 static int
706 buildthread(void *keyp, void *recp, int directory)
707 {
708 int size = 0;
709
710 HFSPlusCatalogKey *key = (HFSPlusCatalogKey *)keyp;
711 HFSPlusCatalogThread *rec = (HFSPlusCatalogThread *)recp;
712
713 size = sizeof(HFSPlusCatalogThread);
714 if (directory)
715 rec->recordType = kHFSPlusFolderThreadRecord;
716 else
717 rec->recordType = kHFSPlusFileThreadRecord;
718 rec->reserved = 0;
719 rec->parentID = key->parentID;
720 bcopy(&key->nodeName, &rec->nodeName,
721 sizeof(UniChar) * (key->nodeName.length + 1));
722
723 /* HFS Plus has variable sized thread records */
724 size -= (sizeof(rec->nodeName.unicode) -
725 (rec->nodeName.length * sizeof(UniChar)));
726
727 return (size);
728 }
729
730 /*
731 * Build a catalog node thread key.
732 */
733 static void
734 buildthreadkey(HFSCatalogNodeID parentID, CatalogKey *key)
735 {
736 key->hfsPlus.keyLength = kHFSPlusCatalogKeyMinimumLength;
737 key->hfsPlus.parentID = parentID;
738 key->hfsPlus.nodeName.length = 0;
739 }
740
741 /*
742 * cat_findname - obtain a descriptor from cnid
743 *
744 * Only a thread lookup is performed.
745 *
746 * Note: The caller is responsible for releasing the output
747 * catalog descriptor (when supplied outdescp is non-null).
748
749 */
750 int
751 cat_findname(struct hfsmount *hfsmp, cnid_t cnid, struct cat_desc *outdescp)
752 {
753 BTreeIterator *iterator = NULL;
754 CatalogRecord * recp = NULL;
755 FSBufferDescriptor btdata;
756 CatalogKey * keyp;
757
758 int isdir = 0;
759 int result;
760
761 iterator = hfs_mallocz(sizeof(BTreeIterator));
762 if (iterator == NULL)
763 {
764 result = ENOMEM;
765 goto exit;
766 }
767
768 buildthreadkey(cnid, (CatalogKey *)&iterator->key);
769 iterator->hint.nodeNum = 0;
770
771 recp = hfs_malloc(sizeof(CatalogRecord));
772 if (recp == NULL)
773 {
774 result = ENOMEM;
775 goto exit;
776 }
777 memset(recp,0,sizeof(CatalogRecord));
778 BDINIT(btdata, recp);
779
780 result = BTSearchRecord(VTOF(hfsmp->hfs_catalog_vp), iterator, &btdata, NULL, NULL);
781 if (result)
782 goto exit;
783
784 /* Turn thread record into a cnode key (in place). */
785 switch (recp->recordType)
786 {
787 case kHFSPlusFolderThreadRecord:
788 isdir = 1;
789 /* fall through */
790 case kHFSPlusFileThreadRecord:
791 keyp = (CatalogKey *)&recp->hfsPlusThread.reserved;
792 keyp->hfsPlus.keyLength = kHFSPlusCatalogKeyMinimumLength +
793 (keyp->hfsPlus.nodeName.length * 2);
794 break;
795 default:
796 result = ENOENT;
797 goto exit;
798 }
799
800
801 builddesc((HFSPlusCatalogKey *)keyp, cnid, 0, 0, isdir, outdescp);
802
803 exit:
804 if (recp)
805 hfs_free(recp);
806 if (iterator)
807 hfs_free(iterator);
808
809 return result;
810 }
811
812 bool IsEntryAJnlFile(struct hfsmount *hfsmp, cnid_t cnid)
813 {
814 return (((hfsmp->jnl || ((HFSTOVCB(hfsmp)->vcbAtrb & kHFSVolumeJournaledMask) && (hfsmp->hfs_flags & HFS_READ_ONLY)))) &&
815 ((cnid == hfsmp->hfs_jnlfileid) || (cnid == hfsmp->hfs_jnlinfoblkid)));
816 }
817
818 static bool IsEntryADirectoryLink(struct hfsmount *hfsmp, const CatalogRecord *crp,time_t itime)
819 {
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)));
825 }
826
827 static bool IsEntryAHardLink(struct hfsmount *hfsmp, const CatalogRecord *crp,time_t itime)
828 {
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)));
831 }
832
833 /*
834 * getdirentries callback for HFS Plus directories.
835 */
836 static int
837 getdirentries_callback(const CatalogKey *ckp, const CatalogRecord *crp, struct packdirentry_state *state)
838 {
839
840 UVFSDirEntry* entry = NULL;
841 const CatalogName *cnp;
842 OSErr result;
843
844 u_int32_t ilinkref = 0;
845 u_int32_t curlinkref = 0;
846 cnid_t cnid;
847 u_int8_t type = 0;
848 time_t itime;
849
850 caddr_t uiobase = NULL;
851 size_t namelen = 0;
852 size_t maxnamelen;
853 size_t uiosize = 0;
854 caddr_t uioaddr;
855
856 Boolean bIsLastRecordInDir = false;
857 Boolean bToHide = false;
858 Boolean bIsLink = false;
859 Boolean bIsMangled = false;
860
861 struct hfsmount *hfsmp = state->cbs_hfsmp;
862 cnid_t curID = ckp->hfsPlus.parentID;
863
864 /* We're done when parent directory changes */
865 if (state->cbs_parentID != curID)
866 {
867 /*
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.
872 *
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.
880 *
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.
886 */
887
888
889 /* The last record has not been returned yet, so we
890 * want to stop after packing the last item
891 */
892 if (state->cbs_hasprevdirentry)
893 {
894 bIsLastRecordInDir = true;
895 }
896 else
897 {
898 state->cbs_eof = true;
899 state->cbs_result = ENOENT;
900 return (0); /* stop */
901 }
902
903 }
904
905 entry = state->cbs_direntry;
906 u_int8_t* nameptr = (u_int8_t *)&entry->de_name;
907 if (state->cbs_flags & VNODE_READDIR_NAMEMAX)
908 {
909 /*
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.
914 */
915 maxnamelen = NAME_MAX + 1;
916 }
917 else
918 {
919 maxnamelen = UVFS_DIRENTRY_RECLEN(MAX_UTF8_NAME_LENGTH);
920 }
921
922 if (bIsLastRecordInDir)
923 {
924 /* The last item returns a non-zero invalid cookie */
925 cnid = INT_MAX;
926 }
927 else
928 {
929 if (crp == NULL)
930 return (0);
931
932 switch(crp->recordType)
933 {
934 case kHFSPlusFolderRecord:
935 type = UVFS_FA_TYPE_DIR;
936 cnid = crp->hfsPlusFolder.folderID;
937 /* Hide our private system directories. */
938 if (curID == kHFSRootFolderID)
939 {
940 if (cnid == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid || cnid == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid)
941 {
942 bToHide = true;
943 }
944 }
945 break;
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;
950 /*
951 * When a hardlink link is encountered save its link ref.
952 */
953 if (IsEntryAHardLink(hfsmp, crp, itime))
954 {
955 /* If link ref is inode's file id then use it directly. */
956 if (crp->hfsPlusFile.flags & kHFSHasLinkChainMask)
957 {
958 cnid = crp->hfsPlusFile.bsdInfo.special.iNodeNum;
959 }
960 else
961 {
962 ilinkref = crp->hfsPlusFile.bsdInfo.special.iNodeNum;
963 }
964 bIsLink =1;
965 }
966 else if (IsEntryADirectoryLink(hfsmp, crp,itime))
967 {
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;
972 bIsLink = true;
973 }
974
975 /* Hide the journal files */
976 if ((curID == kHFSRootFolderID) && IsEntryAJnlFile(hfsmp, cnid))
977 {
978 bToHide = 1;
979 }
980 break;
981
982 default:
983 return (0); /* stop */
984 };
985
986 cnp = (const CatalogName*) &ckp->hfsPlus.nodeName;
987
988 namelen = cnp->ustr.length;
989 /*
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.
993 */
994 if ((namelen < maxnamelen) && (crp->hfsPlusFile.textEncoding == 0)) {
995 int i;
996 u_int16_t ch;
997 const u_int16_t *chp;
998
999 chp = &cnp->ustr.unicode[0];
1000 for (i = 0; i < (int)namelen; ++i) {
1001 ch = *chp++;
1002 if (ch > 0x007f || ch == 0x0000) {
1003 /* Perform expensive utf8_encodestr conversion */
1004 goto encodestr;
1005 }
1006 nameptr[i] = (ch == '/') ? ':' : (u_int8_t)ch;
1007 }
1008 nameptr[namelen] = '\0';
1009 result = 0;
1010 }
1011 else
1012 {
1013 encodestr:
1014 result = utf8_encodestr(cnp->ustr.unicode, namelen * sizeof(UniChar), nameptr, &namelen, maxnamelen, ':', UTF_ADD_NULL_TERM);
1015 }
1016
1017 /* Check result returned from encoding the filename to utf8 */
1018 if (result == ENAMETOOLONG)
1019 {
1020 /*
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
1026 * with is_link.
1027 */
1028 cnid_t linkid = cnid;
1029 if (bIsLink)
1030 {
1031 linkid = crp->hfsPlusFile.fileID;
1032 }
1033
1034 result = ConvertUnicodeToUTF8Mangled(cnp->ustr.length * sizeof(UniChar), cnp->ustr.unicode, maxnamelen, (ByteCount*)&namelen, nameptr, linkid);
1035 if (result) return (0); /* stop */
1036 bIsMangled = 1;
1037 }
1038 }
1039
1040 /*
1041 * The index is 1 relative and includes "." and ".."
1042 *
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
1046 */
1047 state->cbs_prevdirentry->de_nextcookie = (state->cbs_index + 3) | ((u_int64_t)cnid << 32);
1048 uiosize = state->cbs_prevdirentry->de_reclen;
1049
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)
1054 {
1055 state->cbs_prevdirentry->de_reclen = 0;
1056 }
1057
1058 if (bIsLastRecordInDir)
1059 {
1060 state->cbs_prevdirentry->de_reclen = 0;
1061 state->cbs_prevdirentry->de_nextcookie = UVFS_DIRCOOKIE_EOF;
1062 }
1063
1064 uioaddr = (caddr_t) state->cbs_prevdirentry;
1065
1066 /* Save current base address for post processing of hard-links. */
1067 if (ilinkref || state->cbs_previlinkref)
1068 {
1069 uiobase = uioaddr;
1070 }
1071
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))
1074 {
1075 return (0); /* stop */
1076 }
1077
1078 if (state->cbs_hasprevdirentry)
1079 {
1080 // Skip entries marked as "bToHide" on the previous iteration!
1081 if (state->cbs_prevdirentry->de_fileid != 0)
1082 {
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;
1087 }
1088 else if (state->cbs_haslastinsertedentry && bIsLastRecordInDir)
1089 {
1090 state->cbs_lastinsertedentry->de_reclen = 0;
1091 state->cbs_lastinsertedentry->de_nextcookie = UVFS_DIRCOOKIE_EOF;
1092 }
1093
1094 ++state->cbs_index;
1095
1096 /* Remember previous entry */
1097 state->cbs_desc->cd_cnid = cnid;
1098 if (type == UVFS_FA_TYPE_DIR)
1099 {
1100 state->cbs_desc->cd_flags |= CD_ISDIR;
1101 }
1102 else
1103 {
1104 state->cbs_desc->cd_flags &= ~CD_ISDIR;
1105 }
1106
1107 if (state->cbs_desc->cd_nameptr != NULL)
1108 {
1109 state->cbs_desc->cd_namelen = 0;
1110 }
1111
1112 if (!bIsMangled)
1113 {
1114 state->cbs_desc->cd_namelen = namelen;
1115 bcopy(nameptr, state->cbs_namebuf, namelen + 1);
1116 }
1117 else
1118 {
1119 /* Store unmangled name for the directory hint else it will
1120 * restart readdir at the last location again
1121 */
1122 u_int8_t *new_nameptr;
1123 size_t bufsize;
1124 size_t tmp_namelen = 0;
1125
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);
1130 if (result)
1131 {
1132 hfs_free(new_nameptr);
1133 return (0); /* stop */
1134 }
1135
1136
1137 state->cbs_desc->cd_namelen = tmp_namelen;
1138 bcopy(new_nameptr, state->cbs_namebuf, tmp_namelen + 1);
1139
1140 hfs_free(new_nameptr);
1141 }
1142
1143 if (state->cbs_hasprevdirentry)
1144 {
1145 curlinkref = ilinkref; /* save current */
1146 ilinkref = state->cbs_previlinkref; /* use previous */
1147 }
1148 /*
1149 * Record any hard links for post processing.
1150 */
1151 if ((ilinkref != 0) && (state->cbs_result == 0) && (state->cbs_nlinks < state->cbs_maxlinks))
1152 {
1153 state->cbs_linkinfo[state->cbs_nlinks].dirent_addr = uiobase;
1154 state->cbs_linkinfo[state->cbs_nlinks].link_ref = ilinkref;
1155 state->cbs_nlinks++;
1156 }
1157
1158 if (state->cbs_hasprevdirentry)
1159 {
1160 ilinkref = curlinkref; /* restore current */
1161 }
1162 }
1163
1164 /* Fill the direntry to be used the next time */
1165 if (bIsLastRecordInDir)
1166 {
1167 state->cbs_eof = true;
1168 return (0); /* stop */
1169 }
1170
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;
1175
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;
1182
1183 /* Continue iteration if there's room */
1184 return (state->cbs_result == 0 && state->cbs_psReadDirBuffer->uBufferResid >= SMALL_DIRENTRY_SIZE);
1185 }
1186
1187 /*
1188 * Callback to establish directory position.
1189 * Called with position_state for each item in a directory.
1190 */
1191 static int
1192 cat_findposition(const CatalogKey *ckp, const CatalogRecord *crp, struct position_state *state)
1193 {
1194 cnid_t curID = 0;
1195 curID = ckp->hfsPlus.parentID;
1196
1197 /* Make sure parent directory didn't change */
1198 if (state->parentID != curID) {
1199 /*
1200 * The parent ID is different from curID this means we've hit
1201 * the EOF for the directory.
1202 */
1203 state->error = ENOENT;
1204 return (0); /* stop */
1205 }
1206
1207 /* Count this entry */
1208 switch(crp->recordType)
1209 {
1210 case kHFSPlusFolderRecord:
1211 case kHFSPlusFileRecord:
1212 ++state->count;
1213 break;
1214 default:
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 */
1218 };
1219
1220 return (state->count < state->index);
1221 }
1222
1223 /*
1224 * Pack a uio buffer with directory entries from the catalog
1225 */
1226 int
1227 cat_getdirentries(struct hfsmount *hfsmp, u_int32_t entrycnt, directoryhint_t *dirhint, ReadDirBuff_s* psReadDirBuffer, int flags, int *items, bool *eofflag, UVFSDirEntry* psDotDotEntry)
1228 {
1229 FCB* fcb;
1230 BTreeIterator * iterator = NULL;
1231 CatalogKey * key;
1232 struct packdirentry_state state;
1233 int result = 0;
1234 int index;
1235 int have_key;
1236 int extended;
1237
1238 extended = flags & VNODE_READDIR_EXTENDED;
1239
1240 fcb = hfsmp->hfs_catalog_cp->c_datafork;
1241
1242 #define MAX_LINKINFO_ENTRIES 275
1243 /*
1244 * Get a buffer for link info array, btree iterator and a direntry.
1245 *
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.
1249 *
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.
1255 */
1256
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);
1263
1264 if (extended)
1265 {
1266 bufsize += 2 * (sizeof(UVFSDirEntry) + sizeof(char)*MAX_UTF8_NAME_LENGTH);
1267 }
1268 void* buffer = hfs_mallocz(bufsize);
1269
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);
1278 /*
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.
1282 */
1283 state.cbs_eof = false;
1284
1285 iterator = (BTreeIterator *) ((char *)state.cbs_linkinfo + (maxlinks * sizeof(linkinfo_t)));
1286 key = (CatalogKey *)&iterator->key;
1287 have_key = 0;
1288 index = dirhint->dh_index + 1;
1289 if (extended)
1290 {
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);
1293 }
1294 /*
1295 * Attempt to build a key from cached filename
1296 */
1297 if (dirhint->dh_desc.cd_namelen != 0)
1298 {
1299 if (buildkey(&dirhint->dh_desc, (HFSPlusCatalogKey *)key) == 0)
1300 {
1301 iterator->hint.nodeNum = dirhint->dh_desc.cd_hint;
1302 have_key = 1;
1303 }
1304 }
1305
1306 if (index == 0 && dirhint->dh_threadhint != 0)
1307 {
1308 /*
1309 * Position the iterator at the directory's thread record.
1310 * (i.e. just before the first entry)
1311 */
1312 buildthreadkey(dirhint->dh_desc.cd_parentcnid, key);
1313 iterator->hint.nodeNum = dirhint->dh_threadhint;
1314 iterator->hint.index = 0;
1315 have_key = 1;
1316 }
1317
1318 /*
1319 * If the last entry wasn't cached then position the btree iterator
1320 */
1321 if (!have_key)
1322 {
1323 /*
1324 * Position the iterator at the directory's thread record.
1325 * (i.e. just before the first entry)
1326 */
1327 buildthreadkey(dirhint->dh_desc.cd_parentcnid, key);
1328 result = BTSearchRecord(fcb, iterator, NULL, NULL, iterator);
1329 if (result)
1330 {
1331 result = MacToVFSError(result);
1332 goto cleanup;
1333 }
1334 if (index == 0)
1335 {
1336 dirhint->dh_threadhint = iterator->hint.nodeNum;
1337 }
1338 /*
1339 * Iterate until we reach the entry just
1340 * before the one we want to start with.
1341 */
1342 if (index > 0)
1343 {
1344 struct position_state ps;
1345
1346 ps.error = 0;
1347 ps.count = 0;
1348 ps.index = index;
1349 ps.parentID = dirhint->dh_desc.cd_parentcnid;
1350 ps.hfsmp = hfsmp;
1351
1352 result = BTIterateRecords(fcb, kBTreeNextRecord, iterator, (IterateCallBackProcPtr)cat_findposition, &ps);
1353 if (ps.error)
1354 result = ps.error;
1355 else
1356 result = MacToVFSError(result);
1357 if (result) {
1358 result = MacToVFSError(result);
1359 if (result == ENOENT) {
1360 /*
1361 * ENOENT means we've hit the EOF.
1362 * suppress the error, and set the eof flag.
1363 */
1364 result = 0;
1365 dirhint->dh_desc.cd_flags |= CD_EOF;
1366 *eofflag = true;
1367 }
1368 goto cleanup;
1369 }
1370 }
1371 }
1372
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;
1380
1381 /* Use a temporary buffer to hold intermediate descriptor names. */
1382 if (dirhint->dh_desc.cd_namelen > 0 && dirhint->dh_desc.cd_nameptr != NULL)
1383 {
1384 bcopy(dirhint->dh_desc.cd_nameptr, buffer, dirhint->dh_desc.cd_namelen+1);
1385 if (dirhint->dh_desc.cd_flags & CD_HASBUF)
1386 {
1387 dirhint->dh_desc.cd_flags &= ~CD_HASBUF;
1388 hfs_free((void*) dirhint->dh_desc.cd_nameptr);
1389 }
1390 }
1391 dirhint->dh_desc.cd_nameptr = (u_int8_t *)buffer;
1392
1393 enum BTreeIterationOperations op;
1394 if (extended && index != 0 && have_key)
1395 op = kBTreeCurrentRecord;
1396 else
1397 op = kBTreeNextRecord;
1398
1399 /*
1400 * Process as many entries as possible starting at iterator->key.
1401 */
1402 result = BTIterateRecords(fcb, op, iterator, (IterateCallBackProcPtr)getdirentries_callback, &state);
1403
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
1410 */
1411 if (extended && (result == fsBTRecordNotFoundErr))
1412 {
1413 CatalogKey ckp;
1414 bzero(&ckp, sizeof(ckp));
1415 result = getdirentries_callback(&ckp, NULL, &state);
1416 }
1417
1418 /* Note that state.cbs_index is still valid on errors */
1419 *items = state.cbs_index - index;
1420 index = state.cbs_index;
1421
1422 /*
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.
1426 */
1427 if (state.cbs_eof)
1428 {
1429 dirhint->dh_desc.cd_flags |= CD_EOF;
1430 *eofflag = true;
1431 }
1432
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)
1436 {
1437 if (state.cbs_eof)
1438 {
1439 //This is an empty dir
1440 psDotDotEntry->de_nextcookie = UVFS_DIRCOOKIE_EOF;
1441 psDotDotEntry->de_nextrec = 0;
1442 }
1443 else
1444 {
1445 //Buffer is too small to add more entries after ".." entry
1446 psDotDotEntry->de_nextrec = 0;
1447 }
1448 }
1449
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;
1454
1455 /* Fix up the name. */
1456 if (dirhint->dh_desc.cd_namelen > 0)
1457 {
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;
1460 }
1461 else
1462 {
1463 dirhint->dh_desc.cd_nameptr = NULL;
1464 dirhint->dh_desc.cd_namelen = 0;
1465 }
1466
1467 /*
1468 * Post process any hard links to get the real file id.
1469 */
1470 if (state.cbs_nlinks > 0)
1471 {
1472 ino_t fileid = 0;
1473 caddr_t address;
1474 int i;
1475
1476 for (i = 0; i < state.cbs_nlinks; ++i)
1477 {
1478 if (resolvelinkid(hfsmp, state.cbs_linkinfo[i].link_ref, &fileid) != 0)
1479 continue;
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)
1483 continue;
1484
1485 if (extended)
1486 {
1487 ino64_t fileid_64 = (ino64_t)fileid;
1488 memcpy(&fileid_64, (void*) address, sizeof(fileid_64));
1489 }
1490 else
1491 {
1492 memcpy(&fileid, (void*) address, sizeof(fileid));
1493 }
1494
1495 }
1496 }
1497
1498 if (state.cbs_result)
1499 result = state.cbs_result;
1500 else
1501 result = MacToVFSError(result);
1502
1503 if (result == ENOENT)
1504 {
1505 result = 0;
1506 }
1507
1508 cleanup:
1509 hfs_free(buffer);
1510
1511 return (result);
1512 }
1513
1514 /*
1515 * cat_idlookup - lookup a catalog node using a cnode id
1516 *
1517 * Note: The caller is responsible for releasing the output
1518 * catalog descriptor (when supplied outdescp is non-null).
1519 */
1520 int
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)
1523 {
1524 BTreeIterator * iterator = NULL;
1525 FSBufferDescriptor btdata = {0};
1526 u_int16_t datasize = 0;
1527 CatalogKey * keyp = NULL;
1528 CatalogRecord * recp = NULL;
1529 int result = 0;
1530
1531 iterator = hfs_mallocz(sizeof(*iterator));
1532 if (iterator == NULL)
1533 return MacToVFSError(ENOMEM);
1534
1535 buildthreadkey(cnid, (CatalogKey *)&iterator->key);
1536
1537 recp = hfs_malloc(sizeof(CatalogRecord));
1538 BDINIT(btdata, recp);
1539
1540 result = BTSearchRecord(VTOF(HFSTOVCB(hfsmp)->catalogRefNum), iterator,
1541 &btdata, &datasize, iterator);
1542 if (result)
1543 goto exit;
1544
1545 /* Turn thread record into a cnode key (in place) */
1546 switch (recp->recordType) {
1547
1548 case kHFSPlusFileThreadRecord:
1549 case kHFSPlusFolderThreadRecord:
1550 keyp = (CatalogKey *)&recp->hfsPlusThread.reserved;
1551
1552 /* check for NULL name */
1553 if (keyp->hfsPlus.nodeName.length == 0) {
1554 result = ENOENT;
1555 goto exit;
1556 }
1557
1558 keyp->hfsPlus.keyLength = kHFSPlusCatalogKeyMinimumLength +
1559 (keyp->hfsPlus.nodeName.length * 2);
1560 break;
1561
1562 default:
1563 result = ENOENT;
1564 goto exit;
1565 }
1566
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.
1572 */
1573 if (result == 0 && outdescp) {
1574 cnid_t dcnid = outdescp->cd_cnid;
1575 /*
1576 * Just for sanity's case, let's make sure that
1577 * the key in the thread matches the key in the record.
1578 */
1579 if (cnid != dcnid)
1580 {
1581 LFHFS_LOG(LEVEL_ERROR, "cat_idlookup: Requested cnid (%d / %08x) != dcnid (%d / %08x)\n", cnid, cnid, dcnid, dcnid);
1582 result = ENOENT;
1583 }
1584 }
1585 exit:
1586 hfs_free(recp);
1587 hfs_free(iterator);
1588
1589 return MacToVFSError(result);
1590 }
1591
1592 /*
1593 * buildkey - build a Catalog b-tree key from a cnode descriptor
1594 */
1595 static int
1596 buildkey(struct cat_desc *descp, HFSPlusCatalogKey *key)
1597 {
1598 int utf8_flags = UTF_ESCAPE_ILLEGAL;
1599 int result = 0;
1600 size_t unicodeBytes = 0;
1601
1602 if (descp->cd_namelen == 0 || descp->cd_nameptr[0] == '\0')
1603 return (EINVAL); /* invalid name */
1604
1605 key->parentID = descp->cd_parentcnid;
1606 key->nodeName.length = 0;
1607 /*
1608 * Convert filename from UTF-8 into Unicode
1609 */
1610
1611 if ((descp->cd_flags & CD_DECOMPOSED) == 0)
1612 {
1613 utf8_flags |= UTF_DECOMPOSED;
1614 }
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;
1618 if (result)
1619 {
1620 if (result != ENAMETOOLONG)
1621 result = EINVAL; /* name has invalid characters */
1622 return (result);
1623 }
1624
1625 return (0);
1626 }
1627
1628 /*
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.
1631 */
1632
1633 /*
1634 * cat_lookup - lookup a catalog node using a cnode descriptor
1635 *
1636 * Note: The caller is responsible for releasing the output
1637 * catalog descriptor (when supplied outdescp is non-null).
1638 */
1639 int
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)
1643 {
1644 CatalogKey * keyp = NULL;
1645 int result;
1646 int flags = 0;
1647
1648 keyp = hfs_malloc(sizeof(CatalogKey));
1649 if ( keyp == NULL )
1650 {
1651 result = ENOMEM;
1652 goto exit;
1653 }
1654
1655 result = buildkey(descp, (HFSPlusCatalogKey *)keyp);
1656 if (result)
1657 goto exit;
1658
1659 result = cat_lookupbykey(hfsmp, keyp, flags, descp->cd_hint, wantrsrc, outdescp, attrp, forkp, desc_cnid);
1660
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;
1666 }
1667 result = cat_lookupmangled(hfsmp, descp, wantrsrc, outdescp, attrp, forkp);
1668 if (desc_cnid) {
1669 *desc_cnid = outdescp->cd_cnid;
1670 }
1671 if (outdescp == &temp_desc) {
1672 /* Release the local copy of desc */
1673 cat_releasedesc(outdescp);
1674 }
1675 }
1676
1677 exit:
1678 hfs_free(keyp);
1679
1680 return (result);
1681 }
1682
1683 /*
1684 * cat_lookupmangled - lookup a catalog node using a mangled name
1685 */
1686 int
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)
1689 {
1690 cnid_t fileID;
1691 u_int32_t prefixlen;
1692 int result;
1693 u_int8_t utf8[NAME_MAX + 1];
1694 ByteCount utf8len;
1695 u_int16_t unicode[kHFSPlusMaxFileNameChars + 1];
1696 size_t unicodelen;
1697
1698 if (wantrsrc)
1699 return (ENOENT);
1700
1701 fileID = GetEmbeddedFileID(descp->cd_nameptr, descp->cd_namelen, &prefixlen);
1702 if (fileID < (cnid_t)kHFSFirstUserCatalogNodeID)
1703 return (ENOENT);
1704
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)
1709 {
1710 return (ENOENT);
1711 }
1712
1713 result = cat_idlookup(hfsmp, fileID, 0, 0, outdescp, attrp, forkp);
1714 if (result)
1715 return (ENOENT);
1716 /* It must be in the correct directory */
1717 if (descp->cd_parentcnid != outdescp->cd_parentcnid)
1718 goto falsematch;
1719
1720 /*
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
1726 * file system.
1727 */
1728 result = utf8_decodestr(outdescp->cd_nameptr, outdescp->cd_namelen,
1729 unicode, &unicodelen, sizeof(unicode), ':', 0);
1730 if (result) {
1731 goto falsematch;
1732 }
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)) {
1738 goto falsematch;
1739 }
1740
1741 return (0);
1742
1743 falsematch:
1744 cat_releasedesc(outdescp);
1745 return (ENOENT);
1746 }
1747
1748 /*
1749 * Callback to collect directory entries.
1750 * Called with readattr_state for each item in a directory.
1751 */
1752 struct readattr_state {
1753 struct hfsmount *hfsmp;
1754 struct cat_entrylist *list;
1755 cnid_t dir_cnid;
1756 int error;
1757 int reached_eof;
1758 };
1759
1760 static int
1761 getentriesattr_callback(const CatalogKey *key, const CatalogRecord *rec, struct readattr_state *state)
1762 {
1763 struct cat_entrylist *list = state->list;
1764 struct hfsmount *hfsmp = state->hfsmp;
1765 struct cat_entry *cep;
1766 cnid_t parentcnid;
1767
1768 if (list->realentries >= list->maxentries)
1769 return (0); /* stop */
1770
1771 parentcnid = key->hfsPlus.parentID;
1772
1773 switch(rec->recordType)
1774 {
1775 case kHFSPlusFolderRecord:
1776 case kHFSPlusFileRecord:
1777 if (parentcnid != state->dir_cnid)
1778 {
1779 state->error = btNotFound;
1780 state->reached_eof = 1;
1781 return (0); /* stop */
1782 }
1783 break;
1784 case kHFSPlusFolderThreadRecord:
1785 case kHFSPlusFileThreadRecord:
1786 list->skipentries++;
1787 if (parentcnid != state->dir_cnid)
1788 {
1789 state->error = btNotFound;
1790 state->reached_eof = 1;
1791 return (0); /* stop */
1792 }
1793 else
1794 return (1); /*continue */
1795 break;
1796 default:
1797 state->error = btNotFound;
1798 return (0); /* stop */
1799 }
1800
1801 /* Hide the private system directories and journal files */
1802 if (parentcnid == kHFSRootFolderID)
1803 {
1804 if (rec->recordType == kHFSPlusFolderRecord)
1805 {
1806 if (rec->hfsPlusFolder.folderID == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid || rec->hfsPlusFolder.folderID == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid)
1807 {
1808 list->skipentries++;
1809 return (1); /* continue */
1810 }
1811 }
1812
1813 if ((rec->recordType == kHFSPlusFileRecord) && IsEntryAJnlFile(hfsmp, rec->hfsPlusFile.fileID))
1814 {
1815 list->skipentries++;
1816 return (1); /* continue */
1817 }
1818 }
1819
1820 cep = &list->entry[list->realentries++];
1821
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);
1825
1826 if (rec->recordType == kHFSPlusFileRecord)
1827 {
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;
1832
1833 /* Save link reference for later processing. */
1834 if ((SWAP_BE32(rec->hfsPlusFile.userInfo.fdType) == kHardLinkFileType) &&
1835 (SWAP_BE32(rec->hfsPlusFile.userInfo.fdCreator) == kHFSPlusCreator))
1836 {
1837 cep->ce_attr.ca_linkref = rec->hfsPlusFile.bsdInfo.special.iNodeNum;
1838 }
1839 else if ((rec->hfsPlusFile.flags & kHFSHasLinkChainMask) &&
1840 (SWAP_BE32(rec->hfsPlusFile.userInfo.fdType) == kHFSAliasType) &&
1841 (SWAP_BE32(rec->hfsPlusFile.userInfo.fdCreator) == kHFSAliasCreator))
1842 {
1843 cep->ce_attr.ca_linkref = rec->hfsPlusFile.bsdInfo.special.iNodeNum;
1844 }
1845 }
1846
1847
1848 return (list->realentries < list->maxentries);
1849 }
1850
1851 /*
1852 * Pack a cat_entrylist buffer with attributes from the catalog
1853 *
1854 * Note: index is zero relative
1855 */
1856 int
1857 cat_getentriesattr(struct hfsmount *hfsmp, directoryhint_t *dirhint, struct cat_entrylist *ce_list, int *reachedeof)
1858 {
1859 FCB* fcb;
1860 CatalogKey * key;
1861 BTreeIterator * iterator = NULL;
1862 struct readattr_state state;
1863 cnid_t parentcnid;
1864 int i;
1865 int index;
1866 bool bHaveKey = false;
1867 int result = 0;
1868 int reached_eof = 0;
1869
1870 ce_list->realentries = 0;
1871
1872 fcb = GetFileControlBlock(HFSTOVCB(hfsmp)->catalogRefNum);
1873 parentcnid = dirhint->dh_desc.cd_parentcnid;
1874
1875 bzero (&state, sizeof(struct readattr_state));
1876
1877 state.hfsmp = hfsmp;
1878 state.list = ce_list;
1879 state.dir_cnid = parentcnid;
1880 state.error = 0;
1881
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;
1886
1887 /*
1888 * Attempt to build a key from cached filename
1889 */
1890 if (dirhint->dh_desc.cd_namelen != 0)
1891 {
1892 if (buildkey(&dirhint->dh_desc, (HFSPlusCatalogKey *)key) == 0)
1893 {
1894 bHaveKey = true;
1895 }
1896 }
1897
1898 /*
1899 * If the last entry wasn't cached then position the btree iterator
1900 */
1901 if ((index == 0) || !bHaveKey)
1902 {
1903 /*
1904 * Position the iterator at the directory's thread record.
1905 * (i.e. just before the first entry)
1906 */
1907 buildthreadkey(dirhint->dh_desc.cd_parentcnid, key);
1908 result = BTSearchRecord(fcb, iterator, NULL, NULL, iterator);
1909 if (result)
1910 {
1911 result = MacToVFSError(result);
1912 goto exit;
1913 }
1914
1915 /*
1916 * Iterate until we reach the entry just
1917 * before the one we want to start with.
1918 */
1919 if (index > 0)
1920 {
1921
1922 struct position_state ps;
1923
1924 ps.error = 0;
1925 ps.count = 0;
1926 ps.index = index;
1927 ps.parentID = dirhint->dh_desc.cd_parentcnid;
1928 ps.hfsmp = hfsmp;
1929
1930 result = BTIterateRecords(fcb, kBTreeNextRecord, iterator,
1931 (IterateCallBackProcPtr)cat_findposition, &ps);
1932 if (ps.error)
1933 result = ps.error;
1934 else
1935 result = MacToVFSError(result);
1936
1937 if (result)
1938 {
1939 /*
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.
1945 */
1946 result = MacToVFSError(result);
1947 goto exit;
1948 }
1949 }
1950 }
1951
1952 /* Fill list with entries starting at iterator->key. */
1953 result = BTIterateRecords(fcb, kBTreeNextRecord, iterator,
1954 (IterateCallBackProcPtr)getentriesattr_callback, &state);
1955
1956 if (state.error)
1957 {
1958 result = state.error;
1959 reached_eof = state.reached_eof;
1960 }
1961 else if (ce_list->realentries == 0)
1962 {
1963 result = btNotFound;
1964 reached_eof = 1;
1965 }
1966 else
1967 {
1968 result = MacToVFSError(result);
1969 }
1970
1971 /*
1972 * Resolve any hard links.
1973 */
1974 for (i = 0; i < (int)ce_list->realentries; ++i)
1975 {
1976 struct FndrFileInfo *fip;
1977 struct cat_entry *cep;
1978 int isdirlink = 0;
1979 int isfilelink = 0;
1980
1981 cep = &ce_list->entry[i];
1982 if (cep->ce_attr.ca_linkref == 0)
1983 continue;
1984
1985 /* Note: Finder info is still in Big Endian */
1986 fip = (struct FndrFileInfo *)&cep->ce_attr.ca_finderinfo;
1987
1988 if (S_ISREG(cep->ce_attr.ca_mode) &&
1989 (SWAP_BE32(fip->fdType) == kHardLinkFileType) &&
1990 (SWAP_BE32(fip->fdCreator) == kHFSPlusCreator)) {
1991 isfilelink = 1;
1992 }
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)) {
1997 isdirlink = 1;
1998 }
1999
2000 if (isfilelink || isdirlink) {
2001 struct HFSPlusCatalogFile filerec;
2002
2003 if (cat_resolvelink(hfsmp, cep->ce_attr.ca_linkref, isdirlink, &filerec) != 0)
2004 continue;
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;
2011 }
2012 }
2013
2014 exit:
2015 if (iterator)
2016 hfs_free(iterator);
2017 *reachedeof = reached_eof;
2018 return MacToVFSError(result);
2019 }
2020
2021 /*
2022 * Check the run-time ID hashtable.
2023 *
2024 * The catalog lock must be held (like other functions in this file).
2025 *
2026 * Returns:
2027 * 1 if the ID is in the hash table.
2028 * 0 if the ID is not in the hash table
2029 */
2030 int cat_check_idhash (struct hfsmount *hfsmp, cnid_t test_fileid) {
2031
2032 cat_preflightid_t *preflight;
2033 int found = 0;
2034
2035 for (preflight = IDHASH(hfsmp, test_fileid)->lh_first; preflight ; preflight = preflight->id_hash.le_next)
2036 {
2037 if (preflight->fileid == test_fileid)
2038 {
2039 found = 1;
2040 break;
2041 }
2042 }
2043
2044 return found;
2045 }
2046
2047 int
2048 cat_acquire_cnid (struct hfsmount *hfsmp, cnid_t *new_cnid)
2049 {
2050 uint32_t nextCNID;
2051 BTreeIterator *iterator;
2052 FSBufferDescriptor btdata;
2053 uint16_t datasize;
2054 CatalogRecord *recp;
2055 int result = 0;
2056 int wrapped = 0;
2057 /*
2058 * Get the next CNID. We can change it since we hold the catalog lock.
2059 */
2060 nextid:
2061 nextCNID = hfsmp->vcbNxtCNID;
2062 if (nextCNID == 0xFFFFFFFF) {
2063 wrapped++;
2064 if (wrapped > 1) {
2065 /* don't allow more than one wrap-around */
2066 return ENOSPC;
2067 }
2068 hfs_lock_mount (hfsmp);
2069 hfsmp->vcbNxtCNID = kHFSFirstUserCatalogNodeID;
2070 hfsmp->vcbAtrb |= kHFSCatalogNodeIDsReusedMask;
2071 hfs_unlock_mount (hfsmp);
2072 } else {
2073 hfsmp->vcbNxtCNID++;
2074 }
2075 hfs_note_header_minor_change(hfsmp);
2076
2077 /* First check that there are not any entries pending in the hash table with this ID */
2078 if (cat_check_idhash (hfsmp, nextCNID))
2079 {
2080 /* Someone wants to insert this into the catalog but hasn't done so yet. Skip it */
2081 goto nextid;
2082 }
2083
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)
2087 return ENOMEM;
2088
2089 buildthreadkey(nextCNID, (CatalogKey *)&iterator->key);
2090
2091 recp = hfs_malloc(sizeof(CatalogRecord));
2092 BDINIT(btdata, recp);
2093
2094 result = BTSearchRecord(hfsmp->hfs_catalog_cp->c_datafork, iterator, &btdata, &datasize, iterator);
2095 hfs_free(recp);
2096 hfs_free(iterator);
2097
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 */
2103 goto nextid;
2104 }
2105 if (result) {
2106 /* For any other error, return the result */
2107 return result;
2108 }
2109
2110 /*
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.
2116 *
2117 * Note that we pass (existence_only == 1) argument to hfs_chash_snoop.
2118 */
2119 if ((hfsmp->vcbAtrb & kHFSCatalogNodeIDsReusedMask))
2120 {
2121 if (hfs_chash_snoop (hfsmp, nextCNID, 1, NULL, NULL) == 0)
2122 {
2123 goto nextid;
2124 }
2125 }
2126
2127 /*
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.
2130 */
2131 *new_cnid = nextCNID;
2132 }
2133 else if (result == noErr) {
2134 /* move on to the next ID */
2135 goto nextid;
2136 }
2137 else {
2138 /* For any other situation, just bail out */
2139 return EIO;
2140 }
2141
2142 return 0;
2143 }
2144
2145
2146 int
2147 cat_preflight(struct hfsmount *hfsmp, uint32_t ops, cat_cookie_t *cookie)
2148 {
2149 int lockflags = 0;
2150 int result;
2151
2152 if (hfsmp->hfs_catalog_cp->c_lockowner != pthread_self())
2153 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
2154
2155 result = BTReserveSpace(hfsmp->hfs_catalog_cp->c_datafork, ops, (void*)cookie);
2156
2157 if (lockflags)
2158 hfs_systemfile_unlock(hfsmp, lockflags);
2159
2160 return MacToVFSError(result);
2161 }
2162
2163 void
2164 cat_postflight(struct hfsmount *hfsmp, cat_cookie_t *cookie)
2165 {
2166 int lockflags = 0;
2167
2168 if (hfsmp->hfs_catalog_cp->c_lockowner != pthread_self())
2169 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
2170
2171 (void) BTReleaseReserve(hfsmp->hfs_catalog_cp->c_datafork, (void*)cookie);
2172
2173 if (lockflags)
2174 hfs_systemfile_unlock(hfsmp, lockflags);
2175 }
2176 /*
2177 * Extract the parent ID from a catalog node record.
2178 */
2179 static cnid_t
2180 getparentcnid(const CatalogRecord *recp)
2181 {
2182 cnid_t cnid = 0;
2183
2184 switch (recp->recordType)
2185 {
2186 case kHFSPlusFileThreadRecord:
2187 case kHFSPlusFolderThreadRecord:
2188 cnid = recp->hfsPlusThread.parentID;
2189 break;
2190 default:
2191 LFHFS_LOG(LEVEL_ERROR, "getparentcnid: unknown recordType (crp @ %p)\n", recp);
2192 hfs_assert(0);
2193 break;
2194 }
2195
2196 return (cnid);
2197 }
2198
2199 int
2200 cat_rename (
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 )
2206 {
2207
2208 int result = 0;
2209
2210 FSBufferDescriptor btdata;
2211 ExtendedVCB * vcb = HFSTOVCB(hfsmp);
2212 FCB * fcb = GetFileControlBlock(vcb->catalogRefNum);
2213 u_int16_t datasize;
2214 int sourcegone = 0;
2215 int skipthread = 0;
2216 int directory = from_cdp->cd_flags & CD_ISDIR;
2217 int is_dirlink = 0;
2218 u_int32_t encoding = 0;
2219
2220 if (from_cdp->cd_namelen == 0 || to_cdp->cd_namelen == 0)
2221 {
2222 return (EINVAL);
2223 }
2224
2225 CatalogRecord* recp = NULL;
2226 BTreeIterator* to_iterator = NULL;
2227 BTreeIterator* from_iterator = (BTreeIterator*) hfs_mallocz(sizeof(BTreeIterator));
2228 if (from_iterator == NULL)
2229 {
2230 return (ENOMEM);
2231 }
2232
2233 if ((result = buildkey(from_cdp, (HFSPlusCatalogKey*) &from_iterator->key)))
2234 {
2235 goto exit;
2236 }
2237
2238 to_iterator = hfs_mallocz(sizeof(*to_iterator));
2239 if (to_iterator == NULL)
2240 {
2241 result = ENOMEM;
2242 goto exit;
2243 }
2244
2245 if ((result = buildkey(to_cdp, (HFSPlusCatalogKey*) &to_iterator->key)))
2246 {
2247 goto exit;
2248 }
2249
2250 recp = hfs_malloc(sizeof(CatalogRecord));
2251 if (recp == NULL)
2252 {
2253 result = ENOMEM;
2254 goto exit;
2255 }
2256 BDINIT(btdata, recp);
2257
2258 /*
2259 * When moving a directory, make sure its a valid move.
2260 */
2261 if (directory && (from_cdp->cd_parentcnid != to_cdp->cd_parentcnid))
2262 {
2263 cnid_t cnid = from_cdp->cd_cnid;
2264 cnid_t pathcnid = todir_cdp->cd_parentcnid;
2265
2266 /* First check the obvious ones */
2267 if (cnid == fsRtDirID || cnid == to_cdp->cd_parentcnid || cnid == pathcnid)
2268 {
2269 result = EINVAL;
2270 goto exit;
2271 }
2272 /* now allocate the dir_iterator */
2273 BTreeIterator* dir_iterator = hfs_mallocz(sizeof(BTreeIterator));
2274 if (dir_iterator == NULL)
2275 {
2276 result = ENOMEM;
2277 goto exit;
2278 }
2279
2280 /*
2281 * Traverse destination path all the way back to the root
2282 * making sure that source directory is not encountered.
2283 *
2284 */
2285 while (pathcnid > fsRtDirID)
2286 {
2287 buildthreadkey(pathcnid, (CatalogKey *)&dir_iterator->key);
2288 result = BTSearchRecord(fcb, dir_iterator, &btdata, &datasize, NULL);
2289 if (result)
2290 {
2291 hfs_free(dir_iterator);
2292 goto exit;
2293 }
2294 pathcnid = getparentcnid(recp);
2295 if (pathcnid == cnid || pathcnid == 0)
2296 {
2297 result = EINVAL;
2298 hfs_free(dir_iterator);
2299 goto exit;
2300 }
2301 }
2302 hfs_free(dir_iterator);
2303 }
2304
2305 /*
2306 * Step 1: Find cnode data at old location
2307 */
2308 result = BTSearchRecord(fcb, from_iterator, &btdata,
2309 &datasize, from_iterator);
2310 if (result)
2311 {
2312 if (result != btNotFound)
2313 goto exit;
2314
2315 struct cat_desc temp_desc;
2316
2317 /* Probably the node has mangled name */
2318 result = cat_lookupmangled(hfsmp, from_cdp, 0, &temp_desc, NULL, NULL);
2319 if (result)
2320 goto exit;
2321
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);
2325 if (result)
2326 {
2327 cat_releasedesc(&temp_desc);
2328 goto exit;
2329 }
2330
2331 result = BTSearchRecord(fcb, from_iterator, &btdata, &datasize, from_iterator);
2332 if (result)
2333 {
2334 cat_releasedesc(&temp_desc);
2335 goto exit;
2336 }
2337
2338 cat_releasedesc(&temp_desc);
2339 }
2340
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
2343 */
2344 if ((directory) && (recp->recordType == kHFSPlusFileRecord) && (recp->hfsPlusFile.flags & kHFSHasLinkChainMask))
2345 {
2346 is_dirlink = 1;
2347 }
2348
2349 /*
2350 * Update the text encoding (on disk and in descriptor),
2351 * using hfs_pickencoding to get the new encoding when available.
2352 *
2353 * Note that hardlink inodes don't require a text encoding hint.
2354 */
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)
2357 {
2358 encoding = kTextEncodingMacRoman;
2359
2360 hfs_setencodingbits(hfsmp, encoding);
2361 recp->hfsPlusFile.textEncoding = encoding;
2362 if (out_cdp)
2363 out_cdp->cd_encoding = encoding;
2364 }
2365
2366
2367 /* Step 2: Insert cnode at new location */
2368 result = BTInsertRecord(fcb, to_iterator, &btdata, datasize);
2369 if (result == btExists)
2370 {
2371 int fromtype = recp->recordType;
2372 cnid_t cnid = 0;
2373
2374 if (from_cdp->cd_parentcnid != to_cdp->cd_parentcnid)
2375 goto exit; /* EEXIST */
2376
2377 /* Find cnode data at new location */
2378 result = BTSearchRecord(fcb, to_iterator, &btdata, &datasize, NULL);
2379 if (result)
2380 goto exit;
2381
2382 /* Get the CNID after calling searchrecord */
2383 cnid = getcnid (recp);
2384 if (cnid == 0)
2385 {
2386 hfs_mark_inconsistent(hfsmp, HFS_INCONSISTENCY_DETECTED);
2387 result = EINVAL;
2388 goto exit;
2389 }
2390
2391 if ((fromtype != recp->recordType) || (from_cdp->cd_cnid != cnid))
2392 {
2393 result = EEXIST;
2394 goto exit; /* EEXIST */
2395 }
2396 /* The old name is a case variant and must be removed */
2397 result = BTDeleteRecord(fcb, from_iterator);
2398 if (result)
2399 goto exit;
2400
2401 /* Insert cnode (now that case duplicate is gone) */
2402 result = BTInsertRecord(fcb, to_iterator, &btdata, datasize);
2403 if (result)
2404 {
2405 /* Try and restore original before leaving */
2406 // XXXdbg
2407 {
2408 int err;
2409 err = BTInsertRecord(fcb, from_iterator, &btdata, datasize);
2410 if (err)
2411 {
2412 LFHFS_LOG(LEVEL_ERROR, "cat_create: could not undo (BTInsert = %d)\n", err);
2413 hfs_mark_inconsistent(hfsmp, HFS_ROLLBACK_FAILED);
2414 result = err;
2415 goto exit;
2416 }
2417 }
2418
2419 goto exit;
2420 }
2421 sourcegone = 1;
2422 }
2423 if (result)
2424 goto exit;
2425
2426 /* Step 3: Remove cnode from old location */
2427 if (!sourcegone)
2428 {
2429 result = BTDeleteRecord(fcb, from_iterator);
2430 if (result)
2431 {
2432 /* Try and delete new record before leaving */
2433 // XXXdbg
2434 {
2435 int err;
2436 err = BTDeleteRecord(fcb, to_iterator);
2437 if (err)
2438 {
2439 LFHFS_LOG(LEVEL_ERROR, "cat_create: could not undo (BTDelete = %d)\n", err);
2440 hfs_mark_inconsistent(hfsmp, HFS_ROLLBACK_FAILED);
2441 result = err;
2442 goto exit;
2443 }
2444 }
2445
2446 goto exit;
2447 }
2448 }
2449
2450 /* #### POINT OF NO RETURN #### */
2451
2452 /*
2453 * Step 4: Remove cnode's old thread record
2454 */
2455 buildthreadkey(from_cdp->cd_cnid, (CatalogKey *)&from_iterator->key);
2456 (void) BTDeleteRecord(fcb, from_iterator);
2457
2458 /*
2459 * Step 5: Insert cnode's new thread record
2460 * (optional for HFS files)
2461 */
2462 if (!skipthread)
2463 {
2464 /* For directory hard links, always create a file thread
2465 * record. For everything else, use the directory flag.
2466 */
2467 if (is_dirlink)
2468 {
2469 datasize = buildthread(&to_iterator->key, recp, false);
2470 }
2471 else
2472 {
2473 datasize = buildthread(&to_iterator->key, recp, directory);
2474 }
2475 btdata.itemSize = datasize;
2476 buildthreadkey(from_cdp->cd_cnid, (CatalogKey *)&from_iterator->key);
2477 result = BTInsertRecord(fcb, from_iterator, &btdata, datasize);
2478 }
2479
2480 if (out_cdp)
2481 {
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);
2485
2486 }
2487 exit:
2488 (void) BTFlushPath(fcb);
2489
2490 hfs_free(from_iterator);
2491 hfs_free(to_iterator);
2492 hfs_free(recp);
2493
2494 return MacToVFSError(result);
2495 }
2496
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;
2503 };
2504
2505 /*
2506 * catrec_update - Update the fields of a catalog record
2507 * This is called from within BTUpdateRecord.
2508 */
2509 static int
2510 catrec_update(const CatalogKey *ckp, CatalogRecord *crp, struct update_state *state)
2511 {
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;
2517
2518 switch (crp->recordType)
2519 {
2520 case kHFSPlusFolderRecord:
2521 {
2522 HFSPlusCatalogFolder *dir;
2523
2524 dir = (struct HFSPlusCatalogFolder *)crp;
2525 /* Do a quick sanity check */
2526 if (dir->folderID != attrp->ca_fileid)
2527 {
2528 LFHFS_LOG(LEVEL_DEBUG, "catrec_update: id %d != %d, vol=%s\n", dir->folderID, attrp->ca_fileid, hfsmp->vcbVN);
2529 return (btNotFound);
2530 }
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;
2542 }
2543 dir->folderCount = attrp->ca_dircount;
2544 bcopy(&attrp->ca_finderinfo[0], &dir->userInfo, 32);
2545 /*
2546 * Update the BSD Info if it was already initialized on
2547 * disk or if the runtime values have been modified.
2548 *
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.
2556 *
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.
2565 */
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)
2573 {
2574 dir->bsdInfo.ownerID = attrp->ca_uid;
2575 dir->bsdInfo.groupID = attrp->ca_gid;
2576 }
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)
2582 {
2583 dir->hl_linkCount = attrp->ca_linkcount;
2584 }
2585 }
2586 break;
2587 }
2588 case kHFSPlusFileRecord: {
2589 HFSPlusCatalogFile *file;
2590 int is_dirlink;
2591
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);
2603 /*
2604 * Note: file hardlink inodes don't require a text encoding
2605 * hint, but they do have a first link value.
2606 */
2607 if (ckp->hfsPlus.parentID == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid) {
2608 file->hl_firstLinkID = attrp->ca_firstlink;
2609 } else {
2610 file->textEncoding = descp->cd_encoding;
2611 }
2612 bcopy(&attrp->ca_finderinfo[0], &file->userInfo, 32);
2613 /*
2614 * Update the BSD Info if it was already initialized on
2615 * disk or if the runtime values have been modified.
2616 *
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.
2624 *
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.
2633 *
2634 * Do not modify bsdInfo for directory hard link records.
2635 * They are set during creation and are not modifiable, so just
2636 * leave them alone.
2637 */
2638 is_dirlink = (file->flags & kHFSHasLinkChainMask) &&
2639 (SWAP_BE32(file->userInfo.fdType) == kHFSAliasType) &&
2640 (SWAP_BE32(file->userInfo.fdCreator) == kHFSAliasCreator);
2641
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))))
2644 {
2645 if ((file->bsdInfo.fileMode == 0) || (((HFSTOVFS(hfsmp)->mnt_flag) & MNT_UNKNOWNPERMISSIONS) == 0))
2646 {
2647 file->bsdInfo.ownerID = attrp->ca_uid;
2648 file->bsdInfo.groupID = attrp->ca_gid;
2649 }
2650 file->bsdInfo.ownerFlags = attrp->ca_flags & 0x000000FF;
2651 file->bsdInfo.adminFlags = attrp->ca_flags >> 16;
2652 file->bsdInfo.fileMode = attrp->ca_mode;
2653 }
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);
2662 }
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);
2671 }
2672
2673 if ((file->resourceFork.extents[0].startBlock != 0) &&
2674 (file->resourceFork.extents[0].startBlock == file->dataFork.extents[0].startBlock))
2675 {
2676 LFHFS_LOG(LEVEL_ERROR, "catrec_update: rsrc fork == data fork");
2677 hfs_assert(0);
2678 }
2679
2680 /* Synchronize the lock state */
2681 if (attrp->ca_flags & (SF_IMMUTABLE | UF_IMMUTABLE))
2682 file->flags |= kHFSFileLockedMask;
2683 else
2684 file->flags &= ~kHFSFileLockedMask;
2685
2686 /* Push out special field if necessary */
2687 if (S_ISBLK(attrp->ca_mode) || S_ISCHR(attrp->ca_mode))
2688 {
2689 file->bsdInfo.special.rawDevice = attrp->ca_rdev;
2690 }
2691 else
2692 {
2693 /*
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.
2698 */
2699 if ((descp->cd_cnid != attrp->ca_fileid) || (attrp->ca_linkcount > 1 ) || (file->hl_linkCount > 1))
2700 {
2701 file->hl_linkCount = attrp->ca_linkcount;
2702 }
2703 }
2704 break;
2705 }
2706 default:
2707 return (btNotFound);
2708 }
2709 return (0);
2710 }
2711
2712 /*
2713 * getkey - get a key from id by doing a thread lookup
2714 */
2715 static int
2716 getkey(struct hfsmount *hfsmp, cnid_t cnid, CatalogKey * key)
2717 {
2718 FSBufferDescriptor btdata;
2719 u_int16_t datasize;
2720 CatalogKey * keyp = NULL;
2721 CatalogRecord * recp = NULL;
2722 int result = 0;
2723
2724
2725 BTreeIterator* iterator = hfs_mallocz(sizeof(BTreeIterator));
2726 if (iterator == NULL)
2727 {
2728 result = memFullErr;
2729 goto exit;
2730 }
2731 buildthreadkey(cnid, (CatalogKey *)&iterator->key);
2732
2733 recp = hfs_mallocz(sizeof(CatalogRecord));
2734 if (recp == NULL)
2735 {
2736 result = memFullErr;
2737 goto exit;
2738 }
2739 BDINIT(btdata, recp);
2740
2741 result = BTSearchRecord(VTOF(HFSTOVCB(hfsmp)->catalogRefNum), iterator, &btdata, &datasize, iterator);
2742 if (result)
2743 goto exit;
2744
2745 /* Turn thread record into a cnode key (in place) */
2746 switch (recp->recordType)
2747 {
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);
2754 break;
2755
2756 default:
2757 result = cmNotFound;
2758 break;
2759 }
2760
2761 exit:
2762 hfs_free(iterator);
2763 hfs_free(recp);
2764
2765 return MacToVFSError(result);
2766 }
2767
2768 /*
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.
2773 */
2774 static int
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)
2777 {
2778 FCB * fcb = hfsmp->hfs_catalog_cp->c_datafork;
2779 BTreeIterator * iterator;
2780 int result = 0;
2781
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;
2788
2789 /* Borrow the btcb iterator since we have an exclusive catalog lock. */
2790 iterator = &((BTreeControlBlockPtr)(fcb->ff_sysfileinfo))->iterator;
2791
2792 /*
2793 * For open-deleted files we need to do a lookup by cnid
2794 * (using thread rec).
2795 *
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.
2799 */
2800 if ((update_hardlink == false) &&
2801 ((descp->cd_cnid != attrp->ca_fileid) ||
2802 (descp->cd_namelen == 0) ||
2803 (attrp->ca_recflags & kHFSHasLinkChainMask)))
2804 {
2805 result = getkey(hfsmp, attrp->ca_fileid, (CatalogKey *)&iterator->key);
2806 }
2807 else
2808 {
2809 result = buildkey(descp, (HFSPlusCatalogKey *)&iterator->key);
2810 }
2811 if (result)
2812 goto exit;
2813
2814 /* Pass a node hint */
2815 iterator->hint.nodeNum = descp->cd_hint;
2816
2817 result = BTUpdateRecord(fcb, iterator, (IterateCallBackProcPtr)catrec_update, &state);
2818 if (result)
2819 goto exit;
2820
2821 /* Update the node hint. */
2822 descp->cd_hint = iterator->hint.nodeNum;
2823
2824 exit:
2825 (void) BTFlushPath(fcb);
2826
2827 return MacToVFSError(result);
2828 }
2829
2830 /*
2831 * cat_update - update the catalog node described by descp
2832 * using the data from attrp and forkp.
2833 */
2834 int
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)
2837 {
2838 return cat_update_internal(hfsmp, false, descp, attrp, dataforkp, rsrcforkp);
2839 }
2840
2841 /*
2842 * cat_delete - delete a node from the catalog
2843 *
2844 * Order of B-tree operations:
2845 * 1. BTDeleteRecord(cnode);
2846 * 2. BTDeleteRecord(thread);
2847 * 3. BTUpdateRecord(parent);
2848 */
2849 int
2850 cat_delete(struct hfsmount *hfsmp, struct cat_desc *descp, struct cat_attr *attrp)
2851 {
2852 FCB * fcb = hfsmp->hfs_catalog_cp->c_datafork;
2853 BTreeIterator *iterator;
2854 cnid_t cnid;
2855 int result = 0;
2856
2857 /* Preflight check:
2858 *
2859 * The root directory cannot be deleted
2860 * A directory must be empty
2861 * A file must be zero length (no blocks)
2862 */
2863 if (descp->cd_cnid < kHFSFirstUserCatalogNodeID || descp->cd_parentcnid == kHFSRootParentID)
2864 return (EINVAL);
2865
2866 /* XXX Preflight Missing */
2867
2868 /* Borrow the btcb iterator since we have an exclusive catalog lock. */
2869 iterator = &((BTreeControlBlockPtr)(fcb->ff_sysfileinfo))->iterator;
2870 iterator->hint.nodeNum = 0;
2871
2872 /*
2873 * Derive a key from either the file ID (for a virtual inode)
2874 * or the descriptor.
2875 */
2876 if (descp->cd_namelen == 0)
2877 {
2878 result = getkey(hfsmp, attrp->ca_fileid, (CatalogKey *)&iterator->key);
2879 cnid = attrp->ca_fileid;
2880 }
2881 else
2882 {
2883 result = buildkey(descp, (HFSPlusCatalogKey *)&iterator->key);
2884 cnid = descp->cd_cnid;
2885 }
2886 if (result)
2887 goto exit;
2888
2889 /* Delete record */
2890 result = BTDeleteRecord(fcb, iterator);
2891 if (result)
2892 {
2893 if (result != btNotFound)
2894 goto exit;
2895
2896 struct cat_desc temp_desc;
2897
2898 /* Probably the node has mangled name */
2899 result = cat_lookupmangled(hfsmp, descp, 0, &temp_desc, attrp, NULL);
2900 if (result)
2901 goto exit;
2902
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;
2907 if (result)
2908 {
2909 cat_releasedesc(&temp_desc);
2910 goto exit;
2911 }
2912
2913 result = BTDeleteRecord(fcb, iterator);
2914 if (result)
2915 {
2916 cat_releasedesc(&temp_desc);
2917 goto exit;
2918 }
2919
2920 cat_releasedesc(&temp_desc);
2921 }
2922
2923 /* Delete thread record. On error, mark volume inconsistent */
2924 buildthreadkey(cnid, (CatalogKey *)&iterator->key);
2925 if (BTDeleteRecord(fcb, iterator))
2926 {
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);
2929 }
2930
2931 exit:
2932 (void) BTFlushPath(fcb);
2933
2934 return MacToVFSError(result);
2935 }
2936
2937 /*
2938 * buildrecord - build a default catalog directory or file record
2939 */
2940 static void
2941 buildrecord(struct cat_attr *attrp, cnid_t cnid, u_int32_t encoding, CatalogRecord *crp, u_int32_t *recordSize)
2942 {
2943 int type = attrp->ca_mode & S_IFMT;
2944 u_int32_t createtime = to_hfs_time(attrp->ca_itime);
2945
2946 struct HFSPlusBSDInfo * bsdp = NULL;
2947
2948 if (type == S_IFDIR)
2949 {
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);
2965 }
2966 else
2967 {
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)
2983 {
2984 bsdp->special.rawDevice = attrp->ca_rdev;
2985 } else {
2986 bsdp->special.linkCount = 1;
2987 }
2988 bzero(&crp->hfsPlusFile.dataFork, 2*sizeof(HFSPlusForkData));
2989 *recordSize = sizeof(HFSPlusCatalogFile);
2990 }
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;
2996
2997 }
2998
2999 /*
3000 * cat_create - create a node in the catalog
3001 * using MacRoman encoding
3002 *
3003 * NOTE: both the catalog file and attribute file locks must
3004 * be held before calling this function.
3005 *
3006 * The caller is responsible for releasing the output
3007 * catalog descriptor (when supplied outdescp is non-null).
3008 */
3009 int
3010 cat_create(struct hfsmount *hfsmp, cnid_t new_fileid, struct cat_desc *descp, struct cat_attr *attrp, struct cat_desc *out_descp)
3011 {
3012 int result = 0;
3013
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};
3019 u_int32_t datalen;
3020 u_int32_t encoding = kTextEncodingMacRoman;
3021
3022 /* The caller is expected to reserve a CNID before calling this-> function! */
3023
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));
3028
3029 if ( (iterator == NULL) || (key == NULL) || (data == NULL) )
3030 {
3031 result =ENOMEM;
3032 goto exit;
3033 }
3034
3035 result = buildkey(descp, key);
3036 if (result)
3037 goto exit;
3038
3039 /*
3040 * Insert the thread record first
3041 */
3042 datalen = buildthread((void*)key, data, S_ISDIR(attrp->ca_mode));
3043 btdata.bufferAddress = data;
3044 btdata.itemSize = datalen;
3045 btdata.itemCount = 1;
3046
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
3051 */
3052 buildthreadkey(new_fileid, (CatalogKey *) &iterator->key);
3053 result = BTInsertRecord(fcb, iterator, &btdata, datalen);
3054 if (result)
3055 {
3056 goto exit;
3057 }
3058
3059 /*
3060 * Now insert the file/directory record
3061 */
3062 buildrecord(attrp, new_fileid, encoding, data, &datalen);
3063 btdata.bufferAddress = data;
3064 btdata.itemSize = datalen;
3065 btdata.itemCount = 1;
3066
3067 bcopy(key, &iterator->key, sizeof(HFSPlusCatalogKey));
3068
3069 result = BTInsertRecord(fcb, iterator, &btdata, datalen);
3070 if (result)
3071 {
3072 if (result == btExists)
3073 result = EEXIST;
3074
3075 /* Back out the thread record */
3076 buildthreadkey(new_fileid, (CatalogKey *)&iterator->key);
3077 if (BTDeleteRecord(fcb, iterator))
3078 {
3079 /* Error on deleting extra thread record, mark
3080 * volume inconsistent
3081 */
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);
3084 }
3085
3086 goto exit;
3087 }
3088
3089 /*
3090 * Insert was successful, update name, parent and volume
3091 */
3092 if (out_descp != NULL)
3093 {
3094 HFSPlusCatalogKey * pluskey = NULL;
3095
3096 pluskey = (HFSPlusCatalogKey *)&iterator->key;
3097
3098 builddesc(pluskey, new_fileid, iterator->hint.nodeNum, encoding, S_ISDIR(attrp->ca_mode), out_descp);
3099 }
3100 attrp->ca_fileid = new_fileid;
3101
3102 exit:
3103 (void) BTFlushPath(fcb);
3104 if (iterator)
3105 hfs_free(iterator);
3106 if (key)
3107 hfs_free(key);
3108 if (data)
3109 hfs_free(data);
3110
3111 return MacToVFSError(result);
3112 }
3113
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.
3120 */
3121 int
3122 cat_set_childlinkbit(struct hfsmount *hfsmp, cnid_t cnid)
3123 {
3124 int retval = 0;
3125 int lockflags = 0;
3126 struct cat_desc desc;
3127 struct cat_attr attr = {0};
3128
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.
3133 */
3134 retval = hfs_chash_set_childlinkbit(hfsmp, cnid);
3135 if (retval == 0) {
3136 break;
3137 }
3138
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.
3142 */
3143 retval = hfs_start_transaction(hfsmp);
3144 if (retval) {
3145 break;
3146 }
3147 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
3148
3149 /* Look up our catalog folder record */
3150 retval = cat_idlookup(hfsmp, cnid, 0, 0, &desc, &attr, NULL);
3151 if (retval) {
3152 hfs_systemfile_unlock(hfsmp, lockflags);
3153 hfs_end_transaction(hfsmp);
3154 break;
3155 }
3156
3157 /* Update the bit in the catalog record */
3158 attr.ca_recflags |= kHFSHasChildLinkMask;
3159 retval = cat_update(hfsmp, &desc, &attr, NULL, NULL);
3160 if (retval) {
3161 hfs_systemfile_unlock(hfsmp, lockflags);
3162 hfs_end_transaction(hfsmp);
3163 cat_releasedesc(&desc);
3164 break;
3165 }
3166
3167 hfs_systemfile_unlock(hfsmp, lockflags);
3168 hfs_end_transaction(hfsmp);
3169
3170 cnid = desc.cd_parentcnid;
3171 cat_releasedesc(&desc);
3172 }
3173
3174 return retval;
3175 }
3176
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
3179 * ancestors is -
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.
3184 */
3185 int
3186 cat_check_link_ancestry(struct hfsmount *hfsmp, cnid_t cnid, cnid_t pointed_at_cnid)
3187 {
3188 FSBufferDescriptor btdata;
3189 HFSPlusCatalogFolder folder;
3190 int invalid = 0;
3191 int result;
3192
3193 BDINIT(btdata, &folder);
3194 BTreeIterator* ip = hfs_mallocz(sizeof(BTreeIterator));
3195 if (ip == NULL)
3196 return ENOMEM;
3197
3198 HFSPlusCatalogKey* keyp = (HFSPlusCatalogKey *)&ip->key;
3199 FCB *fcb = hfsmp->hfs_catalog_cp->c_datafork;
3200
3201 while (cnid != kHFSRootParentID)
3202 {
3203 /* Check if the 'pointed at' directory is an ancestor */
3204 if (pointed_at_cnid == cnid)
3205 {
3206 invalid = 1;
3207 break;
3208 }
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 */
3212 break;
3213 }
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 */
3217 break;
3218 }
3219 /* Check if this ancestor is a directory hard link */
3220 if (folder.flags & kHFSHasLinkChainMask) {
3221 invalid = 1;
3222 break;
3223 }
3224 cnid = keyp->parentID;
3225 }
3226
3227 hfs_free(ip);
3228 return (invalid);
3229 }
3230
3231
3232 // --------------------------------------- Hard Link Support ---------------------------------------------
3233
3234
3235 /*
3236 * Resolve hard link reference to obtain the inode record.
3237 */
3238 int
3239 cat_resolvelink(struct hfsmount *hfsmp, u_int32_t linkref, int isdirlink, struct HFSPlusCatalogFile *recp)
3240 {
3241 FSBufferDescriptor btdata;
3242 BTreeIterator *iterator;
3243 struct cat_desc idesc;
3244 char inodename[32];
3245 cnid_t parentcnid;
3246 int result = 0;
3247
3248 BDINIT(btdata, recp);
3249
3250 if (isdirlink) {
3251 MAKE_DIRINODE_NAME(inodename, sizeof(inodename), (unsigned int)linkref);
3252 parentcnid = hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid;
3253 } else {
3254 MAKE_INODE_NAME(inodename, sizeof(inodename), (unsigned int)linkref);
3255 parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
3256 }
3257
3258 /* Get space for iterator */
3259 iterator = hfs_mallocz(sizeof(BTreeIterator));
3260 if (iterator == NULL)
3261 {
3262 return ENOMEM;
3263 }
3264
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);
3269 idesc.cd_flags = 0;
3270 idesc.cd_hint = 0;
3271 idesc.cd_encoding = 0;
3272 (void) buildkey(&idesc, (HFSPlusCatalogKey *)&iterator->key);
3273
3274 result = BTSearchRecord(VTOF(HFSTOVCB(hfsmp)->catalogRefNum), iterator,&btdata, NULL, NULL);
3275
3276 if (result == 0) {
3277 /* Make sure there's a reference */
3278 if (recp->hl_linkCount == 0)
3279 recp->hl_linkCount = 2;
3280 } else {
3281 LFHFS_LOG(LEVEL_ERROR, "cat_resolvelink: can't find inode=%s on vol=%s\n", inodename, hfsmp->vcbVN);
3282 }
3283
3284 hfs_free(iterator);
3285
3286 return (result ? ENOENT : 0);
3287 }
3288
3289 /*
3290 * Resolve hard link reference to obtain the inode number.
3291 */
3292 static int
3293 resolvelinkid(struct hfsmount *hfsmp, u_int32_t linkref, ino_t *ino)
3294 {
3295 struct HFSPlusCatalogFile record;
3296 int error;
3297
3298 /*
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).
3303 */
3304 error = cat_resolvelink(hfsmp, linkref, 0, &record);
3305 if (error == 0) {
3306 if (record.fileID == 0)
3307 error = ENOENT;
3308 else
3309 *ino = record.fileID;
3310 }
3311 return (error);
3312 }
3313
3314
3315 /*
3316 * cat_lookup_lastlink - find the last sibling link in the chain (no "next" ptr)
3317 */
3318 int
3319 cat_lookup_lastlink(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *lastlink, struct cat_desc *cdesc)
3320 {
3321 FCB * fcb;
3322 BTreeIterator * iterator;
3323 FSBufferDescriptor btdata = {0};
3324 struct HFSPlusCatalogFile file;
3325 int result = 0;
3326 int itercount = 0;
3327 int foundlast = 0;
3328 cnid_t currentlink = linkfileid;
3329
3330 fcb = hfsmp->hfs_catalog_cp->c_datafork;
3331
3332 /* Create an iterator for use by us temporarily */
3333 iterator = hfs_mallocz(sizeof(*iterator));
3334 if (iterator == NULL)
3335 return ENOMEM;
3336
3337 while ((foundlast == 0) && (itercount < HFS_LINK_MAX )) {
3338 itercount++;
3339 bzero(iterator, sizeof(*iterator));
3340
3341 if ((result = getkey(hfsmp, currentlink, (CatalogKey *)&iterator->key))) {
3342 goto exit;
3343 }
3344 BDINIT(btdata, &file);
3345
3346 if ((result = BTSearchRecord(fcb, iterator, &btdata, NULL, NULL))) {
3347 goto exit;
3348 }
3349
3350 /* The prev/next chain is only valid when kHFSHasLinkChainMask is set. */
3351 if (file.flags & kHFSHasLinkChainMask) {
3352 cnid_t parent;
3353
3354 parent = ((HFSPlusCatalogKey *)&iterator->key)->parentID;
3355 /*
3356 * The raw inode for a directory hardlink doesn't have a chain.
3357 * Its link information lives in an EA.
3358 */
3359 if (parent == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
3360 /* We don't iterate to find the oldest directory hardlink. */
3361 result = ENOLINK;
3362 goto exit;
3363 }
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;
3367
3368 /*
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.
3373 */
3374 if (currentlink == 0) {
3375 result = ENOLINK;
3376 goto exit;
3377 }
3378 }
3379 else {
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 */
3383 foundlast = 1;
3384 *lastlink = currentlink;
3385 /*
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.
3389 */
3390 builddesc ((HFSPlusCatalogKey*)&iterator->key, currentlink, 0, 0, 0, cdesc);
3391 break;
3392 }
3393
3394 currentlink = file.hl_nextLinkID;
3395 }
3396 }
3397 else {
3398 /* Sorry, can't help you without a link chain */
3399 result = ENOLINK;
3400 goto exit;
3401 }
3402 }
3403 exit:
3404 /* If we didn't find what we were looking for, zero out the args */
3405 if (foundlast == 0) {
3406 if (cdesc) {
3407 bzero (cdesc, sizeof(struct cat_desc));
3408 }
3409 if (lastlink) {
3410 *lastlink = 0;
3411 }
3412 }
3413
3414 hfs_free(iterator);
3415 return MacToVFSError(result);
3416 }
3417
3418 /*
3419 * cat_lookuplink - lookup a link by it's name
3420 */
3421 int
3422 cat_lookuplink(struct hfsmount *hfsmp, struct cat_desc *descp, cnid_t *linkfileid, cnid_t *prevlinkid, cnid_t *nextlinkid)
3423 {
3424 FCB * fcb;
3425 BTreeIterator * iterator;
3426 FSBufferDescriptor btdata;
3427 struct HFSPlusCatalogFile file;
3428 int result;
3429
3430 fcb = hfsmp->hfs_catalog_cp->c_datafork;
3431
3432 /* Create an iterator for use by us temporarily */
3433 iterator = hfs_mallocz(sizeof(*iterator));
3434 if (iterator == NULL)
3435 return ENOMEM;
3436
3437 if ((result = buildkey(descp, (HFSPlusCatalogKey *)&iterator->key))) {
3438 goto exit;
3439 }
3440 BDINIT(btdata, &file);
3441
3442 if ((result = BTSearchRecord(fcb, iterator, &btdata, NULL, NULL))) {
3443 goto exit;
3444 }
3445 if (file.recordType != kHFSPlusFileRecord) {
3446 result = ENOENT;
3447 goto exit;
3448 }
3449 *linkfileid = file.fileID;
3450
3451 if (file.flags & kHFSHasLinkChainMask) {
3452 *prevlinkid = file.hl_prevLinkID;
3453 *nextlinkid = file.hl_nextLinkID;
3454 } else {
3455 *prevlinkid = 0;
3456 *nextlinkid = 0;
3457 }
3458 exit:
3459 hfs_free(iterator);
3460 return MacToVFSError(result);
3461 }
3462
3463 /*
3464 * cat_deletelink - delete a link from the catalog
3465 */
3466 int
3467 cat_deletelink(struct hfsmount *hfsmp, struct cat_desc *descp)
3468 {
3469 struct HFSPlusCatalogFile file = {0};
3470 struct cat_attr cattr = {0};
3471 uint32_t totalBlocks;
3472 int result = 0;
3473
3474 cattr.ca_fileid = descp->cd_cnid;
3475
3476 /* Directory links have alias content to remove. */
3477 if (descp->cd_flags & CD_ISDIR) {
3478 FCB * fcb;
3479 BTreeIterator * iterator;
3480 FSBufferDescriptor btdata;
3481
3482 fcb = hfsmp->hfs_catalog_cp->c_datafork;
3483
3484 /* Borrow the btcb iterator since we have an exclusive catalog lock. */
3485 iterator = &((BTreeControlBlockPtr)(fcb->ff_sysfileinfo))->iterator;
3486 iterator->hint.nodeNum = 0;
3487
3488 if ((result = buildkey(descp, (HFSPlusCatalogKey *)&iterator->key))) {
3489 goto exit;
3490 }
3491 BDINIT(btdata, &file);
3492
3493 if ((result = BTSearchRecord(fcb, iterator, &btdata, NULL, NULL))) {
3494 goto exit;
3495 }
3496 }
3497
3498 result = cat_delete(hfsmp, descp, &cattr);
3499
3500 if ((result == 0) &&
3501 (descp->cd_flags & CD_ISDIR) &&
3502 (file.recordType == kHFSPlusFileRecord)) {
3503
3504 totalBlocks = file.resourceFork.totalBlocks;
3505
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)) {
3509 break;
3510 }
3511
3512 (void) BlockDeallocate(hfsmp,file.resourceFork.extents[i].startBlock,file.resourceFork.extents[i].blockCount, 0);
3513
3514 totalBlocks -= file.resourceFork.extents[i].blockCount;
3515 file.resourceFork.extents[i].startBlock = 0;
3516 file.resourceFork.extents[i].blockCount = 0;
3517 }
3518 }
3519 exit:
3520 return (result);
3521 }
3522
3523 /*
3524 * update_siblinglinks_callback - update a link's chain
3525 */
3526
3527 struct linkupdate_state {
3528 cnid_t filelinkid;
3529 cnid_t prevlinkid;
3530 cnid_t nextlinkid;
3531 };
3532
3533 static int
3534 update_siblinglinks_callback(__unused const CatalogKey *ckp, CatalogRecord *crp, struct linkupdate_state *state)
3535 {
3536 HFSPlusCatalogFile *file;
3537
3538 if (crp->recordType != kHFSPlusFileRecord) {
3539 LFHFS_LOG(LEVEL_ERROR, "update_siblinglinks_callback: unexpected rec type %d\n", crp->recordType);
3540 return (btNotFound);
3541 }
3542
3543 file = (struct HFSPlusCatalogFile *)crp;
3544 if (file->flags & kHFSHasLinkChainMask) {
3545 if (state->prevlinkid != HFS_IGNORABLE_LINK) {
3546 file->hl_prevLinkID = state->prevlinkid;
3547 }
3548 if (state->nextlinkid != HFS_IGNORABLE_LINK) {
3549 file->hl_nextLinkID = state->nextlinkid;
3550 }
3551 } else {
3552 LFHFS_LOG(LEVEL_ERROR, "update_siblinglinks_callback: file %d isn't a chain\n", file->fileID);
3553 }
3554 return (0);
3555 }
3556
3557 /*
3558 * cat_update_siblinglinks - update a link's chain
3559 */
3560 int
3561 cat_update_siblinglinks(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t prevlinkid, cnid_t nextlinkid)
3562 {
3563 FCB * fcb;
3564 BTreeIterator * iterator;
3565 struct linkupdate_state state;
3566 int result;
3567
3568 fcb = hfsmp->hfs_catalog_cp->c_datafork;
3569 state.filelinkid = linkfileid;
3570 state.prevlinkid = prevlinkid;
3571 state.nextlinkid = nextlinkid;
3572
3573 /* Create an iterator for use by us temporarily */
3574 iterator = hfs_mallocz(sizeof(*iterator));
3575 if (iterator == NULL)
3576 return ENOMEM;
3577
3578 result = getkey(hfsmp, linkfileid, (CatalogKey *)&iterator->key);
3579 if (result == 0) {
3580 result = BTUpdateRecord(fcb, iterator, (IterateCallBackProcPtr)update_siblinglinks_callback, &state);
3581 (void) BTFlushPath(fcb);
3582 } else {
3583 LFHFS_LOG(LEVEL_ERROR, "cat_update_siblinglinks: couldn't resolve cnid=%d, vol=%s\n", linkfileid, hfsmp->vcbVN);
3584 }
3585
3586 hfs_free(iterator);
3587 return MacToVFSError(result);
3588 }
3589
3590 void
3591 cat_convertattr(
3592 struct hfsmount *hfsmp,
3593 CatalogRecord * recp,
3594 struct cat_attr *attrp,
3595 struct cat_fork *datafp,
3596 struct cat_fork *rsrcfp)
3597 {
3598 getbsdattr(hfsmp, (struct HFSPlusCatalogFile *)recp, attrp);
3599
3600 if (isadir(recp))
3601 {
3602 bzero(datafp, sizeof(*datafp));
3603 }else {
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));
3612
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));
3621 }
3622 }
3623
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.
3627 *
3628 * Note: This code is very specific to creating alias for the purpose
3629 * of directory hard links only, and should not be generalized.
3630 */
3631 static int
3632 cat_makealias(struct hfsmount *hfsmp, u_int32_t inode_num, struct HFSPlusCatalogFile *crp)
3633 {
3634 GenericLFBufPtr bp = NULL;
3635 daddr64_t blkno;
3636 u_int32_t blkcount;
3637 int blksize;
3638 int sectorsize;
3639 int result;
3640 HFSPlusForkData *rsrcforkp;
3641 char *alias;
3642 uint32_t *valptr;
3643
3644 rsrcforkp = &(crp->resourceFork);
3645
3646 blksize = hfsmp->blockSize;
3647 blkcount = howmany(kHFSAliasSize, blksize);
3648 sectorsize = hfsmp->hfs_logical_block_size;
3649 bzero(rsrcforkp, sizeof(HFSPlusForkData));
3650
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);
3662 }
3663
3664 if (result) {
3665 rsrcforkp->extents[0].startBlock = 0;
3666 goto exit;
3667 }
3668
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;
3672
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);
3675 if (result) {
3676 goto exit;
3677 }
3678
3679 if (hfsmp->jnl) {
3680 journal_modify_block_start(hfsmp->jnl, bp);
3681 }
3682
3683 /* Generate alias content */
3684 alias = (char *)bp->pvData;
3685 bzero(alias, bp->uDataSize);
3686 bcopy(hfs_dirlink_alias_rsrc, alias, kHFSAliasSize);
3687
3688 /* Set the volume create date, local time in Mac OS format */
3689 valptr = (uint32_t *)(alias + kHFSAliasVolCreateDateOffset);
3690 *valptr = OSSwapHostToBigInt32(hfsmp->localCreateDate);
3691
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);
3695
3696 /* Set id of the target directory */
3697 valptr = (uint32_t *)(alias + kHFSAliasTargetIDOffset);
3698 *valptr = OSSwapHostToBigInt32(inode_num);
3699
3700 /* Write alias content to disk. */
3701 if (hfsmp->jnl) {
3702 journal_modify_block_end(hfsmp->jnl, bp, NULL, NULL);
3703 } else
3704
3705 if ((result = lf_hfs_generic_buf_write(bp))) {
3706 goto exit;
3707 }
3708
3709 /* Finish initializing the fork data. */
3710 rsrcforkp->logicalSize = kHFSAliasSize;
3711 rsrcforkp->totalBlocks = rsrcforkp->extents[0].blockCount;
3712
3713 exit:
3714 if (bp) {
3715 lf_hfs_generic_buf_release(bp);
3716 }
3717
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;
3724 }
3725 return (result);
3726 }
3727
3728 /*
3729 * cat_createlink - create a link in the catalog
3730 *
3731 * The following cat_attr fields are expected to be set:
3732 * ca_linkref
3733 * ca_itime
3734 * ca_mode (S_IFREG)
3735 * ca_recflags
3736 * ca_flags
3737 * ca_finderinfo (type and creator)
3738 */
3739 int
3740 cat_createlink(struct hfsmount *hfsmp, struct cat_desc *descp, struct cat_attr *attrp, cnid_t nextlinkid, cnid_t *linkfileid)
3741 {
3742 FCB * fcb;
3743 struct btobj * bto;
3744 FSBufferDescriptor btdata;
3745 HFSPlusForkData *rsrcforkp;
3746 u_int32_t nextCNID;
3747 u_int32_t datalen;
3748 int thread_inserted = 0;
3749 int alias_allocated = 0;
3750 int result = 0;
3751
3752 fcb = hfsmp->hfs_catalog_cp->c_datafork;
3753
3754 /*
3755 * Get the next CNID. Note that we are currently holding catalog lock.
3756 */
3757 result = cat_acquire_cnid(hfsmp, &nextCNID);
3758 if (result) {
3759 return result;
3760 }
3761
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;
3766
3767 result = buildkey(descp, &bto->key);
3768 if (result) {
3769 LFHFS_LOG(LEVEL_ERROR, "cat_createlink: err %d from buildkey\n", result);
3770 goto exit;
3771 }
3772
3773 /*
3774 * Insert the thread record first.
3775 */
3776 datalen = buildthread((void*)&bto->key, &bto->data, 0);
3777 btdata.bufferAddress = &bto->data;
3778 btdata.itemSize = datalen;
3779 btdata.itemCount = 1;
3780
3781 buildthreadkey(nextCNID, (CatalogKey *) &bto->iterator.key);
3782 result = BTInsertRecord(fcb, &bto->iterator, &btdata, datalen);
3783 if (result) {
3784 goto exit;
3785 }
3786 thread_inserted = 1;
3787
3788 /*
3789 * Now insert the link record.
3790 */
3791 buildrecord(attrp, nextCNID, kTextEncodingMacUnicode, &bto->data, &datalen);
3792
3793 bto->data.hfsPlusFile.hl_prevLinkID = 0;
3794 bto->data.hfsPlusFile.hl_nextLinkID = nextlinkid;
3795 bto->data.hfsPlusFile.hl_linkReference = attrp->ca_linkref;
3796
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))) {
3800 goto exit;
3801 }
3802 alias_allocated = 1;
3803 }
3804 btdata.bufferAddress = &bto->data;
3805 btdata.itemSize = datalen;
3806 btdata.itemCount = 1;
3807
3808 bcopy(&bto->key, &bto->iterator.key, sizeof(bto->key));
3809
3810 result = BTInsertRecord(fcb, &bto->iterator, &btdata, datalen);
3811 if (result) {
3812 if (result == btExists)
3813 result = EEXIST;
3814 goto exit;
3815 }
3816 if (linkfileid != NULL) {
3817 *linkfileid = nextCNID;
3818 }
3819 exit:
3820 if (result) {
3821 if (thread_inserted) {
3822 LFHFS_LOG(LEVEL_ERROR, "cat_createlink: BTInsertRecord err=%d, vol=%s\n", MacToVFSError(result), hfsmp->vcbVN);
3823
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);
3828 }
3829 }
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;
3835 }
3836 }
3837 (void) BTFlushPath(fcb);
3838 hfs_free(bto);
3839
3840 return MacToVFSError(result);
3841 }