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