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