1/*
2 * Copyright (c) 2000-2008 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29#include <sys/systm.h>
30#include <sys/kernel.h>
31#include <sys/malloc.h>
32#include <sys/stat.h>
33#include <sys/mount.h>
34#include <sys/vnode.h>
35#include <sys/dirent.h>
36#include <vfs/vfs_support.h>
37#include <libkern/libkern.h>
38
39#include <sys/utfconv.h>
40
41#include "hfs.h"
42#include "hfs_catalog.h"
43#include "hfs_format.h"
44#include "hfs_endian.h"
45
46#include "hfscommon/headers/BTreesInternal.h"
47#include "hfscommon/headers/BTreesPrivate.h"
48#include "hfscommon/headers/HFSUnicodeWrappers.h"
49
50
51/*
52 * Initialization of an FSBufferDescriptor structure.
53 */
54#define BDINIT(bd, addr) { \
55	(bd).bufferAddress = (addr); \
56	(bd).itemSize = sizeof(*(addr)); \
57	(bd).itemCount = 1; \
58}
59
60
61struct btobj {
62	BTreeIterator		iterator;
63	HFSPlusCatalogKey 	key;
64	CatalogRecord		data;
65};
66
67struct update_state {
68	struct cat_desc *	s_desc;
69	struct cat_attr *	s_attr;
70	struct cat_fork *	s_datafork;
71	struct cat_fork *	s_rsrcfork;
72	struct hfsmount *	s_hfsmp;
73};
74
75struct position_state {
76	int        error;
77	u_int32_t  count;
78	u_int32_t  index;
79	u_int32_t  parentID;
80	struct hfsmount *hfsmp;
81};
82
83/* Map file mode type to directory entry types */
84u_char modetodirtype[16] = {
85	DT_REG, DT_FIFO, DT_CHR, DT_UNKNOWN,
86	DT_DIR, DT_UNKNOWN, DT_BLK, DT_UNKNOWN,
87	DT_REG, DT_UNKNOWN, DT_LNK, DT_UNKNOWN,
88	DT_SOCK, DT_UNKNOWN, DT_WHT, DT_UNKNOWN
89};
90#define MODE_TO_DT(mode)  (modetodirtype[((mode) & S_IFMT) >> 12])
91
92
93static int cat_lookupbykey(struct hfsmount *hfsmp, CatalogKey *keyp, int allow_system_files, u_long hint, int wantrsrc,
94                  struct cat_desc *descp, struct cat_attr *attrp, struct cat_fork *forkp, cnid_t *desc_cnid);
95
96static int cat_lookupmangled(struct hfsmount *hfsmp, struct cat_desc *descp, int wantrsrc,
97                  struct cat_desc *outdescp, struct cat_attr *attrp, struct cat_fork *forkp);
98
99/* Internal catalog support routines */
100
101static int cat_findposition(const CatalogKey *ckp, const CatalogRecord *crp,
102                            struct position_state *state);
103
104static int resolvelinkid(struct hfsmount *hfsmp, u_long linkref, ino_t *ino);
105
106static int getkey(struct hfsmount *hfsmp, cnid_t cnid, CatalogKey * key);
107
108static int buildkey(struct hfsmount *hfsmp, struct cat_desc *descp,
109			HFSPlusCatalogKey *key, int retry);
110
111static void buildthreadkey(HFSCatalogNodeID parentID, int std_hfs, CatalogKey *key);
112
113static void buildrecord(struct cat_attr *attrp, cnid_t cnid, int std_hfs, u_int32_t encoding, CatalogRecord *crp, u_int32_t *recordSize);
114
115static int catrec_update(const CatalogKey *ckp, CatalogRecord *crp, struct update_state *state);
116
117static int builddesc(const HFSPlusCatalogKey *key, cnid_t cnid, u_long hint, u_long encoding,
118			int isdir, struct cat_desc *descp);
119
120static void getbsdattr(struct hfsmount *hfsmp, const struct HFSPlusCatalogFile *crp, struct cat_attr * attrp);
121
122static void promotekey(struct hfsmount *hfsmp, const HFSCatalogKey *hfskey, HFSPlusCatalogKey *keyp, u_long *encoding);
123static void promotefork(struct hfsmount *hfsmp, const struct HFSCatalogFile *file, int resource, struct cat_fork * forkp);
124static void promoteattr(struct hfsmount *hfsmp, const CatalogRecord *dataPtr, struct HFSPlusCatalogFile *crp);
125
126static cnid_t getcnid(const CatalogRecord *crp);
127static u_long getencoding(const CatalogRecord *crp);
128static cnid_t getparentcnid(const CatalogRecord *recp);
129
130static int isadir(const CatalogRecord *crp);
131
132static int buildthread(void *keyp, void *recp, int std_hfs, int directory);
133
134static int cat_makealias(struct hfsmount *hfsmp, u_int32_t inode_num, struct HFSPlusCatalogFile *crp);
135
136
137__private_extern__
138int
139cat_preflight(struct hfsmount *hfsmp, catops_t ops, cat_cookie_t *cookie, __unused proc_t p)
140{
141	int lockflags = 0;
142	int result;
143
144	if (hfsmp->hfs_catalog_cp->c_lockowner != current_thread())
145		lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
146
147	result = BTReserveSpace(hfsmp->hfs_catalog_cp->c_datafork, ops, (void*)cookie);
148
149	if (lockflags)
150		hfs_systemfile_unlock(hfsmp, lockflags);
151
152	return MacToVFSError(result);
153}
154
155__private_extern__
156void
157cat_postflight(struct hfsmount *hfsmp, cat_cookie_t *cookie, __unused proc_t p)
158{
159	int lockflags = 0;
160
161	if (hfsmp->hfs_catalog_cp->c_lockowner != current_thread())
162		lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
163
164	(void) BTReleaseReserve(hfsmp->hfs_catalog_cp->c_datafork, (void*)cookie);
165
166	if (lockflags)
167		hfs_systemfile_unlock(hfsmp, lockflags);
168}
169
170
171__private_extern__
172void
173cat_convertattr(
174	struct hfsmount *hfsmp,
175	CatalogRecord * recp,
176	struct cat_attr *attrp,
177	struct cat_fork *datafp,
178	struct cat_fork *rsrcfp)
179{
180	int std_hfs = HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord;
181
182	if (std_hfs) {
183		struct HFSPlusCatalogFile cnoderec;
184
185		promoteattr(hfsmp, recp, &cnoderec);
186		getbsdattr(hfsmp, &cnoderec, attrp);
187	} else {
188		getbsdattr(hfsmp, (struct HFSPlusCatalogFile *)recp, attrp);
189	}
190
191	if (isadir(recp))
192		bzero(datafp, sizeof(*datafp));
193	else if (std_hfs) {
194		promotefork(hfsmp, (HFSCatalogFile *)&recp->hfsFile, 0, datafp);
195		promotefork(hfsmp, (HFSCatalogFile *)&recp->hfsFile, 1, rsrcfp);
196	} else {
197		/* Convert the data fork. */
198		datafp->cf_size = recp->hfsPlusFile.dataFork.logicalSize;
199		datafp->cf_new_size = 0;
200		datafp->cf_blocks = recp->hfsPlusFile.dataFork.totalBlocks;
201		if ((hfsmp->hfc_stage == HFC_RECORDING) &&
202		    (attrp->ca_atime >= hfsmp->hfc_timebase)) {
203			datafp->cf_bytesread =
204				recp->hfsPlusFile.dataFork.clumpSize *
205				HFSTOVCB(hfsmp)->blockSize;
206		} else {
207			datafp->cf_bytesread = 0;
208		}
209		datafp->cf_vblocks = 0;
210		bcopy(&recp->hfsPlusFile.dataFork.extents[0],
211		      &datafp->cf_extents[0], sizeof(HFSPlusExtentRecord));
212
213		/* Convert the resource fork. */
214		rsrcfp->cf_size = recp->hfsPlusFile.resourceFork.logicalSize;
215		rsrcfp->cf_new_size = 0;
216		rsrcfp->cf_blocks = recp->hfsPlusFile.resourceFork.totalBlocks;
217		if ((hfsmp->hfc_stage == HFC_RECORDING) &&
218		    (attrp->ca_atime >= hfsmp->hfc_timebase)) {
219			datafp->cf_bytesread =
220				recp->hfsPlusFile.resourceFork.clumpSize *
221				HFSTOVCB(hfsmp)->blockSize;
222		} else {
223			datafp->cf_bytesread = 0;
224		}
225		rsrcfp->cf_vblocks = 0;
226		bcopy(&recp->hfsPlusFile.resourceFork.extents[0],
227		      &rsrcfp->cf_extents[0], sizeof(HFSPlusExtentRecord));
228	}
229}
230
231/*
232 * Convert a raw catalog key and record into an in-core catalog descriptor.
233 *
234 * Note: The caller is responsible for releasing the catalog descriptor.
235 */
236__private_extern__
237int
238cat_convertkey(
239	struct hfsmount *hfsmp,
240	CatalogKey *key,
241	CatalogRecord * recp,
242	struct cat_desc *descp)
243{
244	int std_hfs = HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord;
245	HFSPlusCatalogKey * pluskey = NULL;
246	u_long encoding;
247
248	if (std_hfs) {
249		MALLOC(pluskey, HFSPlusCatalogKey *, sizeof(HFSPlusCatalogKey), M_TEMP, M_WAITOK);
250		promotekey(hfsmp, (HFSCatalogKey *)key, pluskey, &encoding);
251
252	} else {
253		pluskey = (HFSPlusCatalogKey *)key;
254		encoding = getencoding(recp);
255	}
256
257	builddesc(pluskey, getcnid(recp), 0, encoding, isadir(recp), descp);
258	if (std_hfs) {
259		FREE(pluskey, M_TEMP);
260	}
261	return (0);
262}
263
264
265/*
266 * cat_releasedesc
267 */
268__private_extern__
269void
270cat_releasedesc(struct cat_desc *descp)
271{
272	const u_int8_t * name;
273
274	if (descp == NULL)
275		return;
276
277	if ((descp->cd_flags & CD_HASBUF) &&
278	    (descp->cd_nameptr != NULL)) {
279	    	name = descp->cd_nameptr;
280		descp->cd_nameptr = NULL;
281		descp->cd_namelen = 0;
282		vfs_removename((const char *)name);
283	}
284	descp->cd_nameptr = NULL;
285	descp->cd_namelen = 0;
286	descp->cd_flags &= ~CD_HASBUF;
287}
288
289/*
290 * These Catalog functions allow access to the HFS Catalog (database).
291 * The catalog b-tree lock must be acquired before calling any of these routines.
292 */
293
294/*
295 * cat_lookup - lookup a catalog node using a cnode descriptor
296 *
297 * Note: The caller is responsible for releasing the output
298 * catalog descriptor (when supplied outdescp is non-null).
299 */
300__private_extern__
301int
302cat_lookup(struct hfsmount *hfsmp, struct cat_desc *descp, int wantrsrc,
303             struct cat_desc *outdescp, struct cat_attr *attrp,
304             struct cat_fork *forkp, cnid_t *desc_cnid)
305{
306	CatalogKey * keyp;
307	int std_hfs;
308	int result;
309
310	std_hfs = (HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord);
311
312	MALLOC(keyp, CatalogKey *, sizeof(CatalogKey), M_TEMP, M_WAITOK);
313
314	result = buildkey(hfsmp, descp, (HFSPlusCatalogKey *)keyp, 1);
315	if (result)
316		goto exit;
317
318	result = cat_lookupbykey(hfsmp, keyp, 0, descp->cd_hint, wantrsrc, outdescp, attrp, forkp, desc_cnid);
319
320	if (result == ENOENT) {
321		if (!std_hfs) {
322			struct cat_desc temp_desc;
323			if (outdescp == NULL) {
324				bzero(&temp_desc, sizeof(temp_desc));
325				outdescp = &temp_desc;
326			}
327			result = cat_lookupmangled(hfsmp, descp, wantrsrc, outdescp, attrp, forkp);
328			if (desc_cnid) {
329			    *desc_cnid = outdescp->cd_cnid;
330			}
331			if (outdescp == &temp_desc) {
332				/* Release the local copy of desc */
333				cat_releasedesc(outdescp);
334			}
335		} else if (hfsmp->hfs_encoding != kTextEncodingMacRoman) {
336		//	make MacRoman key from utf-8
337		//	result = cat_lookupbykey(hfsmp, keyp, descp->cd_hint, attrp, forkp);
338		//	update desc text encoding so that other catalog ops succeed
339		}
340	}
341exit:
342	FREE(keyp, M_TEMP);
343
344	return (result);
345}
346
347__private_extern__
348int
349cat_insertfilethread(struct hfsmount *hfsmp, struct cat_desc *descp)
350{
351	struct BTreeIterator *iterator;
352	struct FSBufferDescriptor file_data;
353	struct HFSCatalogFile file_rec;
354	u_int16_t datasize;
355	FCB *fcb;
356	int result;
357
358	if (HFSTOVCB(hfsmp)->vcbSigWord != kHFSSigWord)
359		return (EINVAL);
360
361	fcb = GetFileControlBlock(HFSTOVCB(hfsmp)->catalogRefNum);
362
363	MALLOC(iterator, BTreeIterator *, 2 * sizeof(*iterator), M_TEMP, M_WAITOK);
364	bzero(&iterator[0], 2* sizeof(*iterator));
365	result = buildkey(hfsmp, descp, (HFSPlusCatalogKey *)&iterator[0].key, 0);
366	if (result)
367		goto exit;
368
369	BDINIT(file_data, &file_rec);
370	result = BTSearchRecord(fcb, &iterator[0], &file_data, &datasize, &iterator[0]);
371	if (result)
372		goto exit;
373
374	if (file_rec.recordType != kHFSFileRecord) {
375		result = EISDIR;
376		goto exit;
377	}
378
379	if ((file_rec.flags & kHFSThreadExistsMask) == 0) {
380		struct FSBufferDescriptor thread_data;
381		struct HFSCatalogThread thread_rec;
382
383		file_rec.flags |= kHFSThreadExistsMask;
384		BDINIT(thread_data, &thread_rec);
385		thread_data.itemSize = buildthread(&iterator[0].key, &thread_rec, 1, 0);
386		buildthreadkey(file_rec.fileID, 1, (CatalogKey *)&iterator[1].key);
387
388		result = BTInsertRecord(fcb, &iterator[1], &thread_data, thread_data.itemSize);
389		if (result)
390			goto exit;
391
392		(void) BTReplaceRecord(fcb, &iterator[0], &file_data, datasize);
393		(void) BTFlushPath(fcb);
394	}
395exit:
396	(void) BTFlushPath(fcb);
397	FREE(iterator, M_TEMP);
398
399	return MacToVFSError(result);
400}
401
402
403/*
404 * cat_findname - obtain a descriptor from cnid
405 *
406 * Only a thread lookup is performed.
407 *
408 * Note: The caller is responsible for releasing the output
409 * catalog descriptor (when supplied outdescp is non-null).
410
411 */
412__private_extern__
413int
414cat_findname(struct hfsmount *hfsmp, cnid_t cnid, struct cat_desc *outdescp)
415{
416	struct BTreeIterator * iterator;
417	FSBufferDescriptor btdata;
418	CatalogKey * keyp;
419	CatalogRecord * recp;
420	int isdir;
421	int result;
422	int std_hfs;
423
424	isdir = 0;
425	std_hfs = (hfsmp->hfs_flags & HFS_STANDARD);
426
427	MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
428	buildthreadkey(cnid, std_hfs, (CatalogKey *)&iterator->key);
429	iterator->hint.nodeNum = 0;
430
431	MALLOC(recp, CatalogRecord *, sizeof(CatalogRecord), M_TEMP, M_WAITOK);
432	BDINIT(btdata, recp);
433
434	result = BTSearchRecord(VTOF(hfsmp->hfs_catalog_vp), iterator, &btdata, NULL, NULL);
435	if (result)
436		goto exit;
437
438	/* Turn thread record into a cnode key (in place). */
439	switch (recp->recordType) {
440	case kHFSFolderThreadRecord:
441		isdir = 1;
442		/* fall through */
443	case kHFSFileThreadRecord:
444		keyp = (CatalogKey *)((char *)&recp->hfsThread.reserved + 6);
445		keyp->hfs.keyLength = kHFSCatalogKeyMinimumLength + keyp->hfs.nodeName[0];
446		break;
447
448	case kHFSPlusFolderThreadRecord:
449		isdir = 1;
450		/* fall through */
451	case kHFSPlusFileThreadRecord:
452		keyp = (CatalogKey *)&recp->hfsPlusThread.reserved;
453		keyp->hfsPlus.keyLength = kHFSPlusCatalogKeyMinimumLength +
454		                          (keyp->hfsPlus.nodeName.length * 2);
455		break;
456	default:
457		result = ENOENT;
458		goto exit;
459	}
460	if (std_hfs) {
461		HFSPlusCatalogKey * pluskey = NULL;
462		u_long encoding;
463
464		MALLOC(pluskey, HFSPlusCatalogKey *, sizeof(HFSPlusCatalogKey), M_TEMP, M_WAITOK);
465		promotekey(hfsmp, &keyp->hfs, pluskey, &encoding);
466		builddesc(pluskey, cnid, 0, encoding, isdir, outdescp);
467		FREE(pluskey, M_TEMP);
468
469	} else {
470		builddesc((HFSPlusCatalogKey *)keyp, cnid, 0, 0, isdir, outdescp);
471	}
472exit:
473	FREE(recp, M_TEMP);
474	FREE(iterator, M_TEMP);
475
476	return MacToVFSError(result);
477}
478
479/*
480 * cat_idlookup - lookup a catalog node using a cnode id
481 *
482 * Note: The caller is responsible for releasing the output
483 * catalog descriptor (when supplied outdescp is non-null).
484 */
485__private_extern__
486int
487cat_idlookup(struct hfsmount *hfsmp, cnid_t cnid, int allow_system_files,
488    struct cat_desc *outdescp, struct cat_attr *attrp, struct cat_fork *forkp)
489{
490	struct BTreeIterator * iterator;
491	FSBufferDescriptor btdata;
492	u_int16_t	datasize;
493	CatalogKey * keyp;
494	CatalogRecord * recp;
495	int result;
496	int std_hfs;
497
498	std_hfs = (HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord);
499
500	MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
501	bzero(iterator, sizeof(*iterator));
502	buildthreadkey(cnid, std_hfs, (CatalogKey *)&iterator->key);
503
504	MALLOC(recp, CatalogRecord *, sizeof(CatalogRecord), M_TEMP, M_WAITOK);
505	BDINIT(btdata, recp);
506
507	result = BTSearchRecord(VTOF(HFSTOVCB(hfsmp)->catalogRefNum), iterator,
508				&btdata, &datasize, iterator);
509	if (result)
510		goto exit;
511
512	/* Turn thread record into a cnode key (in place) */
513	switch (recp->recordType) {
514	case kHFSFileThreadRecord:
515	case kHFSFolderThreadRecord:
516		keyp = (CatalogKey *)((char *)&recp->hfsThread.reserved + 6);
517		keyp->hfs.keyLength = kHFSCatalogKeyMinimumLength + keyp->hfs.nodeName[0];
518		break;
519
520	case kHFSPlusFileThreadRecord:
521	case kHFSPlusFolderThreadRecord:
522		keyp = (CatalogKey *)&recp->hfsPlusThread.reserved;
523		keyp->hfsPlus.keyLength = kHFSPlusCatalogKeyMinimumLength +
524		                          (keyp->hfsPlus.nodeName.length * 2);
525		break;
526
527	default:
528		result = ENOENT;
529		goto exit;
530	}
531
532	result = cat_lookupbykey(hfsmp, keyp, allow_system_files, 0, 0, outdescp, attrp, forkp, NULL);
533	/* No corresponding file/folder record found for a thread record,
534	 * mark the volume inconsistent.
535	 */
536	if (result == 0 && outdescp) {
537		cnid_t dcnid = outdescp->cd_cnid;
538		/*
539		 * Just for sanity's case, let's make sure that
540		 * the key in the thread matches the key in the record.
541		 */
542		if (cnid != dcnid) {
543			printf("Requested cnid (%d / %08x) != dcnid (%d / %08x)\n", cnid, cnid, dcnid, dcnid);
544			result = ENOENT;
545		}
546	}
547exit:
548	FREE(recp, M_TEMP);
549	FREE(iterator, M_TEMP);
550
551	return MacToVFSError(result);
552}
553
554
555/*
556 * cat_lookupmangled - lookup a catalog node using a mangled name
557 */
558static int
559cat_lookupmangled(struct hfsmount *hfsmp, struct cat_desc *descp, int wantrsrc,
560                  struct cat_desc *outdescp, struct cat_attr *attrp, struct cat_fork *forkp)
561{
562	cnid_t fileID;
563	u_int32_t prefixlen;
564	int result;
565
566	if (wantrsrc)
567		return (ENOENT);
568
569	fileID = GetEmbeddedFileID(descp->cd_nameptr, descp->cd_namelen, &prefixlen);
570	if (fileID < (cnid_t)kHFSFirstUserCatalogNodeID)
571		return (ENOENT);
572
573	if (fileID == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid ||
574		fileID == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid ||
575		fileID == hfsmp->hfs_jnlfileid ||
576		fileID == hfsmp->hfs_jnlinfoblkid) {
577		return (ENOENT);
578	}
579
580	result = cat_idlookup(hfsmp, fileID, 0, outdescp, attrp, forkp);
581	if (result)
582		return (ENOENT);
583	/* It must be in the correct directory */
584	if (descp->cd_parentcnid != outdescp->cd_parentcnid)
585		goto falsematch;
586
587	if (((u_int16_t)outdescp->cd_namelen < prefixlen) ||
588		bcmp(outdescp->cd_nameptr, descp->cd_nameptr, prefixlen-6) != 0)
589		goto falsematch;
590
591	return (0);
592
593falsematch:
594	cat_releasedesc(outdescp);
595	return (ENOENT);
596}
597
598
599/*
600 * cat_lookupbykey - lookup a catalog node using a cnode key
601 */
602static int
603cat_lookupbykey(struct hfsmount *hfsmp, CatalogKey *keyp, int allow_system_files, u_long hint, int wantrsrc,
604                  struct cat_desc *descp, struct cat_attr *attrp, struct cat_fork *forkp, cnid_t *desc_cnid)
605{
606	struct BTreeIterator * iterator;
607	FSBufferDescriptor btdata;
608	CatalogRecord * recp;
609	u_int16_t  datasize;
610	int result;
611	int std_hfs;
612	u_long ilink = 0;
613	cnid_t cnid = 0;
614	u_long encoding = 0;
615
616	std_hfs = (HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord);
617
618	MALLOC(recp, CatalogRecord *, sizeof(CatalogRecord), M_TEMP, M_WAITOK);
619	BDINIT(btdata, recp);
620	MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
621	bzero(iterator, sizeof(*iterator));
622	iterator->hint.nodeNum = hint;
623	bcopy(keyp, &iterator->key, sizeof(CatalogKey));
624
625	result = BTSearchRecord(VTOF(HFSTOVCB(hfsmp)->catalogRefNum), iterator,
626				&btdata, &datasize, iterator);
627	if (result)
628		goto exit;
629
630	/* Save the cnid and encoding now in case there's a hard link */
631	cnid = getcnid(recp);
632	encoding = getencoding(recp);
633	hint = iterator->hint.nodeNum;
634
635	/* Hide the journal files (if any) */
636	if ((hfsmp->jnl || ((HFSTOVCB(hfsmp)->vcbAtrb & kHFSVolumeJournaledMask) && (hfsmp->hfs_flags & HFS_READ_ONLY))) &&
637		((cnid == hfsmp->hfs_jnlfileid) || (cnid == hfsmp->hfs_jnlinfoblkid)) &&
638		 !allow_system_files) {
639
640		result = ENOENT;
641		goto exit;
642	}
643
644	/*
645	 * When a hardlink link is encountered, auto resolve it.
646	 *
647	 * The catalog record will change, and possibly its type.
648	 */
649	if (!std_hfs
650	    && (attrp || forkp)
651	    && (recp->recordType == kHFSPlusFileRecord)
652	    && ((to_bsd_time(recp->hfsPlusFile.createDate) == (time_t)hfsmp->vcbCrDate) ||
653	        (to_bsd_time(recp->hfsPlusFile.createDate) == (time_t)hfsmp->hfs_metadata_createdate))) {
654		int isdirlink = 0;
655		int isfilelink = 0;
656
657		if ((SWAP_BE32(recp->hfsPlusFile.userInfo.fdType) == kHardLinkFileType) &&
658		    (SWAP_BE32(recp->hfsPlusFile.userInfo.fdCreator) == kHFSPlusCreator)) {
659			isfilelink = 1;
660		} else if ((recp->hfsPlusFile.flags & kHFSHasLinkChainMask) &&
661		           (SWAP_BE32(recp->hfsPlusFile.userInfo.fdType) == kHFSAliasType) &&
662			   (SWAP_BE32(recp->hfsPlusFile.userInfo.fdCreator) == kHFSAliasCreator)) {
663			isdirlink = 1;
664		}
665		if (isfilelink || isdirlink) {
666			ilink = recp->hfsPlusFile.hl_linkReference;
667			(void) cat_resolvelink(hfsmp, ilink, isdirlink, (struct HFSPlusCatalogFile *)recp);
668		}
669	}
670
671	if (attrp != NULL) {
672		if (std_hfs) {
673			struct HFSPlusCatalogFile cnoderec;
674
675			promoteattr(hfsmp, recp, &cnoderec);
676			getbsdattr(hfsmp, &cnoderec, attrp);
677		} else {
678			getbsdattr(hfsmp, (struct HFSPlusCatalogFile *)recp, attrp);
679			if (ilink)
680				attrp->ca_linkref = ilink;
681		}
682	}
683	if (forkp != NULL) {
684		if (isadir(recp)) {
685			bzero(forkp, sizeof(*forkp));
686		} else if (std_hfs) {
687			promotefork(hfsmp, (HFSCatalogFile *)&recp->hfsFile, wantrsrc, forkp);
688		} else if (wantrsrc) {
689			/* Convert the resource fork. */
690			forkp->cf_size = recp->hfsPlusFile.resourceFork.logicalSize;
691			forkp->cf_new_size = 0;
692			forkp->cf_blocks = recp->hfsPlusFile.resourceFork.totalBlocks;
693			if ((hfsmp->hfc_stage == HFC_RECORDING) &&
694			    (to_bsd_time(recp->hfsPlusFile.accessDate) >= hfsmp->hfc_timebase)) {
695				forkp->cf_bytesread =
696					recp->hfsPlusFile.resourceFork.clumpSize *
697					HFSTOVCB(hfsmp)->blockSize;
698			} else {
699				forkp->cf_bytesread = 0;
700			}
701			forkp->cf_vblocks = 0;
702			bcopy(&recp->hfsPlusFile.resourceFork.extents[0],
703			      &forkp->cf_extents[0], sizeof(HFSPlusExtentRecord));
704		} else {
705			int i;
706			u_int32_t validblks;
707
708			/* Convert the data fork. */
709			forkp->cf_size = recp->hfsPlusFile.dataFork.logicalSize;
710			forkp->cf_new_size = 0;
711			forkp->cf_blocks = recp->hfsPlusFile.dataFork.totalBlocks;
712			if ((hfsmp->hfc_stage == HFC_RECORDING) &&
713			    (to_bsd_time(recp->hfsPlusFile.accessDate) >= hfsmp->hfc_timebase)) {
714				forkp->cf_bytesread =
715					recp->hfsPlusFile.dataFork.clumpSize *
716					HFSTOVCB(hfsmp)->blockSize;
717			} else {
718				forkp->cf_bytesread = 0;
719			}
720			forkp->cf_vblocks = 0;
721			bcopy(&recp->hfsPlusFile.dataFork.extents[0],
722			      &forkp->cf_extents[0], sizeof(HFSPlusExtentRecord));
723
724			/* Validate the fork's resident extents. */
725			validblks = 0;
726			for (i = 0; i < kHFSPlusExtentDensity; ++i) {
727				if (forkp->cf_extents[i].startBlock + forkp->cf_extents[i].blockCount >= hfsmp->totalBlocks) {
728					/* Suppress any bad extents so a remove can succeed. */
729					forkp->cf_extents[i].startBlock = 0;
730					forkp->cf_extents[i].blockCount = 0;
731					/* Disable writes */
732					if (attrp != NULL) {
733						attrp->ca_mode &= S_IFMT | S_IRUSR | S_IRGRP | S_IROTH;
734					}
735				} else {
736					validblks += forkp->cf_extents[i].blockCount;
737				}
738			}
739			/* Adjust for any missing blocks. */
740			if ((validblks < forkp->cf_blocks) && (forkp->cf_extents[7].blockCount == 0)) {
741				off_t psize;
742
743				forkp->cf_blocks = validblks;
744				if (attrp != NULL) {
745					attrp->ca_blocks = validblks + recp->hfsPlusFile.resourceFork.totalBlocks;
746				}
747				psize = (off_t)validblks * (off_t)hfsmp->blockSize;
748				if (psize < forkp->cf_size) {
749					forkp->cf_size = psize;
750				}
751
752			}
753		}
754	}
755	if (descp != NULL) {
756		HFSPlusCatalogKey * pluskey = NULL;
757
758		if (std_hfs) {
759			MALLOC(pluskey, HFSPlusCatalogKey *, sizeof(HFSPlusCatalogKey), M_TEMP, M_WAITOK);
760			promotekey(hfsmp, (HFSCatalogKey *)&iterator->key, pluskey, &encoding);
761
762		} else {
763			pluskey = (HFSPlusCatalogKey *)&iterator->key;
764		}
765		builddesc(pluskey, cnid, hint, encoding, isadir(recp), descp);
766		if (std_hfs) {
767			FREE(pluskey, M_TEMP);
768		}
769	}
770
771	if (desc_cnid != NULL) {
772	    *desc_cnid = cnid;
773	}
774exit:
775	FREE(iterator, M_TEMP);
776	FREE(recp, M_TEMP);
777
778	return MacToVFSError(result);
779}
780
781
782/*
783 * cat_create - create a node in the catalog
784 *
785 * NOTE: both the catalog file and attribute file locks must
786 *       be held before calling this function.
787 *
788 * The caller is responsible for releasing the output
789 * catalog descriptor (when supplied outdescp is non-null).
790 */
791__private_extern__
792int
793cat_create(struct hfsmount *hfsmp, struct cat_desc *descp, struct cat_attr *attrp,
794	struct cat_desc *out_descp)
795{
796	FCB * fcb;
797	struct btobj * bto;
798	FSBufferDescriptor btdata;
799	u_int32_t nextCNID;
800	u_int32_t datalen;
801	int std_hfs;
802	int result = 0;
803	u_long encoding = kTextEncodingMacRoman;
804	int modeformat;
805
806	modeformat = attrp->ca_mode & S_IFMT;
807
808	fcb = hfsmp->hfs_catalog_cp->c_datafork;
809	std_hfs = (hfsmp->hfs_flags & HFS_STANDARD);
810
811	/*
812	 * Get the next CNID. We can change it since we hold the catalog lock.
813	 */
814	nextCNID = hfsmp->vcbNxtCNID;
815	if (nextCNID == 0xFFFFFFFF) {
816		if (std_hfs) {
817			return (ENOSPC);
818		} else {
819			HFS_MOUNT_LOCK(hfsmp, TRUE)
820			hfsmp->vcbNxtCNID = kHFSFirstUserCatalogNodeID;
821			hfsmp->vcbAtrb |= kHFSCatalogNodeIDsReusedMask;
822			HFS_MOUNT_UNLOCK(hfsmp, TRUE);
823		}
824	} else {
825		hfsmp->vcbNxtCNID++;
826	}
827	MarkVCBDirty(hfsmp);
828
829	/* Get space for iterator, key and data */
830	MALLOC(bto, struct btobj *, sizeof(struct btobj), M_TEMP, M_WAITOK);
831	bto->iterator.hint.nodeNum = 0;
832
833	result = buildkey(hfsmp, descp, &bto->key, 0);
834	if (result)
835		goto exit;
836
837	if (!std_hfs) {
838		encoding = hfs_pickencoding(bto->key.nodeName.unicode,
839			bto->key.nodeName.length);
840		hfs_setencodingbits(hfsmp, encoding);
841	}
842
843	/*
844	 * Insert the thread record first
845	 */
846	if (!std_hfs || (modeformat == S_IFDIR)) {
847		datalen = buildthread((void*)&bto->key, &bto->data, std_hfs,
848				S_ISDIR(attrp->ca_mode));
849		btdata.bufferAddress = &bto->data;
850		btdata.itemSize = datalen;
851		btdata.itemCount = 1;
852
853		for (;;) {
854			// this call requires the attribute file lock to be held
855			result = file_attribute_exist(hfsmp, nextCNID);
856			if (result == EEXIST) {
857				// that cnid has orphaned attributes so just skip it.
858				if (++nextCNID < kHFSFirstUserCatalogNodeID) {
859					nextCNID = kHFSFirstUserCatalogNodeID;
860				}
861				continue;
862			}
863			if (result) goto exit;
864
865			buildthreadkey(nextCNID, std_hfs, (CatalogKey *) &bto->iterator.key);
866
867			result = BTInsertRecord(fcb, &bto->iterator, &btdata, datalen);
868			if ((result == btExists) && !std_hfs && (hfsmp->vcbAtrb & kHFSCatalogNodeIDsReusedMask)) {
869				/*
870				 * Allow CNIDs on HFS Plus volumes to wrap around
871				 */
872				if (++nextCNID < kHFSFirstUserCatalogNodeID) {
873					nextCNID = kHFSFirstUserCatalogNodeID;
874				}
875				continue;
876			}
877			break;
878		}
879		if (result) goto exit;
880	}
881
882	/*
883	 * CNID is now established. If we have wrapped then
884	 * update the vcbNxtCNID.
885	 */
886	if ((hfsmp->vcbAtrb & kHFSCatalogNodeIDsReusedMask)) {
887		hfsmp->vcbNxtCNID = nextCNID + 1;
888		if (hfsmp->vcbNxtCNID < kHFSFirstUserCatalogNodeID) {
889			hfsmp->vcbNxtCNID = kHFSFirstUserCatalogNodeID;
890		}
891	}
892
893	/*
894	 * Now insert the file/directory record
895	 */
896	buildrecord(attrp, nextCNID, std_hfs, encoding, &bto->data, &datalen);
897	btdata.bufferAddress = &bto->data;
898	btdata.itemSize = datalen;
899	btdata.itemCount = 1;
900
901	bcopy(&bto->key, &bto->iterator.key, sizeof(bto->key));
902
903	result = BTInsertRecord(fcb, &bto->iterator, &btdata, datalen);
904	if (result) {
905		if (result == btExists)
906			result = EEXIST;
907
908		/* Back out the thread record */
909		if (!std_hfs || S_ISDIR(attrp->ca_mode)) {
910			buildthreadkey(nextCNID, std_hfs, (CatalogKey *)&bto->iterator.key);
911			if (BTDeleteRecord(fcb, &bto->iterator)) {
912				/* Error on deleting extra thread record, mark
913				 * volume inconsistent
914				 */
915				printf ("hfs: cat_create() failed to delete thread record on volume %s\n", hfsmp->vcbVN);
916				hfs_mark_volume_inconsistent(hfsmp);
917			}
918		}
919		goto exit;
920	}
921
922	/*
923	 * Insert was successful, update name, parent and volume
924	 */
925	if (out_descp != NULL) {
926		HFSPlusCatalogKey * pluskey = NULL;
927
928		if (std_hfs) {
929			MALLOC(pluskey, HFSPlusCatalogKey *, sizeof(HFSPlusCatalogKey), M_TEMP, M_WAITOK);
930			promotekey(hfsmp, (HFSCatalogKey *)&bto->iterator.key, pluskey, &encoding);
931
932		} else
933			pluskey = (HFSPlusCatalogKey *)&bto->iterator.key;
934
935		builddesc(pluskey, nextCNID, bto->iterator.hint.nodeNum,
936			encoding, S_ISDIR(attrp->ca_mode), out_descp);
937		if (std_hfs) {
938			FREE(pluskey, M_TEMP);
939		}
940	}
941	attrp->ca_fileid = nextCNID;
942
943exit:
944	(void) BTFlushPath(fcb);
945	FREE(bto, M_TEMP);
946
947	return MacToVFSError(result);
948}
949
950
951/*
952 * cnode_rename - rename a catalog node
953 *
954 * Assumes that the target's directory exists.
955 *
956 * Order of B-tree operations:
957 *	1. BTSearchRecord(from_cnode, &data);
958 *	2. BTInsertRecord(to_cnode, &data);
959 *	3. BTDeleteRecord(from_cnode);
960 *	4. BTDeleteRecord(from_thread);
961 *	5. BTInsertRecord(to_thread);
962 *
963 * Note: The caller is responsible for releasing the output
964 * catalog descriptor (when supplied out_cdp is non-null).
965 */
966__private_extern__
967int
968cat_rename (
969	struct hfsmount * hfsmp,
970	struct cat_desc * from_cdp,
971	struct cat_desc * todir_cdp,
972	struct cat_desc * to_cdp,
973	struct cat_desc * out_cdp )
974{
975	struct BTreeIterator * to_iterator = NULL;
976	struct BTreeIterator * from_iterator = NULL;
977	FSBufferDescriptor btdata;
978	CatalogRecord * recp = NULL;
979	HFSPlusCatalogKey * to_key;
980	ExtendedVCB * vcb;
981	FCB * fcb;
982	u_int16_t	datasize;
983	int result = 0;
984	int sourcegone = 0;
985	int skipthread = 0;
986	int directory = from_cdp->cd_flags & CD_ISDIR;
987	int is_dirlink = 0;
988	int std_hfs;
989	u_long encoding = 0;
990
991	vcb = HFSTOVCB(hfsmp);
992	fcb = GetFileControlBlock(vcb->catalogRefNum);
993	std_hfs = (vcb->vcbSigWord == kHFSSigWord);
994
995	if (from_cdp->cd_namelen == 0 || to_cdp->cd_namelen == 0)
996		return (EINVAL);
997
998	MALLOC(from_iterator, BTreeIterator *, sizeof(*from_iterator), M_TEMP, M_WAITOK);
999	bzero(from_iterator, sizeof(*from_iterator));
1000	if ((result = buildkey(hfsmp, from_cdp, (HFSPlusCatalogKey *)&from_iterator->key, 0)))
1001		goto exit;
1002
1003	MALLOC(to_iterator, BTreeIterator *, sizeof(*to_iterator), M_TEMP, M_WAITOK);
1004	bzero(to_iterator, sizeof(*to_iterator));
1005	if ((result = buildkey(hfsmp, to_cdp, (HFSPlusCatalogKey *)&to_iterator->key, 0)))
1006		goto exit;
1007
1008	to_key = (HFSPlusCatalogKey *)&to_iterator->key;
1009	MALLOC(recp, CatalogRecord *, sizeof(CatalogRecord), M_TEMP, M_WAITOK);
1010	BDINIT(btdata, recp);
1011
1012	/*
1013	 * When moving a directory, make sure its a valid move.
1014	 */
1015	if (directory && (from_cdp->cd_parentcnid != to_cdp->cd_parentcnid)) {
1016		struct BTreeIterator iterator;
1017		cnid_t cnid = from_cdp->cd_cnid;
1018		cnid_t pathcnid = todir_cdp->cd_parentcnid;
1019
1020		/* First check the obvious ones */
1021		if (cnid == fsRtDirID  ||
1022		    cnid == to_cdp->cd_parentcnid  ||
1023		    cnid == pathcnid) {
1024			result = EINVAL;
1025			goto exit;
1026		}
1027		bzero(&iterator, sizeof(iterator));
1028		/*
1029		 * Traverse destination path all the way back to the root
1030		 * making sure that source directory is not encountered.
1031		 *
1032		 */
1033		while (pathcnid > fsRtDirID) {
1034			buildthreadkey(pathcnid, std_hfs,
1035					(CatalogKey *)&iterator.key);
1036			result = BTSearchRecord(fcb, &iterator, &btdata,
1037					&datasize, NULL);
1038			if (result) goto exit;
1039
1040			pathcnid = getparentcnid(recp);
1041			if (pathcnid == cnid || pathcnid == 0) {
1042				result = EINVAL;
1043				goto exit;
1044			}
1045		}
1046	}
1047
1048	/*
1049	 * Step 1: Find cnode data at old location
1050	 */
1051	result = BTSearchRecord(fcb, from_iterator, &btdata,
1052				&datasize, from_iterator);
1053	if (result) {
1054		if (std_hfs || (result != btNotFound))
1055			goto exit;
1056
1057		struct cat_desc temp_desc;
1058
1059		/* Probably the node has mangled name */
1060		result = cat_lookupmangled(hfsmp, from_cdp, 0, &temp_desc, NULL, NULL);
1061		if (result)
1062			goto exit;
1063
1064		/* The file has mangled name.  Search the cnode data using full name */
1065		bzero(from_iterator, sizeof(*from_iterator));
1066		result = buildkey(hfsmp, &temp_desc, (HFSPlusCatalogKey *)&from_iterator->key, 0);
1067		if (result) {
1068			cat_releasedesc(&temp_desc);
1069			goto exit;
1070		}
1071
1072		result = BTSearchRecord(fcb, from_iterator, &btdata, &datasize, from_iterator);
1073		if (result) {
1074			cat_releasedesc(&temp_desc);
1075			goto exit;
1076		}
1077
1078		cat_releasedesc(&temp_desc);
1079	}
1080
1081	/* Check if the source is directory hard link.  We do not change
1082	 * directory flag because it is later used to initialize result descp
1083	 */
1084	if ((!std_hfs) &&
1085	    (directory) &&
1086	    (recp->recordType == kHFSPlusFileRecord) &&
1087	    (recp->hfsPlusFile.flags & kHFSHasLinkChainMask)) {
1088	    	is_dirlink  = 1;
1089	}
1090
1091	/*
1092	 * Update the text encoding (on disk and in descriptor).
1093	 *
1094	 * Note that hardlink inodes don't require a text encoding hint.
1095	 */
1096	if (!std_hfs &&
1097	    todir_cdp->cd_parentcnid != hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid &&
1098	    todir_cdp->cd_parentcnid != hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
1099		encoding = hfs_pickencoding(to_key->nodeName.unicode, to_key->nodeName.length);
1100		hfs_setencodingbits(hfsmp, encoding);
1101		recp->hfsPlusFile.textEncoding = encoding;
1102		if (out_cdp)
1103			out_cdp->cd_encoding = encoding;
1104	}
1105
1106	if (std_hfs && !directory &&
1107	    !(recp->hfsFile.flags & kHFSThreadExistsMask))
1108		skipthread = 1;
1109#if 0
1110	/*
1111	 * If the keys are identical then there's nothing left to do!
1112	 *
1113	 * update the hint and exit
1114	 *
1115	 */
1116	if (std_hfs && hfskeycompare(to_key, iter->key) == 0)
1117		goto exit;
1118	if (!std_hfs && hfspluskeycompare(to_key, iter->key) == 0)
1119		goto exit;
1120#endif
1121
1122	/* Step 2: Insert cnode at new location */
1123	result = BTInsertRecord(fcb, to_iterator, &btdata, datasize);
1124	if (result == btExists) {
1125		int fromtype = recp->recordType;
1126
1127		if (from_cdp->cd_parentcnid != to_cdp->cd_parentcnid)
1128			goto exit; /* EEXIST */
1129
1130		/* Find cnode data at new location */
1131		result = BTSearchRecord(fcb, to_iterator, &btdata, &datasize, NULL);
1132		if (result)
1133			goto exit;
1134
1135		if ((fromtype != recp->recordType) ||
1136		    (from_cdp->cd_cnid != getcnid(recp))) {
1137			result = EEXIST;
1138			goto exit; /* EEXIST */
1139		}
1140		/* The old name is a case variant and must be removed */
1141		result = BTDeleteRecord(fcb, from_iterator);
1142		if (result)
1143			goto exit;
1144
1145		/* Insert cnode (now that case duplicate is gone) */
1146		result = BTInsertRecord(fcb, to_iterator, &btdata, datasize);
1147		if (result) {
1148			/* Try and restore original before leaving */
1149		    // XXXdbg
1150		    #if 1
1151		       {
1152		       	int err;
1153			err = BTInsertRecord(fcb, from_iterator, &btdata, datasize);
1154			if (err) {
1155				printf("cat_create: could not undo (BTInsert = %d)", err);
1156				hfs_mark_volume_inconsistent(hfsmp);
1157				result = err;
1158				goto exit;
1159			}
1160		       }
1161		    #else
1162			(void) BTInsertRecord(fcb, from_iterator, &btdata, datasize);
1163		    #endif
1164			goto exit;
1165		}
1166		sourcegone = 1;
1167	}
1168	if (result)
1169		goto exit;
1170
1171	/* Step 3: Remove cnode from old location */
1172	if (!sourcegone) {
1173		result = BTDeleteRecord(fcb, from_iterator);
1174		if (result) {
1175			/* Try and delete new record before leaving */
1176		  // XXXdbg
1177		  #if 1
1178		     {
1179		     	int err;
1180			err = BTDeleteRecord(fcb, to_iterator);
1181			if (err) {
1182				printf("cat_create: could not undo (BTDelete = %d)", err);
1183				hfs_mark_volume_inconsistent(hfsmp);
1184				result = err;
1185				goto exit;
1186			}
1187		     }
1188		  #else
1189			(void) BTDeleteRecord(fcb, to_iterator);
1190		  #endif
1191			goto exit;
1192		}
1193	}
1194
1195	/* #### POINT OF NO RETURN #### */
1196
1197	/*
1198	 * Step 4: Remove cnode's old thread record
1199	 */
1200	buildthreadkey(from_cdp->cd_cnid, std_hfs, (CatalogKey *)&from_iterator->key);
1201	(void) BTDeleteRecord(fcb, from_iterator);
1202
1203	/*
1204	 * Step 5: Insert cnode's new thread record
1205	 * (optional for HFS files)
1206	 */
1207	if (!skipthread) {
1208		/* For directory hard links, always create a file thread
1209		 * record.  For everything else, use the directory flag.
1210		 */
1211		if (is_dirlink) {
1212			datasize = buildthread(&to_iterator->key, recp, std_hfs, false);
1213		} else {
1214			datasize = buildthread(&to_iterator->key, recp, std_hfs, directory);
1215		}
1216		btdata.itemSize = datasize;
1217		buildthreadkey(from_cdp->cd_cnid, std_hfs, (CatalogKey *)&from_iterator->key);
1218		result = BTInsertRecord(fcb, from_iterator, &btdata, datasize);
1219	}
1220
1221	if (out_cdp) {
1222		HFSPlusCatalogKey * pluskey = NULL;
1223
1224		if (std_hfs) {
1225			MALLOC(pluskey, HFSPlusCatalogKey *, sizeof(HFSPlusCatalogKey), M_TEMP, M_WAITOK);
1226			promotekey(hfsmp, (HFSCatalogKey *)&to_iterator->key, pluskey, &encoding);
1227
1228			/* Save the real encoding hint in the Finder Info (field 4). */
1229			if (directory && from_cdp->cd_cnid == kHFSRootFolderID) {
1230				u_long realhint;
1231
1232				realhint = hfs_pickencoding(pluskey->nodeName.unicode, pluskey->nodeName.length);
1233				vcb->vcbFndrInfo[4] = SET_HFS_TEXT_ENCODING(realhint);
1234			}
1235
1236		} else
1237			pluskey = (HFSPlusCatalogKey *)&to_iterator->key;
1238
1239		builddesc(pluskey, from_cdp->cd_cnid, to_iterator->hint.nodeNum,
1240			encoding, directory, out_cdp);
1241		if (std_hfs) {
1242			FREE(pluskey, M_TEMP);
1243		}
1244	}
1245exit:
1246	(void) BTFlushPath(fcb);
1247	if (from_iterator)
1248		FREE(from_iterator, M_TEMP);
1249	if (to_iterator)
1250		FREE(to_iterator, M_TEMP);
1251	if (recp)
1252		FREE(recp, M_TEMP);
1253	return MacToVFSError(result);
1254}
1255
1256
1257/*
1258 * cat_delete - delete a node from the catalog
1259 *
1260 * Order of B-tree operations:
1261 *	1. BTDeleteRecord(cnode);
1262 *	2. BTDeleteRecord(thread);
1263 *	3. BTUpdateRecord(parent);
1264 */
1265__private_extern__
1266int
1267cat_delete(struct hfsmount *hfsmp, struct cat_desc *descp, struct cat_attr *attrp)
1268{
1269	FCB * fcb;
1270	BTreeIterator *iterator;
1271	cnid_t cnid;
1272	int std_hfs;
1273	int result;
1274
1275	fcb = hfsmp->hfs_catalog_cp->c_datafork;
1276	std_hfs = (hfsmp->hfs_flags & HFS_STANDARD);
1277
1278	/* Preflight check:
1279	 *
1280	 * The root directory cannot be deleted
1281	 * A directory must be empty
1282	 * A file must be zero length (no blocks)
1283	 */
1284	if (descp->cd_cnid < kHFSFirstUserCatalogNodeID ||
1285	    descp->cd_parentcnid == kHFSRootParentID)
1286		return (EINVAL);
1287
1288	/* XXX Preflight Missing */
1289
1290	/* Borrow the btcb iterator since we have an exclusive catalog lock. */
1291	iterator = &((BTreeControlBlockPtr)(fcb->ff_sysfileinfo))->iterator;
1292	iterator->hint.nodeNum = 0;
1293
1294	/*
1295	 * Derive a key from either the file ID (for a virtual inode)
1296	 * or the descriptor.
1297	 */
1298	if (descp->cd_namelen == 0) {
1299	    result = getkey(hfsmp, attrp->ca_fileid, (CatalogKey *)&iterator->key);
1300	    cnid = attrp->ca_fileid;
1301	} else {
1302		result = buildkey(hfsmp, descp, (HFSPlusCatalogKey *)&iterator->key, 0);
1303		cnid = descp->cd_cnid;
1304	}
1305	if (result)
1306		goto exit;
1307
1308	/* Delete record */
1309	result = BTDeleteRecord(fcb, iterator);
1310	if (result) {
1311		if (std_hfs || (result != btNotFound))
1312			goto exit;
1313
1314		struct cat_desc temp_desc;
1315
1316		/* Probably the node has mangled name */
1317		result = cat_lookupmangled(hfsmp, descp, 0, &temp_desc, attrp, NULL);
1318		if (result)
1319			goto exit;
1320
1321		/* The file has mangled name.  Delete the file using full name  */
1322		bzero(iterator, sizeof(*iterator));
1323		result = buildkey(hfsmp, &temp_desc, (HFSPlusCatalogKey *)&iterator->key, 0);
1324		cnid = temp_desc.cd_cnid;
1325		if (result) {
1326			cat_releasedesc(&temp_desc);
1327			goto exit;
1328		}
1329
1330		result = BTDeleteRecord(fcb, iterator);
1331		if (result) {
1332			cat_releasedesc(&temp_desc);
1333			goto exit;
1334		}
1335
1336		cat_releasedesc(&temp_desc);
1337	}
1338
1339	/* Delete thread record.  On error, mark volume inconsistent */
1340	buildthreadkey(cnid, std_hfs, (CatalogKey *)&iterator->key);
1341	if (BTDeleteRecord(fcb, iterator)) {
1342		if (!std_hfs) {
1343			printf ("hfs: cat_delete() failed to delete thread record on volume %s\n", hfsmp->vcbVN);
1344			hfs_mark_volume_inconsistent(hfsmp);
1345		}
1346	}
1347
1348exit:
1349	(void) BTFlushPath(fcb);
1350
1351	return MacToVFSError(result);
1352}
1353
1354
1355/*
1356 * cnode_update - update the catalog node described by descp
1357 * using the data from attrp and forkp.
1358 */
1359__private_extern__
1360int
1361cat_update(struct hfsmount *hfsmp, struct cat_desc *descp, struct cat_attr *attrp,
1362	struct cat_fork *dataforkp, struct cat_fork *rsrcforkp)
1363{
1364	FCB * fcb;
1365	BTreeIterator * iterator;
1366	struct update_state state;
1367	int std_hfs;
1368	int result;
1369
1370	fcb = hfsmp->hfs_catalog_cp->c_datafork;
1371	std_hfs = (hfsmp->hfs_flags & HFS_STANDARD);
1372
1373	state.s_desc = descp;
1374	state.s_attr = attrp;
1375	state.s_datafork = dataforkp;
1376	state.s_rsrcfork = rsrcforkp;
1377	state.s_hfsmp = hfsmp;
1378
1379	/* Borrow the btcb iterator since we have an exclusive catalog lock. */
1380	iterator = &((BTreeControlBlockPtr)(fcb->ff_sysfileinfo))->iterator;
1381
1382	/*
1383	 * For open-deleted files we need to do a lookup by cnid
1384	 * (using thread rec).
1385	 *
1386	 * For hard links, the target of the update is the inode
1387	 * itself (not the link record) so a lookup by fileid
1388	 * (i.e. thread rec) is needed.
1389	 */
1390	if ((descp->cd_cnid != attrp->ca_fileid) ||
1391	    (descp->cd_namelen == 0) ||
1392	    (attrp->ca_recflags & kHFSHasLinkChainMask)) {
1393		result = getkey(hfsmp, attrp->ca_fileid, (CatalogKey *)&iterator->key);
1394	} else {
1395		result = buildkey(hfsmp, descp, (HFSPlusCatalogKey *)&iterator->key, 0);
1396	}
1397	if (result)
1398		goto exit;
1399
1400	/* Pass a node hint */
1401	iterator->hint.nodeNum = descp->cd_hint;
1402
1403	result = BTUpdateRecord(fcb, iterator,
1404	                        (IterateCallBackProcPtr)catrec_update, &state);
1405	if (result)
1406		goto exit;
1407
1408	/* Update the node hint. */
1409	descp->cd_hint = iterator->hint.nodeNum;
1410
1411exit:
1412	(void) BTFlushPath(fcb);
1413
1414	return MacToVFSError(result);
1415}
1416
1417/*
1418 * catrec_update - Update the fields of a catalog record
1419 * This is called from within BTUpdateRecord.
1420 */
1421static int
1422catrec_update(const CatalogKey *ckp, CatalogRecord *crp, struct update_state *state)
1423{
1424	struct cat_desc *descp;
1425	struct cat_attr *attrp;
1426	struct cat_fork *forkp;
1427	struct hfsmount *hfsmp;
1428	long blksize;
1429	int i;
1430
1431	descp   = state->s_desc;
1432	attrp   = state->s_attr;
1433	hfsmp   = state->s_hfsmp;
1434	blksize = HFSTOVCB(hfsmp)->blockSize;
1435
1436	switch (crp->recordType) {
1437	case kHFSFolderRecord: {
1438		HFSCatalogFolder *dir;
1439
1440		dir = (struct HFSCatalogFolder *)crp;
1441		/* Do a quick sanity check */
1442		if ((ckp->hfs.parentID != descp->cd_parentcnid) ||
1443		    (dir->folderID != descp->cd_cnid))
1444			return (btNotFound);
1445		dir->valence    = attrp->ca_entries;
1446		dir->createDate = UTCToLocal(to_hfs_time(attrp->ca_itime));
1447		dir->modifyDate = UTCToLocal(to_hfs_time(attrp->ca_mtime));
1448		dir->backupDate = UTCToLocal(to_hfs_time(attrp->ca_btime));
1449		bcopy(&attrp->ca_finderinfo[0], &dir->userInfo, 16);
1450		bcopy(&attrp->ca_finderinfo[16], &dir->finderInfo, 16);
1451		break;
1452	}
1453	case kHFSFileRecord: {
1454		HFSCatalogFile *file;
1455
1456		file = (struct HFSCatalogFile *)crp;
1457		/* Do a quick sanity check */
1458		if ((ckp->hfs.parentID != descp->cd_parentcnid) ||
1459		    (file->fileID != attrp->ca_fileid))
1460			return (btNotFound);
1461		file->createDate = UTCToLocal(to_hfs_time(attrp->ca_itime));
1462		file->modifyDate = UTCToLocal(to_hfs_time(attrp->ca_mtime));
1463		file->backupDate = UTCToLocal(to_hfs_time(attrp->ca_btime));
1464		bcopy(&attrp->ca_finderinfo[0], &file->userInfo, 16);
1465		bcopy(&attrp->ca_finderinfo[16], &file->finderInfo, 16);
1466		if (state->s_rsrcfork) {
1467			forkp = state->s_rsrcfork;
1468			file->rsrcLogicalSize  = forkp->cf_size;
1469			file->rsrcPhysicalSize = forkp->cf_blocks * blksize;
1470			for (i = 0; i < kHFSExtentDensity; ++i) {
1471				file->rsrcExtents[i].startBlock =
1472					(u_int16_t)forkp->cf_extents[i].startBlock;
1473				file->rsrcExtents[i].blockCount =
1474					(u_int16_t)forkp->cf_extents[i].blockCount;
1475			}
1476		}
1477		if (state->s_datafork) {
1478			forkp = state->s_datafork;
1479			file->dataLogicalSize  = forkp->cf_size;
1480			file->dataPhysicalSize = forkp->cf_blocks * blksize;
1481			for (i = 0; i < kHFSExtentDensity; ++i) {
1482				file->dataExtents[i].startBlock =
1483					(u_int16_t)forkp->cf_extents[i].startBlock;
1484				file->dataExtents[i].blockCount =
1485					(u_int16_t)forkp->cf_extents[i].blockCount;
1486			}
1487		}
1488
1489		/* Synchronize the lock state */
1490		if (attrp->ca_flags & (SF_IMMUTABLE | UF_IMMUTABLE))
1491			file->flags |= kHFSFileLockedMask;
1492		else
1493			file->flags &= ~kHFSFileLockedMask;
1494		break;
1495	}
1496	case kHFSPlusFolderRecord: {
1497		HFSPlusCatalogFolder *dir;
1498
1499		dir = (struct HFSPlusCatalogFolder *)crp;
1500		/* Do a quick sanity check */
1501		if (dir->folderID != attrp->ca_fileid) {
1502			printf("catrec_update: id %d != %d\n", dir->folderID, attrp->ca_fileid);
1503			return (btNotFound);
1504		}
1505		dir->flags            = attrp->ca_recflags;
1506		dir->valence          = attrp->ca_entries;
1507		dir->createDate       = to_hfs_time(attrp->ca_itime);
1508		dir->contentModDate   = to_hfs_time(attrp->ca_mtime);
1509		dir->backupDate       = to_hfs_time(attrp->ca_btime);
1510		dir->accessDate       = to_hfs_time(attrp->ca_atime);
1511		attrp->ca_atimeondisk = attrp->ca_atime;
1512		dir->attributeModDate = to_hfs_time(attrp->ca_ctime);
1513		/* Note: directory hardlink inodes don't require a text encoding hint. */
1514		if (ckp->hfsPlus.parentID != hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
1515			dir->textEncoding = descp->cd_encoding;
1516		}
1517		dir->folderCount      = attrp->ca_dircount;
1518		bcopy(&attrp->ca_finderinfo[0], &dir->userInfo, 32);
1519		/*
1520		 * Update the BSD Info if it was already initialized on
1521		 * disk or if the runtime values have been modified.
1522		 *
1523		 * If the BSD info was already initialized, but
1524		 * MNT_UNKNOWNPERMISSIONS is set, then the runtime IDs are
1525		 * probably different than what was on disk.  We don't want
1526		 * to overwrite the on-disk values (so if we turn off
1527		 * MNT_UNKNOWNPERMISSIONS, the old IDs get used again).
1528		 * This way, we can still change fields like the mode or
1529		 * dates even when MNT_UNKNOWNPERMISSIONS is set.
1530		 *
1531		 * Note that if MNT_UNKNOWNPERMISSIONS is set, hfs_chown
1532		 * won't change the uid or gid from their defaults.  So, if
1533		 * the BSD info wasn't set, and the runtime values are not
1534		 * default, then what changed was the mode or flags.  We
1535		 * have to set the uid and gid to something, so use the
1536		 * supplied values (which will be default), which has the
1537		 * same effect as creating a new file while
1538		 * MNT_UNKNOWNPERMISSIONS is set.
1539		 */
1540		if ((dir->bsdInfo.fileMode != 0) ||
1541		    (attrp->ca_flags != 0) ||
1542		    (attrp->ca_uid != hfsmp->hfs_uid) ||
1543		    (attrp->ca_gid != hfsmp->hfs_gid) ||
1544		    ((attrp->ca_mode & ALLPERMS) !=
1545		     (hfsmp->hfs_dir_mask & ACCESSPERMS))) {
1546			if ((dir->bsdInfo.fileMode == 0) ||
1547			    (((unsigned int)vfs_flags(HFSTOVFS(hfsmp))) & MNT_UNKNOWNPERMISSIONS) == 0) {
1548				dir->bsdInfo.ownerID = attrp->ca_uid;
1549				dir->bsdInfo.groupID = attrp->ca_gid;
1550			}
1551			dir->bsdInfo.ownerFlags = attrp->ca_flags & 0x000000FF;
1552			dir->bsdInfo.adminFlags = attrp->ca_flags >> 16;
1553			dir->bsdInfo.fileMode   = attrp->ca_mode;
1554			/* A directory hardlink has a link count. */
1555			if (attrp->ca_linkcount > 1 || dir->hl_linkCount > 1) {
1556				dir->hl_linkCount = attrp->ca_linkcount;
1557			}
1558		}
1559		break;
1560	}
1561	case kHFSPlusFileRecord: {
1562		HFSPlusCatalogFile *file;
1563
1564		file = (struct HFSPlusCatalogFile *)crp;
1565		/* Do a quick sanity check */
1566		if (file->fileID != attrp->ca_fileid)
1567			return (btNotFound);
1568		file->flags            = attrp->ca_recflags;
1569		file->createDate       = to_hfs_time(attrp->ca_itime);
1570		file->contentModDate   = to_hfs_time(attrp->ca_mtime);
1571		file->backupDate       = to_hfs_time(attrp->ca_btime);
1572		file->accessDate       = to_hfs_time(attrp->ca_atime);
1573		attrp->ca_atimeondisk  = attrp->ca_atime;
1574		file->attributeModDate = to_hfs_time(attrp->ca_ctime);
1575		/*
1576		 * Note: file hardlink inodes don't require a text encoding
1577		 * hint, but they do have a first link value.
1578		 */
1579		if (ckp->hfsPlus.parentID == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid) {
1580			file->hl_firstLinkID = attrp->ca_firstlink;
1581		} else {
1582			file->textEncoding = descp->cd_encoding;
1583		}
1584		bcopy(&attrp->ca_finderinfo[0], &file->userInfo, 32);
1585		/*
1586		 * Update the BSD Info if it was already initialized on
1587		 * disk or if the runtime values have been modified.
1588		 *
1589		 * If the BSD info was already initialized, but
1590		 * MNT_UNKNOWNPERMISSIONS is set, then the runtime IDs are
1591		 * probably different than what was on disk.  We don't want
1592		 * to overwrite the on-disk values (so if we turn off
1593		 * MNT_UNKNOWNPERMISSIONS, the old IDs get used again).
1594		 * This way, we can still change fields like the mode or
1595		 * dates even when MNT_UNKNOWNPERMISSIONS is set.
1596		 *
1597		 * Note that if MNT_UNKNOWNPERMISSIONS is set, hfs_chown
1598		 * won't change the uid or gid from their defaults.  So, if
1599		 * the BSD info wasn't set, and the runtime values are not
1600		 * default, then what changed was the mode or flags.  We
1601		 * have to set the uid and gid to something, so use the
1602		 * supplied values (which will be default), which has the
1603		 * same effect as creating a new file while
1604		 * MNT_UNKNOWNPERMISSIONS is set.
1605		 */
1606		if ((file->bsdInfo.fileMode != 0) ||
1607		    (attrp->ca_flags != 0) ||
1608		    (attrp->ca_uid != hfsmp->hfs_uid) ||
1609		    (attrp->ca_gid != hfsmp->hfs_gid) ||
1610		    ((attrp->ca_mode & ALLPERMS) !=
1611		     (hfsmp->hfs_file_mask & ACCESSPERMS))) {
1612			if ((file->bsdInfo.fileMode == 0) ||
1613			    (((unsigned int)vfs_flags(HFSTOVFS(hfsmp))) & MNT_UNKNOWNPERMISSIONS) == 0) {
1614				file->bsdInfo.ownerID = attrp->ca_uid;
1615				file->bsdInfo.groupID = attrp->ca_gid;
1616			}
1617			file->bsdInfo.ownerFlags = attrp->ca_flags & 0x000000FF;
1618			file->bsdInfo.adminFlags = attrp->ca_flags >> 16;
1619			file->bsdInfo.fileMode   = attrp->ca_mode;
1620		}
1621		if (state->s_rsrcfork) {
1622			forkp = state->s_rsrcfork;
1623			file->resourceFork.logicalSize = forkp->cf_size;
1624			file->resourceFork.totalBlocks = forkp->cf_blocks;
1625			bcopy(&forkp->cf_extents[0], &file->resourceFork.extents,
1626				sizeof(HFSPlusExtentRecord));
1627			/* Push blocks read to disk */
1628			file->resourceFork.clumpSize =
1629					howmany(forkp->cf_bytesread, blksize);
1630		}
1631		if (state->s_datafork) {
1632			forkp = state->s_datafork;
1633			file->dataFork.logicalSize = forkp->cf_size;
1634			file->dataFork.totalBlocks = forkp->cf_blocks;
1635			bcopy(&forkp->cf_extents[0], &file->dataFork.extents,
1636				sizeof(HFSPlusExtentRecord));
1637			/* Push blocks read to disk */
1638			file->dataFork.clumpSize =
1639					howmany(forkp->cf_bytesread, blksize);
1640		}
1641
1642		if ((file->resourceFork.extents[0].startBlock != 0) &&
1643		    (file->resourceFork.extents[0].startBlock ==
1644		     file->dataFork.extents[0].startBlock)) {
1645			panic("catrec_update: rsrc fork == data fork");
1646		}
1647
1648		/* Synchronize the lock state */
1649		if (attrp->ca_flags & (SF_IMMUTABLE | UF_IMMUTABLE))
1650			file->flags |= kHFSFileLockedMask;
1651		else
1652			file->flags &= ~kHFSFileLockedMask;
1653
1654		/* Push out special field if necessary */
1655		if (S_ISBLK(attrp->ca_mode) || S_ISCHR(attrp->ca_mode)) {
1656			file->bsdInfo.special.rawDevice = attrp->ca_rdev;
1657		} else if (descp->cd_cnid != attrp->ca_fileid || attrp->ca_linkcount == 2) {
1658			file->hl_linkCount = attrp->ca_linkcount;
1659		}
1660		break;
1661	}
1662	default:
1663		return (btNotFound);
1664	}
1665	return (0);
1666}
1667
1668/* This function sets kHFSHasChildLinkBit in a directory hierarchy in the
1669 * catalog btree of given cnid by walking up the parent chain till it reaches
1670 * either the root folder, or the private metadata directory for storing
1671 * directory hard links.  This function updates the corresponding in-core
1672 * cnode, if any, and the directory record in the catalog btree.
1673 * On success, returns zero.  On failure, returns non-zero value.
1674 */
1675__private_extern__
1676int
1677cat_set_childlinkbit(struct hfsmount *hfsmp, cnid_t cnid)
1678{
1679	int retval = 0;
1680	int lockflags = 0;
1681	struct cat_desc desc;
1682	struct cat_attr attr;
1683
1684	while ((cnid != kHFSRootFolderID) && (cnid != kHFSRootParentID) &&
1685	       (cnid != hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid)) {
1686		/* Update the bit in corresponding cnode, if any, in the hash.
1687		 * If the cnode has the bit already set, stop the traversal.
1688		 */
1689		retval = hfs_chash_set_childlinkbit(hfsmp->hfs_raw_dev, cnid);
1690		if (retval == 0) {
1691			break;
1692		}
1693
1694		/* Update the catalog record on disk if either cnode was not
1695		 * found in the hash, or if a cnode was found and the cnode
1696		 * did not have the bit set previously.
1697		 */
1698		retval = hfs_start_transaction(hfsmp);
1699		if (retval) {
1700			break;
1701		}
1702		lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
1703
1704		/* Look up our catalog folder record */
1705		retval = cat_idlookup(hfsmp, cnid, 0, &desc, &attr, NULL);
1706		if (retval) {
1707			hfs_systemfile_unlock(hfsmp, lockflags);
1708			hfs_end_transaction(hfsmp);
1709			break;
1710		}
1711
1712		/* Update the bit in the catalog record */
1713		attr.ca_recflags |= kHFSHasChildLinkMask;
1714		retval = cat_update(hfsmp, &desc, &attr, NULL, NULL);
1715		if (retval) {
1716			hfs_systemfile_unlock(hfsmp, lockflags);
1717			hfs_end_transaction(hfsmp);
1718			cat_releasedesc(&desc);
1719			break;
1720		}
1721
1722		hfs_systemfile_unlock(hfsmp, lockflags);
1723		hfs_end_transaction(hfsmp);
1724
1725		cnid = desc.cd_parentcnid;
1726		cat_releasedesc(&desc);
1727	}
1728
1729	return retval;
1730}
1731
1732/* This function traverses the parent directory hierarchy from the given
1733 * directory to one level below root directory and checks if any of its
1734 * ancestors is -
1735 * 	1. A directory hard link.
1736 * 	2. The 'pointed at' directory.
1737 * If any of these conditions fail or an internal error is encountered
1738 * during look up of the catalog record, this function returns non-zero value.
1739 */
1740__private_extern__
1741int
1742cat_check_link_ancestry(struct hfsmount *hfsmp, cnid_t cnid, cnid_t pointed_at_cnid)
1743{
1744	HFSPlusCatalogKey *keyp;
1745	BTreeIterator *ip;
1746	FSBufferDescriptor btdata;
1747	HFSPlusCatalogFolder folder;
1748	FCB *fcb;
1749	int invalid;
1750	int result;
1751
1752	invalid = 0;
1753	BDINIT(btdata, &folder);
1754	MALLOC(ip, BTreeIterator *, sizeof(*ip), M_TEMP, M_WAITOK);
1755	keyp = (HFSPlusCatalogKey *)&ip->key;
1756	fcb = hfsmp->hfs_catalog_cp->c_datafork;
1757
1758	while (cnid != kHFSRootParentID) {
1759		/* Check if the 'pointed at' directory is an ancestor */
1760		if (pointed_at_cnid == cnid) {
1761			invalid = 1;
1762			break;
1763		}
1764		if ((result = getkey(hfsmp, cnid, (CatalogKey *)keyp))) {
1765			printf("cat_check_link_ancestry: getkey for %u failed\n", cnid);
1766			invalid = 1;  /* On errors, assume an invalid parent */
1767			break;
1768		}
1769		if ((result = BTSearchRecord(fcb, ip, &btdata, NULL, NULL))) {
1770			printf("cat_check_link_ancestry: cannot find %u\n", cnid);
1771			invalid = 1;  /* On errors, assume an invalid parent */
1772			break;
1773		}
1774		/* Check if this ancestor is a directory hard link */
1775		if (folder.flags & kHFSHasLinkChainMask) {
1776			invalid = 1;
1777			break;
1778		}
1779		cnid = keyp->parentID;
1780	}
1781	FREE(ip, M_TEMP);
1782	return (invalid);
1783}
1784
1785
1786/*
1787 * updatelink_callback - update a link's chain
1788 */
1789
1790struct linkupdate_state {
1791	cnid_t filelinkid;
1792	cnid_t prevlinkid;
1793	cnid_t nextlinkid;
1794};
1795
1796static int
1797updatelink_callback(__unused const CatalogKey *ckp, CatalogRecord *crp, struct linkupdate_state *state)
1798{
1799	HFSPlusCatalogFile *file;
1800
1801	if (crp->recordType != kHFSPlusFileRecord) {
1802		printf("updatelink_callback: unexpected rec type %d\n", crp->recordType);
1803		return (btNotFound);
1804	}
1805
1806	file = (struct HFSPlusCatalogFile *)crp;
1807	if (file->flags & kHFSHasLinkChainMask) {
1808		if (state->prevlinkid != HFS_IGNORABLE_LINK) {
1809			file->hl_prevLinkID = state->prevlinkid;
1810		}
1811		if (state->nextlinkid != HFS_IGNORABLE_LINK) {
1812			file->hl_nextLinkID = state->nextlinkid;
1813		}
1814	} else {
1815		printf("updatelink_callback: file %d isn't a chain\n", file->fileID);
1816	}
1817	return (0);
1818}
1819
1820/*
1821 * cat_updatelink - update a link's chain
1822 */
1823__private_extern__
1824int
1825cat_updatelink(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t prevlinkid, cnid_t nextlinkid)
1826{
1827	FCB * fcb;
1828	BTreeIterator * iterator;
1829	struct linkupdate_state state;
1830	int result;
1831
1832	fcb = hfsmp->hfs_catalog_cp->c_datafork;
1833	state.filelinkid = linkfileid;
1834	state.prevlinkid = prevlinkid;
1835	state.nextlinkid = nextlinkid;
1836
1837	/* Borrow the btcb iterator since we have an exclusive catalog lock. */
1838	iterator = &((BTreeControlBlockPtr)(fcb->ff_sysfileinfo))->iterator;
1839	iterator->hint.nodeNum = 0;
1840
1841	result = getkey(hfsmp, linkfileid, (CatalogKey *)&iterator->key);
1842	if (result == 0) {
1843		result = BTUpdateRecord(fcb, iterator, (IterateCallBackProcPtr)updatelink_callback, &state);
1844		(void) BTFlushPath(fcb);
1845	} else {
1846		printf("cat_updatelink: couldn't resolve cnid %d\n", linkfileid);
1847	}
1848	return MacToVFSError(result);
1849}
1850
1851/*
1852 * cat_lookuplink - lookup a link by it's name
1853 */
1854__private_extern__
1855int
1856cat_lookuplink(struct hfsmount *hfsmp, struct cat_desc *descp, cnid_t *linkfileid, cnid_t *prevlinkid,  cnid_t *nextlinkid)
1857{
1858	FCB * fcb;
1859	BTreeIterator * iterator;
1860	struct FSBufferDescriptor btdata;
1861	struct HFSPlusCatalogFile file;
1862	int result;
1863
1864	fcb = hfsmp->hfs_catalog_cp->c_datafork;
1865
1866	/* Borrow the btcb iterator since we have an exclusive catalog lock. */
1867	iterator = &((BTreeControlBlockPtr)(fcb->ff_sysfileinfo))->iterator;
1868	iterator->hint.nodeNum = 0;
1869
1870	if ((result = buildkey(hfsmp, descp, (HFSPlusCatalogKey *)&iterator->key, 0))) {
1871		goto exit;
1872	}
1873	BDINIT(btdata, &file);
1874
1875	if ((result = BTSearchRecord(fcb, iterator, &btdata, NULL, NULL))) {
1876		goto exit;
1877	}
1878	if (file.recordType != kHFSPlusFileRecord) {
1879		result = ENOENT;
1880		goto exit;
1881	}
1882	*linkfileid = file.fileID;
1883
1884	if (file.flags & kHFSHasLinkChainMask) {
1885		*prevlinkid = file.hl_prevLinkID;
1886		*nextlinkid = file.hl_nextLinkID;
1887	} else {
1888		*prevlinkid = 0;
1889		*nextlinkid = 0;
1890	}
1891exit:
1892	return MacToVFSError(result);
1893}
1894
1895
1896/*
1897 * cat_lookuplink - lookup a link by its cnid
1898 */
1899__private_extern__
1900int
1901cat_lookuplinkbyid(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid,  cnid_t *nextlinkid)
1902{
1903	FCB * fcb;
1904	BTreeIterator * iterator;
1905	struct FSBufferDescriptor btdata;
1906	struct HFSPlusCatalogFile file;
1907	int result;
1908
1909	fcb = hfsmp->hfs_catalog_cp->c_datafork;
1910
1911	/* Borrow the btcb iterator since we have an exclusive catalog lock. */
1912	iterator = &((BTreeControlBlockPtr)(fcb->ff_sysfileinfo))->iterator;
1913	iterator->hint.nodeNum = 0;
1914
1915	if ((result = getkey(hfsmp, linkfileid, (CatalogKey *)&iterator->key))) {
1916		printf("cat_lookuplinkbyid: getkey for %d failed %d\n", linkfileid, result);
1917		goto exit;
1918	}
1919	BDINIT(btdata, &file);
1920
1921	if ((result = BTSearchRecord(fcb, iterator, &btdata, NULL, NULL))) {
1922		printf("cat_lookuplinkbyid: cannot find %d\n", linkfileid);
1923		goto exit;
1924	}
1925	/* The prev/next chain is only valid when kHFSHasLinkChainMask is set. */
1926	if (file.flags & kHFSHasLinkChainMask) {
1927		cnid_t parent;
1928
1929		parent = ((HFSPlusCatalogKey *)&iterator->key)->parentID;
1930
1931		/* ADL inodes don't have a chain (its in an EA) */
1932		if (parent == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
1933			result = ENOLINK;  /* signal to caller to get head of list */
1934		} else if (parent == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid) {
1935			*prevlinkid = 0;
1936			*nextlinkid = file.hl_firstLinkID;
1937		} else {
1938			*prevlinkid = file.hl_prevLinkID;
1939			*nextlinkid = file.hl_nextLinkID;
1940		}
1941	} else {
1942		*prevlinkid = 0;
1943		*nextlinkid = 0;
1944	}
1945exit:
1946	return MacToVFSError(result);
1947}
1948
1949
1950/*
1951 * cat_createlink - create a link in the catalog
1952 *
1953 * The following cat_attr fields are expected to be set:
1954 *	 ca_linkref
1955 *	 ca_itime
1956 *	 ca_mode (S_IFREG)
1957 *	 ca_recflags
1958 *	 ca_flags
1959 *	 ca_finderinfo (type and creator)
1960 */
1961__private_extern__
1962int
1963cat_createlink(struct hfsmount *hfsmp, struct cat_desc *descp, struct cat_attr *attrp,
1964               cnid_t nextlinkid, cnid_t *linkfileid)
1965{
1966	FCB * fcb;
1967	struct btobj * bto;
1968	FSBufferDescriptor btdata;
1969	HFSPlusForkData *rsrcforkp;
1970	u_int32_t nextCNID;
1971	u_int32_t datalen;
1972	u_long encoding;
1973	int thread_inserted = 0;
1974	int alias_allocated = 0;
1975	int result = 0;
1976
1977	fcb = hfsmp->hfs_catalog_cp->c_datafork;
1978
1979	/*
1980	 * Get the next CNID. We can change it since we hold the catalog lock.
1981	 */
1982	nextCNID = hfsmp->vcbNxtCNID;
1983	if (nextCNID == 0xFFFFFFFF) {
1984		HFS_MOUNT_LOCK(hfsmp, TRUE)
1985		hfsmp->vcbNxtCNID = kHFSFirstUserCatalogNodeID;
1986		hfsmp->vcbAtrb |= kHFSCatalogNodeIDsReusedMask;
1987		HFS_MOUNT_UNLOCK(hfsmp, TRUE);
1988	} else {
1989		hfsmp->vcbNxtCNID++;
1990	}
1991	MarkVCBDirty(hfsmp);
1992
1993	/* Get space for iterator, key and data */
1994	MALLOC(bto, struct btobj *, sizeof(struct btobj), M_TEMP, M_WAITOK);
1995	bto->iterator.hint.nodeNum = 0;
1996	rsrcforkp = &bto->data.hfsPlusFile.resourceFork;
1997
1998	result = buildkey(hfsmp, descp, &bto->key, 0);
1999	if (result) {
2000		printf("cat_createlink: err %d from buildkey\n", result);
2001		goto exit;
2002	}
2003
2004	/* This is our only chance to set the encoding (other than a rename). */
2005	encoding = hfs_pickencoding(bto->key.nodeName.unicode, bto->key.nodeName.length);
2006
2007	/* Insert the thread record first. */
2008	datalen = buildthread((void*)&bto->key, &bto->data, 0, 0);
2009	btdata.bufferAddress = &bto->data;
2010	btdata.itemSize = datalen;
2011	btdata.itemCount = 1;
2012
2013	for (;;) {
2014		buildthreadkey(nextCNID, 0, (CatalogKey *) &bto->iterator.key);
2015
2016		result = BTInsertRecord(fcb, &bto->iterator, &btdata, datalen);
2017		if ((result == btExists) && (hfsmp->vcbAtrb & kHFSCatalogNodeIDsReusedMask)) {
2018			/*
2019			 * Allow CNIDs on HFS Plus volumes to wrap around
2020			 */
2021			if (++nextCNID < kHFSFirstUserCatalogNodeID) {
2022				nextCNID = kHFSFirstUserCatalogNodeID;
2023			}
2024			continue;
2025		}
2026		if (result == 0) {
2027			thread_inserted = 1;
2028		}
2029		break;
2030	}
2031	if (result)
2032		goto exit;
2033
2034	/*
2035	 * CNID is now established. If we have wrapped then
2036	 * update the vcbNxtCNID.
2037	 */
2038	if ((hfsmp->vcbAtrb & kHFSCatalogNodeIDsReusedMask)) {
2039		hfsmp->vcbNxtCNID = nextCNID + 1;
2040		if (hfsmp->vcbNxtCNID < kHFSFirstUserCatalogNodeID) {
2041			hfsmp->vcbNxtCNID = kHFSFirstUserCatalogNodeID;
2042		}
2043	}
2044
2045	/*
2046	 * Now insert the link record.
2047	 */
2048	buildrecord(attrp, nextCNID, 0, encoding, &bto->data, &datalen);
2049
2050	bto->data.hfsPlusFile.hl_prevLinkID = 0;
2051	bto->data.hfsPlusFile.hl_nextLinkID = nextlinkid;
2052	bto->data.hfsPlusFile.hl_linkReference = attrp->ca_linkref;
2053
2054	/* For directory hard links, create alias in resource fork */
2055	if (descp->cd_flags & CD_ISDIR) {
2056		if ((result = cat_makealias(hfsmp, attrp->ca_linkref, &bto->data.hfsPlusFile))) {
2057			goto exit;
2058		}
2059		alias_allocated = 1;
2060	}
2061	btdata.bufferAddress = &bto->data;
2062	btdata.itemSize = datalen;
2063	btdata.itemCount = 1;
2064
2065	bcopy(&bto->key, &bto->iterator.key, sizeof(bto->key));
2066
2067	result = BTInsertRecord(fcb, &bto->iterator, &btdata, datalen);
2068	if (result) {
2069		if (result == btExists)
2070			result = EEXIST;
2071		goto exit;
2072	}
2073	if (linkfileid != NULL) {
2074		*linkfileid = nextCNID;
2075	}
2076exit:
2077	if (result) {
2078		if (thread_inserted) {
2079			printf("cat_createlink: err %d from BTInsertRecord\n", MacToVFSError(result));
2080
2081			buildthreadkey(nextCNID, 0, (CatalogKey *)&bto->iterator.key);
2082			if (BTDeleteRecord(fcb, &bto->iterator)) {
2083	    			printf("hfs: cat_createlink() failed to delete thread record on volume %s\n", hfsmp->vcbVN);
2084				hfs_mark_volume_inconsistent(hfsmp);
2085			}
2086		}
2087		if (alias_allocated && rsrcforkp->extents[0].startBlock != 0) {
2088			(void) BlockDeallocate(hfsmp, rsrcforkp->extents[0].startBlock,
2089					       rsrcforkp->extents[0].blockCount);
2090			rsrcforkp->extents[0].startBlock = 0;
2091			rsrcforkp->extents[0].blockCount = 0;
2092		}
2093	}
2094	(void) BTFlushPath(fcb);
2095	FREE(bto, M_TEMP);
2096
2097	return MacToVFSError(result);
2098}
2099
2100/* Directory hard links are visible as aliases on pre-Leopard systems and
2101 * as normal directories on Leopard or later.  All directory hard link aliases
2102 * have the same resource fork content except for the three uniquely
2103 * identifying values that are updated in the resource fork data when the alias
2104 * is created.  The following array is the constant resource fork data used
2105 * only for creating directory hard link aliases.
2106 */
2107static const char hfs_dirlink_alias_rsrc[] = {
2108	0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x9e, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x32,
2109	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2110	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2111	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2112	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2113	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2114	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2115	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2116	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2117	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2118	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2119	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2120	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2121	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2122	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2123	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2124	0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00,
2125	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2126	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x2b,
2127	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2128	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2129	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2130	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2131	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2132	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
2133	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
2134	0x01, 0x00, 0x00, 0x00, 0x01, 0x9e, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00,
2135	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x32, 0x00, 0x00, 0x61, 0x6c, 0x69, 0x73,
2136	0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
2137};
2138
2139/* Constants for directory hard link alias */
2140enum {
2141	/* Size of resource fork data array for directory hard link alias */
2142	kHFSAliasSize                = 0x1d0,
2143
2144	/* Volume type for ejectable devices like disk image */
2145	kHFSAliasVolTypeEjectable    = 0x5,
2146
2147	/* Offset for volume create date, in Mac OS local time */
2148	kHFSAliasVolCreateDateOffset = 0x12a,
2149
2150	/* Offset for the type of volume */
2151	kHFSAliasVolTypeOffset       = 0x130,
2152
2153	/* Offset for folder ID of the parent directory of the directory inode */
2154	kHFSAliasParentIDOffset      = 0x132,
2155
2156	/* Offset for folder ID of the directory inode */
2157	kHFSAliasTargetIDOffset	     = 0x176,
2158};
2159
2160/* Create and write an alias that points at the directory represented by given
2161 * inode number on the same volume.  Directory hard links are visible as
2162 * aliases in pre-Leopard systems and this function creates these aliases.
2163 *
2164 * Note: This code is very specific to creating alias for the purpose
2165 * of directory hard links only, and should not be generalized.
2166 */
2167static int
2168cat_makealias(struct hfsmount *hfsmp, u_int32_t inode_num, struct HFSPlusCatalogFile *crp)
2169{
2170	struct buf *bp;
2171	daddr64_t blkno;
2172	u_int32_t blkcount;
2173	int blksize;
2174	int sectorsize;
2175	int result;
2176	HFSPlusForkData *rsrcforkp;
2177	char *alias;
2178	uint32_t *valptr;
2179
2180	rsrcforkp = &(crp->resourceFork);
2181
2182	blksize = hfsmp->blockSize;
2183	blkcount = howmany(kHFSAliasSize, blksize);
2184	sectorsize = hfsmp->hfs_logical_block_size;
2185	bzero(rsrcforkp, sizeof(HFSPlusForkData));
2186
2187	/* Allocate some disk space for the alias content. */
2188	result = BlockAllocate(hfsmp, 0, blkcount, blkcount, 1, 1,
2189	                       &rsrcforkp->extents[0].startBlock,
2190	                       &rsrcforkp->extents[0].blockCount);
2191	if (result) {
2192		rsrcforkp->extents[0].startBlock = 0;
2193		goto exit;
2194	}
2195
2196	/* Acquire a buffer cache block for our block. */
2197	blkno = ((u_int64_t)rsrcforkp->extents[0].startBlock * (u_int64_t)blksize) / sectorsize;
2198	blkno += hfsmp->hfsPlusIOPosOffset / sectorsize;
2199
2200	bp = buf_getblk(hfsmp->hfs_devvp, blkno, roundup(kHFSAliasSize, hfsmp->hfs_logical_block_size), 0, 0, BLK_META);
2201	if (hfsmp->jnl) {
2202		journal_modify_block_start(hfsmp->jnl, bp);
2203	}
2204
2205	/* Generate alias content */
2206	alias = (char *)buf_dataptr(bp);
2207	bzero(alias, buf_size(bp));
2208	bcopy(hfs_dirlink_alias_rsrc, alias, kHFSAliasSize);
2209
2210	/* Set the volume create date, local time in Mac OS format */
2211	valptr = (uint32_t *)(alias + kHFSAliasVolCreateDateOffset);
2212	*valptr = OSSwapHostToBigInt32(hfsmp->localCreateDate);
2213
2214	/* If the file system is on a virtual device like disk image,
2215	 * update the volume type to be ejectable device.
2216	 */
2217	if (hfsmp->hfs_flags & HFS_VIRTUAL_DEVICE) {
2218		*(uint16_t *)(alias + kHFSAliasVolTypeOffset) =
2219		OSSwapHostToBigInt16(kHFSAliasVolTypeEjectable);
2220	}
2221
2222	/* Set id of the parent of the target directory */
2223	valptr = (uint32_t *)(alias + kHFSAliasParentIDOffset);
2224	*valptr = OSSwapHostToBigInt32(hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid);
2225
2226	/* Set id of the target directory */
2227	valptr = (uint32_t *)(alias + kHFSAliasTargetIDOffset);
2228	*valptr = OSSwapHostToBigInt32(inode_num);
2229
2230	/* Write alias content to disk. */
2231	if (hfsmp->jnl) {
2232		journal_modify_block_end(hfsmp->jnl, bp, NULL, NULL);
2233	} else if ((result = buf_bwrite(bp))) {
2234		goto exit;
2235	}
2236
2237	/* Finish initializing the fork data. */
2238	rsrcforkp->logicalSize = kHFSAliasSize;
2239	rsrcforkp->totalBlocks = rsrcforkp->extents[0].blockCount;
2240
2241exit:
2242	if (result && rsrcforkp->extents[0].startBlock != 0) {
2243		(void) BlockDeallocate(hfsmp, rsrcforkp->extents[0].startBlock, rsrcforkp->extents[0].blockCount);
2244		rsrcforkp->extents[0].startBlock = 0;
2245		rsrcforkp->extents[0].blockCount = 0;
2246		rsrcforkp->logicalSize = 0;
2247		rsrcforkp->totalBlocks = 0;
2248	}
2249	return (result);
2250}
2251
2252/*
2253 * cat_deletelink - delete a link from the catalog
2254 */
2255__private_extern__
2256int
2257cat_deletelink(struct hfsmount *hfsmp, struct cat_desc *descp)
2258{
2259	struct HFSPlusCatalogFile file;
2260	struct cat_attr cattr;
2261	uint32_t totalBlocks;
2262	int i;
2263	int result;
2264
2265	bzero(&file, sizeof (file));
2266	bzero(&cattr, sizeof (cattr));
2267	cattr.ca_fileid = descp->cd_cnid;
2268
2269	/* Directory links have alias content to remove. */
2270	if (descp->cd_flags & CD_ISDIR) {
2271		FCB * fcb;
2272		BTreeIterator * iterator;
2273		struct FSBufferDescriptor btdata;
2274
2275		fcb = hfsmp->hfs_catalog_cp->c_datafork;
2276
2277		/* Borrow the btcb iterator since we have an exclusive catalog lock. */
2278		iterator = &((BTreeControlBlockPtr)(fcb->ff_sysfileinfo))->iterator;
2279		iterator->hint.nodeNum = 0;
2280
2281		if ((result = buildkey(hfsmp, descp, (HFSPlusCatalogKey *)&iterator->key, 0))) {
2282			goto exit;
2283		}
2284		BDINIT(btdata, &file);
2285
2286		if ((result = BTSearchRecord(fcb, iterator, &btdata, NULL, NULL))) {
2287			goto exit;
2288		}
2289	}
2290
2291	result = cat_delete(hfsmp, descp, &cattr);
2292
2293	if ((result == 0) &&
2294	    (descp->cd_flags & CD_ISDIR) &&
2295	    (file.recordType == kHFSPlusFileRecord)) {
2296
2297		totalBlocks = file.resourceFork.totalBlocks;
2298
2299		for (i = 0; (i < 8) && (totalBlocks > 0); i++) {
2300			if ((file.resourceFork.extents[i].blockCount == 0) &&
2301			    (file.resourceFork.extents[i].startBlock == 0)) {
2302				break;
2303			}
2304
2305			(void) BlockDeallocate(hfsmp,
2306				file.resourceFork.extents[i].startBlock,
2307				file.resourceFork.extents[i].blockCount);
2308
2309			totalBlocks -= file.resourceFork.extents[i].blockCount;
2310			file.resourceFork.extents[i].startBlock = 0;
2311			file.resourceFork.extents[i].blockCount = 0;
2312		}
2313	}
2314exit:
2315	return (result);
2316}
2317
2318
2319/*
2320 * Callback to collect directory entries.
2321 * Called with readattr_state for each item in a directory.
2322 */
2323struct readattr_state {
2324	struct hfsmount *hfsmp;
2325	struct cat_entrylist *list;
2326	cnid_t	dir_cnid;
2327	int stdhfs;
2328	int error;
2329};
2330
2331static int
2332getentriesattr_callback(const CatalogKey *key, const CatalogRecord *rec,
2333             struct readattr_state *state)
2334{
2335	struct cat_entrylist *list = state->list;
2336	struct hfsmount *hfsmp = state->hfsmp;
2337	struct cat_entry *cep;
2338	cnid_t parentcnid;
2339
2340	if (list->realentries >= list->maxentries)
2341		return (0);  /* stop */
2342
2343	parentcnid = state->stdhfs ? key->hfs.parentID : key->hfsPlus.parentID;
2344
2345	switch(rec->recordType) {
2346	case kHFSPlusFolderRecord:
2347	case kHFSPlusFileRecord:
2348	case kHFSFolderRecord:
2349	case kHFSFileRecord:
2350		if (parentcnid != state->dir_cnid) {
2351			state->error = ENOENT;
2352			return (0);	/* stop */
2353		}
2354		break;
2355	default:
2356		state->error = ENOENT;
2357		return (0);	/* stop */
2358	}
2359
2360	/* Hide the private system directories and journal files */
2361	if (parentcnid == kHFSRootFolderID) {
2362		if (rec->recordType == kHFSPlusFolderRecord) {
2363			if (rec->hfsPlusFolder.folderID == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid ||
2364			    rec->hfsPlusFolder.folderID == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
2365			    	list->skipentries++;
2366				return (1);	/* continue */
2367			}
2368		}
2369		if ((hfsmp->jnl || ((HFSTOVCB(hfsmp)->vcbAtrb & kHFSVolumeJournaledMask) && (hfsmp->hfs_flags & HFS_READ_ONLY))) &&
2370		    (rec->recordType == kHFSPlusFileRecord) &&
2371		    ((rec->hfsPlusFile.fileID == hfsmp->hfs_jnlfileid) ||
2372		     (rec->hfsPlusFile.fileID == hfsmp->hfs_jnlinfoblkid))) {
2373			list->skipentries++;
2374			return (1);	/* continue */
2375		}
2376	}
2377
2378	cep = &list->entry[list->realentries++];
2379
2380	if (state->stdhfs) {
2381		struct HFSPlusCatalogFile cnoderec;
2382		HFSPlusCatalogKey * pluskey;
2383		u_long encoding;
2384
2385		promoteattr(hfsmp, rec, &cnoderec);
2386		getbsdattr(hfsmp, &cnoderec, &cep->ce_attr);
2387
2388		MALLOC(pluskey, HFSPlusCatalogKey *, sizeof(HFSPlusCatalogKey), M_TEMP, M_WAITOK);
2389		promotekey(hfsmp, (const HFSCatalogKey *)key, pluskey, &encoding);
2390		builddesc(pluskey, getcnid(rec), 0, encoding, isadir(rec), &cep->ce_desc);
2391		FREE(pluskey, M_TEMP);
2392
2393		if (rec->recordType == kHFSFileRecord) {
2394			int blksize = HFSTOVCB(hfsmp)->blockSize;
2395
2396			cep->ce_datasize = rec->hfsFile.dataLogicalSize;
2397			cep->ce_datablks = rec->hfsFile.dataPhysicalSize / blksize;
2398			cep->ce_rsrcsize = rec->hfsFile.rsrcLogicalSize;
2399			cep->ce_rsrcblks = rec->hfsFile.rsrcPhysicalSize / blksize;
2400		}
2401	} else {
2402		getbsdattr(hfsmp, (const struct HFSPlusCatalogFile *)rec, &cep->ce_attr);
2403		builddesc((const HFSPlusCatalogKey *)key, getcnid(rec), 0, getencoding(rec),
2404			isadir(rec), &cep->ce_desc);
2405
2406		if (rec->recordType == kHFSPlusFileRecord) {
2407			cep->ce_datasize = rec->hfsPlusFile.dataFork.logicalSize;
2408			cep->ce_datablks = rec->hfsPlusFile.dataFork.totalBlocks;
2409			cep->ce_rsrcsize = rec->hfsPlusFile.resourceFork.logicalSize;
2410			cep->ce_rsrcblks = rec->hfsPlusFile.resourceFork.totalBlocks;
2411
2412			/* Save link reference for later processing. */
2413			if ((SWAP_BE32(rec->hfsPlusFile.userInfo.fdType) == kHardLinkFileType) &&
2414			    (SWAP_BE32(rec->hfsPlusFile.userInfo.fdCreator) == kHFSPlusCreator)) {
2415				cep->ce_attr.ca_linkref = rec->hfsPlusFile.bsdInfo.special.iNodeNum;
2416			} else if ((rec->hfsPlusFile.flags & kHFSHasLinkChainMask) &&
2417			           (SWAP_BE32(rec->hfsPlusFile.userInfo.fdType) == kHFSAliasType) &&
2418				   (SWAP_BE32(rec->hfsPlusFile.userInfo.fdCreator) == kHFSAliasCreator)) {
2419				cep->ce_attr.ca_linkref = rec->hfsPlusFile.bsdInfo.special.iNodeNum;
2420			}
2421		}
2422	}
2423
2424	return (list->realentries < list->maxentries);
2425}
2426
2427/*
2428 * Pack a cat_entrylist buffer with attributes from the catalog
2429 *
2430 * Note: index is zero relative
2431 */
2432__private_extern__
2433int
2434cat_getentriesattr(struct hfsmount *hfsmp, directoryhint_t *dirhint, struct cat_entrylist *ce_list)
2435{
2436	FCB* fcb;
2437	CatalogKey * key;
2438	BTreeIterator * iterator;
2439	struct readattr_state state;
2440	cnid_t parentcnid;
2441	int i;
2442	int std_hfs;
2443	int index;
2444	int have_key;
2445	int result = 0;
2446
2447	ce_list->realentries = 0;
2448
2449	fcb = GetFileControlBlock(HFSTOVCB(hfsmp)->catalogRefNum);
2450	std_hfs = (HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord);
2451	parentcnid = dirhint->dh_desc.cd_parentcnid;
2452
2453	state.hfsmp = hfsmp;
2454	state.list = ce_list;
2455	state.dir_cnid = parentcnid;
2456	state.stdhfs = std_hfs;
2457	state.error = 0;
2458
2459	MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
2460	bzero(iterator, sizeof(*iterator));
2461	key = (CatalogKey *)&iterator->key;
2462	have_key = 0;
2463	iterator->hint.nodeNum = dirhint->dh_desc.cd_hint;
2464	index = dirhint->dh_index + 1;
2465
2466	/*
2467	 * Attempt to build a key from cached filename
2468	 */
2469	if (dirhint->dh_desc.cd_namelen != 0) {
2470		if (buildkey(hfsmp, &dirhint->dh_desc, (HFSPlusCatalogKey *)key, 0) == 0) {
2471			have_key = 1;
2472		}
2473	}
2474
2475	/*
2476	 * If the last entry wasn't cached then position the btree iterator
2477	 */
2478	if ((index == 0) || !have_key) {
2479		/*
2480		 * Position the iterator at the directory's thread record.
2481		 * (i.e. just before the first entry)
2482		 */
2483		buildthreadkey(dirhint->dh_desc.cd_parentcnid, (hfsmp->hfs_flags & HFS_STANDARD), key);
2484		result = BTSearchRecord(fcb, iterator, NULL, NULL, iterator);
2485		if (result) {
2486			result = MacToVFSError(result);
2487			goto exit;
2488		}
2489
2490		/*
2491		 * Iterate until we reach the entry just
2492		 * before the one we want to start with.
2493		 */
2494		if (index > 0) {
2495			struct position_state ps;
2496
2497			ps.error = 0;
2498			ps.count = 0;
2499			ps.index = index;
2500			ps.parentID = dirhint->dh_desc.cd_parentcnid;
2501			ps.hfsmp = hfsmp;
2502
2503			result = BTIterateRecords(fcb, kBTreeNextRecord, iterator,
2504			                          (IterateCallBackProcPtr)cat_findposition, &ps);
2505			if (ps.error)
2506				result = ps.error;
2507			else
2508				result = MacToVFSError(result);
2509			if (result) {
2510				result = MacToVFSError(result);
2511				goto exit;
2512			}
2513		}
2514	}
2515
2516	/* Fill list with entries starting at iterator->key. */
2517	result = BTIterateRecords(fcb, kBTreeNextRecord, iterator,
2518			(IterateCallBackProcPtr)getentriesattr_callback, &state);
2519
2520	if (state.error)
2521		result = state.error;
2522	else if (ce_list->realentries == 0)
2523		result = ENOENT;
2524	else
2525		result = MacToVFSError(result);
2526
2527	if (std_hfs)
2528		goto exit;
2529
2530	/*
2531	 *  Resolve any hard links.
2532	 */
2533	for (i = 0; i < (int)ce_list->realentries; ++i) {
2534		struct FndrFileInfo *fip;
2535		struct cat_entry *cep;
2536		struct HFSPlusCatalogFile filerec;
2537		int isdirlink = 0;
2538		int isfilelink = 0;
2539
2540		cep = &ce_list->entry[i];
2541		if (cep->ce_attr.ca_linkref == 0)
2542			continue;
2543
2544		/* Note: Finder info is still in Big Endian */
2545		fip = (struct FndrFileInfo *)&cep->ce_attr.ca_finderinfo;
2546
2547		if (S_ISREG(cep->ce_attr.ca_mode) &&
2548		    (SWAP_BE32(fip->fdType) == kHardLinkFileType) &&
2549		    (SWAP_BE32(fip->fdCreator) == kHFSPlusCreator)) {
2550			isfilelink = 1;
2551		}
2552		if (S_ISREG(cep->ce_attr.ca_mode) &&
2553		    (SWAP_BE32(fip->fdType) == kHFSAliasType) &&
2554		    (SWAP_BE32(fip->fdCreator) == kHFSAliasCreator) &&
2555		    (cep->ce_attr.ca_recflags & kHFSHasLinkChainMask)) {
2556			isdirlink = 1;
2557		}
2558		if (isfilelink || isdirlink) {
2559			if (cat_resolvelink(hfsmp, cep->ce_attr.ca_linkref, isdirlink, &filerec) != 0)
2560				continue;
2561			/* Repack entry from inode record. */
2562			getbsdattr(hfsmp, &filerec, &cep->ce_attr);
2563			cep->ce_datasize = filerec.dataFork.logicalSize;
2564			cep->ce_datablks = filerec.dataFork.totalBlocks;
2565			cep->ce_rsrcsize = filerec.resourceFork.logicalSize;
2566			cep->ce_rsrcblks = filerec.resourceFork.totalBlocks;
2567		}
2568	}
2569exit:
2570	FREE(iterator, M_TEMP);
2571
2572	return MacToVFSError(result);
2573}
2574
2575#define SMALL_DIRENTRY_SIZE  (int)(sizeof(struct dirent) - (MAXNAMLEN + 1) + 8)
2576
2577/*
2578 * Callback to pack directory entries.
2579 * Called with packdirentry_state for each item in a directory.
2580 */
2581
2582/* Hard link information collected during cat_getdirentries. */
2583struct linkinfo {
2584	u_long       link_ref;
2585	user_addr_t  dirent_addr;
2586};
2587typedef struct linkinfo linkinfo_t;
2588
2589/* State information for the getdirentries_callback function. */
2590struct packdirentry_state {
2591	int            cbs_extended;
2592	u_int32_t      cbs_parentID;
2593	u_int32_t      cbs_index;
2594	uio_t	       cbs_uio;
2595	ExtendedVCB *  cbs_hfsmp;
2596	int            cbs_result;
2597	int32_t        cbs_nlinks;
2598	int32_t        cbs_maxlinks;
2599	linkinfo_t *   cbs_linkinfo;
2600	struct cat_desc * cbs_desc;
2601	u_int8_t        * cbs_namebuf;
2602	/*
2603	 * The following fields are only used for NFS readdir, which
2604	 * uses the next file id as the seek offset of each entry.
2605	 */
2606	struct direntry * cbs_direntry;
2607	struct direntry * cbs_prevdirentry;
2608	u_int32_t      cbs_previlinkref;
2609	Boolean        cbs_hasprevdirentry;
2610	Boolean        cbs_eof;
2611};
2612
2613/*
2614 * getdirentries callback for HFS Plus directories.
2615 */
2616static int
2617getdirentries_callback(const CatalogKey *ckp, const CatalogRecord *crp,
2618                 struct packdirentry_state *state)
2619{
2620	struct hfsmount *hfsmp;
2621	const CatalogName *cnp;
2622	cnid_t curID;
2623	OSErr result;
2624	struct dirent catent;
2625	struct direntry * entry = NULL;
2626	time_t itime;
2627	u_int32_t ilinkref = 0;
2628	u_int32_t curlinkref = 0;
2629	cnid_t  cnid;
2630	int hide = 0;
2631	u_int8_t type = DT_UNKNOWN;
2632	u_int8_t is_mangled = 0;
2633	u_int8_t *nameptr;
2634	user_addr_t uiobase = USER_ADDR_NULL;
2635	size_t namelen = 0;
2636	size_t maxnamelen;
2637	size_t uiosize = 0;
2638	caddr_t uioaddr;
2639	Boolean stop_after_pack = false;
2640
2641	hfsmp = state->cbs_hfsmp;
2642	curID = ckp->hfsPlus.parentID;
2643
2644	/* We're done when parent directory changes */
2645	if (state->cbs_parentID != curID) {
2646		if (state->cbs_extended) {
2647			/* The last record has not been returned yet, so we
2648			 * want to stop after packing the last item
2649			 */
2650			if (state->cbs_hasprevdirentry) {
2651				stop_after_pack = true;
2652			} else {
2653				state->cbs_result = ENOENT;
2654				return (0);	/* stop */
2655			}
2656		} else {
2657			state->cbs_result = ENOENT;
2658			return (0);	/* stop */
2659		}
2660	}
2661
2662	if (state->cbs_extended) {
2663		entry = state->cbs_direntry;
2664		nameptr = (u_int8_t *)&entry->d_name[0];
2665		maxnamelen = NAME_MAX;
2666	} else {
2667		nameptr = (u_int8_t *)&catent.d_name[0];
2668		maxnamelen = NAME_MAX;
2669	}
2670
2671	if (state->cbs_extended && stop_after_pack) {
2672		/* The last item returns a non-zero invalid cookie */
2673		cnid = INT_MAX;
2674	} else {
2675		switch(crp->recordType) {
2676		case kHFSPlusFolderRecord:
2677			type = DT_DIR;
2678			cnid = crp->hfsPlusFolder.folderID;
2679			/* Hide our private system directories. */
2680			if (curID == kHFSRootFolderID) {
2681				if (cnid == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid ||
2682				    cnid == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
2683					hide = 1;
2684				}
2685			}
2686			break;
2687		case kHFSPlusFileRecord:
2688			itime = to_bsd_time(crp->hfsPlusFile.createDate);
2689			type = MODE_TO_DT(crp->hfsPlusFile.bsdInfo.fileMode);
2690			cnid = crp->hfsPlusFile.fileID;
2691			/*
2692			 * When a hardlink link is encountered save its link ref.
2693			 */
2694			if ((SWAP_BE32(crp->hfsPlusFile.userInfo.fdType) == kHardLinkFileType) &&
2695				(SWAP_BE32(crp->hfsPlusFile.userInfo.fdCreator) == kHFSPlusCreator) &&
2696				((itime == (time_t)hfsmp->hfs_itime) ||
2697				 (itime == (time_t)hfsmp->hfs_metadata_createdate))) {
2698				/* If link ref is inode's file id then use it directly. */
2699				if (crp->hfsPlusFile.flags & kHFSHasLinkChainMask) {
2700					cnid = crp->hfsPlusFile.hl_linkReference;
2701				} else {
2702					ilinkref = crp->hfsPlusFile.hl_linkReference;
2703				}
2704			} else if ((SWAP_BE32(crp->hfsPlusFile.userInfo.fdType) == kHFSAliasType) &&
2705				(SWAP_BE32(crp->hfsPlusFile.userInfo.fdCreator) == kHFSAliasCreator) &&
2706				(crp->hfsPlusFile.flags & kHFSHasLinkChainMask) &&
2707				(crp->hfsPlusFile.hl_linkReference >= kHFSFirstUserCatalogNodeID) &&
2708				((itime == (time_t)hfsmp->hfs_itime) ||
2709				 (itime == (time_t)hfsmp->hfs_metadata_createdate))) {
2710				/* A directory's link resolves to a directory. */
2711				type = DT_DIR;
2712				/* A directory's link ref is always inode's file id. */
2713				cnid = crp->hfsPlusFile.hl_linkReference;
2714			}
2715			/* Hide the journal files */
2716			if ((curID == kHFSRootFolderID) &&
2717				((hfsmp->jnl || ((HFSTOVCB(hfsmp)->vcbAtrb & kHFSVolumeJournaledMask) && (hfsmp->hfs_flags & HFS_READ_ONLY)))) &&
2718				((cnid == hfsmp->hfs_jnlfileid) ||
2719				 (cnid == hfsmp->hfs_jnlinfoblkid))) {
2720				hide = 1;
2721			}
2722			break;
2723		default:
2724			return (0);	/* stop */
2725		};
2726
2727		cnp = (const CatalogName*) &ckp->hfsPlus.nodeName;
2728
2729		namelen = cnp->ustr.length;
2730		/*
2731		 * For MacRoman encoded names, assume that its ascii and
2732		 * convert it directly in an attempt to avoid the more
2733		 * expensive utf8_encodestr conversion.
2734		 */
2735		if ((namelen < maxnamelen) && (crp->hfsPlusFile.textEncoding == 0)) {
2736			int i;
2737			u_int16_t ch;
2738			const u_int16_t *chp;
2739
2740			chp = &cnp->ustr.unicode[0];
2741			for (i = 0; i < (int)namelen; ++i) {
2742				ch = *chp++;
2743				if (ch > 0x007f || ch == 0x0000) {
2744					/* Perform expensive utf8_encodestr conversion */
2745					goto encodestr;
2746				}
2747				nameptr[i] = (ch == '/') ? ':' : (u_int8_t)ch;
2748			}
2749			nameptr[namelen] = '\0';
2750			result = 0;
2751		} else {
2752encodestr:
2753			result = utf8_encodestr(cnp->ustr.unicode, namelen * sizeof(UniChar),
2754						nameptr, &namelen, maxnamelen + 1, ':', 0);
2755		}
2756
2757		/* Check result returned from encoding the filename to utf8 */
2758		if (result == ENAMETOOLONG) {
2759			result = ConvertUnicodeToUTF8Mangled(cnp->ustr.length * sizeof(UniChar),
2760							     cnp->ustr.unicode, maxnamelen + 1,
2761							     (ByteCount*)&namelen, nameptr, cnid);
2762			is_mangled = 1;
2763		}
2764	}
2765
2766	if (state->cbs_extended) {
2767		/*
2768		 * The index is 1 relative and includes "." and ".."
2769		 *
2770		 * Also stuff the cnid in the upper 32 bits of the cookie.
2771		 * The cookie is stored to the previous entry, which will
2772		 * be packed and copied this time
2773		 */
2774		state->cbs_prevdirentry->d_seekoff = (state->cbs_index + 3) | ((u_int64_t)cnid << 32);
2775		uiosize = state->cbs_prevdirentry->d_reclen;
2776		uioaddr = (caddr_t) state->cbs_prevdirentry;
2777	} else {
2778		catent.d_type = type;
2779		catent.d_namlen = namelen;
2780		catent.d_reclen = uiosize = STD_DIRENT_LEN(namelen);
2781		if (hide)
2782			catent.d_fileno = 0;  /* file number = 0 means skip entry */
2783		else
2784			catent.d_fileno = cnid;
2785		uioaddr = (caddr_t) &catent;
2786	}
2787
2788	/* Save current base address for post processing of hard-links. */
2789	if (ilinkref || state->cbs_previlinkref) {
2790		uiobase = uio_curriovbase(state->cbs_uio);
2791	}
2792	/* If this entry won't fit then we're done */
2793	if ((uiosize > uio_resid(state->cbs_uio)) ||
2794	    (ilinkref != 0 && state->cbs_nlinks == state->cbs_maxlinks)) {
2795		return (0);	/* stop */
2796	}
2797
2798	if (!state->cbs_extended || state->cbs_hasprevdirentry) {
2799		state->cbs_result = uiomove(uioaddr, uiosize, state->cbs_uio);
2800		if (state->cbs_result == 0) {
2801			++state->cbs_index;
2802
2803			/* Remember previous entry */
2804			state->cbs_desc->cd_cnid = cnid;
2805			if (type == DT_DIR) {
2806				state->cbs_desc->cd_flags |= CD_ISDIR;
2807			} else {
2808				state->cbs_desc->cd_flags &= ~CD_ISDIR;
2809			}
2810			if (state->cbs_desc->cd_nameptr != NULL) {
2811				state->cbs_desc->cd_namelen = 0;
2812			}
2813#if 0
2814			state->cbs_desc->cd_encoding = xxxx;
2815#endif
2816			if (!is_mangled) {
2817				state->cbs_desc->cd_namelen = namelen;
2818				bcopy(nameptr, state->cbs_namebuf, namelen + 1);
2819			} else {
2820				/* Store unmangled name for the directory hint else it will
2821				 * restart readdir at the last location again
2822				 */
2823				u_int8_t *new_nameptr;
2824				size_t bufsize;
2825				size_t tmp_namelen = 0;
2826
2827				cnp = (const CatalogName *)&ckp->hfsPlus.nodeName;
2828				bufsize = 1 + utf8_encodelen(cnp->ustr.unicode,
2829				                             cnp->ustr.length * sizeof(UniChar),
2830				                             ':', 0);
2831				MALLOC(new_nameptr, u_int8_t *, bufsize, M_TEMP, M_WAITOK);
2832				result = utf8_encodestr(cnp->ustr.unicode,
2833				                        cnp->ustr.length * sizeof(UniChar),
2834				                        new_nameptr, &tmp_namelen, bufsize, ':', 0);
2835
2836				state->cbs_desc->cd_namelen = tmp_namelen;
2837				bcopy(new_nameptr, state->cbs_namebuf, tmp_namelen + 1);
2838
2839				FREE(new_nameptr, M_TEMP);
2840			}
2841		}
2842		if (state->cbs_hasprevdirentry) {
2843			curlinkref = ilinkref;               /* save current */
2844			ilinkref = state->cbs_previlinkref;  /* use previous */
2845		}
2846		/*
2847		 * Record any hard links for post processing.
2848		 */
2849		if ((ilinkref != 0) &&
2850			(state->cbs_result == 0) &&
2851			(state->cbs_nlinks < state->cbs_maxlinks)) {
2852			state->cbs_linkinfo[state->cbs_nlinks].dirent_addr = uiobase;
2853			state->cbs_linkinfo[state->cbs_nlinks].link_ref = ilinkref;
2854			state->cbs_nlinks++;
2855		}
2856		if (state->cbs_hasprevdirentry) {
2857			ilinkref = curlinkref;   /* restore current */
2858		}
2859	}
2860
2861	/* Fill the direntry to be used the next time */
2862	if (state->cbs_extended) {
2863		if (stop_after_pack) {
2864			state->cbs_eof = true;
2865			return (0);	/* stop */
2866		}
2867		entry->d_type = type;
2868		entry->d_namlen = namelen;
2869		entry->d_reclen = EXT_DIRENT_LEN(namelen);
2870		if (hide) {
2871			/* File number = 0 means skip entry */
2872			entry->d_fileno = 0;
2873		} else {
2874			entry->d_fileno = cnid;
2875		}
2876		/* swap the current and previous entry */
2877		struct direntry * tmp;
2878		tmp = state->cbs_direntry;
2879		state->cbs_direntry = state->cbs_prevdirentry;
2880		state->cbs_prevdirentry = tmp;
2881		state->cbs_hasprevdirentry = true;
2882		state->cbs_previlinkref = ilinkref;
2883	}
2884
2885	/* Continue iteration if there's room */
2886	return (state->cbs_result == 0  &&
2887		uio_resid(state->cbs_uio) >= SMALL_DIRENTRY_SIZE);
2888}
2889
2890/*
2891 * getdirentries callback for standard HFS (non HFS+) directories.
2892 */
2893static int
2894getdirentries_std_callback(const CatalogKey *ckp, const CatalogRecord *crp,
2895                           struct packdirentry_state *state)
2896{
2897	struct hfsmount *hfsmp;
2898	const CatalogName *cnp;
2899	cnid_t curID;
2900	OSErr result;
2901	struct dirent catent;
2902	cnid_t  cnid;
2903	u_int8_t type = DT_UNKNOWN;
2904	u_int8_t *nameptr;
2905	size_t namelen = 0;
2906	size_t maxnamelen;
2907	size_t uiosize = 0;
2908	caddr_t uioaddr;
2909
2910	hfsmp = state->cbs_hfsmp;
2911
2912	curID = ckp->hfs.parentID;
2913
2914	/* We're done when parent directory changes */
2915	if (state->cbs_parentID != curID) {
2916		state->cbs_result = ENOENT;
2917		return (0);	/* stop */
2918	}
2919
2920	nameptr = (u_int8_t *)&catent.d_name[0];
2921	maxnamelen = NAME_MAX;
2922
2923	switch(crp->recordType) {
2924	case kHFSFolderRecord:
2925		type = DT_DIR;
2926		cnid = crp->hfsFolder.folderID;
2927		break;
2928	case kHFSFileRecord:
2929		type = DT_REG;
2930		cnid = crp->hfsFile.fileID;
2931		break;
2932	default:
2933		return (0);	/* stop */
2934	};
2935
2936	cnp = (const CatalogName*) ckp->hfs.nodeName;
2937	result = hfs_to_utf8(hfsmp, cnp->pstr, maxnamelen + 1, (ByteCount *)&namelen, nameptr);
2938	/*
2939	 * When an HFS name cannot be encoded with the current
2940	 * volume encoding we use MacRoman as a fallback.
2941	 */
2942	if (result) {
2943		result = mac_roman_to_utf8(cnp->pstr, maxnamelen + 1, (ByteCount *)&namelen, nameptr);
2944	}
2945	catent.d_type = type;
2946	catent.d_namlen = namelen;
2947	catent.d_reclen = uiosize = STD_DIRENT_LEN(namelen);
2948	catent.d_fileno = cnid;
2949	uioaddr = (caddr_t) &catent;
2950
2951	/* If this entry won't fit then we're done */
2952	if (uiosize > uio_resid(state->cbs_uio)) {
2953		return (0);	/* stop */
2954	}
2955
2956	state->cbs_result = uiomove(uioaddr, uiosize, state->cbs_uio);
2957	if (state->cbs_result == 0) {
2958		++state->cbs_index;
2959
2960		/* Remember previous entry */
2961		state->cbs_desc->cd_cnid = cnid;
2962		if (type == DT_DIR) {
2963			state->cbs_desc->cd_flags |= CD_ISDIR;
2964		} else {
2965			state->cbs_desc->cd_flags &= ~CD_ISDIR;
2966		}
2967		if (state->cbs_desc->cd_nameptr != NULL) {
2968			state->cbs_desc->cd_namelen = 0;
2969		}
2970		state->cbs_desc->cd_namelen = namelen;
2971		bcopy(nameptr, state->cbs_namebuf, namelen + 1);
2972	}
2973
2974	/* Continue iteration if there's room */
2975	return (state->cbs_result == 0  && uio_resid(state->cbs_uio) >= SMALL_DIRENTRY_SIZE);
2976}
2977
2978/*
2979 * Pack a uio buffer with directory entries from the catalog
2980 */
2981__private_extern__
2982int
2983cat_getdirentries(struct hfsmount *hfsmp, int entrycnt, directoryhint_t *dirhint,
2984				  uio_t uio, int extended, int * items, int * eofflag)
2985{
2986	FCB* fcb;
2987	BTreeIterator * iterator;
2988	CatalogKey * key;
2989	struct packdirentry_state state;
2990	void * buffer;
2991	int bufsize;
2992	int maxlinks;
2993	int result;
2994	int index;
2995	int have_key;
2996
2997	if (extended && (hfsmp->hfs_flags & HFS_STANDARD)) {
2998		return (ENOTSUP);
2999	}
3000	fcb = hfsmp->hfs_catalog_cp->c_datafork;
3001
3002	/*
3003	 * Get a buffer for link info array, btree iterator and a direntry:
3004	 */
3005	maxlinks = MIN(entrycnt, uio_resid(uio) / SMALL_DIRENTRY_SIZE);
3006	bufsize = MAXPATHLEN + (maxlinks * sizeof(linkinfo_t)) + sizeof(*iterator);
3007	if (extended) {
3008		bufsize += 2*sizeof(struct direntry);
3009	}
3010	MALLOC(buffer, void *, bufsize, M_TEMP, M_WAITOK);
3011	bzero(buffer, bufsize);
3012
3013	state.cbs_extended = extended;
3014	state.cbs_hasprevdirentry = false;
3015	state.cbs_previlinkref = 0;
3016	state.cbs_nlinks = 0;
3017	state.cbs_maxlinks = maxlinks;
3018	state.cbs_linkinfo = (linkinfo_t *)((char *)buffer + MAXPATHLEN);
3019
3020	iterator = (BTreeIterator *) ((char *)state.cbs_linkinfo + (maxlinks * sizeof(linkinfo_t)));
3021	key = (CatalogKey *)&iterator->key;
3022	have_key = 0;
3023	index = dirhint->dh_index + 1;
3024	if (extended) {
3025		state.cbs_direntry = (struct direntry *)((char *)iterator + sizeof(BTreeIterator));
3026		state.cbs_prevdirentry = state.cbs_direntry + 1;
3027		state.cbs_eof = false;
3028	}
3029	/*
3030	 * Attempt to build a key from cached filename
3031	 */
3032	if (dirhint->dh_desc.cd_namelen != 0) {
3033		if (buildkey(hfsmp, &dirhint->dh_desc, (HFSPlusCatalogKey *)key, 0) == 0) {
3034			iterator->hint.nodeNum = dirhint->dh_desc.cd_hint;
3035			have_key = 1;
3036		}
3037	}
3038
3039	if (index == 0 && dirhint->dh_threadhint != 0) {
3040		/*
3041		 * Position the iterator at the directory's thread record.
3042		 * (i.e. just before the first entry)
3043		 */
3044		buildthreadkey(dirhint->dh_desc.cd_parentcnid, (hfsmp->hfs_flags & HFS_STANDARD), key);
3045		iterator->hint.nodeNum = dirhint->dh_threadhint;
3046		iterator->hint.index = 0;
3047		have_key = 1;
3048	}
3049
3050	/*
3051	 * If the last entry wasn't cached then position the btree iterator
3052	 */
3053	if (!have_key) {
3054		/*
3055		 * Position the iterator at the directory's thread record.
3056		 * (i.e. just before the first entry)
3057		 */
3058		buildthreadkey(dirhint->dh_desc.cd_parentcnid, (hfsmp->hfs_flags & HFS_STANDARD), key);
3059		result = BTSearchRecord(fcb, iterator, NULL, NULL, iterator);
3060		if (result) {
3061			result = MacToVFSError(result);
3062			goto cleanup;
3063		}
3064		if (index == 0) {
3065			dirhint->dh_threadhint = iterator->hint.nodeNum;
3066		}
3067		/*
3068		 * Iterate until we reach the entry just
3069		 * before the one we want to start with.
3070		 */
3071		if (index > 0) {
3072			struct position_state ps;
3073
3074			ps.error = 0;
3075			ps.count = 0;
3076			ps.index = index;
3077			ps.parentID = dirhint->dh_desc.cd_parentcnid;
3078			ps.hfsmp = hfsmp;
3079
3080			result = BTIterateRecords(fcb, kBTreeNextRecord, iterator,
3081			                          (IterateCallBackProcPtr)cat_findposition, &ps);
3082			if (ps.error)
3083				result = ps.error;
3084			else
3085				result = MacToVFSError(result);
3086			if (result) {
3087				result = MacToVFSError(result);
3088				goto cleanup;
3089			}
3090		}
3091	}
3092
3093	state.cbs_index = index;
3094	state.cbs_hfsmp = hfsmp;
3095	state.cbs_uio = uio;
3096	state.cbs_desc = &dirhint->dh_desc;
3097	state.cbs_namebuf = (u_int8_t *)buffer;
3098	state.cbs_result = 0;
3099	state.cbs_parentID = dirhint->dh_desc.cd_parentcnid;
3100
3101	/* Use a temporary buffer to hold intermediate descriptor names. */
3102	if (dirhint->dh_desc.cd_namelen > 0 && dirhint->dh_desc.cd_nameptr != NULL) {
3103		bcopy(dirhint->dh_desc.cd_nameptr, buffer, dirhint->dh_desc.cd_namelen+1);
3104		if (dirhint->dh_desc.cd_flags & CD_HASBUF) {
3105			dirhint->dh_desc.cd_flags &= ~CD_HASBUF;
3106			vfs_removename((const char *)dirhint->dh_desc.cd_nameptr);
3107		}
3108	}
3109	dirhint->dh_desc.cd_nameptr = (u_int8_t *)buffer;
3110
3111	enum BTreeIterationOperations op;
3112	if (extended && index != 0 && have_key)
3113		op = kBTreeCurrentRecord;
3114	else
3115		op = kBTreeNextRecord;
3116
3117	/*
3118	 * Process as many entries as possible starting at iterator->key.
3119	 */
3120	if (hfsmp->hfs_flags & HFS_STANDARD)
3121		result = BTIterateRecords(fcb, op, iterator,
3122	                          (IterateCallBackProcPtr)getdirentries_std_callback, &state);
3123	else {
3124		result = BTIterateRecords(fcb, op, iterator,
3125	                          (IterateCallBackProcPtr)getdirentries_callback, &state);
3126
3127		/* For extended calls, every call to getdirentries_callback()
3128		 * transfers the previous directory entry found to the user
3129		 * buffer.  Therefore when BTIterateRecords reaches the end of
3130		 * Catalog BTree, call getdirentries_callback() again with
3131		 * dummy values to copy the last directory entry stored in
3132		 * packdirentry_state
3133		 */
3134		if (state.cbs_extended && (result == fsBTRecordNotFoundErr)) {
3135			CatalogKey ckp;
3136			CatalogRecord crp;
3137
3138			bzero(&ckp, sizeof(ckp));
3139			bzero(&crp, sizeof(crp));
3140
3141			result = getdirentries_callback(&ckp, &crp, &state);
3142		}
3143	}
3144
3145	/* Note that state.cbs_index is still valid on errors */
3146	*items = state.cbs_index - index;
3147	index = state.cbs_index;
3148
3149	if (state.cbs_eof) {
3150		*eofflag = 1;
3151	}
3152
3153	/* Finish updating the catalog iterator. */
3154	dirhint->dh_desc.cd_hint = iterator->hint.nodeNum;
3155	dirhint->dh_desc.cd_flags |= CD_DECOMPOSED;
3156	dirhint->dh_index = index - 1;
3157
3158	/* Fix up the name. */
3159	if (dirhint->dh_desc.cd_namelen > 0) {
3160		dirhint->dh_desc.cd_nameptr = (const u_int8_t *)vfs_addname((char *)buffer, dirhint->dh_desc.cd_namelen, 0, 0);
3161		dirhint->dh_desc.cd_flags |= CD_HASBUF;
3162	} else {
3163		dirhint->dh_desc.cd_nameptr = NULL;
3164		dirhint->dh_desc.cd_namelen = 0;
3165	}
3166
3167	/*
3168	 * Post process any hard links to get the real file id.
3169	 */
3170	if (state.cbs_nlinks > 0) {
3171		u_int32_t fileid = 0;
3172		user_addr_t address;
3173		int i;
3174
3175		for (i = 0; i < state.cbs_nlinks; ++i) {
3176			if (resolvelinkid(hfsmp, state.cbs_linkinfo[i].link_ref, &fileid) != 0)
3177				continue;
3178			/* This assumes that d_ino is always first field. */
3179			address = state.cbs_linkinfo[i].dirent_addr;
3180			if (address == (user_addr_t)0)
3181				continue;
3182			if (uio_isuserspace(uio)) {
3183				if (extended) {
3184					ino64_t fileid_64 = (ino64_t)fileid;
3185					(void) copyout(&fileid_64, address, sizeof(fileid_64));
3186				} else {
3187					(void) copyout(&fileid, address, sizeof(fileid));
3188				}
3189			} else /* system space */ {
3190				if (extended) {
3191					ino64_t fileid_64 = (ino64_t)fileid;
3192					bcopy(&fileid_64, (void*) CAST_DOWN(caddr_t, address), sizeof(fileid_64));
3193				} else {
3194					bcopy(&fileid, (void*) CAST_DOWN(caddr_t, address), sizeof(fileid));
3195				}
3196			}
3197		}
3198	}
3199
3200	if (state.cbs_result)
3201		result = state.cbs_result;
3202	else
3203		result = MacToVFSError(result);
3204
3205	if (result == ENOENT) {
3206		result = 0;
3207	}
3208
3209cleanup:
3210	FREE(buffer, M_TEMP);
3211
3212	return (result);
3213}
3214
3215
3216/*
3217 * Callback to establish directory position.
3218 * Called with position_state for each item in a directory.
3219 */
3220static int
3221cat_findposition(const CatalogKey *ckp, const CatalogRecord *crp,
3222                 struct position_state *state)
3223{
3224	cnid_t curID;
3225
3226	if (state->hfsmp->hfs_flags & HFS_STANDARD)
3227		curID = ckp->hfs.parentID;
3228	else
3229		curID = ckp->hfsPlus.parentID;
3230
3231	/* Make sure parent directory didn't change */
3232	if (state->parentID != curID) {
3233		state->error = EINVAL;
3234		return (0);  /* stop */
3235	}
3236
3237	/* Count this entry */
3238	switch(crp->recordType) {
3239	case kHFSPlusFolderRecord:
3240	case kHFSPlusFileRecord:
3241	case kHFSFolderRecord:
3242	case kHFSFileRecord:
3243		++state->count;
3244		break;
3245	default:
3246		printf("cat_findposition: invalid record type %d in dir %d\n",
3247			crp->recordType, curID);
3248		state->error = EINVAL;
3249		return (0);  /* stop */
3250	};
3251
3252	return (state->count < state->index);
3253}
3254
3255
3256/*
3257 * cat_binarykeycompare - compare two HFS Plus catalog keys.
3258
3259 * The name portion of the key is compared using a 16-bit binary comparison.
3260 * This is called from the b-tree code.
3261 */
3262__private_extern__
3263int
3264cat_binarykeycompare(HFSPlusCatalogKey *searchKey, HFSPlusCatalogKey *trialKey)
3265{
3266	u_int32_t searchParentID, trialParentID;
3267	int result;
3268
3269	searchParentID = searchKey->parentID;
3270	trialParentID = trialKey->parentID;
3271	result = 0;
3272
3273	if (searchParentID > trialParentID) {
3274		++result;
3275	} else if (searchParentID < trialParentID) {
3276		--result;
3277	} else {
3278		u_int16_t * str1 = &searchKey->nodeName.unicode[0];
3279		u_int16_t * str2 = &trialKey->nodeName.unicode[0];
3280		int length1 = searchKey->nodeName.length;
3281		int length2 = trialKey->nodeName.length;
3282		u_int16_t c1, c2;
3283		int length;
3284
3285		if (length1 < length2) {
3286			length = length1;
3287			--result;
3288		} else if (length1 > length2) {
3289			length = length2;
3290			++result;
3291		} else {
3292			length = length1;
3293		}
3294
3295		while (length--) {
3296			c1 = *(str1++);
3297			c2 = *(str2++);
3298
3299			if (c1 > c2) {
3300				result = 1;
3301				break;
3302			}
3303			if (c1 < c2) {
3304				result = -1;
3305				break;
3306			}
3307		}
3308	}
3309
3310	return result;
3311}
3312
3313
3314/*
3315 * Compare two standard HFS catalog keys
3316 *
3317 * Result: +n  search key > trial key
3318 *          0  search key = trial key
3319 *         -n  search key < trial key
3320 */
3321int
3322CompareCatalogKeys(HFSCatalogKey *searchKey, HFSCatalogKey *trialKey)
3323{
3324	cnid_t searchParentID, trialParentID;
3325	int result;
3326
3327	searchParentID = searchKey->parentID;
3328	trialParentID = trialKey->parentID;
3329
3330	if (searchParentID > trialParentID)
3331		result = 1;
3332	else if (searchParentID < trialParentID)
3333		result = -1;
3334	else /* parent dirID's are equal, compare names */
3335		result = FastRelString(searchKey->nodeName, trialKey->nodeName);
3336
3337	return result;
3338}
3339
3340
3341/*
3342 * Compare two HFS+ catalog keys
3343 *
3344 * Result: +n  search key > trial key
3345 *          0  search key = trial key
3346 *         -n  search key < trial key
3347 */
3348int
3349CompareExtendedCatalogKeys(HFSPlusCatalogKey *searchKey, HFSPlusCatalogKey *trialKey)
3350{
3351	cnid_t searchParentID, trialParentID;
3352	int result;
3353
3354	searchParentID = searchKey->parentID;
3355	trialParentID = trialKey->parentID;
3356
3357	if (searchParentID > trialParentID) {
3358		result = 1;
3359	}
3360	else if (searchParentID < trialParentID) {
3361		result = -1;
3362	} else {
3363		/* parent node ID's are equal, compare names */
3364		if ( searchKey->nodeName.length == 0 || trialKey->nodeName.length == 0 )
3365			result = searchKey->nodeName.length - trialKey->nodeName.length;
3366		else
3367			result = FastUnicodeCompare(&searchKey->nodeName.unicode[0],
3368			                            searchKey->nodeName.length,
3369			                            &trialKey->nodeName.unicode[0],
3370			                            trialKey->nodeName.length);
3371	}
3372
3373	return result;
3374}
3375
3376
3377/*
3378 * buildkey - build a Catalog b-tree key from a cnode descriptor
3379 */
3380static int
3381buildkey(struct hfsmount *hfsmp, struct cat_desc *descp,
3382	HFSPlusCatalogKey *key, int retry)
3383{
3384	int utf8_flags = UTF_ESCAPE_ILLEGAL;
3385	int result = 0;
3386	size_t unicodeBytes = 0;
3387
3388	if (descp->cd_namelen == 0 || descp->cd_nameptr[0] == '\0')
3389		return (EINVAL);  /* invalid name */
3390
3391	key->parentID = descp->cd_parentcnid;
3392	key->nodeName.length = 0;
3393	/*
3394	 * Convert filename from UTF-8 into Unicode
3395	 */
3396
3397	if ((descp->cd_flags & CD_DECOMPOSED) == 0)
3398		utf8_flags |= UTF_DECOMPOSED;
3399	result = utf8_decodestr(descp->cd_nameptr, descp->cd_namelen,
3400		key->nodeName.unicode, &unicodeBytes,
3401		sizeof(key->nodeName.unicode), ':', utf8_flags);
3402	key->nodeName.length = unicodeBytes / sizeof(UniChar);
3403	key->keyLength = kHFSPlusCatalogKeyMinimumLength + unicodeBytes;
3404	if (result) {
3405		if (result != ENAMETOOLONG)
3406			result = EINVAL;  /* name has invalid characters */
3407		return (result);
3408	}
3409
3410	/*
3411	 * For HFS volumes convert to an HFS compatible key
3412	 *
3413	 * XXX need to save the encoding that succeeded
3414	 */
3415	if (HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord) {
3416		HFSCatalogKey hfskey;
3417
3418		bzero(&hfskey, sizeof(hfskey));
3419		hfskey.keyLength = kHFSCatalogKeyMinimumLength;
3420		hfskey.parentID = key->parentID;
3421		hfskey.nodeName[0] = 0;
3422		if (key->nodeName.length > 0) {
3423			if (unicode_to_hfs(HFSTOVCB(hfsmp),
3424				key->nodeName.length * 2,
3425				key->nodeName.unicode,
3426				&hfskey.nodeName[0], retry) != 0) {
3427				return (EINVAL);
3428			}
3429			hfskey.keyLength += hfskey.nodeName[0];
3430		}
3431		bcopy(&hfskey, key, sizeof(hfskey));
3432	}
3433	return (0);
3434 }
3435
3436
3437/*
3438 * Resolve hard link reference to obtain the inode record.
3439 */
3440__private_extern__
3441int
3442cat_resolvelink(struct hfsmount *hfsmp, u_long linkref, int isdirlink, struct HFSPlusCatalogFile *recp)
3443{
3444	FSBufferDescriptor btdata;
3445	struct BTreeIterator *iterator;
3446	struct cat_desc idesc;
3447	char inodename[32];
3448	cnid_t parentcnid;
3449	int result = 0;
3450
3451	BDINIT(btdata, recp);
3452
3453	if (isdirlink) {
3454		MAKE_DIRINODE_NAME(inodename, sizeof(inodename), (unsigned int)linkref);
3455		parentcnid = hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid;
3456	} else {
3457		MAKE_INODE_NAME(inodename, sizeof(inodename), (unsigned int)linkref);
3458		parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
3459	}
3460
3461	/* Get space for iterator */
3462	MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
3463	bzero(iterator, sizeof(*iterator));
3464
3465	/* Build a descriptor for private dir. */
3466	idesc.cd_parentcnid = parentcnid;
3467	idesc.cd_nameptr = (const u_int8_t *)inodename;
3468	idesc.cd_namelen = strlen(inodename);
3469	idesc.cd_flags = 0;
3470	idesc.cd_hint = 0;
3471	idesc.cd_encoding = 0;
3472	(void) buildkey(hfsmp, &idesc, (HFSPlusCatalogKey *)&iterator->key, 0);
3473
3474	result = BTSearchRecord(VTOF(HFSTOVCB(hfsmp)->catalogRefNum), iterator,
3475				&btdata, NULL, NULL);
3476
3477	if (result == 0) {
3478		/* Make sure there's a reference */
3479		if (recp->hl_linkCount == 0)
3480			recp->hl_linkCount = 2;
3481	} else {
3482		printf("HFS resolvelink: can't find %s\n", inodename);
3483	}
3484
3485	FREE(iterator, M_TEMP);
3486
3487	return (result ? ENOENT : 0);
3488}
3489
3490/*
3491 * Resolve hard link reference to obtain the inode number.
3492 */
3493static int
3494resolvelinkid(struct hfsmount *hfsmp, u_long linkref, ino_t *ino)
3495{
3496	struct HFSPlusCatalogFile record;
3497	int error;
3498
3499	/*
3500	 * Since we know resolvelinkid is only called from
3501	 * cat_getdirentries, we can assume that only file
3502	 * hardlinks need to be resolved (cat_getdirentries
3503	 * can resolve directory hardlinks in place).
3504	 */
3505	error = cat_resolvelink(hfsmp, linkref, 0, &record);
3506	if (error == 0) {
3507		if (record.fileID == 0)
3508			error = ENOENT;
3509		else
3510			*ino = record.fileID;
3511	}
3512	return (error);
3513}
3514
3515/*
3516 * getkey - get a key from id by doing a thread lookup
3517 */
3518static int
3519getkey(struct hfsmount *hfsmp, cnid_t cnid, CatalogKey * key)
3520{
3521	struct BTreeIterator * iterator;
3522	FSBufferDescriptor btdata;
3523	u_int16_t	datasize;
3524	CatalogKey * keyp;
3525	CatalogRecord * recp;
3526	int result;
3527	int std_hfs;
3528
3529	std_hfs = (HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord);
3530
3531	MALLOC(iterator, BTreeIterator *, sizeof(*iterator), M_TEMP, M_WAITOK);
3532	bzero(iterator, sizeof(*iterator));
3533	buildthreadkey(cnid, std_hfs, (CatalogKey *)&iterator->key);
3534
3535	MALLOC(recp, CatalogRecord *, sizeof(CatalogRecord), M_TEMP, M_WAITOK);
3536	BDINIT(btdata, recp);
3537
3538	result = BTSearchRecord(VTOF(HFSTOVCB(hfsmp)->catalogRefNum), iterator,
3539				&btdata, &datasize, iterator);
3540	if (result)
3541		goto exit;
3542
3543	/* Turn thread record into a cnode key (in place) */
3544	switch (recp->recordType) {
3545	case kHFSFileThreadRecord:
3546	case kHFSFolderThreadRecord:
3547		keyp = (CatalogKey *)((char *)&recp->hfsThread.reserved + 6);
3548		keyp->hfs.keyLength = kHFSCatalogKeyMinimumLength + keyp->hfs.nodeName[0];
3549		bcopy(keyp, key, keyp->hfs.keyLength + 1);
3550		break;
3551
3552	case kHFSPlusFileThreadRecord:
3553	case kHFSPlusFolderThreadRecord:
3554		keyp = (CatalogKey *)&recp->hfsPlusThread.reserved;
3555		keyp->hfsPlus.keyLength = kHFSPlusCatalogKeyMinimumLength +
3556		                          (keyp->hfsPlus.nodeName.length * 2);
3557		bcopy(keyp, key, keyp->hfsPlus.keyLength + 2);
3558		break;
3559
3560	default:
3561		result = ENOENT;
3562		break;
3563	}
3564
3565exit:
3566	FREE(iterator, M_TEMP);
3567	FREE(recp, M_TEMP);
3568
3569	return MacToVFSError(result);
3570}
3571
3572/*
3573 * getkeyplusattr - From id, fetch the key and the bsd attrs for a file/dir (could pass
3574 * null arguments to cat_idlookup instead, but we save around 10% by not building the
3575 * cat_desc here). Both key and attrp must point to real structures.
3576 *
3577 * The key's parent id is the only part of the key expected to be used by the caller.
3578 * The name portion of the key may not always be valid (ie in the case of a hard link).
3579 */
3580__private_extern__
3581int
3582cat_getkeyplusattr(struct hfsmount *hfsmp, cnid_t cnid, CatalogKey * key, struct cat_attr *attrp)
3583{
3584	int result;
3585
3586	result = getkey(hfsmp, cnid, key);
3587
3588	if (result == 0) {
3589		result = cat_lookupbykey(hfsmp, key, 0, 0, 0, NULL, attrp, NULL, NULL);
3590	}
3591	/*
3592	 * Check for a raw file hardlink inode.
3593	 * Fix up the parent id in the key if necessary.
3594	 * Only hard links created by Mac OS X 10.5 or later can be resolved here.
3595	 */
3596	if ((result == 0) &&
3597	    (key->hfsPlus.parentID == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid) &&
3598	    (attrp->ca_recflags & kHFSHasLinkChainMask)) {
3599		cnid_t nextlinkid = 0;
3600		cnid_t prevlinkid = 0;
3601		struct cat_desc linkdesc;
3602
3603		/*
3604		 * Pick up the first link in the chain and get a descriptor for it.
3605		 * This allows blind bulk access checks to work for hardlinks.
3606		 */
3607		if ((cat_lookuplinkbyid(hfsmp, cnid, &prevlinkid,  &nextlinkid) == 0) &&
3608		    (nextlinkid != 0)) {
3609			if (cat_findname(hfsmp, nextlinkid, &linkdesc) == 0) {
3610				key->hfsPlus.parentID = linkdesc.cd_parentcnid;
3611				cat_releasedesc(&linkdesc);
3612			}
3613		}
3614	}
3615	return MacToVFSError(result);
3616}
3617
3618
3619/*
3620 * buildrecord - build a default catalog directory or file record
3621 */
3622static void
3623buildrecord(struct cat_attr *attrp, cnid_t cnid, int std_hfs, u_int32_t encoding,
3624            CatalogRecord *crp, u_int32_t *recordSize)
3625{
3626	int type = attrp->ca_mode & S_IFMT;
3627	u_int32_t createtime = to_hfs_time(attrp->ca_itime);
3628
3629	if (std_hfs) {
3630		createtime = UTCToLocal(createtime);
3631		if (type == S_IFDIR) {
3632			bzero(crp, sizeof(HFSCatalogFolder));
3633			crp->recordType = kHFSFolderRecord;
3634			crp->hfsFolder.folderID = cnid;
3635			crp->hfsFolder.createDate = createtime;
3636			crp->hfsFolder.modifyDate = createtime;
3637			bcopy(attrp->ca_finderinfo, &crp->hfsFolder.userInfo, 32);
3638			*recordSize = sizeof(HFSCatalogFolder);
3639		} else {
3640			bzero(crp, sizeof(HFSCatalogFile));
3641			crp->recordType = kHFSFileRecord;
3642			crp->hfsFile.fileID = cnid;
3643			crp->hfsFile.createDate = createtime;
3644			crp->hfsFile.modifyDate = createtime;
3645			bcopy(attrp->ca_finderinfo, &crp->hfsFile.userInfo, 16);
3646			bcopy(&attrp->ca_finderinfo[16], &crp->hfsFile.finderInfo, 16);
3647			*recordSize = sizeof(HFSCatalogFile);
3648		}
3649	} else {
3650		struct HFSPlusBSDInfo * bsdp = NULL;
3651
3652		if (type == S_IFDIR) {
3653			crp->recordType = kHFSPlusFolderRecord;
3654			crp->hfsPlusFolder.flags = attrp->ca_recflags;
3655			crp->hfsPlusFolder.valence = 0;
3656			crp->hfsPlusFolder.folderID = cnid;
3657			crp->hfsPlusFolder.createDate = createtime;
3658			crp->hfsPlusFolder.contentModDate = createtime;
3659			crp->hfsPlusFolder.attributeModDate = createtime;
3660			crp->hfsPlusFolder.accessDate = createtime;
3661			crp->hfsPlusFolder.backupDate = 0;
3662			crp->hfsPlusFolder.textEncoding = encoding;
3663			crp->hfsPlusFolder.folderCount = 0;
3664			bcopy(attrp->ca_finderinfo, &crp->hfsPlusFolder.userInfo, 32);
3665			bsdp = &crp->hfsPlusFolder.bsdInfo;
3666			bsdp->special.linkCount = 1;
3667			*recordSize = sizeof(HFSPlusCatalogFolder);
3668		} else {
3669			crp->recordType = kHFSPlusFileRecord;
3670			crp->hfsPlusFile.flags = attrp->ca_recflags;
3671			crp->hfsPlusFile.reserved1 = 0;
3672			crp->hfsPlusFile.fileID = cnid;
3673			crp->hfsPlusFile.createDate = createtime;
3674			crp->hfsPlusFile.contentModDate = createtime;
3675			crp->hfsPlusFile.accessDate = createtime;
3676			crp->hfsPlusFile.attributeModDate = createtime;
3677			crp->hfsPlusFile.backupDate = 0;
3678			crp->hfsPlusFile.textEncoding = encoding;
3679			crp->hfsPlusFile.reserved2 = 0;
3680			bcopy(attrp->ca_finderinfo, &crp->hfsPlusFile.userInfo, 32);
3681			bsdp = &crp->hfsPlusFile.bsdInfo;
3682			/* BLK/CHR need to save the device info */
3683			if (type == S_IFBLK || type == S_IFCHR) {
3684				bsdp->special.rawDevice = attrp->ca_rdev;
3685			} else {
3686				bsdp->special.linkCount = 1;
3687			}
3688			bzero(&crp->hfsPlusFile.dataFork, 2*sizeof(HFSPlusForkData));
3689			*recordSize = sizeof(HFSPlusCatalogFile);
3690		}
3691		bsdp->ownerID    = attrp->ca_uid;
3692		bsdp->groupID    = attrp->ca_gid;
3693		bsdp->fileMode   = attrp->ca_mode;
3694		bsdp->adminFlags = attrp->ca_flags >> 16;
3695		bsdp->ownerFlags = attrp->ca_flags & 0x000000FF;
3696	}
3697}
3698
3699
3700/*
3701 * builddesc - build a cnode descriptor from an HFS+ key
3702 */
3703static int
3704builddesc(const HFSPlusCatalogKey *key, cnid_t cnid, u_long hint, u_long encoding,
3705	int isdir, struct cat_desc *descp)
3706{
3707	int result = 0;
3708	unsigned char * nameptr;
3709	size_t bufsize;
3710	size_t utf8len;
3711	unsigned char tmpbuff[128];
3712
3713	/* guess a size... */
3714	bufsize = (3 * key->nodeName.length) + 1;
3715	if (bufsize >= sizeof(tmpbuff) - 1) {
3716	    MALLOC(nameptr, unsigned char *, bufsize, M_TEMP, M_WAITOK);
3717	} else {
3718	    nameptr = &tmpbuff[0];
3719	}
3720
3721	result = utf8_encodestr(key->nodeName.unicode,
3722			key->nodeName.length * sizeof(UniChar),
3723			nameptr, (size_t *)&utf8len,
3724			bufsize, ':', 0);
3725
3726	if (result == ENAMETOOLONG) {
3727		bufsize = 1 + utf8_encodelen(key->nodeName.unicode,
3728		                             key->nodeName.length * sizeof(UniChar),
3729		                             ':', 0);
3730		FREE(nameptr, M_TEMP);
3731		MALLOC(nameptr, unsigned char *, bufsize, M_TEMP, M_WAITOK);
3732
3733		result = utf8_encodestr(key->nodeName.unicode,
3734		                        key->nodeName.length * sizeof(UniChar),
3735		                        nameptr, (size_t *)&utf8len,
3736		                        bufsize, ':', 0);
3737	}
3738	descp->cd_parentcnid = key->parentID;
3739	descp->cd_nameptr = (const u_int8_t *)vfs_addname((char *)nameptr, utf8len, 0, 0);
3740	descp->cd_namelen = utf8len;
3741	descp->cd_cnid = cnid;
3742	descp->cd_hint = hint;
3743	descp->cd_flags = CD_DECOMPOSED | CD_HASBUF;
3744	if (isdir)
3745		descp->cd_flags |= CD_ISDIR;
3746	descp->cd_encoding = encoding;
3747	if (nameptr != &tmpbuff[0]) {
3748	    FREE(nameptr, M_TEMP);
3749	}
3750	return result;
3751}
3752
3753
3754/*
3755 * getbsdattr - get attributes in bsd format
3756 *
3757 */
3758static void
3759getbsdattr(struct hfsmount *hfsmp, const struct HFSPlusCatalogFile *crp, struct cat_attr * attrp)
3760{
3761	int isDirectory = (crp->recordType == kHFSPlusFolderRecord);
3762	const struct HFSPlusBSDInfo *bsd = &crp->bsdInfo;
3763
3764	attrp->ca_recflags = crp->flags;
3765	attrp->ca_atime = to_bsd_time(crp->accessDate);
3766	attrp->ca_atimeondisk = attrp->ca_atime;
3767	attrp->ca_mtime = to_bsd_time(crp->contentModDate);
3768	attrp->ca_ctime = to_bsd_time(crp->attributeModDate);
3769	attrp->ca_itime = to_bsd_time(crp->createDate);
3770	attrp->ca_btime = to_bsd_time(crp->backupDate);
3771
3772	if ((bsd->fileMode & S_IFMT) == 0) {
3773		attrp->ca_flags = 0;
3774		attrp->ca_uid = hfsmp->hfs_uid;
3775		attrp->ca_gid = hfsmp->hfs_gid;
3776		if (isDirectory) {
3777			attrp->ca_mode = S_IFDIR | (hfsmp->hfs_dir_mask & ACCESSPERMS);
3778		} else {
3779			attrp->ca_mode = S_IFREG | (hfsmp->hfs_file_mask & ACCESSPERMS);
3780		}
3781		attrp->ca_linkcount = 1;
3782		attrp->ca_rdev = 0;
3783	} else {
3784		attrp->ca_linkcount = 1;  /* may be overridden below */
3785		attrp->ca_rdev = 0;
3786		attrp->ca_uid = bsd->ownerID;
3787		attrp->ca_gid = bsd->groupID;
3788		attrp->ca_flags = bsd->ownerFlags | (bsd->adminFlags << 16);
3789		attrp->ca_mode = (mode_t)bsd->fileMode;
3790		switch (attrp->ca_mode & S_IFMT) {
3791		case S_IFCHR: /* fall through */
3792		case S_IFBLK:
3793			attrp->ca_rdev = bsd->special.rawDevice;
3794			break;
3795
3796		case S_IFDIR: /* fall through */
3797		case S_IFREG:
3798			/* Pick up the hard link count */
3799			if (bsd->special.linkCount > 0)
3800				attrp->ca_linkcount = bsd->special.linkCount;
3801			break;
3802		}
3803
3804		/*
3805		 *  Override the permissions as determined by the mount auguments
3806		 *  in ALMOST the same way unset permissions are treated but keep
3807		 *  track of whether or not the file or folder is hfs locked
3808		 *  by leaving the h_pflags field unchanged from what was unpacked
3809		 *  out of the catalog.
3810		 */
3811		/*
3812		 * This code was used to do UID translation with MNT_IGNORE_OWNERS
3813		 * (aka MNT_UNKNOWNPERMISSIONS) at the HFS layer.  It's largely done
3814		 * at the VFS layer, so there is no need to do it here now; this also
3815		 * allows VFS to let root see the real UIDs.
3816		 *
3817		 * if (((unsigned int)vfs_flags(HFSTOVFS(hfsmp))) & MNT_UNKNOWNPERMISSIONS) {
3818		 * 	attrp->ca_uid = hfsmp->hfs_uid;
3819		 * 	attrp->ca_gid = hfsmp->hfs_gid;
3820		 * }
3821		 */
3822	}
3823
3824	if (isDirectory) {
3825		if (!S_ISDIR(attrp->ca_mode)) {
3826			attrp->ca_mode &= ~S_IFMT;
3827			attrp->ca_mode |= S_IFDIR;
3828		}
3829		attrp->ca_entries = ((const HFSPlusCatalogFolder *)crp)->valence;
3830		attrp->ca_dircount = ((hfsmp->hfs_flags & HFS_FOLDERCOUNT) && (attrp->ca_recflags & kHFSHasFolderCountMask)) ?
3831					     ((const HFSPlusCatalogFolder *)crp)->folderCount : 0;
3832
3833		/* Keep UF_HIDDEN bit in sync with Finder Info's invisible bit */
3834		if (((const HFSPlusCatalogFolder *)crp)->userInfo.frFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask))
3835			attrp->ca_flags |= UF_HIDDEN;
3836	} else {
3837		/* Keep IMMUTABLE bits in sync with HFS locked flag */
3838		if (crp->flags & kHFSFileLockedMask) {
3839			/* The file's supposed to be locked:
3840			   Make sure at least one of the IMMUTABLE bits is set: */
3841			if ((attrp->ca_flags & (SF_IMMUTABLE | UF_IMMUTABLE)) == 0)
3842				attrp->ca_flags |= UF_IMMUTABLE;
3843		} else {
3844			/* The file's supposed to be unlocked: */
3845			attrp->ca_flags &= ~(SF_IMMUTABLE | UF_IMMUTABLE);
3846		}
3847		/* Keep UF_HIDDEN bit in sync with Finder Info's invisible bit */
3848		if (crp->userInfo.fdFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask))
3849			attrp->ca_flags |= UF_HIDDEN;
3850		/* get total blocks (both forks) */
3851		attrp->ca_blocks = crp->dataFork.totalBlocks + crp->resourceFork.totalBlocks;
3852
3853		/* On HFS+ the ThreadExists flag must always be set. */
3854		if ((hfsmp->hfs_flags & HFS_STANDARD) == 0)
3855			attrp->ca_recflags |= kHFSThreadExistsMask;
3856
3857		/* Pick up the hardlink first link, if any. */
3858		attrp->ca_firstlink = (attrp->ca_recflags & kHFSHasLinkChainMask) ? crp->hl_firstLinkID : 0;
3859	}
3860
3861	attrp->ca_fileid = crp->fileID;
3862
3863	bcopy(&crp->userInfo, attrp->ca_finderinfo, 32);
3864}
3865
3866/*
3867 * promotekey - promote hfs key to hfs plus key
3868 *
3869 */
3870static void
3871promotekey(struct hfsmount *hfsmp, const HFSCatalogKey *hfskey,
3872           HFSPlusCatalogKey *keyp, u_long *encoding)
3873{
3874	hfs_to_unicode_func_t hfs_get_unicode = hfsmp->hfs_get_unicode;
3875	u_int32_t uniCount;
3876	int error;
3877
3878	*encoding = hfsmp->hfs_encoding;
3879
3880	error = hfs_get_unicode(hfskey->nodeName, keyp->nodeName.unicode,
3881	                        kHFSPlusMaxFileNameChars, &uniCount);
3882	/*
3883	 * When an HFS name cannot be encoded with the current
3884	 * encoding use MacRoman as a fallback.
3885	 */
3886	if (error && hfsmp->hfs_encoding != kTextEncodingMacRoman) {
3887		*encoding = 0;
3888		(void) mac_roman_to_unicode(hfskey->nodeName,
3889		                            keyp->nodeName.unicode,
3890		                            kHFSPlusMaxFileNameChars,
3891		                            &uniCount);
3892	}
3893
3894	keyp->nodeName.length = uniCount;
3895	keyp->parentID = hfskey->parentID;
3896}
3897
3898/*
3899 * promotefork - promote hfs fork info to hfs plus
3900 *
3901 */
3902static void
3903promotefork(struct hfsmount *hfsmp, const struct HFSCatalogFile *filep,
3904            int resource, struct cat_fork * forkp)
3905{
3906	struct HFSPlusExtentDescriptor *xp;
3907	u_long blocksize = HFSTOVCB(hfsmp)->blockSize;
3908
3909	bzero(forkp, sizeof(*forkp));
3910	xp = &forkp->cf_extents[0];
3911	if (resource) {
3912		forkp->cf_size = filep->rsrcLogicalSize;
3913		forkp->cf_blocks = filep->rsrcPhysicalSize / blocksize;
3914		forkp->cf_bytesread = 0;
3915		forkp->cf_vblocks = 0;
3916		xp[0].startBlock = (u_int32_t)filep->rsrcExtents[0].startBlock;
3917		xp[0].blockCount = (u_int32_t)filep->rsrcExtents[0].blockCount;
3918		xp[1].startBlock = (u_int32_t)filep->rsrcExtents[1].startBlock;
3919		xp[1].blockCount = (u_int32_t)filep->rsrcExtents[1].blockCount;
3920		xp[2].startBlock = (u_int32_t)filep->rsrcExtents[2].startBlock;
3921		xp[2].blockCount = (u_int32_t)filep->rsrcExtents[2].blockCount;
3922	} else {
3923		forkp->cf_size = filep->dataLogicalSize;
3924		forkp->cf_blocks = filep->dataPhysicalSize / blocksize;
3925		forkp->cf_bytesread = 0;
3926		forkp->cf_vblocks = 0;
3927		xp[0].startBlock = (u_int32_t)filep->dataExtents[0].startBlock;
3928		xp[0].blockCount = (u_int32_t)filep->dataExtents[0].blockCount;
3929		xp[1].startBlock = (u_int32_t)filep->dataExtents[1].startBlock;
3930		xp[1].blockCount = (u_int32_t)filep->dataExtents[1].blockCount;
3931		xp[2].startBlock = (u_int32_t)filep->dataExtents[2].startBlock;
3932		xp[2].blockCount = (u_int32_t)filep->dataExtents[2].blockCount;
3933	}
3934}
3935
3936/*
3937 * promoteattr - promote standard hfs catalog attributes to hfs plus
3938 *
3939 */
3940static void
3941promoteattr(struct hfsmount *hfsmp, const CatalogRecord *dataPtr, struct HFSPlusCatalogFile *crp)
3942{
3943	u_long blocksize = HFSTOVCB(hfsmp)->blockSize;
3944
3945	if (dataPtr->recordType == kHFSFolderRecord) {
3946		const struct HFSCatalogFolder * folder;
3947
3948		folder = (const struct HFSCatalogFolder *) dataPtr;
3949		crp->recordType       = kHFSPlusFolderRecord;
3950		crp->flags            = folder->flags;
3951		crp->fileID           = folder->folderID;
3952		crp->createDate       = LocalToUTC(folder->createDate);
3953		crp->contentModDate   = LocalToUTC(folder->modifyDate);
3954		crp->backupDate       = LocalToUTC(folder->backupDate);
3955		crp->reserved1        = folder->valence;
3956		crp->reserved2        = 0;
3957		bcopy(&folder->userInfo, &crp->userInfo, 32);
3958	} else /* file */ {
3959		const struct HFSCatalogFile * file;
3960
3961		file = (const struct HFSCatalogFile *) dataPtr;
3962		crp->recordType       = kHFSPlusFileRecord;
3963		crp->flags            = file->flags;
3964		crp->fileID           = file->fileID;
3965		crp->createDate       = LocalToUTC(file->createDate);
3966		crp->contentModDate   = LocalToUTC(file->modifyDate);
3967		crp->backupDate       = LocalToUTC(file->backupDate);
3968		crp->reserved1        = 0;
3969		crp->reserved2        = 0;
3970		bcopy(&file->userInfo, &crp->userInfo, 16);
3971		bcopy(&file->finderInfo, &crp->finderInfo, 16);
3972		crp->dataFork.totalBlocks = file->dataPhysicalSize / blocksize;
3973		crp->resourceFork.totalBlocks = file->rsrcPhysicalSize / blocksize;
3974	}
3975	crp->textEncoding = 0;
3976	crp->attributeModDate = crp->contentModDate;
3977	crp->accessDate = crp->contentModDate;
3978	bzero(&crp->bsdInfo, sizeof(HFSPlusBSDInfo));
3979}
3980
3981/*
3982 * Build a catalog node thread record from a catalog key
3983 * and return the size of the record.
3984 */
3985static int
3986buildthread(void *keyp, void *recp, int std_hfs, int directory)
3987{
3988	int size = 0;
3989
3990	if (std_hfs) {
3991		HFSCatalogKey *key = (HFSCatalogKey *)keyp;
3992		HFSCatalogThread *rec = (HFSCatalogThread *)recp;
3993
3994		size = sizeof(HFSCatalogThread);
3995		bzero(rec, size);
3996		if (directory)
3997			rec->recordType = kHFSFolderThreadRecord;
3998		else
3999			rec->recordType = kHFSFileThreadRecord;
4000		rec->parentID = key->parentID;
4001		bcopy(key->nodeName, rec->nodeName, key->nodeName[0]+1);
4002
4003	} else /* HFS+ */ {
4004		HFSPlusCatalogKey *key = (HFSPlusCatalogKey *)keyp;
4005		HFSPlusCatalogThread *rec = (HFSPlusCatalogThread *)recp;
4006
4007		size = sizeof(HFSPlusCatalogThread);
4008		if (directory)
4009			rec->recordType = kHFSPlusFolderThreadRecord;
4010		else
4011			rec->recordType = kHFSPlusFileThreadRecord;
4012		rec->reserved = 0;
4013		rec->parentID = key->parentID;
4014		bcopy(&key->nodeName, &rec->nodeName,
4015			sizeof(UniChar) * (key->nodeName.length + 1));
4016
4017		/* HFS Plus has varaible sized thread records */
4018		size -= (sizeof(rec->nodeName.unicode) -
4019			  (rec->nodeName.length * sizeof(UniChar)));
4020	}
4021
4022	return (size);
4023}
4024
4025/*
4026 * Build a catalog node thread key.
4027 */
4028static void
4029buildthreadkey(HFSCatalogNodeID parentID, int std_hfs, CatalogKey *key)
4030{
4031	if (std_hfs) {
4032		key->hfs.keyLength = kHFSCatalogKeyMinimumLength;
4033		key->hfs.reserved = 0;
4034		key->hfs.parentID = parentID;
4035		key->hfs.nodeName[0] = 0;
4036	} else {
4037		key->hfsPlus.keyLength = kHFSPlusCatalogKeyMinimumLength;
4038		key->hfsPlus.parentID = parentID;
4039		key->hfsPlus.nodeName.length = 0;
4040	}
4041}
4042
4043/*
4044 * Extract the text encoding from a catalog node record.
4045 */
4046static u_long
4047getencoding(const CatalogRecord *crp)
4048{
4049	u_long encoding;
4050
4051	if (crp->recordType == kHFSPlusFolderRecord)
4052		encoding = crp->hfsPlusFolder.textEncoding;
4053	else if (crp->recordType == kHFSPlusFileRecord)
4054		encoding = crp->hfsPlusFile.textEncoding;
4055	else
4056		encoding = 0;
4057
4058	return (encoding);
4059}
4060
4061/*
4062 * Extract the CNID from a catalog node record.
4063 */
4064static cnid_t
4065getcnid(const CatalogRecord *crp)
4066{
4067	cnid_t cnid = 0;
4068
4069	switch (crp->recordType) {
4070	case kHFSFolderRecord:
4071		cnid = crp->hfsFolder.folderID;
4072		break;
4073	case kHFSFileRecord:
4074		cnid = crp->hfsFile.fileID;
4075		break;
4076	case kHFSPlusFolderRecord:
4077		cnid = crp->hfsPlusFolder.folderID;
4078		break;
4079	case kHFSPlusFileRecord:
4080		cnid = crp->hfsPlusFile.fileID;
4081		break;
4082	default:
4083		panic("hfs: getcnid: unknown recordType (crp @ %p)\n", crp);
4084		break;
4085	}
4086
4087	return (cnid);
4088}
4089
4090/*
4091 * Extract the parent ID from a catalog node record.
4092 */
4093static cnid_t
4094getparentcnid(const CatalogRecord *recp)
4095{
4096	cnid_t cnid = 0;
4097
4098	switch (recp->recordType) {
4099	case kHFSFileThreadRecord:
4100	case kHFSFolderThreadRecord:
4101		cnid = recp->hfsThread.parentID;
4102		break;
4103
4104	case kHFSPlusFileThreadRecord:
4105	case kHFSPlusFolderThreadRecord:
4106		cnid = recp->hfsPlusThread.parentID;
4107		break;
4108	default:
4109		panic("hfs: getparentcnid: unknown recordType (crp @ %p)\n", recp);
4110		break;
4111	}
4112
4113	return (cnid);
4114}
4115
4116/*
4117 * Determine if a catalog node record is a directory.
4118 */
4119static int
4120isadir(const CatalogRecord *crp)
4121{
4122	return (crp->recordType == kHFSFolderRecord ||
4123		crp->recordType == kHFSPlusFolderRecord);
4124}
4125
4126