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