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