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