Reuse decompression contexts

This change allows one ntfs_system_decompression_ctx to be reused for
all reads from a given open FUSE file.  To make this possible, it was
necessary to update the decompression context to no longer hold a
reference to the open compressed stream.

Based on a proposal by Mikalai Ramanovich.
pull/3/head
Eric Biggers 2016-10-28 23:11:57 -07:00
parent a53113cf1f
commit cd6017fbab
5 changed files with 108 additions and 73 deletions

View File

@ -49,6 +49,12 @@ licensed LGPLv3+, but I have relicensed the version in this plugin to GPLv2+ for
consistency with NTFS-3G's license. (Public domain portions remain public
domain.)
# Known issues
Various problems occur if the NTFS volume is mounted with `lowntfs-3g` instead
of with `ntfs-3g`. This is caused by a bug in `lowntfs-3g`, and a fix to
`lowntfs-3g` has been proposed.
# Notices
The NTFS-3G system compression plugin was written by Eric Biggers, with

View File

@ -1,4 +1,4 @@
AC_INIT([ntfs-3g-system-compression], [0.2], [ebiggers3@gmail.com])
AC_INIT([ntfs-3g-system-compression], [0.3], [ebiggers3@gmail.com])
AC_CONFIG_SRCDIR([src/plugin.c])
AC_CONFIG_MACRO_DIR([m4])

View File

@ -34,6 +34,27 @@
#include "system_compression.h"
/*
* For each open file description for a system-compressed file, we cache an
* ntfs_system_decompression_ctx for the file in the FUSE file handle.
*
* A decompression context includes a decompressor, cached data, and cached
* metadata. It does not include an open ntfs_inode for the file or an open
* ntfs_attr for the file's compressed stream. This is necessary because
* NTFS-3G is not guaranteed to keep the inode open the whole time the file is
* open. Indeed, NTFS-3G may close an inode after a read request and re-open it
* for the next one, though it does maintain an open inode cache.
*
* As a result of the decompression context caching, the results of reads from a
* system-compressed file that has been written to since being opened for
* reading are unspecified. Stale data might be returned. Currently, this
* doesn't matter because this plugin blocks writes to system-compressed files.
* (It might still be possible for adventurous users to play with the
* WofCompressedData named data stream directly.)
*/
#define DECOMPRESSION_CTX(fi) \
((struct ntfs_system_decompression_ctx *)(uintptr_t)((fi)->fh))
static int compressed_getattr(ntfs_inode *ni, const REPARSE_POINT *reparse,
struct stat *stbuf)
{
@ -51,40 +72,38 @@ static int compressed_getattr(ntfs_inode *ni, const REPARSE_POINT *reparse,
return -errno;
}
static int compressed_open(ntfs_inode *ni __attribute__((unused)),
const REPARSE_POINT *reparse __attribute__((unused)),
static int compressed_open(ntfs_inode *ni, const REPARSE_POINT *reparse,
struct fuse_file_info *fi)
{
struct ntfs_system_decompression_ctx *dctx;
if ((fi->flags & O_ACCMODE) != O_RDONLY)
return -EOPNOTSUPP;
return 0;
}
static int compressed_release(ntfs_inode *ni __attribute__((unused)),
const REPARSE_POINT *reparse __attribute__((unused)),
struct fuse_file_info *fi __attribute__((unused)))
{
return 0;
}
static int compressed_read(ntfs_inode *ni, const REPARSE_POINT *reparse,
char *buf, size_t size, off_t offset,
struct fuse_file_info *fi __attribute__((unused)))
{
struct ntfs_system_decompression_ctx *dctx;
ssize_t res;
/* TODO: there needs to be more investigation into reusing decompression
* contexts for multiple reads. */
dctx = ntfs_open_system_decompression_ctx(ni, reparse);
if (!dctx)
return -errno;
res = ntfs_read_system_compressed_data(dctx, offset, size, buf);
fi->fh = (uintptr_t)dctx;
return 0;
}
ntfs_close_system_decompression_ctx(dctx);
static int compressed_release(ntfs_inode *ni __attribute__((unused)),
const REPARSE_POINT *reparse __attribute__((unused)),
struct fuse_file_info *fi)
{
ntfs_close_system_decompression_ctx(DECOMPRESSION_CTX(fi));
return 0;
}
static int compressed_read(ntfs_inode *ni, const REPARSE_POINT *reparse,
char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
ssize_t res;
res = ntfs_read_system_compressed_data(DECOMPRESSION_CTX(fi), ni,
offset, size, buf);
if (res < 0)
return -errno;
return res;

View File

@ -149,12 +149,9 @@ static ntfschar compressed_stream_name[] = {
/* A special marker value not used by any chunk index */
#define INVALID_CHUNK_INDEX UINT64_MAX
/* The decompression context for a system compressed file */
/* A decompression context for a system compressed file */
struct ntfs_system_decompression_ctx {
/* The open compressed stream ("WofCompressedData") */
ntfs_attr *na;
/* The compression format of the file */
WOF_FILE_PROVIDER_COMPRESSION_FORMAT format;
@ -313,6 +310,30 @@ static u32 get_chunk_order(WOF_FILE_PROVIDER_COMPRESSION_FORMAT format)
return 0;
}
/*
* Get the compressed size of a system compressed file. This is the size of its
* WofCompressedData stream.
*/
static s64 get_compressed_size(ntfs_inode *ni)
{
ntfs_attr_search_ctx *actx;
s64 ret;
actx = ntfs_attr_get_search_ctx(ni, NULL);
if (!actx)
return -1;
ret = ntfs_attr_lookup(AT_DATA, compressed_stream_name,
sizeof(compressed_stream_name) /
sizeof(compressed_stream_name[0]),
CASE_SENSITIVE, 0, NULL, 0, actx);
if (!ret)
ret = ntfs_get_attribute_value_length(actx->attr);
ntfs_attr_put_search_ctx(actx);
return ret;
}
/*
* ntfs_get_system_compressed_file_size - Return the compressed size of a system
* compressed file
@ -328,33 +349,16 @@ s64 ntfs_get_system_compressed_file_size(ntfs_inode *ni,
const REPARSE_POINT *reparse)
{
WOF_FILE_PROVIDER_COMPRESSION_FORMAT format;
ntfs_attr_search_ctx *actx;
s64 ret;
/* Verify this is a system compressed file. */
if (get_compression_format(ni, reparse, &format))
return -1;
/* Get the size of the WofCompressedData named data stream. */
actx = ntfs_attr_get_search_ctx(ni, NULL);
if (!actx)
return -1;
ret = ntfs_attr_lookup(AT_DATA, compressed_stream_name,
sizeof(compressed_stream_name) /
sizeof(compressed_stream_name[0]),
CASE_SENSITIVE, 0, NULL, 0, actx);
if (!ret)
ret = ntfs_get_attribute_value_length(actx->attr);
ntfs_attr_put_search_ctx(actx);
return ret;
return get_compressed_size(ni);
}
/*
* ntfs_open_system_decompression_ctx - Open a system-compressed file
* ntfs_open_system_decompression_ctx - Prepare to read a system-compressed file
*
* @ni: The NTFS inode for the file
* @reparse: (Optional) the contents of the file's reparse point attribute
@ -368,6 +372,7 @@ ntfs_open_system_decompression_ctx(ntfs_inode *ni, const REPARSE_POINT *reparse)
{
WOF_FILE_PROVIDER_COMPRESSION_FORMAT format;
struct ntfs_system_decompression_ctx *ctx;
s64 csize;
/* Get the compression format. This also validates that the file really
* is a system-compressed file. */
@ -384,12 +389,11 @@ ntfs_open_system_decompression_ctx(ntfs_inode *ni, const REPARSE_POINT *reparse)
if (allocate_decompressor(ctx))
goto err_free_ctx;
/* Open the WofCompressedData stream. */
ctx->na = ntfs_attr_open(ni, AT_DATA, compressed_stream_name,
sizeof(compressed_stream_name) /
sizeof(compressed_stream_name[0]));
if (!ctx->na)
/* Determine the compressed size of the file. */
csize = get_compressed_size(ni);
if (csize < 0)
goto err_free_decompressor;
ctx->compressed_size = csize;
/* The uncompressed size of a system-compressed file is the size of its
* unnamed data stream, which should be sparse so that it consumes no
@ -404,10 +408,6 @@ ntfs_open_system_decompression_ctx(ntfs_inode *ni, const REPARSE_POINT *reparse)
ctx->num_chunks = (ctx->uncompressed_size +
ctx->chunk_size - 1) >> ctx->chunk_order;
/* The compressed size of a system compressed file is the size of its
* WofCompressedData stream. */
ctx->compressed_size = ctx->na->data_size;
/* Initially, no chunk offsets are cached. */
ctx->base_chunk_idx = INVALID_CHUNK_INDEX;
@ -417,14 +417,13 @@ ntfs_open_system_decompression_ctx(ntfs_inode *ni, const REPARSE_POINT *reparse)
ctx->cached_chunk = ntfs_malloc(ctx->chunk_size);
ctx->cached_chunk_idx = INVALID_CHUNK_INDEX;
if (!ctx->temp_buffer || !ctx->cached_chunk)
goto err_close_ctx;
goto err_free_buffers;
return ctx;
err_close_ctx:
err_free_buffers:
free(ctx->cached_chunk);
free(ctx->temp_buffer);
ntfs_attr_close(ctx->na);
err_free_decompressor:
free_decompressor(ctx);
err_free_ctx:
@ -436,7 +435,7 @@ err:
/* Retrieve the stored offset and size of a chunk stored in the compressed file
* stream. */
static int get_chunk_location(struct ntfs_system_decompression_ctx *ctx,
u64 chunk_idx,
ntfs_attr *na, u64 chunk_idx,
u64 *offset_ret, u32 *stored_size_ret)
{
size_t cache_idx;
@ -475,8 +474,7 @@ static int get_chunk_location(struct ntfs_system_decompression_ctx *ctx,
num_entries_to_read++;
/* Read the chunk table entries into a temporary buffer. */
res = ntfs_attr_pread(ctx->na,
first_entry_to_read << entry_shift,
res = ntfs_attr_pread(na, first_entry_to_read << entry_shift,
num_entries_to_read << entry_shift,
ctx->temp_buffer);
@ -541,7 +539,7 @@ static int get_chunk_location(struct ntfs_system_decompression_ctx *ctx,
/* Retrieve into @buffer the uncompressed data of chunk @chunk_idx. */
static int read_and_decompress_chunk(struct ntfs_system_decompression_ctx *ctx,
u64 chunk_idx, void *buffer)
ntfs_attr *na, u64 chunk_idx, void *buffer)
{
u64 offset;
u32 stored_size;
@ -550,7 +548,7 @@ static int read_and_decompress_chunk(struct ntfs_system_decompression_ctx *ctx,
s64 res;
/* Get the location of the chunk data as stored in the file. */
if (get_chunk_location(ctx, chunk_idx, &offset, &stored_size))
if (get_chunk_location(ctx, na, chunk_idx, &offset, &stored_size))
return -1;
/* All chunks decompress to 'chunk_size' bytes except possibly the last,
@ -578,7 +576,7 @@ static int read_and_decompress_chunk(struct ntfs_system_decompression_ctx *ctx,
}
/* Read the stored chunk data. */
res = ntfs_attr_pread(ctx->na, offset, stored_size, read_buffer);
res = ntfs_attr_pread(na, offset, stored_size, read_buffer);
if (res != stored_size) {
if (res >= 0)
errno = EINVAL;
@ -602,11 +600,12 @@ static int read_and_decompress_chunk(struct ntfs_system_decompression_ctx *ctx,
/* Retrieve a pointer to the uncompressed data of the specified chunk. On
* failure, return NULL and set errno. */
static const void *get_chunk_data(struct ntfs_system_decompression_ctx *ctx,
u64 chunk_idx)
ntfs_attr *na, u64 chunk_idx)
{
if (chunk_idx != ctx->cached_chunk_idx) {
ctx->cached_chunk_idx = INVALID_CHUNK_INDEX;
if (read_and_decompress_chunk(ctx, chunk_idx, ctx->cached_chunk))
if (read_and_decompress_chunk(ctx, na, chunk_idx,
ctx->cached_chunk))
return NULL;
ctx->cached_chunk_idx = chunk_idx;
}
@ -617,6 +616,7 @@ static const void *get_chunk_data(struct ntfs_system_decompression_ctx *ctx,
* ntfs_read_system_compressed_data - Read data from a system-compressed file
*
* @ctx: The decompression context for the file
* @ni: The NTFS inode for the file
* @pos: The byte offset into the uncompressed data to read from
* @count: The number of bytes of uncompressed data to read
* @buf: The buffer into which to read the data
@ -625,16 +625,18 @@ static const void *get_chunk_data(struct ntfs_system_decompression_ctx *ctx,
* end-of-file). On complete failure, return -1 and set errno.
*/
ssize_t ntfs_read_system_compressed_data(struct ntfs_system_decompression_ctx *ctx,
s64 pos, size_t count, void *buf)
ntfs_inode *ni, s64 pos, size_t count,
void *buf)
{
u64 offset;
ntfs_attr *na;
u8 *p;
u8 *end_p;
u64 chunk_idx;
u32 offset_in_chunk;
u32 chunk_size;
if (!ctx || pos < 0) {
if (!ctx || !ni || pos < 0) {
errno = EINVAL;
return -1;
}
@ -647,6 +649,12 @@ ssize_t ntfs_read_system_compressed_data(struct ntfs_system_decompression_ctx *c
if (!count)
return 0;
na = ntfs_attr_open(ni, AT_DATA, compressed_stream_name,
sizeof(compressed_stream_name) /
sizeof(compressed_stream_name[0]));
if (!na)
return -1;
p = buf;
end_p = p + count;
chunk_idx = offset >> ctx->chunk_order;
@ -663,7 +671,7 @@ ssize_t ntfs_read_system_compressed_data(struct ntfs_system_decompression_ctx *c
len_to_copy = min((size_t)(end_p - p),
chunk_size - offset_in_chunk);
chunk = get_chunk_data(ctx, chunk_idx);
chunk = get_chunk_data(ctx, na, chunk_idx);
if (!chunk)
break;
@ -674,6 +682,8 @@ ssize_t ntfs_read_system_compressed_data(struct ntfs_system_decompression_ctx *c
offset_in_chunk = 0;
} while (p != end_p);
ntfs_attr_close(na);
return (p == buf) ? -1 : p - (u8 *)buf;
}
@ -685,7 +695,6 @@ void ntfs_close_system_decompression_ctx(struct ntfs_system_decompression_ctx *c
if (ctx) {
free(ctx->cached_chunk);
free(ctx->temp_buffer);
ntfs_attr_close(ctx->na);
free_decompressor(ctx);
free(ctx);
}

View File

@ -28,7 +28,7 @@
/* System compressed file access */
struct system_decompression_ctx;
struct ntfs_system_decompression_ctx;
extern s64 ntfs_get_system_compressed_file_size(ntfs_inode *ni,
const REPARSE_POINT *reparse);
@ -39,7 +39,8 @@ ntfs_open_system_decompression_ctx(ntfs_inode *ni,
extern ssize_t
ntfs_read_system_compressed_data(struct ntfs_system_decompression_ctx *ctx,
s64 pos, size_t count, void *buf);
ntfs_inode *ni, s64 pos, size_t count,
void *buf);
extern void
ntfs_close_system_decompression_ctx(struct ntfs_system_decompression_ctx *ctx);