]>
Commit | Line | Data |
---|---|---|
1 | /* Copyright © 2017-2018 Apple Inc. All rights reserved. | |
2 | * | |
3 | * lf_hfs_xattr.c | |
4 | * livefiles_hfs | |
5 | * | |
6 | * Created by Or Haimovich on 28/3/18. | |
7 | */ | |
8 | ||
9 | #include <sys/xattr.h> | |
10 | #include <sys/acl.h> | |
11 | #include <sys/kauth.h> | |
12 | #include "lf_hfs_xattr.h" | |
13 | #include "lf_hfs.h" | |
14 | #include "lf_hfs_vnops.h" | |
15 | #include "lf_hfs_raw_read_write.h" | |
16 | #include "lf_hfs_btrees_io.h" | |
17 | #include "lf_hfs_btrees_internal.h" | |
18 | #include "lf_hfs_vfsutils.h" | |
19 | #include "lf_hfs_sbunicode.h" | |
20 | #include "lf_hfs_endian.h" | |
21 | #include "lf_hfs_logger.h" | |
22 | #include "lf_hfs_utils.h" | |
23 | ||
24 | #define ATTRIBUTE_FILE_NODE_SIZE 8192 | |
25 | ||
26 | //#define HFS_XATTR_VERBOSE 1 | |
27 | ||
28 | /* State information for the listattr_callback callback function. */ | |
29 | struct listattr_callback_state { | |
30 | u_int32_t fileID; | |
31 | int result; | |
32 | void *buf; | |
33 | size_t bufsize; | |
34 | size_t size; | |
35 | }; | |
36 | ||
37 | static u_int32_t emptyfinfo[8] = {0}; | |
38 | ||
39 | ||
40 | static int hfs_zero_hidden_fields (struct cnode *cp, u_int8_t *finderinfo); | |
41 | ||
42 | const char hfs_attrdatafilename[] = "Attribute Data"; | |
43 | ||
44 | static int listattr_callback(const HFSPlusAttrKey *key, const HFSPlusAttrData *data, | |
45 | struct listattr_callback_state *state); | |
46 | ||
47 | static int remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator); | |
48 | ||
49 | static int getnodecount(struct hfsmount *hfsmp, size_t nodesize); | |
50 | ||
51 | static size_t getmaxinlineattrsize(struct vnode * attrvp); | |
52 | ||
53 | static int read_attr_data(struct hfsmount *hfsmp, void * buf, size_t datasize, HFSPlusExtentDescriptor *extents); | |
54 | ||
55 | static int write_attr_data(struct hfsmount *hfsmp, void * buf, size_t datasize, HFSPlusExtentDescriptor *extents); | |
56 | ||
57 | static int alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks); | |
58 | ||
59 | static void free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents); | |
60 | ||
61 | static int has_overflow_extents(HFSPlusForkData *forkdata); | |
62 | ||
63 | static int count_extent_blocks(int maxblks, HFSPlusExtentRecord extents); | |
64 | ||
65 | ||
66 | /* Zero out the date added field for the specified cnode */ | |
67 | static int hfs_zero_hidden_fields (struct cnode *cp, u_int8_t *finderinfo) | |
68 | { | |
69 | u_int8_t *finfo = finderinfo; | |
70 | ||
71 | /* Advance finfo by 16 bytes to the 2nd half of the finderinfo */ | |
72 | finfo = finfo + 16; | |
73 | ||
74 | if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { | |
75 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; | |
76 | extinfo->document_id = 0; | |
77 | extinfo->date_added = 0; | |
78 | extinfo->write_gen_counter = 0; | |
79 | } else if (S_ISDIR(cp->c_attr.ca_mode)) { | |
80 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; | |
81 | extinfo->document_id = 0; | |
82 | extinfo->date_added = 0; | |
83 | extinfo->write_gen_counter = 0; | |
84 | } else { | |
85 | /* Return an error */ | |
86 | return -1; | |
87 | } | |
88 | return 0; | |
89 | } | |
90 | ||
91 | /* | |
92 | * Retrieve the data of an extended attribute. | |
93 | */ | |
94 | int | |
95 | hfs_vnop_getxattr(vnode_t vp, const char *attr_name, void *buf, size_t bufsize, size_t *actual_size) | |
96 | { | |
97 | struct cnode *cp; | |
98 | struct hfsmount *hfsmp; | |
99 | int result; | |
100 | ||
101 | if (attr_name == NULL || attr_name[0] == '\0') { | |
102 | return (EINVAL); /* invalid name */ | |
103 | } | |
104 | if (strlen(attr_name) > XATTR_MAXNAMELEN) { | |
105 | return (ENAMETOOLONG); | |
106 | } | |
107 | if (actual_size == NULL) { | |
108 | return (EINVAL); | |
109 | } | |
110 | if (VNODE_IS_RSRC(vp)) { | |
111 | return (EPERM); | |
112 | } | |
113 | ||
114 | cp = VTOC(vp); | |
115 | ||
116 | /* Get the Finder Info. */ | |
117 | if (strcmp(attr_name, XATTR_FINDERINFO_NAME) == 0) { | |
118 | u_int8_t finderinfo[32]; | |
119 | size_t attrsize = 32; | |
120 | ||
121 | if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { | |
122 | return (result); | |
123 | } | |
124 | /* Make a copy since we may not export all of it. */ | |
125 | bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo)); | |
126 | hfs_unlock(cp); | |
127 | ||
128 | /* Zero out the date added field in the local copy */ | |
129 | hfs_zero_hidden_fields (cp, finderinfo); | |
130 | ||
131 | /* Don't expose a symlink's private type/creator. */ | |
132 | if (vnode_islnk(vp)) { | |
133 | struct FndrFileInfo *fip; | |
134 | ||
135 | fip = (struct FndrFileInfo *)&finderinfo; | |
136 | fip->fdType = 0; | |
137 | fip->fdCreator = 0; | |
138 | } | |
139 | /* If Finder Info is empty then it doesn't exist. */ | |
140 | if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) { | |
141 | return (ENOATTR); | |
142 | } | |
143 | *actual_size = attrsize; | |
144 | ||
145 | if (buf == NULL) { | |
146 | return (0); | |
147 | } | |
148 | if (bufsize < attrsize) | |
149 | return (ERANGE); | |
150 | ||
151 | memcpy(buf, (caddr_t)&finderinfo, attrsize); | |
152 | return (0); | |
153 | } | |
154 | ||
155 | /* Read the Resource Fork. */ | |
156 | if (strcmp(attr_name, XATTR_RESOURCEFORK_NAME) == 0) { | |
157 | return (ENOATTR); | |
158 | } | |
159 | ||
160 | hfsmp = VTOHFS(vp); | |
161 | if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { | |
162 | return (result); | |
163 | } | |
164 | ||
165 | /* Check for non-rsrc, non-finderinfo EAs - getxattr_internal */ | |
166 | ||
167 | struct filefork *btfile; | |
168 | BTreeIterator * iterator = NULL; | |
169 | size_t attrsize = 0; | |
170 | HFSPlusAttrRecord *recp = NULL; | |
171 | size_t recp_size = 0; | |
172 | FSBufferDescriptor btdata; | |
173 | int lockflags = 0; | |
174 | u_int16_t datasize = 0; | |
175 | u_int32_t target_id = 0; | |
176 | ||
177 | if (cp) { | |
178 | target_id = cp->c_fileid; | |
179 | } else { | |
180 | target_id = kHFSRootParentID; | |
181 | } | |
182 | ||
183 | /* Bail if we don't have an EA B-Tree. */ | |
184 | if ((hfsmp->hfs_attribute_vp == NULL) || | |
185 | ((cp) && (cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0)) { | |
186 | result = ENOATTR; | |
187 | goto exit; | |
188 | } | |
189 | ||
190 | /* Initialize the B-Tree iterator for searching for the proper EA */ | |
191 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
192 | ||
193 | iterator = hfs_mallocz(sizeof(*iterator)); | |
194 | ||
195 | /* Allocate memory for reading in the attribute record. This buffer is | |
196 | * big enough to read in all types of attribute records. It is not big | |
197 | * enough to read inline attribute data which is read in later. | |
198 | */ | |
199 | recp = hfs_malloc(recp_size = sizeof(HFSPlusAttrRecord)); | |
200 | btdata.bufferAddress = recp; | |
201 | btdata.itemSize = sizeof(HFSPlusAttrRecord); | |
202 | btdata.itemCount = 1; | |
203 | ||
204 | result = hfs_buildattrkey(target_id, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
205 | if (result) { | |
206 | goto exit; | |
207 | } | |
208 | ||
209 | /* Lookup the attribute in the Attribute B-Tree */ | |
210 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
211 | result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); | |
212 | hfs_systemfile_unlock(hfsmp, lockflags); | |
213 | ||
214 | if (result) { | |
215 | if (result == btNotFound) { | |
216 | result = ENOATTR; | |
217 | } | |
218 | goto exit; | |
219 | } | |
220 | ||
221 | /* | |
222 | * Operate differently if we have inline EAs that can fit in the attribute B-Tree or if | |
223 | * we have extent based EAs. | |
224 | */ | |
225 | switch (recp->recordType) { | |
226 | ||
227 | /* Attribute fits in the Attribute B-Tree */ | |
228 | case kHFSPlusAttrInlineData: { | |
229 | /* | |
230 | * Sanity check record size. It's not required to have any | |
231 | * user data, so the minimum size is 2 bytes less that the | |
232 | * size of HFSPlusAttrData (since HFSPlusAttrData struct | |
233 | * has 2 bytes set aside for attribute data). | |
234 | */ | |
235 | if (datasize < (sizeof(HFSPlusAttrData) - 2)) { | |
236 | LFHFS_LOG(LEVEL_DEBUG, "hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n", | |
237 | hfsmp->vcbVN, target_id, attr_name, datasize, sizeof(HFSPlusAttrData)); | |
238 | result = ENOATTR; | |
239 | break; | |
240 | } | |
241 | *actual_size = recp->attrData.attrSize; | |
242 | if (buf && recp->attrData.attrSize != 0) { | |
243 | if (*actual_size > bufsize) { | |
244 | /* User provided buffer is not large enough for the xattr data */ | |
245 | result = ERANGE; | |
246 | } else { | |
247 | /* Previous BTreeSearchRecord() read in only the attribute record, | |
248 | * and not the attribute data. Now allocate enough memory for | |
249 | * both attribute record and data, and read the attribute record again. | |
250 | */ | |
251 | attrsize = sizeof(HFSPlusAttrData) - 2 + recp->attrData.attrSize; | |
252 | hfs_free(recp); | |
253 | recp = hfs_malloc(recp_size = attrsize); | |
254 | ||
255 | btdata.bufferAddress = recp; | |
256 | btdata.itemSize = attrsize; | |
257 | btdata.itemCount = 1; | |
258 | ||
259 | bzero(iterator, sizeof(*iterator)); | |
260 | result = hfs_buildattrkey(target_id, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
261 | if (result) { | |
262 | goto exit; | |
263 | } | |
264 | ||
265 | /* Lookup the attribute record and inline data */ | |
266 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
267 | result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); | |
268 | hfs_systemfile_unlock(hfsmp, lockflags); | |
269 | if (result) { | |
270 | if (result == btNotFound) { | |
271 | result = ENOATTR; | |
272 | } | |
273 | goto exit; | |
274 | } | |
275 | ||
276 | /* Copy-out the attribute data to the user buffer */ | |
277 | *actual_size = recp->attrData.attrSize; | |
278 | memcpy(buf, (caddr_t) &recp->attrData.attrData, recp->attrData.attrSize); | |
279 | } | |
280 | } | |
281 | break; | |
282 | } | |
283 | ||
284 | /* Extent-Based EAs */ | |
285 | case kHFSPlusAttrForkData: { | |
286 | if (datasize < sizeof(HFSPlusAttrForkData)) { | |
287 | LFHFS_LOG(LEVEL_DEBUG, "hfs_getxattr: vol=%s %d,%s invalid record size %d (expecting %lu)\n", | |
288 | hfsmp->vcbVN, target_id, attr_name, datasize, sizeof(HFSPlusAttrForkData)); | |
289 | result = ENOATTR; | |
290 | break; | |
291 | } | |
292 | *actual_size = recp->forkData.theFork.logicalSize; | |
293 | if (buf == NULL) { | |
294 | break; | |
295 | } | |
296 | if (*actual_size > bufsize) { | |
297 | result = ERANGE; | |
298 | break; | |
299 | } | |
300 | /* Process overflow extents if necessary. */ | |
301 | if (has_overflow_extents(&recp->forkData.theFork)) { | |
302 | HFSPlusExtentDescriptor *extentbuf; | |
303 | HFSPlusExtentDescriptor *extentptr; | |
304 | size_t extentbufsize; | |
305 | u_int32_t totalblocks; | |
306 | u_int32_t blkcnt; | |
307 | u_int64_t attrlen; | |
308 | ||
309 | totalblocks = recp->forkData.theFork.totalBlocks; | |
310 | /* Ignore bogus block counts. */ | |
311 | if (totalblocks > howmany(HFS_XATTR_MAXSIZE, hfsmp->blockSize)) { | |
312 | result = ERANGE; | |
313 | break; | |
314 | } | |
315 | attrlen = recp->forkData.theFork.logicalSize; | |
316 | ||
317 | /* Get a buffer to hold the worst case amount of extents. */ | |
318 | extentbufsize = totalblocks * sizeof(HFSPlusExtentDescriptor); | |
319 | extentbufsize = roundup(extentbufsize, sizeof(HFSPlusExtentRecord)); | |
320 | extentbuf = hfs_mallocz(extentbufsize); | |
321 | extentptr = extentbuf; | |
322 | ||
323 | /* Grab the first 8 extents. */ | |
324 | bcopy(&recp->forkData.theFork.extents[0], extentptr, sizeof(HFSPlusExtentRecord)); | |
325 | extentptr += kHFSPlusExtentDensity; | |
326 | blkcnt = count_extent_blocks(totalblocks, recp->forkData.theFork.extents); | |
327 | ||
328 | /* Now lookup the overflow extents. */ | |
329 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
330 | while (blkcnt < totalblocks) { | |
331 | ((HFSPlusAttrKey *)&iterator->key)->startBlock = blkcnt; | |
332 | result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); | |
333 | if (result || | |
334 | (recp->recordType != kHFSPlusAttrExtents) || | |
335 | (datasize < sizeof(HFSPlusAttrExtents))) { | |
336 | LFHFS_LOG(LEVEL_DEBUG, "hfs_getxattr: %s missing extents, only %d blks of %d found\n", | |
337 | attr_name, blkcnt, totalblocks); | |
338 | break; /* break from while */ | |
339 | } | |
340 | /* Grab the next 8 extents. */ | |
341 | bcopy(&recp->overflowExtents.extents[0], extentptr, sizeof(HFSPlusExtentRecord)); | |
342 | extentptr += kHFSPlusExtentDensity; | |
343 | blkcnt += count_extent_blocks(totalblocks, recp->overflowExtents.extents); | |
344 | } | |
345 | ||
346 | /* Release Attr B-Tree lock */ | |
347 | hfs_systemfile_unlock(hfsmp, lockflags); | |
348 | ||
349 | if (blkcnt < totalblocks) { | |
350 | result = ENOATTR; | |
351 | } else { | |
352 | result = read_attr_data(hfsmp, buf, attrlen, extentbuf); | |
353 | } | |
354 | hfs_free(extentbuf); | |
355 | ||
356 | } else { /* No overflow extents. */ | |
357 | result = read_attr_data(hfsmp, buf, recp->forkData.theFork.logicalSize, recp->forkData.theFork.extents); | |
358 | } | |
359 | break; | |
360 | } | |
361 | ||
362 | default: | |
363 | /* We only support inline EAs. Default to ENOATTR for anything else */ | |
364 | result = ENOATTR; | |
365 | break; | |
366 | } | |
367 | ||
368 | exit: | |
369 | hfs_free(iterator); | |
370 | hfs_free(recp); | |
371 | hfs_unlock(cp); | |
372 | ||
373 | return MacToVFSError(result); | |
374 | } | |
375 | ||
376 | /* | |
377 | * Set the data of an extended attribute. | |
378 | */ | |
379 | int | |
380 | hfs_vnop_setxattr(vnode_t vp, const char *attr_name, const void *buf, size_t bufsize, UVFSXattrHow option) | |
381 | { | |
382 | struct cnode *cp = NULL; | |
383 | struct hfsmount *hfsmp; | |
384 | size_t attrsize; | |
385 | int result; | |
386 | ||
387 | if (attr_name == NULL || attr_name[0] == '\0') { | |
388 | return (EINVAL); /* invalid name */ | |
389 | } | |
390 | if (strlen(attr_name) > XATTR_MAXNAMELEN) { | |
391 | return (ENAMETOOLONG); | |
392 | } | |
393 | if (buf == NULL) { | |
394 | return (EINVAL); | |
395 | } | |
396 | if (VNODE_IS_RSRC(vp)) { | |
397 | return (EPERM); | |
398 | } | |
399 | ||
400 | hfsmp = VTOHFS(vp); | |
401 | ||
402 | /* Set the Finder Info. */ | |
403 | if (strcmp(attr_name, XATTR_FINDERINFO_NAME) == 0) { | |
404 | union { | |
405 | uint8_t data[32]; | |
406 | char cdata[32]; | |
407 | struct FndrFileInfo info; | |
408 | } fi; | |
409 | void * finderinfo_start; | |
410 | u_int8_t *finfo = NULL; | |
411 | u_int16_t fdFlags; | |
412 | u_int32_t dateadded = 0; | |
413 | u_int32_t write_gen_counter = 0; | |
414 | u_int32_t document_id = 0; | |
415 | ||
416 | attrsize = sizeof(VTOC(vp)->c_finderinfo); | |
417 | ||
418 | if (bufsize != attrsize) { | |
419 | return (ERANGE); | |
420 | } | |
421 | /* Grab the new Finder Info data. */ | |
422 | memcpy(fi.cdata, buf, attrsize); | |
423 | ||
424 | if ((result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { | |
425 | return (result); | |
426 | } | |
427 | cp = VTOC(vp); | |
428 | ||
429 | /* Symlink's don't have an external type/creator. */ | |
430 | if (vnode_islnk(vp)) { | |
431 | /* Skip over type/creator fields. */ | |
432 | finderinfo_start = &cp->c_finderinfo[8]; | |
433 | attrsize -= 8; | |
434 | } else { | |
435 | finderinfo_start = &cp->c_finderinfo[0]; | |
436 | /* | |
437 | * Don't allow the external setting of | |
438 | * file type to kHardLinkFileType. | |
439 | */ | |
440 | if (fi.info.fdType == SWAP_BE32(kHardLinkFileType)) { | |
441 | hfs_unlock(cp); | |
442 | return (EPERM); | |
443 | } | |
444 | } | |
445 | ||
446 | /* Grab the current date added from the cnode */ | |
447 | dateadded = hfs_get_dateadded (cp); | |
448 | if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { | |
449 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)((u_int8_t*)cp->c_finderinfo + 16); | |
450 | /* | |
451 | * Grab generation counter directly from the cnode | |
452 | * instead of calling hfs_get_gencount(), because | |
453 | * for zero generation count values hfs_get_gencount() | |
454 | * lies and bumps it up to one. | |
455 | */ | |
456 | write_gen_counter = extinfo->write_gen_counter; | |
457 | document_id = extinfo->document_id; | |
458 | } else if (S_ISDIR(cp->c_attr.ca_mode)) { | |
459 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)cp->c_finderinfo + 16); | |
460 | write_gen_counter = extinfo->write_gen_counter; | |
461 | document_id = extinfo->document_id; | |
462 | } | |
463 | ||
464 | /* | |
465 | * Zero out the finder info's reserved fields like date added, | |
466 | * generation counter, and document id to ignore user's attempts | |
467 | * to set it | |
468 | */ | |
469 | hfs_zero_hidden_fields(cp, fi.data); | |
470 | ||
471 | if (bcmp(finderinfo_start, emptyfinfo, attrsize)) { | |
472 | /* attr exists and "create" was specified. */ | |
473 | if (option == UVFSXattrHowCreate) { | |
474 | hfs_unlock(cp); | |
475 | return (EEXIST); | |
476 | } | |
477 | } else { /* empty */ | |
478 | /* attr doesn't exists and "replace" was specified. */ | |
479 | if (option == UVFSXattrHowReplace) { | |
480 | hfs_unlock(cp); | |
481 | return (ENOATTR); | |
482 | } | |
483 | } | |
484 | ||
485 | /* | |
486 | * Now restore the date added and other reserved fields to the finderinfo to | |
487 | * be written out. Advance to the 2nd half of the finderinfo to write them | |
488 | * out into the buffer. | |
489 | * | |
490 | * Make sure to endian swap the date added back into big endian. When we used | |
491 | * hfs_get_dateadded above to retrieve it, it swapped into local endianness | |
492 | * for us. But now that we're writing it out, put it back into big endian. | |
493 | */ | |
494 | finfo = &fi.data[16]; | |
495 | if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { | |
496 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; | |
497 | extinfo->date_added = OSSwapHostToBigInt32(dateadded); | |
498 | extinfo->write_gen_counter = write_gen_counter; | |
499 | extinfo->document_id = document_id; | |
500 | } else if (S_ISDIR(cp->c_attr.ca_mode)) { | |
501 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; | |
502 | extinfo->date_added = OSSwapHostToBigInt32(dateadded); | |
503 | extinfo->write_gen_counter = write_gen_counter; | |
504 | extinfo->document_id = document_id; | |
505 | } | |
506 | ||
507 | /* Set the cnode's Finder Info. */ | |
508 | if (attrsize == sizeof(cp->c_finderinfo)) { | |
509 | bcopy(&fi.data[0], finderinfo_start, attrsize); | |
510 | } else { | |
511 | bcopy(&fi.data[8], finderinfo_start, attrsize); | |
512 | } | |
513 | ||
514 | /* Updating finderInfo updates change time and modified time */ | |
515 | cp->c_touch_chgtime = TRUE; | |
516 | cp->c_flag |= C_MODIFIED; | |
517 | ||
518 | /* | |
519 | * Mirror the invisible bit to the UF_HIDDEN flag. | |
520 | * | |
521 | * The fdFlags for files and frFlags for folders are both 8 bytes | |
522 | * into the userInfo (the first 16 bytes of the Finder Info). They | |
523 | * are both 16-bit fields. | |
524 | */ | |
525 | fdFlags = *((u_int16_t *) &cp->c_finderinfo[8]); | |
526 | if (fdFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask)) { | |
527 | cp->c_bsdflags |= UF_HIDDEN; | |
528 | } else { | |
529 | cp->c_bsdflags &= ~UF_HIDDEN; | |
530 | } | |
531 | ||
532 | result = hfs_update(vp, 0); | |
533 | ||
534 | hfs_unlock(cp); | |
535 | return (result); | |
536 | } | |
537 | ||
538 | /* Write the Resource Fork. */ | |
539 | if (strcmp(attr_name, XATTR_RESOURCEFORK_NAME) == 0) { | |
540 | return (ENOTSUP); | |
541 | } | |
542 | ||
543 | attrsize = bufsize; | |
544 | ||
545 | result = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); | |
546 | if (result) { | |
547 | goto exit; | |
548 | } | |
549 | cp = VTOC(vp); | |
550 | ||
551 | /* | |
552 | * If we're trying to set a non-finderinfo, non-resourcefork EA, then | |
553 | * call the breakout function - hfs_setxattr_internal. | |
554 | */ | |
555 | int started_transaction = 0; | |
556 | BTreeIterator * iterator = NULL; | |
557 | struct filefork *btfile = NULL; | |
558 | FSBufferDescriptor btdata; | |
559 | HFSPlusAttrRecord attrdata; /* 90 bytes */ | |
560 | HFSPlusAttrRecord *recp = NULL; | |
561 | size_t recp_size = 0; | |
562 | HFSPlusExtentDescriptor *extentptr = NULL; | |
563 | size_t extentbufsize = 0; | |
564 | int lockflags = 0; | |
565 | int exists = 0; | |
566 | int allocatedblks = 0; | |
567 | u_int32_t target_id; | |
568 | ||
569 | if (cp) { | |
570 | target_id = cp->c_fileid; | |
571 | } else { | |
572 | target_id = kHFSRootParentID; | |
573 | } | |
574 | ||
575 | /* Start a transaction for our changes. */ | |
576 | if (hfs_start_transaction(hfsmp) != 0) { | |
577 | result = EINVAL; | |
578 | goto exit; | |
579 | } | |
580 | started_transaction = 1; | |
581 | ||
582 | /* | |
583 | * Once we started the transaction, nobody can compete | |
584 | * with us, so make sure this file is still there. | |
585 | */ | |
586 | if ((cp) && (cp->c_flag & C_NOEXISTS)) { | |
587 | result = ENOENT; | |
588 | goto exit; | |
589 | } | |
590 | ||
591 | /* | |
592 | * If there isn't an attributes b-tree then create one. | |
593 | */ | |
594 | if (hfsmp->hfs_attribute_vp == NULL) { | |
595 | result = hfs_create_attr_btree(hfsmp, ATTRIBUTE_FILE_NODE_SIZE, | |
596 | getnodecount(hfsmp, ATTRIBUTE_FILE_NODE_SIZE)); | |
597 | if (result) { | |
598 | goto exit; | |
599 | } | |
600 | } | |
601 | if (hfsmp->hfs_max_inline_attrsize == 0) { | |
602 | hfsmp->hfs_max_inline_attrsize = getmaxinlineattrsize(hfsmp->hfs_attribute_vp); | |
603 | } | |
604 | ||
605 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK); | |
606 | ||
607 | /* Build the b-tree key. */ | |
608 | iterator = hfs_mallocz(sizeof(*iterator)); | |
609 | result = hfs_buildattrkey(target_id, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
610 | if (result) { | |
611 | goto exit_lock; | |
612 | } | |
613 | ||
614 | /* Preflight for replace/create semantics. */ | |
615 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
616 | btdata.bufferAddress = &attrdata; | |
617 | btdata.itemSize = sizeof(attrdata); | |
618 | btdata.itemCount = 1; | |
619 | exists = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL) == 0; | |
620 | ||
621 | /* Replace requires that the attribute already exists. */ | |
622 | if ((option == UVFSXattrHowReplace) && !exists) { | |
623 | result = ENOATTR; | |
624 | goto exit_lock; | |
625 | } | |
626 | /* Create requires that the attribute doesn't exist. */ | |
627 | if ((option == UVFSXattrHowCreate) && exists) { | |
628 | result = EEXIST; | |
629 | goto exit_lock; | |
630 | } | |
631 | ||
632 | /* Enforce an upper limit. */ | |
633 | if (attrsize > HFS_XATTR_MAXSIZE) { | |
634 | result = E2BIG; | |
635 | goto exit_lock; | |
636 | } | |
637 | ||
638 | /* If it won't fit inline then use extent-based attributes. */ | |
639 | if (attrsize > hfsmp->hfs_max_inline_attrsize) { | |
640 | int blkcnt; | |
641 | int extentblks; | |
642 | u_int32_t *keystartblk; | |
643 | int i; | |
644 | ||
645 | /* Get some blocks. */ | |
646 | blkcnt = (int)howmany(attrsize, hfsmp->blockSize); | |
647 | extentbufsize = blkcnt * sizeof(HFSPlusExtentDescriptor); | |
648 | extentbufsize = roundup(extentbufsize, sizeof(HFSPlusExtentRecord)); | |
649 | extentptr = hfs_mallocz(extentbufsize); | |
650 | result = alloc_attr_blks(hfsmp, attrsize, extentbufsize, extentptr, &allocatedblks); | |
651 | if (result) { | |
652 | allocatedblks = 0; | |
653 | goto exit_lock; /* no more space */ | |
654 | } | |
655 | /* Copy data into the blocks. */ | |
656 | result = write_attr_data(hfsmp, (void*)buf, attrsize, extentptr); | |
657 | if (result) { | |
658 | if (vp) { | |
659 | LFHFS_LOG(LEVEL_DEBUG, "hfs_setxattr: write_attr_data vol=%s err (%d) :%s\n", | |
660 | hfsmp->vcbVN, result, attr_name); | |
661 | } | |
662 | goto exit_lock; | |
663 | } | |
664 | ||
665 | /* Now remove any previous attribute. */ | |
666 | if (exists) { | |
667 | result = remove_attribute_records(hfsmp, iterator); | |
668 | if (result) { | |
669 | if (vp) { | |
670 | LFHFS_LOG(LEVEL_DEBUG, "hfs_setxattr: remove_attribute_records vol=%s err (%d) %s:%s\n", | |
671 | hfsmp->vcbVN, result, "", attr_name); | |
672 | } | |
673 | goto exit_lock; | |
674 | } | |
675 | } | |
676 | /* Create attribute fork data record. */ | |
677 | recp = hfs_malloc(recp_size = sizeof(HFSPlusAttrRecord)); | |
678 | ||
679 | btdata.bufferAddress = recp; | |
680 | btdata.itemCount = 1; | |
681 | btdata.itemSize = sizeof(HFSPlusAttrForkData); | |
682 | ||
683 | recp->recordType = kHFSPlusAttrForkData; | |
684 | recp->forkData.reserved = 0; | |
685 | recp->forkData.theFork.logicalSize = attrsize; | |
686 | recp->forkData.theFork.clumpSize = 0; | |
687 | recp->forkData.theFork.totalBlocks = blkcnt; | |
688 | bcopy(extentptr, recp->forkData.theFork.extents, sizeof(HFSPlusExtentRecord)); | |
689 | ||
690 | (void) hfs_buildattrkey(target_id, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
691 | ||
692 | result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize); | |
693 | if (result) { | |
694 | LFHFS_LOG(LEVEL_DEBUG, "hfs_setxattr: BTInsertRecord(): vol=%s %d,%s err=%d\n", | |
695 | hfsmp->vcbVN, target_id, attr_name, result); | |
696 | goto exit_lock; | |
697 | } | |
698 | extentblks = count_extent_blocks(blkcnt, recp->forkData.theFork.extents); | |
699 | blkcnt -= extentblks; | |
700 | keystartblk = &((HFSPlusAttrKey *)&iterator->key)->startBlock; | |
701 | i = 0; | |
702 | ||
703 | /* Create overflow extents as needed. */ | |
704 | while (blkcnt > 0) { | |
705 | /* Initialize the key and record. */ | |
706 | *keystartblk += (u_int32_t)extentblks; | |
707 | btdata.itemSize = sizeof(HFSPlusAttrExtents); | |
708 | recp->recordType = kHFSPlusAttrExtents; | |
709 | recp->overflowExtents.reserved = 0; | |
710 | ||
711 | /* Copy the next set of extents. */ | |
712 | i += kHFSPlusExtentDensity; | |
713 | bcopy(&extentptr[i], recp->overflowExtents.extents, sizeof(HFSPlusExtentRecord)); | |
714 | ||
715 | result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize); | |
716 | if (result) { | |
717 | LFHFS_LOG(LEVEL_DEBUG, "hfs_setxattr: BTInsertRecord() overflow: vol=%s %d,%s err=%d\n", | |
718 | hfsmp->vcbVN, target_id, attr_name, result); | |
719 | goto exit_lock; | |
720 | } | |
721 | extentblks = count_extent_blocks(blkcnt, recp->overflowExtents.extents); | |
722 | blkcnt -= extentblks; | |
723 | } | |
724 | } else { /* Inline data */ | |
725 | if (exists) { | |
726 | result = remove_attribute_records(hfsmp, iterator); | |
727 | if (result) { | |
728 | goto exit_lock; | |
729 | } | |
730 | } | |
731 | ||
732 | /* Calculate size of record rounded up to multiple of 2 bytes. */ | |
733 | btdata.itemSize = sizeof(HFSPlusAttrData) - 2 + attrsize + ((attrsize & 1) ? 1 : 0); | |
734 | recp = hfs_malloc(recp_size = btdata.itemSize); | |
735 | ||
736 | recp->recordType = kHFSPlusAttrInlineData; | |
737 | recp->attrData.reserved[0] = 0; | |
738 | recp->attrData.reserved[1] = 0; | |
739 | recp->attrData.attrSize = (u_int32_t)attrsize; | |
740 | ||
741 | /* Copy in the attribute data (if any). */ | |
742 | if (attrsize > 0) { | |
743 | bcopy(buf, &recp->attrData.attrData, attrsize); | |
744 | } | |
745 | ||
746 | (void) hfs_buildattrkey(target_id, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
747 | ||
748 | btdata.bufferAddress = recp; | |
749 | btdata.itemCount = 1; | |
750 | result = BTInsertRecord(btfile, iterator, &btdata, btdata.itemSize); | |
751 | } | |
752 | ||
753 | exit_lock: | |
754 | if (btfile && started_transaction) { | |
755 | (void) BTFlushPath(btfile); | |
756 | } | |
757 | hfs_systemfile_unlock(hfsmp, lockflags); | |
758 | if (result == 0) { | |
759 | if (vp) { | |
760 | cp = VTOC(vp); | |
761 | /* Setting an attribute only updates change time and not | |
762 | * modified time of the file. | |
763 | */ | |
764 | cp->c_touch_chgtime = TRUE; | |
765 | cp->c_flag |= C_MODIFIED; | |
766 | cp->c_attr.ca_recflags |= kHFSHasAttributesMask; | |
767 | if ((strcmp(attr_name, KAUTH_FILESEC_XATTR) == 0)) { | |
768 | cp->c_attr.ca_recflags |= kHFSHasSecurityMask; | |
769 | } | |
770 | (void) hfs_update(vp, 0); | |
771 | } | |
772 | } | |
773 | if (started_transaction) { | |
774 | if (result && allocatedblks) { | |
775 | free_attr_blks(hfsmp, allocatedblks, extentptr); | |
776 | } | |
777 | hfs_end_transaction(hfsmp); | |
778 | } | |
779 | ||
780 | hfs_free(recp); | |
781 | hfs_free(extentptr); | |
782 | hfs_free(iterator); | |
783 | ||
784 | exit: | |
785 | if (cp) { | |
786 | hfs_unlock(cp); | |
787 | } | |
788 | ||
789 | return (result == btNotFound ? ENOATTR : MacToVFSError(result)); | |
790 | } | |
791 | ||
792 | /* | |
793 | * Remove an extended attribute. | |
794 | */ | |
795 | int | |
796 | hfs_vnop_removexattr(vnode_t vp, const char *attr_name) | |
797 | { | |
798 | struct cnode *cp = VTOC(vp); | |
799 | struct hfsmount *hfsmp; | |
800 | BTreeIterator * iterator = NULL; | |
801 | int lockflags; | |
802 | int result; | |
803 | ||
804 | if (attr_name == NULL || attr_name[0] == '\0') { | |
805 | return (EINVAL); /* invalid name */ | |
806 | } | |
807 | hfsmp = VTOHFS(vp); | |
808 | if (VNODE_IS_RSRC(vp)) { | |
809 | return (EPERM); | |
810 | } | |
811 | ||
812 | /* Write the Resource Fork. */ | |
813 | if (strcmp(attr_name, XATTR_RESOURCEFORK_NAME) == 0) { | |
814 | return (ENOTSUP); | |
815 | } | |
816 | ||
817 | /* Clear out the Finder Info. */ | |
818 | if (strcmp(attr_name, XATTR_FINDERINFO_NAME) == 0) { | |
819 | void * finderinfo_start; | |
820 | int finderinfo_size; | |
821 | u_int8_t finderinfo[32]; | |
822 | u_int32_t date_added = 0, write_gen_counter = 0, document_id = 0; | |
823 | u_int8_t *finfo = NULL; | |
824 | ||
825 | if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { | |
826 | return (result); | |
827 | } | |
828 | ||
829 | /* Use the local copy to store our temporary changes. */ | |
830 | bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo)); | |
831 | ||
832 | /* Zero out the date added field in the local copy */ | |
833 | hfs_zero_hidden_fields (cp, finderinfo); | |
834 | ||
835 | /* Don't expose a symlink's private type/creator. */ | |
836 | if (vnode_islnk(vp)) { | |
837 | struct FndrFileInfo *fip; | |
838 | ||
839 | fip = (struct FndrFileInfo *)&finderinfo; | |
840 | fip->fdType = 0; | |
841 | fip->fdCreator = 0; | |
842 | } | |
843 | ||
844 | /* Do the byte compare against the local copy */ | |
845 | if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) == 0) { | |
846 | hfs_unlock(cp); | |
847 | return (ENOATTR); | |
848 | } | |
849 | ||
850 | /* | |
851 | * If there was other content, zero out everything except | |
852 | * type/creator and date added. First, save the date added. | |
853 | */ | |
854 | finfo = cp->c_finderinfo; | |
855 | finfo = finfo + 16; | |
856 | if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { | |
857 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; | |
858 | date_added = extinfo->date_added; | |
859 | write_gen_counter = extinfo->write_gen_counter; | |
860 | document_id = extinfo->document_id; | |
861 | } else if (S_ISDIR(cp->c_attr.ca_mode)) { | |
862 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; | |
863 | date_added = extinfo->date_added; | |
864 | write_gen_counter = extinfo->write_gen_counter; | |
865 | document_id = extinfo->document_id; | |
866 | } | |
867 | ||
868 | if (vnode_islnk(vp)) { | |
869 | /* Ignore type/creator */ | |
870 | finderinfo_start = &cp->c_finderinfo[8]; | |
871 | finderinfo_size = sizeof(cp->c_finderinfo) - 8; | |
872 | } else { | |
873 | finderinfo_start = &cp->c_finderinfo[0]; | |
874 | finderinfo_size = sizeof(cp->c_finderinfo); | |
875 | } | |
876 | bzero(finderinfo_start, finderinfo_size); | |
877 | ||
878 | /* Now restore the date added */ | |
879 | if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK(cp->c_attr.ca_mode)) { | |
880 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; | |
881 | extinfo->date_added = date_added; | |
882 | extinfo->write_gen_counter = write_gen_counter; | |
883 | extinfo->document_id = document_id; | |
884 | } else if (S_ISDIR(cp->c_attr.ca_mode)) { | |
885 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)finfo; | |
886 | extinfo->date_added = date_added; | |
887 | extinfo->write_gen_counter = write_gen_counter; | |
888 | extinfo->document_id = document_id; | |
889 | } | |
890 | ||
891 | /* Updating finderInfo updates change time and modified time */ | |
892 | cp->c_touch_chgtime = TRUE; | |
893 | cp->c_flag |= C_MODIFIED; | |
894 | hfs_update(vp, 0); | |
895 | ||
896 | hfs_unlock(cp); | |
897 | ||
898 | return (0); | |
899 | } | |
900 | ||
901 | if (hfsmp->hfs_attribute_vp == NULL) { | |
902 | return (ENOATTR); | |
903 | } | |
904 | ||
905 | iterator = hfs_mallocz(sizeof(*iterator)); | |
906 | ||
907 | if ((result = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { | |
908 | goto exit_nolock; | |
909 | } | |
910 | ||
911 | result = hfs_buildattrkey(cp->c_fileid, attr_name, (HFSPlusAttrKey *)&iterator->key); | |
912 | if (result) { | |
913 | goto exit; | |
914 | } | |
915 | ||
916 | if (hfs_start_transaction(hfsmp) != 0) { | |
917 | result = EINVAL; | |
918 | goto exit; | |
919 | } | |
920 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK); | |
921 | ||
922 | result = remove_attribute_records(hfsmp, iterator); | |
923 | ||
924 | hfs_systemfile_unlock(hfsmp, lockflags); | |
925 | ||
926 | if (result == 0) { | |
927 | cp->c_touch_chgtime = TRUE; | |
928 | ||
929 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
930 | ||
931 | /* If no more attributes exist, clear attribute bit */ | |
932 | result = file_attribute_exist(hfsmp, cp->c_fileid); | |
933 | if (result == 0) { | |
934 | cp->c_attr.ca_recflags &= ~kHFSHasAttributesMask; | |
935 | cp->c_flag |= C_MODIFIED; | |
936 | } | |
937 | if (result == EEXIST) { | |
938 | result = 0; | |
939 | } | |
940 | ||
941 | hfs_systemfile_unlock(hfsmp, lockflags); | |
942 | ||
943 | /* If ACL was removed, clear security bit */ | |
944 | if (strcmp(attr_name, KAUTH_FILESEC_XATTR) == 0) { | |
945 | cp->c_attr.ca_recflags &= ~kHFSHasSecurityMask; | |
946 | cp->c_flag |= C_MODIFIED; | |
947 | } | |
948 | (void) hfs_update(vp, 0); | |
949 | } | |
950 | ||
951 | hfs_end_transaction(hfsmp); | |
952 | exit: | |
953 | hfs_unlock(cp); | |
954 | exit_nolock: | |
955 | hfs_free(iterator); | |
956 | return MacToVFSError(result); | |
957 | } | |
958 | ||
959 | /* | |
960 | * Initialize vnode for attribute data I/O. | |
961 | * | |
962 | * On success, | |
963 | * - returns zero | |
964 | * - the attrdata vnode is initialized as hfsmp->hfs_attrdata_vp | |
965 | * - an iocount is taken on the attrdata vnode which exists | |
966 | * for the entire duration of the mount. It is only dropped | |
967 | * during unmount | |
968 | * - the attrdata cnode is not locked | |
969 | * | |
970 | * On failure, | |
971 | * - returns non-zero value | |
972 | * - the caller does not have to worry about any locks or references | |
973 | */ | |
974 | int init_attrdata_vnode(struct hfsmount *hfsmp) | |
975 | { | |
976 | vnode_t vp; | |
977 | int result = 0; | |
978 | struct cat_desc cat_desc; | |
979 | struct cat_attr cat_attr; | |
980 | struct cat_fork cat_fork; | |
981 | int newvnode_flags = 0; | |
982 | ||
983 | bzero(&cat_desc, sizeof(cat_desc)); | |
984 | cat_desc.cd_parentcnid = kHFSRootParentID; | |
985 | cat_desc.cd_nameptr = (const u_int8_t *)hfs_attrdatafilename; | |
986 | cat_desc.cd_namelen = strlen(hfs_attrdatafilename); | |
987 | cat_desc.cd_cnid = kHFSAttributeDataFileID; | |
988 | /* Tag vnode as system file, note that we can still use cluster I/O */ | |
989 | cat_desc.cd_flags |= CD_ISMETA; | |
990 | ||
991 | bzero(&cat_attr, sizeof(cat_attr)); | |
992 | cat_attr.ca_linkcount = 1; | |
993 | cat_attr.ca_mode = S_IFREG; | |
994 | cat_attr.ca_fileid = cat_desc.cd_cnid; | |
995 | cat_attr.ca_blocks = hfsmp->totalBlocks; | |
996 | ||
997 | /* | |
998 | * The attribute data file is a virtual file that spans the | |
999 | * entire file system space. | |
1000 | * | |
1001 | * Each extent-based attribute occupies a unique portion of | |
1002 | * in this virtual file. The cluster I/O is done using actual | |
1003 | * allocation block offsets so no additional mapping is needed | |
1004 | * for the VNOP_BLOCKMAP call. | |
1005 | * | |
1006 | * This approach allows the attribute data to be cached without | |
1007 | * incurring the high cost of using a separate vnode per attribute. | |
1008 | * | |
1009 | * Since we need to acquire the attribute b-tree file lock anyways, | |
1010 | * the virtual file doesn't introduce any additional serialization. | |
1011 | */ | |
1012 | bzero(&cat_fork, sizeof(cat_fork)); | |
1013 | cat_fork.cf_size = (u_int64_t)hfsmp->totalBlocks * (u_int64_t)hfsmp->blockSize; | |
1014 | cat_fork.cf_blocks = hfsmp->totalBlocks; | |
1015 | cat_fork.cf_extents[0].startBlock = 0; | |
1016 | cat_fork.cf_extents[0].blockCount = cat_fork.cf_blocks; | |
1017 | ||
1018 | result = hfs_getnewvnode(hfsmp, NULL, NULL, &cat_desc, 0, &cat_attr, | |
1019 | &cat_fork, &vp, &newvnode_flags); | |
1020 | if (result == 0) { | |
1021 | hfsmp->hfs_attrdata_vp = vp; | |
1022 | hfs_unlock(VTOC(vp)); | |
1023 | } | |
1024 | return (result); | |
1025 | } | |
1026 | ||
1027 | /* Check if any attribute record exist for given fileID. This function | |
1028 | * is called by hfs_vnop_removexattr to determine if it should clear the | |
1029 | * attribute bit in the catalog record or not. | |
1030 | * | |
1031 | * Note - you must acquire a shared lock on the attribute btree before | |
1032 | * calling this function. | |
1033 | * | |
1034 | * Output: | |
1035 | * EEXIST - If attribute record was found | |
1036 | * 0 - Attribute was not found | |
1037 | * (other) - Other error (such as EIO) | |
1038 | */ | |
1039 | int | |
1040 | file_attribute_exist(struct hfsmount *hfsmp, uint32_t fileID) | |
1041 | { | |
1042 | HFSPlusAttrKey *key; | |
1043 | BTreeIterator * iterator = NULL; | |
1044 | struct filefork *btfile; | |
1045 | int result = 0; | |
1046 | ||
1047 | // if there's no attribute b-tree we sure as heck | |
1048 | // can't have any attributes! | |
1049 | if (hfsmp->hfs_attribute_vp == NULL) { | |
1050 | return 0; | |
1051 | } | |
1052 | ||
1053 | iterator = hfs_mallocz(sizeof(BTreeIterator)); | |
1054 | if (iterator == NULL) return ENOMEM; | |
1055 | ||
1056 | key = (HFSPlusAttrKey *)&iterator->key; | |
1057 | ||
1058 | result = hfs_buildattrkey(fileID, NULL, key); | |
1059 | if (result) { | |
1060 | goto out; | |
1061 | } | |
1062 | ||
1063 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
1064 | result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL); | |
1065 | if (result && (result != btNotFound)) { | |
1066 | goto out; | |
1067 | } | |
1068 | ||
1069 | result = BTIterateRecord(btfile, kBTreeNextRecord, iterator, NULL, NULL); | |
1070 | /* If no next record was found or fileID for next record did not match, | |
1071 | * no more attributes exist for this fileID | |
1072 | */ | |
1073 | if ((result && (result == btNotFound)) || (key->fileID != fileID)) { | |
1074 | result = 0; | |
1075 | } else { | |
1076 | result = EEXIST; | |
1077 | } | |
1078 | ||
1079 | out: | |
1080 | hfs_free(iterator); | |
1081 | return result; | |
1082 | } | |
1083 | ||
1084 | /* | |
1085 | * Read an extent based attribute. | |
1086 | */ | |
1087 | static int | |
1088 | read_attr_data(struct hfsmount *hfsmp, void *buf, size_t datasize, HFSPlusExtentDescriptor *extents) | |
1089 | { | |
1090 | vnode_t evp = hfsmp->hfs_attrdata_vp; | |
1091 | uint64_t iosize; | |
1092 | uint64_t attrsize; | |
1093 | uint64_t blksize; | |
1094 | uint64_t alreadyread; | |
1095 | int i; | |
1096 | int result = 0; | |
1097 | ||
1098 | hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); | |
1099 | ||
1100 | attrsize = (uint64_t)datasize; | |
1101 | blksize = (uint64_t)hfsmp->blockSize; | |
1102 | alreadyread = 0; | |
1103 | ||
1104 | /* | |
1105 | * Read the attribute data one extent at a time. | |
1106 | * For the typical case there is only one extent. | |
1107 | */ | |
1108 | for (i = 0; (attrsize > 0) && (extents[i].startBlock != 0); ++i) { | |
1109 | iosize = extents[i].blockCount * blksize; | |
1110 | iosize = MIN(iosize, attrsize); | |
1111 | ||
1112 | uint64_t actualread = 0; | |
1113 | ||
1114 | result = raw_readwrite_read_internal( evp, extents[i].startBlock, extents[i].blockCount * blksize, | |
1115 | alreadyread, iosize, buf, &actualread ); | |
1116 | #if HFS_XATTR_VERBOSE | |
1117 | LFHFS_LOG(LEVEL_DEBUG, "hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n", | |
1118 | actualread, extents[i].startBlock, extents[i].blockCount, result); | |
1119 | #endif | |
1120 | if (result) | |
1121 | break; | |
1122 | ||
1123 | // read the remaining part after sector boundary if we have such | |
1124 | if (iosize != actualread) | |
1125 | { | |
1126 | result = raw_readwrite_read_internal( evp, extents[i].startBlock, extents[i].blockCount * blksize, | |
1127 | alreadyread + actualread, iosize - actualread, | |
1128 | (uint8_t*)buf + actualread, &actualread ); | |
1129 | #if HFS_XATTR_VERBOSE | |
1130 | LFHFS_LOG(LEVEL_DEBUG, "hfs: read_attr_data: cr iosize %lld [%d, %d] (%d)\n", | |
1131 | actualread, extents[i].startBlock, extents[i].blockCount, result); | |
1132 | #endif | |
1133 | if (result) | |
1134 | break; | |
1135 | } | |
1136 | ||
1137 | attrsize -= iosize; | |
1138 | ||
1139 | alreadyread += iosize; | |
1140 | buf = (uint8_t*)buf + iosize; | |
1141 | } | |
1142 | ||
1143 | hfs_unlock_truncate(VTOC(evp), HFS_LOCK_DEFAULT); | |
1144 | return (result); | |
1145 | } | |
1146 | ||
1147 | /* | |
1148 | * Write an extent based attribute. | |
1149 | */ | |
1150 | static int | |
1151 | write_attr_data(struct hfsmount *hfsmp, void *buf, size_t datasize, HFSPlusExtentDescriptor *extents) | |
1152 | { | |
1153 | vnode_t evp = hfsmp->hfs_attrdata_vp; | |
1154 | uint64_t iosize; | |
1155 | uint64_t attrsize; | |
1156 | uint64_t blksize; | |
1157 | uint64_t alreadywritten; | |
1158 | int i; | |
1159 | int result = 0; | |
1160 | ||
1161 | hfs_lock_truncate(VTOC(evp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); | |
1162 | ||
1163 | attrsize = (uint64_t)datasize; | |
1164 | blksize = (uint64_t)hfsmp->blockSize; | |
1165 | alreadywritten = 0; | |
1166 | ||
1167 | /* | |
1168 | * Write the attribute data one extent at a time. | |
1169 | */ | |
1170 | for (i = 0; (attrsize > 0) && (extents[i].startBlock != 0); ++i) { | |
1171 | iosize = extents[i].blockCount * blksize; | |
1172 | iosize = MIN(iosize, attrsize); | |
1173 | ||
1174 | uint64_t actualwritten = 0; | |
1175 | ||
1176 | result = raw_readwrite_write_internal( evp, extents[i].startBlock, extents[i].blockCount * blksize, | |
1177 | alreadywritten, iosize, buf, &actualwritten ); | |
1178 | #if HFS_XATTR_VERBOSE | |
1179 | LFHFS_LOG(LEVEL_DEBUG, "hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n", | |
1180 | actualwritten, extents[i].startBlock, extents[i].blockCount, result); | |
1181 | #endif | |
1182 | if (result) | |
1183 | break; | |
1184 | ||
1185 | // write the remaining part after sector boundary if we have such | |
1186 | if (iosize != actualwritten) | |
1187 | { | |
1188 | result = raw_readwrite_write_internal( evp, extents[i].startBlock, extents[i].blockCount * blksize, | |
1189 | alreadywritten + actualwritten, iosize - actualwritten, | |
1190 | (uint8_t*)buf + actualwritten, &actualwritten ); | |
1191 | #if HFS_XATTR_VERBOSE | |
1192 | LFHFS_LOG(LEVEL_DEBUG, "hfs: write_attr_data: cw iosize %lld [%d, %d] (%d)\n", | |
1193 | actualwritten, extents[i].startBlock, extents[i].blockCount, result); | |
1194 | #endif | |
1195 | if (result) | |
1196 | break; | |
1197 | } | |
1198 | ||
1199 | attrsize -= iosize; | |
1200 | ||
1201 | alreadywritten += iosize; | |
1202 | buf = (uint8_t*)buf + iosize; | |
1203 | } | |
1204 | ||
1205 | hfs_unlock_truncate(VTOC(evp), HFS_LOCK_DEFAULT); | |
1206 | return (result); | |
1207 | } | |
1208 | ||
1209 | /* | |
1210 | * Allocate blocks for an extent based attribute. | |
1211 | */ | |
1212 | static int | |
1213 | alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks) | |
1214 | { | |
1215 | int blkcnt; | |
1216 | int startblk; | |
1217 | int lockflags; | |
1218 | int i; | |
1219 | int maxextents; | |
1220 | int result = 0; | |
1221 | ||
1222 | startblk = hfsmp->hfs_metazone_end; | |
1223 | blkcnt = (int)howmany(attrsize, hfsmp->blockSize); | |
1224 | if (blkcnt > (int)hfs_freeblks(hfsmp, 0)) { | |
1225 | return (ENOSPC); | |
1226 | } | |
1227 | *blocks = blkcnt; | |
1228 | maxextents = (int)extentbufsize / sizeof(HFSPlusExtentDescriptor); | |
1229 | ||
1230 | lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK); | |
1231 | ||
1232 | for (i = 0; (blkcnt > 0) && (i < maxextents); i++) { | |
1233 | /* Try allocating and see if we find something decent */ | |
1234 | result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, 0, | |
1235 | &extents[i].startBlock, &extents[i].blockCount); | |
1236 | /* | |
1237 | * If we couldn't find anything, then re-try the allocation but allow | |
1238 | * journal flushes. | |
1239 | */ | |
1240 | if (result == dskFulErr) { | |
1241 | result = BlockAllocate(hfsmp, startblk, blkcnt, blkcnt, HFS_ALLOC_FLUSHTXN, | |
1242 | &extents[i].startBlock, &extents[i].blockCount); | |
1243 | } | |
1244 | #if HFS_XATTR_VERBOSE | |
1245 | LFHFS_LOG(LEVEL_DEBUG,"hfs: alloc_attr_blks: BA blkcnt %d [%d, %d] (%d)\n", | |
1246 | blkcnt, extents[i].startBlock, extents[i].blockCount, result); | |
1247 | #endif | |
1248 | if (result) { | |
1249 | extents[i].startBlock = 0; | |
1250 | extents[i].blockCount = 0; | |
1251 | break; | |
1252 | } | |
1253 | blkcnt -= extents[i].blockCount; | |
1254 | startblk = extents[i].startBlock + extents[i].blockCount; | |
1255 | } | |
1256 | /* | |
1257 | * If it didn't fit in the extents buffer then bail. | |
1258 | */ | |
1259 | if (blkcnt) { | |
1260 | result = ENOSPC; | |
1261 | #if HFS_XATTR_VERBOSE | |
1262 | LFHFS_LOG(LEVEL_DEBUG, "hfs: alloc_attr_blks: unexpected failure, %d blocks unallocated\n", blkcnt); | |
1263 | #endif | |
1264 | for (; i >= 0; i--) { | |
1265 | if ((blkcnt = extents[i].blockCount) != 0) { | |
1266 | (void) BlockDeallocate(hfsmp, extents[i].startBlock, blkcnt, 0); | |
1267 | extents[i].startBlock = 0; | |
1268 | extents[i].blockCount = 0; | |
1269 | } | |
1270 | } | |
1271 | } | |
1272 | ||
1273 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1274 | return MacToVFSError(result); | |
1275 | } | |
1276 | ||
1277 | /* | |
1278 | * Release blocks from an extent based attribute. | |
1279 | */ | |
1280 | static void | |
1281 | free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents) | |
1282 | { | |
1283 | vnode_t evp = hfsmp->hfs_attrdata_vp; | |
1284 | int remblks = blkcnt; | |
1285 | int lockflags; | |
1286 | int i; | |
1287 | ||
1288 | lockflags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK); | |
1289 | ||
1290 | for (i = 0; (remblks > 0) && (extents[i].blockCount != 0); i++) { | |
1291 | if (extents[i].blockCount > (u_int32_t)blkcnt) { | |
1292 | #if HFS_XATTR_VERBOSE | |
1293 | LFHFS_LOG(LEVEL_DEBUG, "hfs: free_attr_blks: skipping bad extent [%d, %d]\n", | |
1294 | extents[i].startBlock, extents[i].blockCount); | |
1295 | #endif | |
1296 | extents[i].blockCount = 0; | |
1297 | continue; | |
1298 | } | |
1299 | if (extents[i].startBlock == 0) { | |
1300 | break; | |
1301 | } | |
1302 | (void)BlockDeallocate(hfsmp, extents[i].startBlock, extents[i].blockCount, 0); | |
1303 | remblks -= extents[i].blockCount; | |
1304 | #if HFS_XATTR_VERBOSE | |
1305 | LFHFS_LOG(LEVEL_DEBUG, "hfs: free_attr_blks: BlockDeallocate [%d, %d]\n", | |
1306 | extents[i].startBlock, extents[i].blockCount); | |
1307 | #endif | |
1308 | extents[i].startBlock = 0; | |
1309 | extents[i].blockCount = 0; | |
1310 | ||
1311 | /* Discard any resident pages for this block range. */ | |
1312 | if (evp) { | |
1313 | #if LF_HFS_FULL_VNODE_SUPPORT | |
1314 | off_t start, end; | |
1315 | start = (u_int64_t)extents[i].startBlock * (u_int64_t)hfsmp->blockSize; | |
1316 | end = start + (u_int64_t)extents[i].blockCount * (u_int64_t)hfsmp->blockSize; | |
1317 | //TBD - Need to update this vnode | |
1318 | (void) ubc_msync(hfsmp->hfs_attrdata_vp, start, end, &start, UBC_INVALIDATE); | |
1319 | #endif | |
1320 | } | |
1321 | } | |
1322 | ||
1323 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1324 | } | |
1325 | ||
1326 | static int | |
1327 | has_overflow_extents(HFSPlusForkData *forkdata) | |
1328 | { | |
1329 | u_int32_t blocks; | |
1330 | ||
1331 | if (forkdata->extents[7].blockCount == 0) | |
1332 | return (0); | |
1333 | ||
1334 | blocks = forkdata->extents[0].blockCount + | |
1335 | forkdata->extents[1].blockCount + | |
1336 | forkdata->extents[2].blockCount + | |
1337 | forkdata->extents[3].blockCount + | |
1338 | forkdata->extents[4].blockCount + | |
1339 | forkdata->extents[5].blockCount + | |
1340 | forkdata->extents[6].blockCount + | |
1341 | forkdata->extents[7].blockCount; | |
1342 | ||
1343 | return (forkdata->totalBlocks > blocks); | |
1344 | } | |
1345 | ||
1346 | static int | |
1347 | count_extent_blocks(int maxblks, HFSPlusExtentRecord extents) | |
1348 | { | |
1349 | int blocks; | |
1350 | int i; | |
1351 | ||
1352 | for (i = 0, blocks = 0; i < kHFSPlusExtentDensity; ++i) { | |
1353 | /* Ignore obvious bogus extents. */ | |
1354 | if (extents[i].blockCount > (u_int32_t)maxblks) | |
1355 | continue; | |
1356 | if (extents[i].startBlock == 0 || extents[i].blockCount == 0) | |
1357 | break; | |
1358 | blocks += extents[i].blockCount; | |
1359 | } | |
1360 | return (blocks); | |
1361 | } | |
1362 | ||
1363 | /* | |
1364 | * Remove all the records for a given attribute. | |
1365 | * | |
1366 | * - Used by hfs_vnop_removexattr, hfs_vnop_setxattr and hfs_removeallattr. | |
1367 | * - A transaction must have been started. | |
1368 | * - The Attribute b-tree file must be locked exclusive. | |
1369 | * - The Allocation Bitmap file must be locked exclusive. | |
1370 | * - The iterator key must be initialized. | |
1371 | */ | |
1372 | static int | |
1373 | remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator) | |
1374 | { | |
1375 | struct filefork *btfile; | |
1376 | FSBufferDescriptor btdata; | |
1377 | HFSPlusAttrRecord attrdata; /* 90 bytes */ | |
1378 | u_int16_t datasize; | |
1379 | int result; | |
1380 | ||
1381 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
1382 | ||
1383 | btdata.bufferAddress = &attrdata; | |
1384 | btdata.itemSize = sizeof(attrdata); | |
1385 | btdata.itemCount = 1; | |
1386 | result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); | |
1387 | if (result) { | |
1388 | goto exit; /* no records. */ | |
1389 | } | |
1390 | /* | |
1391 | * Free the blocks from extent based attributes. | |
1392 | * | |
1393 | * Note that the block references (btree records) are removed | |
1394 | * before releasing the blocks in the allocation bitmap. | |
1395 | */ | |
1396 | if (attrdata.recordType == kHFSPlusAttrForkData) { | |
1397 | int totalblks; | |
1398 | int extentblks; | |
1399 | u_int32_t *keystartblk; | |
1400 | ||
1401 | if (datasize < sizeof(HFSPlusAttrForkData)) { | |
1402 | LFHFS_LOG(LEVEL_DEBUG, "remove_attribute_records: bad record size %d (expecting %lu)\n", datasize, sizeof(HFSPlusAttrForkData)); | |
1403 | } | |
1404 | totalblks = attrdata.forkData.theFork.totalBlocks; | |
1405 | ||
1406 | /* Process the first 8 extents. */ | |
1407 | extentblks = count_extent_blocks(totalblks, attrdata.forkData.theFork.extents); | |
1408 | if (extentblks > totalblks) | |
1409 | { | |
1410 | LFHFS_LOG(LEVEL_ERROR, "remove_attribute_records: corruption (1)..."); | |
1411 | hfs_assert(0); | |
1412 | } | |
1413 | if (BTDeleteRecord(btfile, iterator) == 0) { | |
1414 | free_attr_blks(hfsmp, extentblks, attrdata.forkData.theFork.extents); | |
1415 | } | |
1416 | totalblks -= extentblks; | |
1417 | keystartblk = &((HFSPlusAttrKey *)&iterator->key)->startBlock; | |
1418 | ||
1419 | /* Process any overflow extents. */ | |
1420 | while (totalblks) { | |
1421 | *keystartblk += (u_int32_t)extentblks; | |
1422 | ||
1423 | result = BTSearchRecord(btfile, iterator, &btdata, &datasize, NULL); | |
1424 | if (result || | |
1425 | (attrdata.recordType != kHFSPlusAttrExtents) || | |
1426 | (datasize < sizeof(HFSPlusAttrExtents))) { | |
1427 | LFHFS_LOG(LEVEL_ERROR, "remove_attribute_records: BTSearchRecord: vol=%s, err=%d (%d), totalblks %d\n", | |
1428 | hfsmp->vcbVN, MacToVFSError(result), attrdata.recordType != kHFSPlusAttrExtents, totalblks); | |
1429 | result = ENOATTR; | |
1430 | break; /* break from while */ | |
1431 | } | |
1432 | /* Process the next 8 extents. */ | |
1433 | extentblks = count_extent_blocks(totalblks, attrdata.overflowExtents.extents); | |
1434 | if (extentblks > totalblks) | |
1435 | { | |
1436 | LFHFS_LOG(LEVEL_ERROR, "remove_attribute_records: corruption (2)..."); | |
1437 | hfs_assert(0); | |
1438 | } | |
1439 | if (BTDeleteRecord(btfile, iterator) == 0) { | |
1440 | free_attr_blks(hfsmp, extentblks, attrdata.overflowExtents.extents); | |
1441 | } | |
1442 | totalblks -= extentblks; | |
1443 | } | |
1444 | } else { | |
1445 | result = BTDeleteRecord(btfile, iterator); | |
1446 | } | |
1447 | (void) BTFlushPath(btfile); | |
1448 | exit: | |
1449 | return (result == btNotFound ? ENOATTR : MacToVFSError(result)); | |
1450 | } | |
1451 | ||
1452 | /* | |
1453 | * Retrieve the list of extended attribute names. | |
1454 | */ | |
1455 | int | |
1456 | hfs_vnop_listxattr(vnode_t vp, void *buf, size_t bufsize, size_t *actual_size) | |
1457 | { | |
1458 | struct cnode *cp = VTOC(vp); | |
1459 | struct hfsmount *hfsmp; | |
1460 | BTreeIterator * iterator = NULL; | |
1461 | struct filefork *btfile; | |
1462 | struct listattr_callback_state state; | |
1463 | int lockflags; | |
1464 | int result; | |
1465 | u_int8_t finderinfo[32]; | |
1466 | ||
1467 | if (actual_size == NULL) { | |
1468 | return (EINVAL); | |
1469 | } | |
1470 | if (VNODE_IS_RSRC(vp)) { | |
1471 | return (EPERM); | |
1472 | } | |
1473 | ||
1474 | hfsmp = VTOHFS(vp); | |
1475 | *actual_size = 0; | |
1476 | ||
1477 | /* | |
1478 | * Take the truncate lock; this serializes us against the ioctl | |
1479 | * to truncate data & reset the decmpfs state | |
1480 | * in the compressed file handler. | |
1481 | */ | |
1482 | hfs_lock_truncate(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); | |
1483 | ||
1484 | /* Now the regular cnode lock (shared) */ | |
1485 | if ((result = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { | |
1486 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); | |
1487 | return (result); | |
1488 | } | |
1489 | ||
1490 | /* | |
1491 | * Make a copy of the cnode's finderinfo to a local so we can | |
1492 | * zero out the date added field. Also zero out the private type/creator | |
1493 | * for symlinks. | |
1494 | */ | |
1495 | bcopy(cp->c_finderinfo, finderinfo, sizeof(finderinfo)); | |
1496 | hfs_zero_hidden_fields (cp, finderinfo); | |
1497 | ||
1498 | /* Don't expose a symlink's private type/creator. */ | |
1499 | if (vnode_islnk(vp)) { | |
1500 | struct FndrFileInfo *fip; | |
1501 | ||
1502 | fip = (struct FndrFileInfo *)&finderinfo; | |
1503 | fip->fdType = 0; | |
1504 | fip->fdCreator = 0; | |
1505 | } | |
1506 | ||
1507 | ||
1508 | /* If Finder Info is non-empty then export it's name. */ | |
1509 | if (bcmp(finderinfo, emptyfinfo, sizeof(emptyfinfo)) != 0) { | |
1510 | if (buf == NULL) { | |
1511 | *actual_size += sizeof(XATTR_FINDERINFO_NAME); | |
1512 | } else if (bufsize < sizeof(XATTR_FINDERINFO_NAME)) { | |
1513 | result = ERANGE; | |
1514 | goto exit; | |
1515 | } else { | |
1516 | *actual_size += sizeof(XATTR_FINDERINFO_NAME); | |
1517 | strcpy((char*)buf, XATTR_FINDERINFO_NAME); | |
1518 | } | |
1519 | } | |
1520 | ||
1521 | /* Bail if we don't have any extended attributes. */ | |
1522 | if ((hfsmp->hfs_attribute_vp == NULL) || | |
1523 | (cp->c_attr.ca_recflags & kHFSHasAttributesMask) == 0) { | |
1524 | result = 0; | |
1525 | goto exit; | |
1526 | } | |
1527 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
1528 | ||
1529 | iterator = hfs_mallocz(sizeof(*iterator)); | |
1530 | ||
1531 | result = hfs_buildattrkey(cp->c_fileid, NULL, (HFSPlusAttrKey *)&iterator->key); | |
1532 | if (result) { | |
1533 | goto exit; | |
1534 | } | |
1535 | ||
1536 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
1537 | ||
1538 | result = BTSearchRecord(btfile, iterator, NULL, NULL, NULL); | |
1539 | if (result && result != btNotFound) { | |
1540 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1541 | goto exit; | |
1542 | } | |
1543 | ||
1544 | state.fileID = cp->c_fileid; | |
1545 | state.result = 0; | |
1546 | state.buf = (buf == NULL ? NULL : ((u_int8_t*)buf + *actual_size)); | |
1547 | state.bufsize = bufsize - *actual_size; | |
1548 | state.size = 0; | |
1549 | ||
1550 | /* | |
1551 | * Process entries starting just after iterator->key. | |
1552 | */ | |
1553 | result = BTIterateRecords(btfile, kBTreeNextRecord, iterator, | |
1554 | (IterateCallBackProcPtr)listattr_callback, &state); | |
1555 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1556 | ||
1557 | *actual_size += state.size; | |
1558 | ||
1559 | if (state.result || result == btNotFound) { | |
1560 | result = state.result; | |
1561 | } | |
1562 | ||
1563 | exit: | |
1564 | hfs_free(iterator); | |
1565 | hfs_unlock(cp); | |
1566 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); | |
1567 | ||
1568 | return MacToVFSError(result); | |
1569 | } | |
1570 | ||
1571 | /* | |
1572 | * Callback - called for each attribute record | |
1573 | */ | |
1574 | static int | |
1575 | listattr_callback(const HFSPlusAttrKey *key, __unused const HFSPlusAttrData *data, struct listattr_callback_state *state) | |
1576 | { | |
1577 | char attrname[XATTR_MAXNAMELEN + 1]; | |
1578 | ssize_t bytecount; | |
1579 | int result; | |
1580 | ||
1581 | if (state->fileID != key->fileID) { | |
1582 | state->result = 0; | |
1583 | return (0); /* stop */ | |
1584 | } | |
1585 | /* | |
1586 | * Skip over non-primary keys | |
1587 | */ | |
1588 | if (key->startBlock != 0) { | |
1589 | return (1); /* continue */ | |
1590 | } | |
1591 | ||
1592 | /* Convert the attribute name into UTF-8. */ | |
1593 | result = utf8_encodestr(key->attrName, key->attrNameLen * sizeof(UniChar), | |
1594 | (u_int8_t *)attrname, (size_t *)&bytecount, sizeof(attrname), '/', UTF_ADD_NULL_TERM); | |
1595 | if (result) { | |
1596 | state->result = result; | |
1597 | return (0); /* stop */ | |
1598 | } | |
1599 | bytecount++; /* account for null termination char */ | |
1600 | ||
1601 | state->size += bytecount; | |
1602 | ||
1603 | if (state->buf != NULL) { | |
1604 | if ((size_t)bytecount > state->bufsize) { | |
1605 | state->result = ERANGE; | |
1606 | return (0); /* stop */ | |
1607 | } | |
1608 | ||
1609 | memcpy(state->buf, attrname, bytecount); | |
1610 | ||
1611 | state->buf = (state->buf == NULL ? NULL : ((u_int8_t*)state->buf + bytecount)); | |
1612 | state->bufsize -= bytecount; | |
1613 | } | |
1614 | return (1); /* continue */ | |
1615 | } | |
1616 | ||
1617 | /* | |
1618 | * Remove all the attributes from a cnode. | |
1619 | * | |
1620 | * This function creates/ends its own transaction so that each | |
1621 | * attribute is deleted in its own transaction (to avoid having | |
1622 | * a transaction grow too large). | |
1623 | * | |
1624 | * This function takes the necessary locks on the attribute | |
1625 | * b-tree file and the allocation (bitmap) file. | |
1626 | * | |
1627 | * NOTE: Upon sucecss, this function will return with an open | |
1628 | * transaction. The reason we do it this way is because when we | |
1629 | * delete the last attribute, we must make sure the flag in the | |
1630 | * catalog record that indicates there are no more records is cleared. | |
1631 | * The caller is responsible for doing this and *must* do it before | |
1632 | * ending the transaction. | |
1633 | */ | |
1634 | int | |
1635 | hfs_removeallattr(struct hfsmount *hfsmp, u_int32_t fileid, bool *open_transaction) | |
1636 | { | |
1637 | BTreeIterator *iterator = NULL; | |
1638 | HFSPlusAttrKey *key; | |
1639 | struct filefork *btfile; | |
1640 | int result, lockflags = 0; | |
1641 | ||
1642 | *open_transaction = false; | |
1643 | ||
1644 | if (hfsmp->hfs_attribute_vp == NULL) | |
1645 | return 0; | |
1646 | ||
1647 | btfile = VTOF(hfsmp->hfs_attribute_vp); | |
1648 | ||
1649 | iterator = hfs_mallocz(sizeof(BTreeIterator)); | |
1650 | if (iterator == NULL) | |
1651 | return ENOMEM; | |
1652 | ||
1653 | key = (HFSPlusAttrKey *)&iterator->key; | |
1654 | ||
1655 | /* Loop until there are no more attributes for this file id */ | |
1656 | do { | |
1657 | if (!*open_transaction) | |
1658 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); | |
1659 | ||
1660 | (void) hfs_buildattrkey(fileid, NULL, key); | |
1661 | result = BTIterateRecord(btfile, kBTreeNextRecord, iterator, NULL, NULL); | |
1662 | if (result || key->fileID != fileid) | |
1663 | goto exit; | |
1664 | ||
1665 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1666 | lockflags = 0; | |
1667 | ||
1668 | if (*open_transaction) { | |
1669 | hfs_end_transaction(hfsmp); | |
1670 | *open_transaction = false; | |
1671 | } | |
1672 | ||
1673 | if (hfs_start_transaction(hfsmp) != 0) { | |
1674 | result = EINVAL; | |
1675 | goto exit; | |
1676 | } | |
1677 | ||
1678 | *open_transaction = true; | |
1679 | ||
1680 | lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK); | |
1681 | ||
1682 | result = remove_attribute_records(hfsmp, iterator); | |
1683 | ||
1684 | } while (!result); | |
1685 | ||
1686 | exit: | |
1687 | hfs_free(iterator); | |
1688 | ||
1689 | if (lockflags) | |
1690 | hfs_systemfile_unlock(hfsmp, lockflags); | |
1691 | ||
1692 | result = result == btNotFound ? 0 : MacToVFSError(result); | |
1693 | ||
1694 | if (result && *open_transaction) { | |
1695 | hfs_end_transaction(hfsmp); | |
1696 | *open_transaction = false; | |
1697 | } | |
1698 | ||
1699 | return result; | |
1700 | } | |
1701 | ||
1702 | /* | |
1703 | * hfs_attrkeycompare - compare two attribute b-tree keys. | |
1704 | * | |
1705 | * The name portion of the key is compared using a 16-bit binary comparison. | |
1706 | * This is called from the b-tree code. | |
1707 | */ | |
1708 | int | |
1709 | hfs_attrkeycompare(HFSPlusAttrKey *searchKey, HFSPlusAttrKey *trialKey) | |
1710 | { | |
1711 | u_int32_t searchFileID, trialFileID; | |
1712 | int result; | |
1713 | ||
1714 | searchFileID = searchKey->fileID; | |
1715 | trialFileID = trialKey->fileID; | |
1716 | result = 0; | |
1717 | ||
1718 | if (searchFileID > trialFileID) { | |
1719 | ++result; | |
1720 | } else if (searchFileID < trialFileID) { | |
1721 | --result; | |
1722 | } else { | |
1723 | u_int16_t * str1 = &searchKey->attrName[0]; | |
1724 | u_int16_t * str2 = &trialKey->attrName[0]; | |
1725 | int length1 = searchKey->attrNameLen; | |
1726 | int length2 = trialKey->attrNameLen; | |
1727 | u_int16_t c1, c2; | |
1728 | int length; | |
1729 | ||
1730 | if (length1 < length2) { | |
1731 | length = length1; | |
1732 | --result; | |
1733 | } else if (length1 > length2) { | |
1734 | length = length2; | |
1735 | ++result; | |
1736 | } else { | |
1737 | length = length1; | |
1738 | } | |
1739 | ||
1740 | while (length--) { | |
1741 | c1 = *(str1++); | |
1742 | c2 = *(str2++); | |
1743 | ||
1744 | if (c1 > c2) { | |
1745 | result = 1; | |
1746 | break; | |
1747 | } | |
1748 | if (c1 < c2) { | |
1749 | result = -1; | |
1750 | break; | |
1751 | } | |
1752 | } | |
1753 | if (result) | |
1754 | return (result); | |
1755 | /* | |
1756 | * Names are equal; compare startBlock | |
1757 | */ | |
1758 | if (searchKey->startBlock == trialKey->startBlock) { | |
1759 | return (0); | |
1760 | } else { | |
1761 | return (searchKey->startBlock < trialKey->startBlock ? -1 : 1); | |
1762 | } | |
1763 | } | |
1764 | ||
1765 | return result; | |
1766 | } | |
1767 | ||
1768 | /* | |
1769 | * hfs_buildattrkey - build an Attribute b-tree key | |
1770 | */ | |
1771 | int | |
1772 | hfs_buildattrkey(u_int32_t fileID, const char *attrname, HFSPlusAttrKey *key) | |
1773 | { | |
1774 | int result = 0; | |
1775 | size_t unicodeBytes = 0; | |
1776 | ||
1777 | if (attrname != NULL) { | |
1778 | /* | |
1779 | * Convert filename from UTF-8 into Unicode | |
1780 | */ | |
1781 | result = utf8_decodestr((const u_int8_t *)attrname, strlen(attrname), key->attrName, | |
1782 | &unicodeBytes, sizeof(key->attrName), 0, 0); | |
1783 | if (result) { | |
1784 | if (result != ENAMETOOLONG) | |
1785 | result = EINVAL; /* name has invalid characters */ | |
1786 | return (result); | |
1787 | } | |
1788 | key->attrNameLen = unicodeBytes / sizeof(UniChar); | |
1789 | key->keyLength = kHFSPlusAttrKeyMinimumLength + unicodeBytes; | |
1790 | } else { | |
1791 | key->attrNameLen = 0; | |
1792 | key->keyLength = kHFSPlusAttrKeyMinimumLength; | |
1793 | } | |
1794 | key->pad = 0; | |
1795 | key->fileID = fileID; | |
1796 | key->startBlock = 0; | |
1797 | ||
1798 | return (0); | |
1799 | } | |
1800 | ||
1801 | /* | |
1802 | * getnodecount - calculate starting node count for attributes b-tree. | |
1803 | */ | |
1804 | static int | |
1805 | getnodecount(struct hfsmount *hfsmp, size_t nodesize) | |
1806 | { | |
1807 | u_int64_t freebytes; | |
1808 | u_int64_t calcbytes; | |
1809 | ||
1810 | /* | |
1811 | * 10.4: Scale base on current catalog file size (20 %) up to 20 MB. | |
1812 | * 10.5: Attempt to be as big as the catalog clump size. | |
1813 | * | |
1814 | * Use no more than 10 % of the remaining free space. | |
1815 | */ | |
1816 | freebytes = (u_int64_t)hfs_freeblks(hfsmp, 0) * (u_int64_t)hfsmp->blockSize; | |
1817 | ||
1818 | calcbytes = MIN(hfsmp->hfs_catalog_cp->c_datafork->ff_size / 5, 20 * 1024 * 1024); | |
1819 | ||
1820 | calcbytes = MAX(calcbytes, hfsmp->hfs_catalog_cp->c_datafork->ff_clumpsize); | |
1821 | ||
1822 | calcbytes = MIN(calcbytes, freebytes / 10); | |
1823 | ||
1824 | return (MAX(2, (int)(calcbytes / nodesize))); | |
1825 | } | |
1826 | ||
1827 | /* | |
1828 | * getmaxinlineattrsize - calculate maximum inline attribute size. | |
1829 | * | |
1830 | * This yields 3,802 bytes for an 8K node size. | |
1831 | */ | |
1832 | static size_t | |
1833 | getmaxinlineattrsize(struct vnode * attrvp) | |
1834 | { | |
1835 | BTreeInfoRec btinfo; | |
1836 | size_t nodesize = ATTRIBUTE_FILE_NODE_SIZE; | |
1837 | size_t maxsize; | |
1838 | ||
1839 | if (attrvp != NULL) { | |
1840 | (void) hfs_lock(VTOC(attrvp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); | |
1841 | if (BTGetInformation(VTOF(attrvp), 0, &btinfo) == 0) | |
1842 | nodesize = btinfo.nodeSize; | |
1843 | hfs_unlock(VTOC(attrvp)); | |
1844 | } | |
1845 | maxsize = nodesize; | |
1846 | maxsize -= sizeof(BTNodeDescriptor); /* minus node descriptor */ | |
1847 | maxsize -= 3 * sizeof(u_int16_t); /* minus 3 index slots */ | |
1848 | maxsize /= 2; /* 2 key/rec pairs minumum */ | |
1849 | maxsize -= sizeof(HFSPlusAttrKey); /* minus maximum key size */ | |
1850 | maxsize -= sizeof(HFSPlusAttrData) - 2; /* minus data header */ | |
1851 | maxsize &= 0xFFFFFFFE; /* multiple of 2 bytes */ | |
1852 | ||
1853 | return (maxsize); | |
1854 | } |