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