]> git.saurik.com Git - apple/xnu.git/blame - bsd/kern/decmpfs.c
xnu-3248.60.10.tar.gz
[apple/xnu.git] / bsd / kern / decmpfs.c
CommitLineData
b0d623f7 1/*
3e170ce0 2 * Copyright (c) 2008-2015 Apple Inc. All rights reserved.
b0d623f7
A
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28#if !HFS_COMPRESSION
29/* we need these symbols even though compression is turned off */
30char register_decmpfs_decompressor;
31char unregister_decmpfs_decompressor;
32#else /* HFS_COMPRESSION */
33#include <sys/kernel.h>
34#include <sys/vnode_internal.h>
35#include <sys/file_internal.h>
36#include <sys/stat.h>
37#include <sys/fcntl.h>
38#include <sys/xattr.h>
39#include <sys/namei.h>
40#include <sys/user.h>
41#include <sys/mount_internal.h>
42#include <sys/ubc.h>
43#include <sys/decmpfs.h>
44#include <sys/uio_internal.h>
45#include <libkern/OSByteOrder.h>
46
47#pragma mark --- debugging ---
48
49#define COMPRESSION_DEBUG 0
50#define COMPRESSION_DEBUG_VERBOSE 0
51#define MALLOC_DEBUG 0
52
53static const char *
54baseName(const char *path)
55{
56 if (!path)
57 return NULL;
58 const char *ret = path;
59 int i;
60 for (i = 0; path[i] != 0; i++) {
61 if (path[i] == '/')
62 ret = &path[i + 1];
63 }
64 return ret;
65}
66
3e170ce0
A
67static char*
68vnpath(vnode_t vp, char *path, int len)
69{
70 int origlen = len;
71 path[0] = 0;
72 vn_getpath(vp, path, &len);
73 path[origlen - 1] = 0;
74 return path;
75}
76
b0d623f7 77#define ErrorLog(x, args...) printf("%s:%d:%s: " x, baseName(__FILE__), __LINE__, __FUNCTION__, ## args)
3e170ce0 78#define ErrorLogWithPath(x, args...) do { char *path; MALLOC(path, char *, PATH_MAX, M_TEMP, M_WAITOK); printf("%s:%d:%s: %s: " x, baseName(__FILE__), __LINE__, __FUNCTION__, vnpath(vp, path, PATH_MAX), ## args); FREE(path, M_TEMP); } while(0)
b0d623f7
A
79
80#if COMPRESSION_DEBUG
81#define DebugLog ErrorLog
3e170ce0 82#define DebugLogWithPath ErrorLogWithPath
b0d623f7
A
83#else
84#define DebugLog(x...) do { } while(0)
3e170ce0 85#define DebugLogWithPath(x...) do { } while(0)
b0d623f7
A
86#endif
87
88#if COMPRESSION_DEBUG_VERBOSE
89#define VerboseLog ErrorLog
3e170ce0 90#define VerboseLogWithPath ErrorLogWithPath
b0d623f7
A
91#else
92#define VerboseLog(x...) do { } while(0)
3e170ce0 93#define VerboseLogWithPath(x...) do { } while(0)
b0d623f7
A
94#endif
95
96#if MALLOC_DEBUG
97
98static SInt32 totalAlloc;
99
100typedef struct {
101 uint32_t allocSz;
102 uint32_t magic;
103 const char *file;
104 int line;
105} allocated;
106
107static void *
108_malloc(uint32_t sz, __unused int type, __unused int flags, const char *file, int line)
109{
110 uint32_t allocSz = sz + 2 * sizeof(allocated);
111
112 allocated *alloc = NULL;
113 MALLOC(alloc, allocated *, allocSz, type, flags);
114 if (!alloc) {
115 ErrorLog("malloc failed\n");
116 return NULL;
117 }
118
119 char *ret = (char*)&alloc[1];
120 allocated *alloc2 = (allocated*)(ret + sz);
121
122 alloc->allocSz = allocSz;
123 alloc->magic = 0xdadadada;
124 alloc->file = file;
125 alloc->line = line;
126
127 *alloc2 = *alloc;
128
129 int s = OSAddAtomic(sz, &totalAlloc);
130 ErrorLog("malloc(%d) -> %p, total allocations %d\n", sz, ret, s + sz);
131
132 return ret;
133}
134
135static void
136_free(char *ret, __unused int type, const char *file, int line)
137{
138 if (!ret) {
139 ErrorLog("freeing null\n");
140 return;
141 }
142 allocated *alloc = (allocated*)ret;
143 alloc--;
144 uint32_t sz = alloc->allocSz - 2 * sizeof(allocated);
145 allocated *alloc2 = (allocated*)(ret + sz);
146
147 if (alloc->magic != 0xdadadada) {
148 panic("freeing bad pointer");
149 }
150
151 if (memcmp(alloc, alloc2, sizeof(*alloc)) != 0) {
152 panic("clobbered data");
153 }
154
155 memset(ret, 0xce, sz);
156 alloc2->file = file;
157 alloc2->line = line;
158 FREE(alloc, type);
159 int s = OSAddAtomic(-sz, &totalAlloc);
160 ErrorLog("free(%p,%d) -> total allocations %d\n", ret, sz, s - sz);
161}
162
163#undef MALLOC
164#undef FREE
165#define MALLOC(space, cast, size, type, flags) (space) = (cast)_malloc(size, type, flags, __FILE__, __LINE__)
166#define FREE(addr, type) _free((void *)addr, type, __FILE__, __LINE__)
167
168#endif /* MALLOC_DEBUG */
169
170#pragma mark --- globals ---
171
172static lck_grp_t *decmpfs_lockgrp;
173
174static decmpfs_registration * decompressors[CMP_MAX]; /* the registered compressors */
175static lck_rw_t * decompressorsLock;
176static int decompress_channel; /* channel used by decompress_file to wake up waiters */
177static lck_mtx_t *decompress_channel_mtx;
178
179vfs_context_t decmpfs_ctx;
180
181#pragma mark --- decmp_get_func ---
182
183#define offsetof_func(func) ((uintptr_t)(&(((decmpfs_registration*)NULL)->func)))
184
185static void *
316670eb 186_func_from_offset(uint32_t type, uintptr_t offset)
b0d623f7
A
187{
188 /* get the function at the given offset in the registration for the given type */
189 decmpfs_registration *reg = decompressors[type];
190 char *regChar = (char*)reg;
191 char *func = &regChar[offset];
192 void **funcPtr = (void**)func;
316670eb
A
193
194 switch (reg->decmpfs_registration) {
195 case DECMPFS_REGISTRATION_VERSION_V1:
196 if (offset > offsetof_func(free_data))
197 return NULL;
198 break;
199 case DECMPFS_REGISTRATION_VERSION_V3:
200 if (offset > offsetof_func(get_flags))
201 return NULL;
202 break;
203 default:
204 return NULL;
205 }
206
b0d623f7
A
207 return funcPtr[0];
208}
209
d1ecb069
A
210extern void IOServicePublishResource( const char * property, boolean_t value );
211extern boolean_t IOServiceWaitForMatchingResource( const char * property, uint64_t timeout );
212extern boolean_t IOCatalogueMatchingDriversPresent( const char * property );
213
b0d623f7 214static void *
3e170ce0 215_decmp_get_func(vnode_t vp, uint32_t type, uintptr_t offset)
b0d623f7
A
216{
217 /*
218 this function should be called while holding a shared lock to decompressorsLock,
219 and will return with the lock held
220 */
221
222 if (type >= CMP_MAX)
223 return NULL;
224
225 if (decompressors[type] != NULL) {
226 // the compressor has already registered but the function might be null
227 return _func_from_offset(type, offset);
228 }
229
d1ecb069
A
230 // does IOKit know about a kext that is supposed to provide this type?
231 char providesName[80];
232 snprintf(providesName, sizeof(providesName), "com.apple.AppleFSCompression.providesType%u", type);
233 if (IOCatalogueMatchingDriversPresent(providesName)) {
234 // there is a kext that says it will register for this type, so let's wait for it
235 char resourceName[80];
6d2010ae 236 uint64_t delay = 10000000ULL; // 10 milliseconds.
d1ecb069 237 snprintf(resourceName, sizeof(resourceName), "com.apple.AppleFSCompression.Type%u", type);
3e170ce0 238 ErrorLogWithPath("waiting for %s\n", resourceName);
d1ecb069 239 while(decompressors[type] == NULL) {
316670eb 240 lck_rw_unlock_shared(decompressorsLock); // we have to unlock to allow the kext to register
6d2010ae 241 if (IOServiceWaitForMatchingResource(resourceName, delay)) {
316670eb 242 lck_rw_lock_shared(decompressorsLock);
d1ecb069
A
243 break;
244 }
245 if (!IOCatalogueMatchingDriversPresent(providesName)) {
246 //
3e170ce0 247 ErrorLogWithPath("the kext with %s is no longer present\n", providesName);
316670eb 248 lck_rw_lock_shared(decompressorsLock);
d1ecb069
A
249 break;
250 }
3e170ce0 251 ErrorLogWithPath("still waiting for %s\n", resourceName);
6d2010ae 252 delay *= 2;
d1ecb069
A
253 lck_rw_lock_shared(decompressorsLock);
254 }
255 // IOKit says the kext is loaded, so it should be registered too!
256 if (decompressors[type] == NULL) {
3e170ce0 257 ErrorLogWithPath("we found %s, but the type still isn't registered\n", providesName);
d1ecb069
A
258 return NULL;
259 }
260 // it's now registered, so let's return the function
261 return _func_from_offset(type, offset);
262 }
263
b0d623f7 264 // the compressor hasn't registered, so it never will unless someone manually kextloads it
3e170ce0 265 ErrorLogWithPath("tried to access a compressed file of unregistered type %d\n", type);
b0d623f7
A
266 return NULL;
267}
268
3e170ce0 269#define decmp_get_func(vp, type, func) ((typeof(((decmpfs_registration*)NULL)->func))_decmp_get_func(vp, type, offsetof_func(func)))
b0d623f7
A
270
271#pragma mark --- utilities ---
272
273#if COMPRESSION_DEBUG
b0d623f7
A
274static int
275vnsize(vnode_t vp, uint64_t *size)
276{
277 struct vnode_attr va;
278 VATTR_INIT(&va);
279 VATTR_WANTED(&va, va_data_size);
280 int error = vnode_getattr(vp, &va, decmpfs_ctx);
281 if (error != 0) {
3e170ce0 282 ErrorLogWithPath("vnode_getattr err %d\n", error);
b0d623f7
A
283 return error;
284 }
285 *size = va.va_data_size;
286 return 0;
287}
288#endif /* COMPRESSION_DEBUG */
289
290#pragma mark --- cnode routines ---
291
292void
293decmpfs_cnode_init(decmpfs_cnode *cp)
294{
295 memset(cp, 0, sizeof(*cp));
296 lck_rw_init(&cp->compressed_data_lock, decmpfs_lockgrp, NULL);
b0d623f7
A
297}
298
299void
300decmpfs_cnode_destroy(decmpfs_cnode *cp)
301{
302 lck_rw_destroy(&cp->compressed_data_lock, decmpfs_lockgrp);
b0d623f7
A
303}
304
305boolean_t
306decmpfs_trylock_compressed_data(decmpfs_cnode *cp, int exclusive)
307{
308 void *thread = current_thread();
309 boolean_t retval = FALSE;
310
311 if (cp->lockowner == thread) {
312 /* this thread is already holding an exclusive lock, so bump the count */
313 cp->lockcount++;
314 retval = TRUE;
315 } else if (exclusive) {
316 if ((retval = lck_rw_try_lock_exclusive(&cp->compressed_data_lock))) {
317 cp->lockowner = thread;
318 cp->lockcount = 1;
319 }
320 } else {
321 if ((retval = lck_rw_try_lock_shared(&cp->compressed_data_lock))) {
322 cp->lockowner = (void *)-1;
323 }
324 }
325 return retval;
326}
327
328void
329decmpfs_lock_compressed_data(decmpfs_cnode *cp, int exclusive)
330{
331 void *thread = current_thread();
332
333 if (cp->lockowner == thread) {
334 /* this thread is already holding an exclusive lock, so bump the count */
335 cp->lockcount++;
336 } else if (exclusive) {
337 lck_rw_lock_exclusive(&cp->compressed_data_lock);
338 cp->lockowner = thread;
339 cp->lockcount = 1;
340 } else {
341 lck_rw_lock_shared(&cp->compressed_data_lock);
342 cp->lockowner = (void *)-1;
343 }
344}
345
346void
347decmpfs_unlock_compressed_data(decmpfs_cnode *cp, __unused int exclusive)
348{
349 void *thread = current_thread();
350
351 if (cp->lockowner == thread) {
352 /* this thread is holding an exclusive lock, so decrement the count */
353 if ((--cp->lockcount) > 0) {
354 /* the caller still has outstanding locks, so we're done */
355 return;
356 }
357 cp->lockowner = NULL;
358 }
359
360 lck_rw_done(&cp->compressed_data_lock);
361}
362
363uint32_t
364decmpfs_cnode_get_vnode_state(decmpfs_cnode *cp)
365{
366 return cp->cmp_state;
367}
368
369void
370decmpfs_cnode_set_vnode_state(decmpfs_cnode *cp, uint32_t state, int skiplock)
371{
372 if (!skiplock) decmpfs_lock_compressed_data(cp, 1);
373 cp->cmp_state = state;
374 if (state == FILE_TYPE_UNKNOWN) {
375 /* clear out the compression type too */
376 cp->cmp_type = 0;
377 }
378 if (!skiplock) decmpfs_unlock_compressed_data(cp, 1);
379}
380
381static void
382decmpfs_cnode_set_vnode_cmp_type(decmpfs_cnode *cp, uint32_t cmp_type, int skiplock)
383{
384 if (!skiplock) decmpfs_lock_compressed_data(cp, 1);
385 cp->cmp_type = cmp_type;
386 if (!skiplock) decmpfs_unlock_compressed_data(cp, 1);
387}
388
389static void
390decmpfs_cnode_set_vnode_minimal_xattr(decmpfs_cnode *cp, int minimal_xattr, int skiplock)
391{
392 if (!skiplock) decmpfs_lock_compressed_data(cp, 1);
393 cp->cmp_minimal_xattr = minimal_xattr;
394 if (!skiplock) decmpfs_unlock_compressed_data(cp, 1);
395}
396
397uint64_t
398decmpfs_cnode_get_vnode_cached_size(decmpfs_cnode *cp)
399{
b0d623f7 400 return cp->uncompressed_size;
b0d623f7
A
401}
402
403static void
404decmpfs_cnode_set_vnode_cached_size(decmpfs_cnode *cp, uint64_t size)
405{
b0d623f7
A
406 while(1) {
407 uint64_t old = cp->uncompressed_size;
408 if (OSCompareAndSwap64(old, size, (UInt64*)&cp->uncompressed_size)) {
409 return;
410 } else {
411 /* failed to write our value, so loop */
412 }
413 }
316670eb
A
414}
415
416static uint64_t
417decmpfs_cnode_get_decompression_flags(decmpfs_cnode *cp)
418{
419 return cp->decompression_flags;
420}
421
422static void
423decmpfs_cnode_set_decompression_flags(decmpfs_cnode *cp, uint64_t flags)
424{
425 while(1) {
426 uint64_t old = cp->decompression_flags;
427 if (OSCompareAndSwap64(old, flags, (UInt64*)&cp->decompression_flags)) {
428 return;
429 } else {
430 /* failed to write our value, so loop */
431 }
432 }
b0d623f7
A
433}
434
435#pragma mark --- decmpfs state routines ---
436
437static int
438decmpfs_fetch_compressed_header(vnode_t vp, decmpfs_cnode *cp, decmpfs_header **hdrOut, int returnInvalid)
439{
440 /*
441 fetches vp's compression xattr, converting it into a decmpfs_header; returns 0 or errno
442 if returnInvalid == 1, returns the header even if the type was invalid (out of range),
443 and return ERANGE in that case
444 */
445
446 size_t read_size = 0;
447 size_t attr_size = 0;
448 uio_t attr_uio = NULL;
449 int err = 0;
450 char *data = NULL;
451 decmpfs_header *hdr = NULL;
452 char uio_buf[ UIO_SIZEOF(1) ];
453
454 if ((cp != NULL) &&
455 (cp->cmp_type != 0) &&
456 (cp->cmp_minimal_xattr != 0)) {
457 /* this file's xattr didn't have any extra data when we fetched it, so we can synthesize a header from the data in the cnode */
458
459 MALLOC(data, char *, sizeof(decmpfs_header), M_TEMP, M_WAITOK);
460 if (!data) {
461 err = ENOMEM;
462 goto out;
463 }
464 hdr = (decmpfs_header*)data;
465 hdr->attr_size = sizeof(decmpfs_disk_header);
466 hdr->compression_magic = DECMPFS_MAGIC;
467 hdr->compression_type = cp->cmp_type;
468 hdr->uncompressed_size = decmpfs_cnode_get_vnode_cached_size(cp);
469 } else {
470 /* figure out how big the xattr is on disk */
471 err = vn_getxattr(vp, DECMPFS_XATTR_NAME, NULL, &attr_size, XATTR_NOSECURITY, decmpfs_ctx);
472 if (err != 0)
473 goto out;
474
475 if (attr_size < sizeof(decmpfs_disk_header) || attr_size > MAX_DECMPFS_XATTR_SIZE) {
476 err = EINVAL;
477 goto out;
478 }
479
480 /* allocation includes space for the extra attr_size field of a compressed_header */
481 MALLOC(data, char *, attr_size + sizeof(hdr->attr_size), M_TEMP, M_WAITOK);
482 if (!data) {
483 err = ENOMEM;
484 goto out;
485 }
486
487 /* read the xattr into our buffer, skipping over the attr_size field at the beginning */
488 attr_uio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ, &uio_buf[0], sizeof(uio_buf));
489 uio_addiov(attr_uio, CAST_USER_ADDR_T(data + sizeof(hdr->attr_size)), attr_size);
490
491 err = vn_getxattr(vp, DECMPFS_XATTR_NAME, attr_uio, &read_size, XATTR_NOSECURITY, decmpfs_ctx);
492 if (err != 0)
493 goto out;
494 if (read_size != attr_size) {
495 err = EINVAL;
496 goto out;
497 }
498 hdr = (decmpfs_header*)data;
499 hdr->attr_size = attr_size;
500 /* swap the fields to native endian */
501 hdr->compression_magic = OSSwapLittleToHostInt32(hdr->compression_magic);
502 hdr->compression_type = OSSwapLittleToHostInt32(hdr->compression_type);
503 hdr->uncompressed_size = OSSwapLittleToHostInt64(hdr->uncompressed_size);
504 }
505
506 if (hdr->compression_magic != DECMPFS_MAGIC) {
3e170ce0 507 ErrorLogWithPath("invalid compression_magic 0x%08x, should be 0x%08x\n", hdr->compression_magic, DECMPFS_MAGIC);
b0d623f7
A
508 err = EINVAL;
509 goto out;
510 }
511
512 if (hdr->compression_type >= CMP_MAX) {
513 if (returnInvalid) {
514 /* return the header even though the type is out of range */
515 err = ERANGE;
516 } else {
3e170ce0 517 ErrorLogWithPath("compression_type %d out of range\n", hdr->compression_type);
b0d623f7
A
518 err = EINVAL;
519 }
520 goto out;
521 }
522
523out:
524 if (err && (err != ERANGE)) {
3e170ce0 525 DebugLogWithPath("err %d\n", err);
b0d623f7
A
526 if (data) FREE(data, M_TEMP);
527 *hdrOut = NULL;
528 } else {
529 *hdrOut = hdr;
530 }
531 return err;
532}
533
534static int
535decmpfs_fast_get_state(decmpfs_cnode *cp)
536{
537 /*
538 return the cached state
539 this should *only* be called when we know that decmpfs_file_is_compressed has already been called,
540 because this implies that the cached state is valid
541 */
542 int cmp_state = decmpfs_cnode_get_vnode_state(cp);
543
544 switch(cmp_state) {
545 case FILE_IS_NOT_COMPRESSED:
546 case FILE_IS_COMPRESSED:
547 case FILE_IS_CONVERTING:
548 return cmp_state;
549 case FILE_TYPE_UNKNOWN:
550 /*
551 we should only get here if decmpfs_file_is_compressed was not called earlier on this vnode,
552 which should not be possible
553 */
554 ErrorLog("decmpfs_fast_get_state called on unknown file\n");
555 return FILE_IS_NOT_COMPRESSED;
556 default:
557 /* */
558 ErrorLog("unknown cmp_state %d\n", cmp_state);
559 return FILE_IS_NOT_COMPRESSED;
560 }
561}
562
563static int
564decmpfs_fast_file_is_compressed(decmpfs_cnode *cp)
565{
566 int cmp_state = decmpfs_cnode_get_vnode_state(cp);
567
568 switch(cmp_state) {
569 case FILE_IS_NOT_COMPRESSED:
570 return 0;
571 case FILE_IS_COMPRESSED:
572 case FILE_IS_CONVERTING:
573 return 1;
574 case FILE_TYPE_UNKNOWN:
575 /*
576 we should only get here if decmpfs_file_is_compressed was not called earlier on this vnode,
577 which should not be possible
578 */
579 ErrorLog("decmpfs_fast_get_state called on unknown file\n");
580 return 0;
581 default:
582 /* */
583 ErrorLog("unknown cmp_state %d\n", cmp_state);
584 return 0;
585 }
586}
587
588errno_t
589decmpfs_validate_compressed_file(vnode_t vp, decmpfs_cnode *cp)
590{
591 /* give a compressor a chance to indicate that a compressed file is invalid */
592
593 decmpfs_header *hdr = NULL;
594 errno_t err = decmpfs_fetch_compressed_header(vp, cp, &hdr, 0);
595 if (err) {
596 /* we couldn't get the header */
597 if (decmpfs_fast_get_state(cp) == FILE_IS_NOT_COMPRESSED) {
598 /* the file is no longer compressed, so return success */
599 err = 0;
600 }
601 goto out;
602 }
603
604 lck_rw_lock_shared(decompressorsLock);
3e170ce0 605 decmpfs_validate_compressed_file_func validate = decmp_get_func(vp, hdr->compression_type, validate);
b0d623f7
A
606 if (validate) { /* make sure this validation function is valid */
607 /* is the data okay? */
608 err = validate(vp, decmpfs_ctx, hdr);
3e170ce0 609 } else if (decmp_get_func(vp, hdr->compression_type, fetch) == NULL) {
b0d623f7
A
610 /* the type isn't registered */
611 err = EIO;
612 } else {
613 /* no validate registered, so nothing to do */
614 err = 0;
615 }
316670eb 616 lck_rw_unlock_shared(decompressorsLock);
b0d623f7
A
617out:
618 if (hdr) FREE(hdr, M_TEMP);
619#if COMPRESSION_DEBUG
620 if (err) {
3e170ce0 621 DebugLogWithPath("decmpfs_validate_compressed_file ret %d, vp->v_flag %d\n", err, vp->v_flag);
b0d623f7
A
622 }
623#endif
624 return err;
625}
626
627int
628decmpfs_file_is_compressed(vnode_t vp, decmpfs_cnode *cp)
629{
630 /*
631 determines whether vp points to a compressed file
632
633 to speed up this operation, we cache the result in the cnode, and do as little as possible
634 in the case where the cnode already has a valid cached state
635
636 */
637
638 int ret = 0;
639 int error = 0;
640 uint32_t cmp_state;
641 struct vnode_attr va_fetch;
642 decmpfs_header *hdr = NULL;
643 mount_t mp = NULL;
644 int cnode_locked = 0;
645 int saveInvalid = 0; // save the header data even though the type was out of range
316670eb 646 uint64_t decompression_flags = 0;
b0d623f7
A
647
648 if (vnode_isnamedstream(vp)) {
649 /*
650 named streams can't be compressed
651 since named streams of the same file share the same cnode,
652 we don't want to get/set the state in the cnode, just return 0
653 */
654 return 0;
655 }
656
657 /* examine the cached a state in this cnode */
658 cmp_state = decmpfs_cnode_get_vnode_state(cp);
659 switch(cmp_state) {
660 case FILE_IS_NOT_COMPRESSED:
661 return 0;
662 case FILE_IS_COMPRESSED:
663 return 1;
664 case FILE_IS_CONVERTING:
665 /* treat the file as compressed, because this gives us a way to block future reads until decompression is done */
666 return 1;
667 case FILE_TYPE_UNKNOWN:
668 /* the first time we encountered this vnode, so we need to check it out */
669 break;
670 default:
671 /* unknown state, assume file is not compressed */
3e170ce0 672 ErrorLogWithPath("unknown cmp_state %d\n", cmp_state);
b0d623f7
A
673 return 0;
674 }
675
39236c6e
A
676 if (!vnode_isreg(vp)) {
677 /* only regular files can be compressed */
678 ret = FILE_IS_NOT_COMPRESSED;
679 goto done;
680 }
b0d623f7
A
681
682 mp = vnode_mount(vp);
683 if (mp == NULL) {
684 /*
685 this should only be true before we mount the root filesystem
686 we short-cut this return to avoid the call to getattr below, which
687 will fail before root is mounted
688 */
689 ret = FILE_IS_NOT_COMPRESSED;
690 goto done;
691 }
692 if ((mp->mnt_flag & MNT_LOCAL) == 0) {
693 /* compression only supported on local filesystems */
694 ret = FILE_IS_NOT_COMPRESSED;
695 goto done;
696 }
697
698 /* lock our cnode data so that another caller doesn't change the state under us */
699 decmpfs_lock_compressed_data(cp, 1);
700 cnode_locked = 1;
701
702 VATTR_INIT(&va_fetch);
703 VATTR_WANTED(&va_fetch, va_flags);
704 error = vnode_getattr(vp, &va_fetch, decmpfs_ctx);
705 if (error) {
706 /* failed to get the bsd flags so the file is not compressed */
707 ret = FILE_IS_NOT_COMPRESSED;
708 goto done;
709 }
710 if (va_fetch.va_flags & UF_COMPRESSED) {
711 /* UF_COMPRESSED is on, make sure the file has the DECMPFS_XATTR_NAME xattr */
712 error = decmpfs_fetch_compressed_header(vp, cp, &hdr, 1);
713 if ((hdr != NULL) && (error == ERANGE)) {
714 saveInvalid = 1;
715 }
716 if (error) {
717 /* failed to get the xattr so the file is not compressed */
718 ret = FILE_IS_NOT_COMPRESSED;
719 goto done;
720 }
721 /* we got the xattr, so the file is compressed */
722 ret = FILE_IS_COMPRESSED;
723 goto done;
724 }
725 /* UF_COMPRESSED isn't on, so the file isn't compressed */
726 ret = FILE_IS_NOT_COMPRESSED;
727
728done:
729 if (((ret == FILE_IS_COMPRESSED) || saveInvalid) && hdr) {
730 /*
731 cache the uncompressed size away in the cnode
732 */
733
734 if (!cnode_locked) {
735 /*
736 we should never get here since the only place ret is set to FILE_IS_COMPRESSED
737 is after the call to decmpfs_lock_compressed_data above
738 */
739 decmpfs_lock_compressed_data(cp, 1);
740 cnode_locked = 1;
741 }
742
743 decmpfs_cnode_set_vnode_cached_size(cp, hdr->uncompressed_size);
744 decmpfs_cnode_set_vnode_state(cp, ret, 1);
745 decmpfs_cnode_set_vnode_cmp_type(cp, hdr->compression_type, 1);
746 /* remember if the xattr's size was equal to the minimal xattr */
747 if (hdr->attr_size == sizeof(decmpfs_disk_header)) {
748 decmpfs_cnode_set_vnode_minimal_xattr(cp, 1, 1);
749 }
750 if (ret == FILE_IS_COMPRESSED) {
751 /* update the ubc's size for this file */
752 ubc_setsize(vp, hdr->uncompressed_size);
316670eb
A
753
754 /* update the decompression flags in the decmpfs cnode */
755 lck_rw_lock_shared(decompressorsLock);
3e170ce0 756 decmpfs_get_decompression_flags_func get_flags = decmp_get_func(vp, hdr->compression_type, get_flags);
316670eb
A
757 if (get_flags) {
758 decompression_flags = get_flags(vp, decmpfs_ctx, hdr);
759 }
760 lck_rw_unlock_shared(decompressorsLock);
761 decmpfs_cnode_set_decompression_flags(cp, decompression_flags);
b0d623f7
A
762 }
763 } else {
764 /* we might have already taken the lock above; if so, skip taking it again by passing cnode_locked as the skiplock parameter */
765 decmpfs_cnode_set_vnode_state(cp, ret, cnode_locked);
766 }
767
768 if (cnode_locked) decmpfs_unlock_compressed_data(cp, 1);
769
770 if (hdr) FREE(hdr, M_TEMP);
771
772 switch(ret) {
773 case FILE_IS_NOT_COMPRESSED:
774 return 0;
775 case FILE_IS_COMPRESSED:
776 case FILE_IS_CONVERTING:
777 return 1;
778 default:
779 /* unknown state, assume file is not compressed */
3e170ce0 780 ErrorLogWithPath("unknown ret %d\n", ret);
b0d623f7
A
781 return 0;
782 }
783}
784
785int
786decmpfs_update_attributes(vnode_t vp, struct vnode_attr *vap)
787{
788 int error = 0;
789
790 if (VATTR_IS_ACTIVE(vap, va_flags)) {
791 /* the BSD flags are being updated */
792 if (vap->va_flags & UF_COMPRESSED) {
793 /* the compressed bit is being set, did it change? */
794 struct vnode_attr va_fetch;
795 int old_flags = 0;
796 VATTR_INIT(&va_fetch);
797 VATTR_WANTED(&va_fetch, va_flags);
798 error = vnode_getattr(vp, &va_fetch, decmpfs_ctx);
799 if (error)
800 return error;
801
802 old_flags = va_fetch.va_flags;
803
804 if (!(old_flags & UF_COMPRESSED)) {
805 /*
806 * Compression bit was turned on, make sure the file has the DECMPFS_XATTR_NAME attribute.
807 * This precludes anyone from using the UF_COMPRESSED bit for anything else, and it enforces
808 * an order of operation -- you must first do the setxattr and then the chflags.
809 */
810
811 if (VATTR_IS_ACTIVE(vap, va_data_size)) {
812 /*
813 * don't allow the caller to set the BSD flag and the size in the same call
814 * since this doesn't really make sense
815 */
816 vap->va_flags &= ~UF_COMPRESSED;
817 return 0;
818 }
819
820 decmpfs_header *hdr = NULL;
821 error = decmpfs_fetch_compressed_header(vp, NULL, &hdr, 1);
822 if (error == 0) {
823 /*
824 allow the flag to be set since the decmpfs attribute is present
825 in that case, we also want to truncate the data fork of the file
826 */
827 VATTR_SET_ACTIVE(vap, va_data_size);
828 vap->va_data_size = 0;
829 } else if (error == ERANGE) {
830 /* the file had a decmpfs attribute but the type was out of range, so don't muck with the file's data size */
831 } else {
832 /* no DECMPFS_XATTR_NAME attribute, so deny the update */
833 vap->va_flags &= ~UF_COMPRESSED;
834 }
835 if (hdr) FREE(hdr, M_TEMP);
836 }
837 }
838 }
839
840 return 0;
841}
842
843static int
844wait_for_decompress(decmpfs_cnode *cp)
845{
846 int state;
847 lck_mtx_lock(decompress_channel_mtx);
848 do {
849 state = decmpfs_fast_get_state(cp);
850 if (state != FILE_IS_CONVERTING) {
851 /* file is not decompressing */
852 lck_mtx_unlock(decompress_channel_mtx);
853 return state;
854 }
855 msleep((caddr_t)&decompress_channel, decompress_channel_mtx, PINOD, "wait_for_decompress", NULL);
856 } while(1);
857}
858
859#pragma mark --- decmpfs hide query routines ---
860
861int
862decmpfs_hides_rsrc(vfs_context_t ctx, decmpfs_cnode *cp)
863{
864 /*
865 WARNING!!!
866 callers may (and do) pass NULL for ctx, so we should only use it
867 for this equality comparison
868
869 This routine should only be called after a file has already been through decmpfs_file_is_compressed
870 */
871
872 if (ctx == decmpfs_ctx)
873 return 0;
874
875 if (!decmpfs_fast_file_is_compressed(cp))
876 return 0;
877
878 /* all compressed files hide their resource fork */
879 return 1;
880}
881
882int
883decmpfs_hides_xattr(vfs_context_t ctx, decmpfs_cnode *cp, const char *xattr)
884{
885 /*
886 WARNING!!!
887 callers may (and do) pass NULL for ctx, so we should only use it
888 for this equality comparison
889
890 This routine should only be called after a file has already been through decmpfs_file_is_compressed
891 */
892
893 if (ctx == decmpfs_ctx)
894 return 0;
3e170ce0 895 if (strncmp(xattr, XATTR_RESOURCEFORK_NAME, sizeof(XATTR_RESOURCEFORK_NAME) - 1) == 0)
b0d623f7
A
896 return decmpfs_hides_rsrc(ctx, cp);
897 if (!decmpfs_fast_file_is_compressed(cp))
898 /* file is not compressed, so don't hide this xattr */
899 return 0;
3e170ce0 900 if (strncmp(xattr, DECMPFS_XATTR_NAME, sizeof(DECMPFS_XATTR_NAME) - 1) == 0)
b0d623f7
A
901 /* it's our xattr, so hide it */
902 return 1;
903 /* don't hide this xattr */
904 return 0;
905}
906
907#pragma mark --- registration/validation routines ---
908
316670eb
A
909static inline int registration_valid(decmpfs_registration *registration)
910{
911 return registration && ((registration->decmpfs_registration == DECMPFS_REGISTRATION_VERSION_V1) || (registration->decmpfs_registration == DECMPFS_REGISTRATION_VERSION_V3));
912}
913
b0d623f7
A
914errno_t
915register_decmpfs_decompressor(uint32_t compression_type, decmpfs_registration *registration)
916{
917 /* called by kexts to register decompressors */
918
919 errno_t ret = 0;
920 int locked = 0;
d1ecb069 921 char resourceName[80];
b0d623f7 922
316670eb 923 if ((compression_type >= CMP_MAX) || !registration_valid(registration)) {
b0d623f7
A
924 ret = EINVAL;
925 goto out;
926 }
927
928 lck_rw_lock_exclusive(decompressorsLock); locked = 1;
929
930 /* make sure the registration for this type is zero */
931 if (decompressors[compression_type] != NULL) {
932 ret = EEXIST;
933 goto out;
934 }
935 decompressors[compression_type] = registration;
d1ecb069
A
936 snprintf(resourceName, sizeof(resourceName), "com.apple.AppleFSCompression.Type%u", compression_type);
937 IOServicePublishResource(resourceName, TRUE);
b0d623f7
A
938
939out:
316670eb 940 if (locked) lck_rw_unlock_exclusive(decompressorsLock);
b0d623f7
A
941 return ret;
942}
943
944errno_t
945unregister_decmpfs_decompressor(uint32_t compression_type, decmpfs_registration *registration)
946{
947 /* called by kexts to unregister decompressors */
948
949 errno_t ret = 0;
950 int locked = 0;
d1ecb069
A
951 char resourceName[80];
952
316670eb 953 if ((compression_type >= CMP_MAX) || !registration_valid(registration)) {
b0d623f7
A
954 ret = EINVAL;
955 goto out;
956 }
957
958 lck_rw_lock_exclusive(decompressorsLock); locked = 1;
959 if (decompressors[compression_type] != registration) {
960 ret = EEXIST;
961 goto out;
962 }
963 decompressors[compression_type] = NULL;
d1ecb069
A
964 snprintf(resourceName, sizeof(resourceName), "com.apple.AppleFSCompression.Type%u", compression_type);
965 IOServicePublishResource(resourceName, FALSE);
b0d623f7
A
966
967out:
316670eb 968 if (locked) lck_rw_unlock_exclusive(decompressorsLock);
b0d623f7
A
969 return ret;
970}
971
972static int
3e170ce0 973compression_type_valid(vnode_t vp, decmpfs_header *hdr)
b0d623f7
A
974{
975 /* fast pre-check to determine if the given compressor has checked in */
976 int ret = 0;
977
978 /* every compressor must have at least a fetch function */
979 lck_rw_lock_shared(decompressorsLock);
3e170ce0 980 if (decmp_get_func(vp, hdr->compression_type, fetch) != NULL) {
b0d623f7
A
981 ret = 1;
982 }
316670eb 983 lck_rw_unlock_shared(decompressorsLock);
b0d623f7
A
984
985 return ret;
986}
987
988#pragma mark --- compression/decompression routines ---
989
990static int
316670eb 991decmpfs_fetch_uncompressed_data(vnode_t vp, decmpfs_cnode *cp, decmpfs_header *hdr, off_t offset, user_ssize_t size, int nvec, decmpfs_vector *vec, uint64_t *bytes_read)
b0d623f7
A
992{
993 /* get the uncompressed bytes for the specified region of vp by calling out to the registered compressor */
994
995 int err = 0;
996
997 *bytes_read = 0;
998
999 if ((uint64_t)offset >= hdr->uncompressed_size) {
1000 /* reading past end of file; nothing to do */
1001 err = 0;
1002 goto out;
1003 }
1004 if (offset < 0) {
1005 /* tried to read from before start of file */
1006 err = EINVAL;
1007 goto out;
1008 }
1009 if ((uint64_t)(offset + size) > hdr->uncompressed_size) {
1010 /* adjust size so we don't read past the end of the file */
1011 size = hdr->uncompressed_size - offset;
1012 }
1013 if (size == 0) {
1014 /* nothing to read */
1015 err = 0;
1016 goto out;
1017 }
1018
1019 lck_rw_lock_shared(decompressorsLock);
3e170ce0 1020 decmpfs_fetch_uncompressed_data_func fetch = decmp_get_func(vp, hdr->compression_type, fetch);
b0d623f7
A
1021 if (fetch) {
1022 err = fetch(vp, decmpfs_ctx, hdr, offset, size, nvec, vec, bytes_read);
316670eb
A
1023 lck_rw_unlock_shared(decompressorsLock);
1024 if (err == 0) {
1025 uint64_t decompression_flags = decmpfs_cnode_get_decompression_flags(cp);
1026 if (decompression_flags & DECMPFS_FLAGS_FORCE_FLUSH_ON_DECOMPRESS) {
1027#if !defined(__i386__) && !defined(__x86_64__)
1028 int i;
1029 for (i = 0; i < nvec; i++) {
1030 flush_dcache64((addr64_t)(uintptr_t)vec[i].buf, vec[i].size, FALSE);
1031 }
1032#endif
1033 }
1034 }
b0d623f7
A
1035 } else {
1036 err = ENOTSUP;
316670eb 1037 lck_rw_unlock_shared(decompressorsLock);
b0d623f7 1038 }
b0d623f7
A
1039
1040out:
1041 return err;
1042}
1043
1044static kern_return_t
1045commit_upl(upl_t upl, upl_offset_t pl_offset, size_t uplSize, int flags, int abort)
1046{
1047 kern_return_t kr = 0;
fe8ab488
A
1048
1049#if CONFIG_IOSCHED
1050 upl_unmark_decmp(upl);
1051#endif /* CONFIG_IOSCHED */
b0d623f7
A
1052
1053 /* commit the upl pages */
1054 if (abort) {
1055 VerboseLog("aborting upl, flags 0x%08x\n", flags);
1056 kr = ubc_upl_abort_range(upl, pl_offset, uplSize, flags);
1057 if (kr != KERN_SUCCESS)
3e170ce0 1058 ErrorLog("ubc_upl_abort_range error %d\n", (int)kr);
b0d623f7
A
1059 } else {
1060 VerboseLog("committing upl, flags 0x%08x\n", flags | UPL_COMMIT_CLEAR_DIRTY);
15129b1c 1061 kr = ubc_upl_commit_range(upl, pl_offset, uplSize, flags | UPL_COMMIT_CLEAR_DIRTY | UPL_COMMIT_WRITTEN_BY_KERNEL);
b0d623f7
A
1062 if (kr != KERN_SUCCESS)
1063 ErrorLog("ubc_upl_commit_range error %d\n", (int)kr);
1064 }
1065 return kr;
1066}
1067
fe8ab488 1068
b0d623f7
A
1069errno_t
1070decmpfs_pagein_compressed(struct vnop_pagein_args *ap, int *is_compressed, decmpfs_cnode *cp)
1071{
1072 /* handles a page-in request from vfs for a compressed file */
1073
1074 int err = 0;
3e170ce0 1075 vnode_t vp = ap->a_vp;
b0d623f7
A
1076 upl_t pl = ap->a_pl;
1077 upl_offset_t pl_offset = ap->a_pl_offset;
1078 off_t f_offset = ap->a_f_offset;
1079 size_t size = ap->a_size;
1080 int flags = ap->a_flags;
1081 off_t uplPos = 0;
1082 user_ssize_t uplSize = 0;
1083 void *data = NULL;
1084 decmpfs_header *hdr = NULL;
1085 int abort_pagein = 0;
1086 uint64_t cachedSize = 0;
1087 int cmpdata_locked = 0;
1088
1089 if(!decmpfs_trylock_compressed_data(cp, 0)) {
1090 return EAGAIN;
1091 }
1092 cmpdata_locked = 1;
1093
1094
1095 if (flags & ~(UPL_IOSYNC | UPL_NOCOMMIT | UPL_NORDAHEAD)) {
3e170ce0 1096 DebugLogWithPath("pagein: unknown flags 0x%08x\n", (flags & ~(UPL_IOSYNC | UPL_NOCOMMIT | UPL_NORDAHEAD)));
b0d623f7
A
1097 }
1098
1099 err = decmpfs_fetch_compressed_header(vp, cp, &hdr, 0);
1100 if (err != 0) {
1101 goto out;
1102 }
1103
1104 cachedSize = hdr->uncompressed_size;
1105
3e170ce0 1106 if (!compression_type_valid(vp, hdr)) {
b0d623f7
A
1107 /* compressor not registered */
1108 err = ENOTSUP;
1109 goto out;
1110 }
fe8ab488
A
1111
1112#if CONFIG_IOSCHED
1113 /* Mark the UPL as the requesting UPL for decompression */
1114 upl_mark_decmp(pl);
1115#endif /* CONFIG_IOSCHED */
1116
b0d623f7
A
1117 /* map the upl so we can fetch into it */
1118 kern_return_t kr = ubc_upl_map(pl, (vm_offset_t*)&data);
1119 if ((kr != KERN_SUCCESS) || (data == NULL)) {
fe8ab488
A
1120 err = ENOSPC;
1121#if CONFIG_IOSCHED
1122 upl_unmark_decmp(pl);
1123#endif /* CONFIG_IOSCHED */
b0d623f7
A
1124 goto out;
1125 }
1126
1127 uplPos = f_offset;
1128 uplSize = size;
fe8ab488 1129
b0d623f7
A
1130 /* clip the size to the size of the file */
1131 if ((uint64_t)uplPos + uplSize > cachedSize) {
1132 /* truncate the read to the size of the file */
1133 uplSize = cachedSize - uplPos;
1134 }
1135
1136 /* do the fetch */
1137 decmpfs_vector vec;
1138
1139decompress:
1140 /* the mapped data pointer points to the first page of the page list, so we want to start filling in at an offset of pl_offset */
1141 vec.buf = (char*)data + pl_offset;
1142 vec.size = size;
1143
1144 uint64_t did_read = 0;
1145 if (decmpfs_fast_get_state(cp) == FILE_IS_CONVERTING) {
3e170ce0 1146 ErrorLogWithPath("unexpected pagein during decompress\n");
b0d623f7
A
1147 /*
1148 if the file is converting, this must be a recursive call to pagein from underneath a call to decmpfs_decompress_file;
1149 pretend that it succeeded but don't do anything since we're just going to write over the pages anyway
1150 */
1151 err = 0;
1152 did_read = 0;
1153 } else {
316670eb 1154 err = decmpfs_fetch_uncompressed_data(vp, cp, hdr, uplPos, uplSize, 1, &vec, &did_read);
b0d623f7
A
1155 }
1156 if (err) {
3e170ce0 1157 DebugLogWithPath("decmpfs_fetch_uncompressed_data err %d\n", err);
b0d623f7
A
1158 int cmp_state = decmpfs_fast_get_state(cp);
1159 if (cmp_state == FILE_IS_CONVERTING) {
3e170ce0 1160 DebugLogWithPath("cmp_state == FILE_IS_CONVERTING\n");
b0d623f7
A
1161 cmp_state = wait_for_decompress(cp);
1162 if (cmp_state == FILE_IS_COMPRESSED) {
3e170ce0 1163 DebugLogWithPath("cmp_state == FILE_IS_COMPRESSED\n");
b0d623f7
A
1164 /* a decompress was attempted but it failed, let's try calling fetch again */
1165 goto decompress;
1166 }
1167 }
1168 if (cmp_state == FILE_IS_NOT_COMPRESSED) {
3e170ce0 1169 DebugLogWithPath("cmp_state == FILE_IS_NOT_COMPRESSED\n");
b0d623f7
A
1170 /* the file was decompressed after we started reading it */
1171 abort_pagein = 1; /* we're not going to commit our data */
1172 *is_compressed = 0; /* instruct caller to fall back to its normal path */
1173 }
1174 }
1175
1176 /* zero out whatever we didn't read, and zero out the end of the last page(s) */
1177 uint64_t total_size = (size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
1178 if (did_read < total_size) {
1179 memset((char*)vec.buf + did_read, 0, total_size - did_read);
1180 }
fe8ab488
A
1181
1182#if CONFIG_IOSCHED
1183 upl_unmark_decmp(pl);
1184#endif /* CONFIG_IOSCHED */
1185
b0d623f7
A
1186 kr = ubc_upl_unmap(pl); data = NULL; /* make sure to set data to NULL so we don't try to unmap again below */
1187 if (kr != KERN_SUCCESS)
3e170ce0 1188 ErrorLogWithPath("ubc_upl_unmap error %d\n", (int)kr);
b0d623f7
A
1189 else {
1190 if (!abort_pagein) {
1191 /* commit our pages */
6d2010ae 1192 kr = commit_upl(pl, pl_offset, total_size, UPL_COMMIT_FREE_ON_EMPTY, 0);
b0d623f7
A
1193 }
1194 }
1195
1196out:
1197 if (data) ubc_upl_unmap(pl);
1198 if (hdr) FREE(hdr, M_TEMP);
1199 if (cmpdata_locked) decmpfs_unlock_compressed_data(cp, 0);
3e170ce0
A
1200 if (err) {
1201#if DEVELOPMENT || DEBUG
1202 char *path;
1203 MALLOC(path, char *, PATH_MAX, M_TEMP, M_WAITOK);
1204 panic("%s: decmpfs_pagein_compressed: err %d", vnpath(vp, path, PATH_MAX), err);
1205 FREE(path, M_TEMP);
1206#else
1207 ErrorLogWithPath("err %d\n", err);
1208#endif
1209 }
b0d623f7
A
1210 return err;
1211}
1212
1213errno_t
1214decmpfs_read_compressed(struct vnop_read_args *ap, int *is_compressed, decmpfs_cnode *cp)
1215{
1216 /* handles a read request from vfs for a compressed file */
1217
1218 uio_t uio = ap->a_uio;
1219 vnode_t vp = ap->a_vp;
1220 int err = 0;
1221 int countInt = 0;
1222 off_t uplPos = 0;
1223 user_ssize_t uplSize = 0;
1224 user_ssize_t uplRemaining = 0;
1225 off_t curUplPos = 0;
1226 user_ssize_t curUplSize = 0;
1227 kern_return_t kr = KERN_SUCCESS;
1228 int abort_read = 0;
1229 void *data = NULL;
1230 uint64_t did_read = 0;
1231 upl_t upl = NULL;
1232 upl_page_info_t *pli = NULL;
1233 decmpfs_header *hdr = NULL;
1234 uint64_t cachedSize = 0;
1235 off_t uioPos = 0;
1236 user_ssize_t uioRemaining = 0;
1237 int cmpdata_locked = 0;
1238
1239 decmpfs_lock_compressed_data(cp, 0); cmpdata_locked = 1;
1240
1241 uplPos = uio_offset(uio);
1242 uplSize = uio_resid(uio);
3e170ce0 1243 VerboseLogWithPath("uplPos %lld uplSize %lld\n", uplPos, uplSize);
b0d623f7
A
1244
1245 cachedSize = decmpfs_cnode_get_vnode_cached_size(cp);
1246
1247 if ((uint64_t)uplPos + uplSize > cachedSize) {
1248 /* truncate the read to the size of the file */
1249 uplSize = cachedSize - uplPos;
1250 }
1251
1252 /* give the cluster layer a chance to fill in whatever it already has */
1253 countInt = (uplSize > INT_MAX) ? INT_MAX : uplSize;
1254 err = cluster_copy_ubc_data(vp, uio, &countInt, 0);
1255 if (err != 0)
1256 goto out;
1257
1258 /* figure out what's left */
1259 uioPos = uio_offset(uio);
1260 uioRemaining = uio_resid(uio);
1261 if ((uint64_t)uioPos + uioRemaining > cachedSize) {
1262 /* truncate the read to the size of the file */
1263 uioRemaining = cachedSize - uioPos;
1264 }
1265
1266 if (uioRemaining <= 0) {
1267 /* nothing left */
1268 goto out;
1269 }
1270
1271 err = decmpfs_fetch_compressed_header(vp, cp, &hdr, 0);
1272 if (err != 0) {
1273 goto out;
1274 }
3e170ce0 1275 if (!compression_type_valid(vp, hdr)) {
b0d623f7
A
1276 err = ENOTSUP;
1277 goto out;
1278 }
1279
1280 uplPos = uioPos;
1281 uplSize = uioRemaining;
1282#if COMPRESSION_DEBUG
3e170ce0 1283 DebugLogWithPath("uplPos %lld uplSize %lld\n", (uint64_t)uplPos, (uint64_t)uplSize);
b0d623f7
A
1284#endif
1285
1286 lck_rw_lock_shared(decompressorsLock);
3e170ce0 1287 decmpfs_adjust_fetch_region_func adjust_fetch = decmp_get_func(vp, hdr->compression_type, adjust_fetch);
b0d623f7
A
1288 if (adjust_fetch) {
1289 /* give the compressor a chance to adjust the portion of the file that we read */
1290 adjust_fetch(vp, decmpfs_ctx, hdr, &uplPos, &uplSize);
3e170ce0 1291 VerboseLogWithPath("adjusted uplPos %lld uplSize %lld\n", (uint64_t)uplPos, (uint64_t)uplSize);
b0d623f7 1292 }
316670eb 1293 lck_rw_unlock_shared(decompressorsLock);
b0d623f7
A
1294
1295 /* clip the adjusted size to the size of the file */
1296 if ((uint64_t)uplPos + uplSize > cachedSize) {
1297 /* truncate the read to the size of the file */
1298 uplSize = cachedSize - uplPos;
1299 }
1300
1301 if (uplSize <= 0) {
1302 /* nothing left */
1303 goto out;
1304 }
1305
1306 /*
1307 since we're going to create a upl for the given region of the file,
1308 make sure we're on page boundaries
1309 */
1310
1311 if (uplPos & (PAGE_SIZE - 1)) {
1312 /* round position down to page boundary */
1313 uplSize += (uplPos & (PAGE_SIZE - 1));
1314 uplPos &= ~(PAGE_SIZE - 1);
1315 }
1316 /* round size up to page multiple */
1317 uplSize = (uplSize + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
1318
3e170ce0 1319 VerboseLogWithPath("new uplPos %lld uplSize %lld\n", (uint64_t)uplPos, (uint64_t)uplSize);
b0d623f7
A
1320
1321 uplRemaining = uplSize;
1322 curUplPos = uplPos;
1323 curUplSize = 0;
1324
1325 while(uplRemaining > 0) {
1326 /* start after the last upl */
1327 curUplPos += curUplSize;
1328
1329 /* clip to max upl size */
1330 curUplSize = uplRemaining;
fe8ab488
A
1331 if (curUplSize > MAX_UPL_SIZE_BYTES) {
1332 curUplSize = MAX_UPL_SIZE_BYTES;
b0d623f7
A
1333 }
1334
1335 /* create the upl */
1336 kr = ubc_create_upl(vp, curUplPos, curUplSize, &upl, &pli, UPL_SET_LITE);
1337 if (kr != KERN_SUCCESS) {
3e170ce0 1338 ErrorLogWithPath("ubc_create_upl error %d\n", (int)kr);
b0d623f7
A
1339 err = EINVAL;
1340 goto out;
1341 }
3e170ce0 1342 VerboseLogWithPath("curUplPos %lld curUplSize %lld\n", (uint64_t)curUplPos, (uint64_t)curUplSize);
fe8ab488
A
1343
1344#if CONFIG_IOSCHED
1345 /* Mark the UPL as the requesting UPL for decompression */
1346 upl_mark_decmp(upl);
1347#endif /* CONFIG_IOSCHED */
1348
b0d623f7
A
1349 /* map the upl */
1350 kr = ubc_upl_map(upl, (vm_offset_t*)&data);
1351 if (kr != KERN_SUCCESS) {
fe8ab488
A
1352
1353 commit_upl(upl, 0, curUplSize, UPL_ABORT_FREE_ON_EMPTY, 1);
3e170ce0
A
1354#if DEVELOPMENT || DEBUG
1355 char *path;
1356 MALLOC(path, char *, PATH_MAX, M_TEMP, M_WAITOK);
1357 panic("%s: decmpfs_read_compressed: ubc_upl_map error %d", vnpath(vp, path, PATH_MAX), (int)kr);
1358 FREE(path, M_TEMP);
1359#else
1360 ErrorLogWithPath("ubc_upl_map error %d\n", (int)kr);
1361#endif
b0d623f7
A
1362 err = EINVAL;
1363 goto out;
1364 }
1365
1366 /* make sure the map succeeded */
1367 if (!data) {
fe8ab488
A
1368
1369 commit_upl(upl, 0, curUplSize, UPL_ABORT_FREE_ON_EMPTY, 1);
1370
3e170ce0 1371 ErrorLogWithPath("ubc_upl_map mapped null\n");
b0d623f7
A
1372 err = EINVAL;
1373 goto out;
1374 }
1375
1376 /* fetch uncompressed data into the mapped upl */
1377 decmpfs_vector vec;
1378 decompress:
1379 vec = (decmpfs_vector){ .buf = data, .size = curUplSize };
316670eb 1380 err = decmpfs_fetch_uncompressed_data(vp, cp, hdr, curUplPos, curUplSize, 1, &vec, &did_read);
b0d623f7 1381 if (err) {
3e170ce0 1382 ErrorLogWithPath("decmpfs_fetch_uncompressed_data err %d\n", err);
b0d623f7
A
1383
1384 /* maybe the file is converting to decompressed */
1385 int cmp_state = decmpfs_fast_get_state(cp);
1386 if (cmp_state == FILE_IS_CONVERTING) {
3e170ce0 1387 ErrorLogWithPath("cmp_state == FILE_IS_CONVERTING\n");
b0d623f7
A
1388 cmp_state = wait_for_decompress(cp);
1389 if (cmp_state == FILE_IS_COMPRESSED) {
3e170ce0 1390 ErrorLogWithPath("cmp_state == FILE_IS_COMPRESSED\n");
b0d623f7
A
1391 /* a decompress was attempted but it failed, let's try fetching again */
1392 goto decompress;
1393 }
1394 }
1395 if (cmp_state == FILE_IS_NOT_COMPRESSED) {
3e170ce0 1396 ErrorLogWithPath("cmp_state == FILE_IS_NOT_COMPRESSED\n");
b0d623f7
A
1397 /* the file was decompressed after we started reading it */
1398 abort_read = 1; /* we're not going to commit our data */
1399 *is_compressed = 0; /* instruct caller to fall back to its normal path */
1400 }
1401 kr = KERN_FAILURE;
1402 did_read = 0;
1403 }
1404 /* zero out the remainder of the last page */
1405 memset((char*)data + did_read, 0, curUplSize - did_read);
1406 kr = ubc_upl_unmap(upl);
1407 if (kr == KERN_SUCCESS) {
1408 if (abort_read) {
1409 kr = commit_upl(upl, 0, curUplSize, UPL_ABORT_FREE_ON_EMPTY, 1);
1410 } else {
3e170ce0 1411 VerboseLogWithPath("uioPos %lld uioRemaining %lld\n", (uint64_t)uioPos, (uint64_t)uioRemaining);
b0d623f7
A
1412 if (uioRemaining) {
1413 off_t uplOff = uioPos - curUplPos;
1414 if (uplOff < 0) {
3e170ce0 1415 ErrorLogWithPath("uplOff %lld should never be negative\n", (int64_t)uplOff);
b0d623f7
A
1416 err = EINVAL;
1417 } else {
1418 off_t count = curUplPos + curUplSize - uioPos;
1419 if (count < 0) {
1420 /* this upl is entirely before the uio */
1421 } else {
1422 if (count > uioRemaining)
1423 count = uioRemaining;
1424 int io_resid = count;
1425 err = cluster_copy_upl_data(uio, upl, uplOff, &io_resid);
1426 int copied = count - io_resid;
3e170ce0 1427 VerboseLogWithPath("uplOff %lld count %lld copied %lld\n", (uint64_t)uplOff, (uint64_t)count, (uint64_t)copied);
b0d623f7 1428 if (err) {
3e170ce0 1429 ErrorLogWithPath("cluster_copy_upl_data err %d\n", err);
b0d623f7
A
1430 }
1431 uioPos += copied;
1432 uioRemaining -= copied;
1433 }
1434 }
1435 }
1436 kr = commit_upl(upl, 0, curUplSize, UPL_COMMIT_FREE_ON_EMPTY | UPL_COMMIT_INACTIVATE, 0);
1437 if (err) {
1438 goto out;
1439 }
1440 }
1441 } else {
3e170ce0 1442 ErrorLogWithPath("ubc_upl_unmap error %d\n", (int)kr);
b0d623f7 1443 }
fe8ab488 1444
b0d623f7
A
1445 uplRemaining -= curUplSize;
1446 }
1447
1448out:
fe8ab488 1449
b0d623f7
A
1450 if (hdr) FREE(hdr, M_TEMP);
1451 if (cmpdata_locked) decmpfs_unlock_compressed_data(cp, 0);
1452 if (err) {/* something went wrong */
3e170ce0 1453 ErrorLogWithPath("err %d\n", err);
b0d623f7
A
1454 return err;
1455 }
1456
1457#if COMPRESSION_DEBUG
1458 uplSize = uio_resid(uio);
1459 if (uplSize)
3e170ce0 1460 VerboseLogWithPath("still %lld bytes to copy\n", uplSize);
b0d623f7
A
1461#endif
1462 return 0;
1463}
1464
1465int
1466decmpfs_free_compressed_data(vnode_t vp, decmpfs_cnode *cp)
1467{
1468 /*
1469 call out to the decompressor to free remove any data associated with this compressed file
1470 then delete the file's compression xattr
1471 */
1472
1473 decmpfs_header *hdr = NULL;
1474 int err = decmpfs_fetch_compressed_header(vp, cp, &hdr, 0);
1475 if (err) {
3e170ce0 1476 ErrorLogWithPath("decmpfs_fetch_compressed_header err %d\n", err);
b0d623f7
A
1477 } else {
1478 lck_rw_lock_shared(decompressorsLock);
3e170ce0 1479 decmpfs_free_compressed_data_func free_data = decmp_get_func(vp, hdr->compression_type, free_data);
b0d623f7
A
1480 if (free_data) {
1481 err = free_data(vp, decmpfs_ctx, hdr);
1482 } else {
1483 /* nothing to do, so no error */
1484 err = 0;
1485 }
316670eb 1486 lck_rw_unlock_shared(decompressorsLock);
b0d623f7
A
1487
1488 if (err != 0) {
3e170ce0 1489 ErrorLogWithPath("decompressor err %d\n", err);
b0d623f7
A
1490 }
1491 }
1492
1493 /* delete the xattr */
1494 err = vn_removexattr(vp, DECMPFS_XATTR_NAME, 0, decmpfs_ctx);
1495 if (err != 0) {
1496 goto out;
1497 }
1498
1499out:
1500 if (hdr) FREE(hdr, M_TEMP);
1501 return err;
1502}
1503
1504#pragma mark --- file conversion routines ---
1505
1506static int
1507unset_compressed_flag(vnode_t vp)
1508{
1509 int err = 0;
1510 struct vnode_attr va;
1511 int new_bsdflags = 0;
1512
1513 VATTR_INIT(&va);
1514 VATTR_WANTED(&va, va_flags);
1515 err = vnode_getattr(vp, &va, decmpfs_ctx);
1516
1517 if (err != 0) {
3e170ce0 1518 ErrorLogWithPath("vnode_getattr err %d\n", err);
b0d623f7
A
1519 } else {
1520 new_bsdflags = va.va_flags & ~UF_COMPRESSED;
1521
1522 VATTR_INIT(&va);
1523 VATTR_SET(&va, va_flags, new_bsdflags);
1524 err = vnode_setattr(vp, &va, decmpfs_ctx);
1525 if (err != 0) {
3e170ce0 1526 ErrorLogWithPath("vnode_setattr err %d\n", err);
b0d623f7
A
1527 }
1528 }
1529 return err;
1530}
1531
1532int
1533decmpfs_decompress_file(vnode_t vp, decmpfs_cnode *cp, off_t toSize, int truncate_okay, int skiplock)
1534{
1535 /* convert a compressed file to an uncompressed file */
1536
1537 int err = 0;
1538 char *data = NULL;
1539 uio_t uio_w = 0;
1540 off_t offset = 0;
1541 uint32_t old_state = 0;
1542 uint32_t new_state = 0;
1543 int update_file_state = 0;
1544 int allocSize = 0;
1545 decmpfs_header *hdr = NULL;
1546 int cmpdata_locked = 0;
1547 off_t remaining = 0;
1548 uint64_t uncompressed_size = 0;
1549
1550 if (!skiplock) {
1551 decmpfs_lock_compressed_data(cp, 1); cmpdata_locked = 1;
1552 }
1553
1554decompress:
1555 old_state = decmpfs_fast_get_state(cp);
1556
1557 switch(old_state) {
1558 case FILE_IS_NOT_COMPRESSED:
1559 {
1560 /* someone else decompressed the file */
1561 err = 0;
1562 goto out;
1563 }
1564
1565 case FILE_TYPE_UNKNOWN:
1566 {
1567 /* the file is in an unknown state, so update the state and retry */
1568 (void)decmpfs_file_is_compressed(vp, cp);
1569
1570 /* try again */
1571 goto decompress;
1572 }
1573
1574 case FILE_IS_COMPRESSED:
1575 {
1576 /* the file is compressed, so decompress it */
1577 break;
1578 }
1579
1580 default:
1581 {
1582 /*
1583 this shouldn't happen since multiple calls to decmpfs_decompress_file lock each other out,
1584 and when decmpfs_decompress_file returns, the state should be always be set back to
1585 FILE_IS_NOT_COMPRESSED or FILE_IS_UNKNOWN
1586 */
1587 err = EINVAL;
1588 goto out;
1589 }
1590 }
1591
1592 err = decmpfs_fetch_compressed_header(vp, cp, &hdr, 0);
1593 if (err != 0) {
1594 goto out;
1595 }
1596
1597 uncompressed_size = hdr->uncompressed_size;
1598 if (toSize == -1)
1599 toSize = hdr->uncompressed_size;
1600
1601 if (toSize == 0) {
1602 /* special case truncating the file to zero bytes */
1603 goto nodecmp;
1604 } else if ((uint64_t)toSize > hdr->uncompressed_size) {
1605 /* the caller is trying to grow the file, so we should decompress all the data */
1606 toSize = hdr->uncompressed_size;
1607 }
1608
1609 allocSize = MIN(64*1024, toSize);
1610 MALLOC(data, char *, allocSize, M_TEMP, M_WAITOK);
1611 if (!data) {
1612 err = ENOMEM;
1613 goto out;
1614 }
1615
1616 uio_w = uio_create(1, 0LL, UIO_SYSSPACE, UIO_WRITE);
1617 if (!uio_w) {
1618 err = ENOMEM;
1619 goto out;
1620 }
1621 uio_w->uio_flags |= UIO_FLAGS_IS_COMPRESSED_FILE;
1622
1623 remaining = toSize;
1624
1625 /* tell the buffer cache that this is an empty file */
1626 ubc_setsize(vp, 0);
1627
1628 /* if we got here, we need to decompress the file */
1629 decmpfs_cnode_set_vnode_state(cp, FILE_IS_CONVERTING, 1);
1630
1631 while(remaining > 0) {
1632 /* loop decompressing data from the file and writing it into the data fork */
1633
1634 uint64_t bytes_read = 0;
1635 decmpfs_vector vec = { .buf = data, .size = MIN(allocSize, remaining) };
316670eb 1636 err = decmpfs_fetch_uncompressed_data(vp, cp, hdr, offset, vec.size, 1, &vec, &bytes_read);
b0d623f7 1637 if (err != 0) {
3e170ce0 1638 ErrorLogWithPath("decmpfs_fetch_uncompressed_data err %d\n", err);
b0d623f7
A
1639 goto out;
1640 }
1641
1642 if (bytes_read == 0) {
1643 /* we're done reading data */
1644 break;
1645 }
1646
1647 uio_reset(uio_w, offset, UIO_SYSSPACE, UIO_WRITE);
1648 err = uio_addiov(uio_w, CAST_USER_ADDR_T(data), bytes_read);
1649 if (err != 0) {
3e170ce0 1650 ErrorLogWithPath("uio_addiov err %d\n", err);
b0d623f7
A
1651 err = ENOMEM;
1652 goto out;
1653 }
1654
1655 err = VNOP_WRITE(vp, uio_w, 0, decmpfs_ctx);
1656 if (err != 0) {
1657 /* if the write failed, truncate the file to zero bytes */
3e170ce0 1658 ErrorLogWithPath("VNOP_WRITE err %d\n", err);
b0d623f7
A
1659 break;
1660 }
1661 offset += bytes_read;
1662 remaining -= bytes_read;
1663 }
1664
1665 if (err == 0) {
1666 if (offset != toSize) {
3e170ce0 1667 ErrorLogWithPath("file decompressed to %lld instead of %lld\n", offset, toSize);
b0d623f7
A
1668 err = EINVAL;
1669 goto out;
1670 }
1671 }
1672
1673 if (err == 0) {
1674 /* sync the data and metadata */
1675 err = VNOP_FSYNC(vp, MNT_WAIT, decmpfs_ctx);
1676 if (err != 0) {
3e170ce0 1677 ErrorLogWithPath("VNOP_FSYNC err %d\n", err);
b0d623f7
A
1678 goto out;
1679 }
1680 }
1681
1682 if (err != 0) {
1683 /* write, setattr, or fsync failed */
3e170ce0 1684 ErrorLogWithPath("aborting decompress, err %d\n", err);
b0d623f7
A
1685 if (truncate_okay) {
1686 /* truncate anything we might have written */
1687 int error = vnode_setsize(vp, 0, 0, decmpfs_ctx);
3e170ce0 1688 ErrorLogWithPath("vnode_setsize err %d\n", error);
b0d623f7
A
1689 }
1690 goto out;
1691 }
1692
1693nodecmp:
1694 /* if we're truncating the file to zero bytes, we'll skip ahead to here */
1695
1696 /* unset the compressed flag */
1697 unset_compressed_flag(vp);
1698
1699 /* free the compressed data associated with this file */
1700 err = decmpfs_free_compressed_data(vp, cp);
1701 if (err != 0) {
3e170ce0 1702 ErrorLogWithPath("decmpfs_free_compressed_data err %d\n", err);
b0d623f7
A
1703 }
1704
1705 /*
1706 even if free_compressed_data or vnode_getattr/vnode_setattr failed, return success
1707 since we succeeded in writing all of the file data to the data fork
1708 */
1709 err = 0;
1710
1711 /* if we got this far, the file was successfully decompressed */
1712 update_file_state = 1;
1713 new_state = FILE_IS_NOT_COMPRESSED;
1714
1715#if COMPRESSION_DEBUG
1716 {
1717 uint64_t filesize = 0;
1718 vnsize(vp, &filesize);
3e170ce0 1719 DebugLogWithPath("new file size %lld\n", filesize);
b0d623f7
A
1720 }
1721#endif
1722
1723out:
1724 if (hdr) FREE(hdr, M_TEMP);
1725 if (data) FREE(data, M_TEMP);
1726 if (uio_w) uio_free(uio_w);
1727
1728 if (err != 0) {
1729 /* if there was a failure, reset compression flags to unknown and clear the buffer cache data */
1730 update_file_state = 1;
1731 new_state = FILE_TYPE_UNKNOWN;
1732 if (uncompressed_size) {
1733 ubc_setsize(vp, 0);
1734 ubc_setsize(vp, uncompressed_size);
1735 }
1736 }
1737
1738 if (update_file_state) {
1739 lck_mtx_lock(decompress_channel_mtx);
1740 decmpfs_cnode_set_vnode_state(cp, new_state, 1);
1741 wakeup((caddr_t)&decompress_channel); /* wake up anyone who might have been waiting for decompression */
1742 lck_mtx_unlock(decompress_channel_mtx);
1743 }
1744
1745 if (cmpdata_locked) decmpfs_unlock_compressed_data(cp, 1);
1746
1747 return err;
1748}
1749
1750#pragma mark --- Type1 compressor ---
1751
1752/*
1753 The "Type1" compressor stores the data fork directly in the compression xattr
1754 */
1755
1756static int
1757decmpfs_validate_compressed_file_Type1(__unused vnode_t vp, __unused vfs_context_t ctx, decmpfs_header *hdr)
1758{
1759 int err = 0;
1760
1761 if (hdr->uncompressed_size + sizeof(decmpfs_disk_header) != (uint64_t)hdr->attr_size) {
1762 err = EINVAL;
1763 goto out;
1764 }
1765out:
1766 return err;
1767}
1768
1769static int
1770decmpfs_fetch_uncompressed_data_Type1(__unused vnode_t vp, __unused vfs_context_t ctx, decmpfs_header *hdr, off_t offset, user_ssize_t size, int nvec, decmpfs_vector *vec, uint64_t *bytes_read)
1771{
1772 int err = 0;
1773 int i;
1774 user_ssize_t remaining;
1775
1776 if (hdr->uncompressed_size + sizeof(decmpfs_disk_header) != (uint64_t)hdr->attr_size) {
1777 err = EINVAL;
1778 goto out;
1779 }
1780
1781#if COMPRESSION_DEBUG
1782 static int dummy = 0; // prevent syslog from coalescing printfs
3e170ce0 1783 DebugLogWithPath("%d memcpy %lld at %lld\n", dummy++, size, (uint64_t)offset);
b0d623f7
A
1784#endif
1785
1786 remaining = size;
1787 for (i = 0; (i < nvec) && (remaining > 0); i++) {
1788 user_ssize_t curCopy = vec[i].size;
1789 if (curCopy > remaining)
1790 curCopy = remaining;
1791 memcpy(vec[i].buf, hdr->attr_bytes + offset, curCopy);
1792 offset += curCopy;
1793 remaining -= curCopy;
1794 }
1795
1796 if ((bytes_read) && (err == 0))
1797 *bytes_read = (size - remaining);
1798
1799out:
1800 return err;
1801}
1802
1803static decmpfs_registration Type1Reg =
1804{
1805 .decmpfs_registration = DECMPFS_REGISTRATION_VERSION,
1806 .validate = decmpfs_validate_compressed_file_Type1,
1807 .adjust_fetch = NULL, /* no adjust necessary */
1808 .fetch = decmpfs_fetch_uncompressed_data_Type1,
316670eb
A
1809 .free_data = NULL, /* no free necessary */
1810 .get_flags = NULL /* no flags */
b0d623f7
A
1811};
1812
1813#pragma mark --- decmpfs initialization ---
1814
1815void decmpfs_init()
1816{
1817 static int done = 0;
1818 if (done) return;
1819
1820 decmpfs_ctx = vfs_context_create(vfs_context_kernel());
1821
1822 lck_grp_attr_t *attr = lck_grp_attr_alloc_init();
1823 decmpfs_lockgrp = lck_grp_alloc_init("VFSCOMP", attr);
1824 decompressorsLock = lck_rw_alloc_init(decmpfs_lockgrp, NULL);
1825 decompress_channel_mtx = lck_mtx_alloc_init(decmpfs_lockgrp, NULL);
1826
1827 register_decmpfs_decompressor(CMP_Type1, &Type1Reg);
1828
1829 done = 1;
1830}
1831#endif /* HFS_COMPRESSION */