-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
hfsfuse will now transparently present these as regular files and decompress them when read as the native driver does. zlib and lzvn/lzfse compression is supported if the relevant libraries are available. Otherwise files using these compression types will continue to display as 0-size entries as they did previously, although their com.apple.decmpfs extended attribute may still be inspected directly. hfsdump will also transparently decompress these for reading. its stat command makes clear when this is the case by printing the decmpfs type and logical_size separately and otherwise retaining the display of the file's record structure as-is.
- Loading branch information
Showing
9 changed files
with
492 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,333 @@ | ||
/* | ||
* libhfsuser - Userspace support library for NetBSD's libhfs | ||
* Copyright 2013-2017 0x09.net. | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining | ||
* a copy of this software and associated documentation files (the | ||
* "Software"), to deal in the Software without restriction, including | ||
* without limitation the rights to use, copy, modify, merge, publish, | ||
* distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to | ||
* the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
*/ | ||
|
||
#include "byteorder.h" | ||
#include "features.h" | ||
#include "hfsuser.h" | ||
|
||
#include <inttypes.h> | ||
#include <errno.h> | ||
|
||
// note: these values are scaled down from the full decmpfs type to account for inline/rsrc variants, | ||
// e.g. a decmpfs_compression value of 2 corresponds to decmpfs types 3 and 4 (zlib compressed, inline or resource fork data respectively) | ||
// see the decmpfs_compression(type) macro below | ||
enum decmpfs_compression { | ||
DECMPFS_COMPRESSION_ZLIB = 2, | ||
DECMPFS_COMPRESSION_SPARSE = 3, | ||
DECMPFS_COMPRESSION_LZVN = 4, | ||
DECMPFS_COMPRESSION_LZFSE = 6, | ||
}; | ||
|
||
#define decmpfs_compression(type) (((type)+1)/2) | ||
|
||
#define decmpfs_compression_zlib(type) (decmpfs_compression(type) == DECMPFS_COMPRESSION_ZLIB) | ||
#define decmpfs_compression_lzvn(type) (decmpfs_compression(type) == DECMPFS_COMPRESSION_LZVN) | ||
#define decmpfs_compression_lzfse(type) (decmpfs_compression(type) == DECMPFS_COMPRESSION_LZFSE) | ||
// liblzfse handles both lzvn and lzfse | ||
#define decmpfs_compression_lzx(type) (decmpfs_compression_lzvn(type) || decmpfs_compression_lzfse(type)) | ||
|
||
#define decmpfs_storage_inline(type) ((type)%2) | ||
|
||
struct hfs_decmpfs_context { | ||
struct hfs_decmpfs_header header; | ||
unsigned char* buf; | ||
size_t buflen; | ||
uint32_t (*chunk_map)[2]; | ||
uint32_t nchunks; | ||
// buffered for small reads; | ||
uint16_t current_chunk; | ||
size_t current_chunk_len; | ||
hfs_extent_descriptor_t* extents; | ||
uint16_t nextents; | ||
}; | ||
|
||
bool hfs_decmpfs_compression_supported(uint8_t type) { | ||
switch(decmpfs_compression(type)) { | ||
case DECMPFS_COMPRESSION_ZLIB: | ||
#if HAVE_ZLIB | ||
return true; | ||
#endif | ||
return false; | ||
case DECMPFS_COMPRESSION_SPARSE: | ||
return decmpfs_storage_inline(type); | ||
case DECMPFS_COMPRESSION_LZVN: | ||
case DECMPFS_COMPRESSION_LZFSE: | ||
#if HAVE_LZFSE | ||
return true; | ||
#endif | ||
return false; | ||
} | ||
return false; | ||
} | ||
|
||
bool hfs_decmpfs_get_header(struct hfs_decmpfs_context* ctx, struct hfs_decmpfs_header* h) { | ||
if(!(ctx && h)) | ||
return false; | ||
*h = ctx->header; | ||
return true; | ||
} | ||
|
||
bool hfs_decmpfs_parse_record(struct hfs_decmpfs_header* h, uint32_t length, unsigned char* data) { | ||
if(!data || length < 16 || memcmp(data,"fpmc",4)) | ||
return false; | ||
h->type = data[4]; | ||
memcpy(&h->logical_size,data+8,8); | ||
return true; | ||
} | ||
|
||
int hfs_decmpfs_decompress(uint8_t type, unsigned char* decompressed_buf, size_t decompressed_buf_len, unsigned char* compressed_buf, size_t compressed_buf_len, size_t* bytes_read, void* scratch_buffer) { | ||
if((decmpfs_compression_zlib(type) && compressed_buf[0] == 0xFF) || | ||
(decmpfs_compression_lzx(type) && compressed_buf[0] == 0x06)) { | ||
*bytes_read = compressed_buf_len-1; | ||
memcpy(decompressed_buf,compressed_buf+1,*bytes_read); | ||
return 0; | ||
} | ||
|
||
#if HAVE_ZLIB | ||
if(decmpfs_compression_zlib(type)) { | ||
unsigned long bytes_decoded = decompressed_buf_len; | ||
int inflate_ret = uncompress(decompressed_buf, &bytes_decoded, compressed_buf, compressed_buf_len); | ||
*bytes_read = bytes_decoded; | ||
return inflate_ret; | ||
} | ||
#endif | ||
|
||
#if HAVE_LZFSE | ||
if(decmpfs_compression_lzx(type)) { | ||
*bytes_read = lzfse_decode_buffer(decompressed_buf, decompressed_buf_len, compressed_buf, compressed_buf_len,scratch_buffer); | ||
return 0; | ||
} | ||
#endif | ||
|
||
hfslib_error("invalid decmpfs type %" PRIu8 "\n",NULL,0,type); | ||
return 1; | ||
} | ||
|
||
struct hfs_decmpfs_context* hfs_decmpfs_create_context(hfs_volume* vol, hfs_cnid_t cnid, uint32_t length, unsigned char* data) { | ||
struct hfs_decmpfs_header h; | ||
if(!hfs_decmpfs_parse_record(&h,length,data)) | ||
return NULL; | ||
|
||
uint8_t compression_type = decmpfs_compression(h.type); | ||
// only reject entirely unknown types, allow unsupported ones since these may contain uncompressed data | ||
switch(compression_type) { | ||
case DECMPFS_COMPRESSION_ZLIB: | ||
case DECMPFS_COMPRESSION_SPARSE: | ||
case DECMPFS_COMPRESSION_LZVN: | ||
case DECMPFS_COMPRESSION_LZFSE: | ||
break; | ||
default: return NULL; | ||
} | ||
|
||
struct hfs_decmpfs_context* ctx = malloc(sizeof(*ctx)); | ||
if(!ctx) | ||
return NULL; | ||
ctx->header = h; | ||
ctx->buf = NULL; | ||
ctx->buflen = 0; | ||
ctx->chunk_map = NULL; | ||
ctx->nchunks = 0; | ||
ctx->current_chunk = 0; | ||
ctx->current_chunk_len = 0; | ||
ctx->extents = NULL; | ||
ctx->nextents = 0; | ||
|
||
if(compression_type == DECMPFS_COMPRESSION_SPARSE) { | ||
if(!decmpfs_storage_inline(ctx->header.type)) | ||
goto err; | ||
} | ||
else if(decmpfs_storage_inline(ctx->header.type)) { | ||
ctx->buflen = ctx->header.logical_size; | ||
if(!(ctx->buf = malloc(ctx->buflen)) || | ||
hfs_decmpfs_decompress(ctx->header.type, ctx->buf, ctx->buflen, data+16, length-16, &ctx->buflen, NULL)) | ||
goto err; | ||
} | ||
else { | ||
// resource fork | ||
if(!(ctx->nextents = hfslib_get_file_extents(vol,cnid,HFS_RSRCFORK,&ctx->extents,NULL))) | ||
goto err; | ||
uint64_t bytes; | ||
uint32_t rsrc_start; // usually 256 | ||
if(hfslib_readd_with_extents(vol,&rsrc_start,&bytes,4,0,ctx->extents,ctx->nextents,NULL) || bytes < 4) | ||
goto err; | ||
rsrc_start = be32toh(rsrc_start); | ||
|
||
if(hfslib_readd_with_extents(vol,&ctx->nchunks,&bytes,4,rsrc_start+4,ctx->extents,ctx->nextents,NULL) || bytes < 4) | ||
goto err; | ||
if(!(ctx->chunk_map = malloc(sizeof(*ctx->chunk_map)*ctx->nchunks))) | ||
goto err; | ||
if(hfslib_readd_with_extents(vol,ctx->chunk_map,&bytes,ctx->nchunks*sizeof(*ctx->chunk_map),rsrc_start+8,ctx->extents,ctx->nextents,NULL) || bytes < ctx->nchunks*sizeof(*ctx->chunk_map)) | ||
goto err; | ||
// adjust offets to be relative to start block | ||
for(size_t i = 0; i < ctx->nchunks; i++) | ||
ctx->chunk_map[i][0] += rsrc_start+4; | ||
} | ||
|
||
return ctx; | ||
|
||
err: | ||
hfs_decmpfs_destroy_context(ctx); | ||
return NULL; | ||
} | ||
|
||
void hfs_decmpfs_destroy_context(struct hfs_decmpfs_context* ctx) { | ||
if(!ctx) | ||
return; | ||
free(ctx->extents); | ||
free(ctx->chunk_map); | ||
free(ctx->buf); | ||
free(ctx); | ||
} | ||
|
||
static const size_t CHUNK_SIZE = 65536; | ||
|
||
static int decmpfs_read_rsrc(hfs_volume* vol, struct hfs_decmpfs_context* ctx, char* buf, size_t size, off_t offset) { | ||
int ret = 0; | ||
if((uint64_t)offset > ctx->header.logical_size) | ||
return 0; | ||
|
||
size = min(size,ctx->header.logical_size-offset); | ||
|
||
size_t bytes_written = 0; | ||
|
||
size_t chunk_start = offset/CHUNK_SIZE, | ||
chunk_end = chunk_start + size/CHUNK_SIZE + (offset%CHUNK_SIZE + size%CHUNK_SIZE + (CHUNK_SIZE-1))/CHUNK_SIZE; // (offset+size+65535)/65536 with no overflow | ||
chunk_start = min(chunk_start,ctx->nchunks); | ||
chunk_end = min(chunk_end,ctx->nchunks); | ||
|
||
size_t decompressed_buf_len = min(ctx->header.logical_size,CHUNK_SIZE); | ||
|
||
unsigned char* compressed_buf = NULL; | ||
|
||
for(size_t i = chunk_start; i < chunk_end && bytes_written < size; i++) { | ||
uint32_t chunk_len = ctx->chunk_map[i][1], | ||
chunk_offset = ctx->chunk_map[i][0]; | ||
|
||
size_t bytes_read = 0; | ||
if(!ctx->buf || ctx->current_chunk != i) { | ||
if(!ctx->buf) { | ||
if(!(ctx->buf = malloc(decompressed_buf_len))) | ||
return -ENOMEM; | ||
ctx->buflen = decompressed_buf_len; | ||
} | ||
|
||
if(!compressed_buf) { | ||
uint32_t max_chunk_len = ctx->chunk_map[i][1]; | ||
for(size_t j = chunk_start+1; j < chunk_end; j++) | ||
if(max_chunk_len < ctx->chunk_map[j][1]) | ||
max_chunk_len = ctx->chunk_map[j][1]; | ||
if(!(compressed_buf = malloc(max_chunk_len))) | ||
return -ENOMEM; | ||
} | ||
|
||
uint64_t compressed_bytes_read; | ||
hfslib_readd_with_extents(vol,compressed_buf,&compressed_bytes_read,chunk_len,chunk_offset,ctx->extents,ctx->nextents,NULL); | ||
if((ret = hfs_decmpfs_decompress(ctx->header.type, ctx->buf, ctx->buflen, compressed_buf, compressed_bytes_read, &bytes_read, NULL))) | ||
break; | ||
ctx->current_chunk = i; | ||
ctx->current_chunk_len = bytes_read; | ||
} | ||
else bytes_read = ctx->current_chunk_len; | ||
|
||
size_t decode_offset = i > chunk_start ? 0 : offset%CHUNK_SIZE; | ||
if(decode_offset < bytes_read) { | ||
size_t writesize = min(bytes_read-decode_offset,size-bytes_written); | ||
memcpy(buf+bytes_written,ctx->buf+decode_offset,writesize); | ||
bytes_written += writesize; | ||
} | ||
} | ||
free(compressed_buf); | ||
|
||
if(ret < 0) | ||
return ret; | ||
return bytes_written; | ||
} | ||
|
||
int hfs_decmpfs_read(hfs_volume* vol, struct hfs_decmpfs_context* ctx, char* buf, size_t size, off_t offset) { | ||
if(offset < 0) | ||
return -EINVAL; | ||
|
||
if(decmpfs_compression(ctx->header.type) == DECMPFS_COMPRESSION_SPARSE) { | ||
if((uint64_t)offset >= ctx->header.logical_size) | ||
return 0; | ||
size_t bytes = min(size,ctx->header.logical_size-offset); | ||
memset(buf,0,bytes); | ||
return bytes; | ||
} | ||
|
||
if(!decmpfs_storage_inline(ctx->header.type)) | ||
return decmpfs_read_rsrc(vol,ctx,buf,size,offset); | ||
|
||
if(ctx->buf && (uint64_t)offset < ctx->buflen) { | ||
size_t bytes = min(size,ctx->buflen-offset); | ||
memcpy(buf,ctx->buf+offset,bytes); | ||
return bytes; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
size_t hfs_decmpfs_buffer_size(struct hfs_decmpfs_header* h) { | ||
if(!h) | ||
return 0; | ||
return decmpfs_storage_inline(h->type) ? h->logical_size : min(h->logical_size,CHUNK_SIZE); | ||
} | ||
|
||
int hfs_decmpfs_lookup(hfs_volume* vol, hfs_file_record_t* file, struct hfs_decmpfs_header* h, uint32_t* length, unsigned char** data) { | ||
if(data) | ||
*data = NULL; | ||
if(length) | ||
*length = 0; | ||
|
||
if(file->data_fork.logical_size) | ||
return 1; | ||
|
||
hfs_attribute_key_t attrkey; | ||
hfslib_make_attribute_key(file->cnid,0,strlen("com.apple.decmpfs"),u"com.apple.decmpfs",&attrkey); | ||
hfs_attribute_record_t attr; | ||
unsigned char* buf = NULL; | ||
if(hfslib_find_attribute_record_with_key(vol,&attrkey,&attr,(void*)&buf,NULL)) | ||
return 1; | ||
|
||
// if this is a zlib or lzfse compressed file and hfsfuse wasn't built with these libraries, continue to treat it as a zero-length file | ||
// the com.apple.decmpfs xattr may still be inspected directly to access the compressed data | ||
if(attr.type != HFS_ATTR_INLINE_DATA || !hfs_decmpfs_parse_record(h,attr.inline_record.length,buf)) { | ||
free(buf); | ||
return -EINVAL; | ||
} | ||
if(!hfs_decmpfs_compression_supported(h->type)) { | ||
free(buf); | ||
hfslib_error("unsupported decmpfs type %" PRIu8 " for cnid %" PRIu32 "\n",NULL,0,h->type,file->cnid); | ||
return -ENOTSUP; | ||
} | ||
|
||
if(length) | ||
*length = attr.inline_record.length; | ||
if(data) | ||
*data = buf; | ||
else | ||
free(buf); | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.