]> git.saurik.com Git - apple/hfs.git/blame - livefiles_hfs_plugin/lf_hfs_xattr.c
hfs-522.100.5.tar.gz
[apple/hfs.git] / livefiles_hfs_plugin / lf_hfs_xattr.c
CommitLineData
de8ee011
A
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. */
29struct listattr_callback_state {
30 u_int32_t fileID;
31 int result;
32 void *buf;
33 size_t bufsize;
34 size_t size;
35};
36
37static u_int32_t emptyfinfo[8] = {0};
38
39
40static int hfs_zero_hidden_fields (struct cnode *cp, u_int8_t *finderinfo);
41
42const char hfs_attrdatafilename[] = "Attribute Data";
43
44static int listattr_callback(const HFSPlusAttrKey *key, const HFSPlusAttrData *data,
45 struct listattr_callback_state *state);
46
47static int remove_attribute_records(struct hfsmount *hfsmp, BTreeIterator * iterator);
48
49static int getnodecount(struct hfsmount *hfsmp, size_t nodesize);
50
51static size_t getmaxinlineattrsize(struct vnode * attrvp);
52
53static int read_attr_data(struct hfsmount *hfsmp, void * buf, size_t datasize, HFSPlusExtentDescriptor *extents);
54
55static int write_attr_data(struct hfsmount *hfsmp, void * buf, size_t datasize, HFSPlusExtentDescriptor *extents);
56
57static int alloc_attr_blks(struct hfsmount *hfsmp, size_t attrsize, size_t extentbufsize, HFSPlusExtentDescriptor *extents, int *blocks);
58
59static void free_attr_blks(struct hfsmount *hfsmp, int blkcnt, HFSPlusExtentDescriptor *extents);
60
61static int has_overflow_extents(HFSPlusForkData *forkdata);
62
63static int count_extent_blocks(int maxblks, HFSPlusExtentRecord extents);
64
65
66/* Zero out the date added field for the specified cnode */
67static 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 */
94int
95hfs_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
368exit:
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 */
379int
380hfs_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
753exit_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
784exit:
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 */
795int
796hfs_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);
952exit:
953 hfs_unlock(cp);
954exit_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 */
974int 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 */
1039int
1040file_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
1079out:
1080 hfs_free(iterator);
1081 return result;
1082}
1083
1084/*
1085 * Read an extent based attribute.
1086 */
1087static int
1088read_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 */
1150static int
1151write_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 */
1212static int
1213alloc_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 */
1280static void
1281free_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
1326static int
1327has_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
1346static int
1347count_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 */
1372static int
1373remove_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);
1448exit:
1449 return (result == btNotFound ? ENOATTR : MacToVFSError(result));
1450}
1451
1452/*
1453 * Retrieve the list of extended attribute names.
1454 */
1455int
1456hfs_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
1563exit:
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 */
1574static int
1575listattr_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 */
1634int
1635hfs_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
1686exit:
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 */
1708int
1709hfs_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 */
1771int
1772hfs_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 */
1804static int
1805getnodecount(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 */
1832static size_t
1833getmaxinlineattrsize(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}