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