diff --git a/configure.ac b/configure.ac index 1d99097e..7e85411b 100644 --- a/configure.ac +++ b/configure.ac @@ -23,8 +23,8 @@ # Autoconf AC_PREREQ(2.59) -AC_INIT([ntfs-3g],[2009.4.4],[ntfs-3g-devel@lists.sf.net]) -LIBNTFS_3G_VERSION="54" +AC_INIT([ntfs-3g],[2009.2.1],[ntfs-3g-devel@lists.sf.net]) +LIBNTFS_3G_VERSION="49" AC_CONFIG_SRCDIR([src/ntfs-3g.c]) # Environment diff --git a/include/ntfs-3g/attrib.h b/include/ntfs-3g/attrib.h index 6a9d31f3..073286ea 100644 --- a/include/ntfs-3g/attrib.h +++ b/include/ntfs-3g/attrib.h @@ -175,6 +175,7 @@ struct _ntfs_attr { runlist_element *rl; ntfs_inode *ni; ATTR_TYPES type; + ATTR_FLAGS data_flags; ntfschar *name; u32 name_len; unsigned long state; @@ -246,7 +247,8 @@ typedef union { } attr_val; extern void ntfs_attr_init(ntfs_attr *na, const BOOL non_resident, - const BOOL compressed, const BOOL encrypted, const BOOL sparse, + const ATTR_FLAGS data_flags, const BOOL encrypted, + const BOOL sparse, const s64 allocated_size, const s64 data_size, const s64 initialized_size, const s64 compressed_size, const u8 compression_unit); @@ -261,6 +263,7 @@ extern s64 ntfs_attr_pread(ntfs_attr *na, const s64 pos, s64 count, void *b); extern s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b); +extern int ntfs_attr_pclose(ntfs_attr *na); extern void *ntfs_attr_readall(ntfs_inode *ni, const ATTR_TYPES type, ntfschar *name, u32 name_len, s64 *data_size); @@ -295,6 +298,8 @@ extern int ntfs_attr_record_rm(ntfs_attr_search_ctx *ctx); extern int ntfs_attr_add(ntfs_inode *ni, ATTR_TYPES type, ntfschar *name, u8 name_len, u8 *val, s64 size); +extern int ntfs_attr_set_flags(ntfs_inode *ni, ATTR_TYPES type, + ntfschar *name, u8 name_len, ATTR_FLAGS flags, ATTR_FLAGS mask); extern int ntfs_attr_rm(ntfs_attr *na); extern int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size); diff --git a/include/ntfs-3g/compress.h b/include/ntfs-3g/compress.h index 83eb490d..809c3c93 100644 --- a/include/ntfs-3g/compress.h +++ b/include/ntfs-3g/compress.h @@ -29,5 +29,11 @@ extern s64 ntfs_compressed_attr_pread(ntfs_attr *na, s64 pos, s64 count, void *b); +extern s64 ntfs_compressed_pwrite(ntfs_attr *na, runlist_element *brl, s64 wpos, + s64 offs, s64 to_write, s64 rounded, + const void *b, int compressed_part); + +extern int ntfs_compressed_close(ntfs_attr *na, runlist_element *brl, s64 offs); + #endif /* defined _NTFS_COMPRESS_H */ diff --git a/include/ntfs-3g/runlist.h b/include/ntfs-3g/runlist.h index 0a3f87dd..43de53cc 100644 --- a/include/ntfs-3g/runlist.h +++ b/include/ntfs-3g/runlist.h @@ -49,6 +49,8 @@ struct _runlist_element {/* In memory vcn to lcn mapping structure element. */ s64 length; /* Run length in clusters. */ }; +extern runlist_element *ntfs_rl_extend(runlist_element *rl, int more_entries); + extern LCN ntfs_rl_vcn_to_lcn(const runlist_element *rl, const VCN vcn); extern s64 ntfs_rl_pread(const ntfs_volume *vol, const runlist_element *rl, diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c index 359b1a23..9b556b9d 100644 --- a/libntfs-3g/attrib.c +++ b/libntfs-3g/attrib.c @@ -59,6 +59,8 @@ #include "logging.h" #include "misc.h" +#define STANDARD_COMPRESSION_UNIT 4 + ntfschar AT_UNNAMED[] = { const_cpu_to_le16('\0') }; ntfschar STREAM_SDS[] = { const_cpu_to_le16('$'), const_cpu_to_le16('S'), @@ -330,15 +332,17 @@ static void __ntfs_attr_init(ntfs_attr *na, ntfs_inode *ni, * Final initialization for an ntfs attribute. */ void ntfs_attr_init(ntfs_attr *na, const BOOL non_resident, - const BOOL compressed, const BOOL encrypted, const BOOL sparse, + const ATTR_FLAGS data_flags, + const BOOL encrypted, const BOOL sparse, const s64 allocated_size, const s64 data_size, const s64 initialized_size, const s64 compressed_size, const u8 compression_unit) { if (!NAttrInitialized(na)) { + na->data_flags = data_flags; if (non_resident) NAttrSetNonResident(na); - if (compressed) + if (data_flags & ATTR_COMPRESSION_MASK) NAttrSetCompressed(na); if (encrypted) NAttrSetEncrypted(na); @@ -347,7 +351,7 @@ void ntfs_attr_init(ntfs_attr *na, const BOOL non_resident, na->allocated_size = allocated_size; na->data_size = data_size; na->initialized_size = initialized_size; - if (compressed || sparse) { + if ((data_flags & ATTR_COMPRESSION_MASK) || sparse) { ntfs_volume *vol = na->ni->vol; na->compressed_size = compressed_size; @@ -434,12 +438,25 @@ ntfs_attr *ntfs_attr_open(ntfs_inode *ni, const ATTR_TYPES type, */ if (type == AT_ATTRIBUTE_LIST) a->flags = 0; + + if ((type == AT_DATA) && !a->initialized_size) { + /* + * Define/redefine the compression state if stream is + * empty, based on the compression mark on parent + * directory (for unnamed data streams) or on current + * inode (for named data streams). The compression mark + * may change any time, the compression state can only + * change when stream is void. + */ + a->flags &= ~ATTR_COMPRESSION_MASK; + if (na->ni->flags & FILE_ATTR_COMPRESSED) + a->flags |= ATTR_IS_COMPRESSED; + } cs = a->flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE); if (na->type == AT_DATA && na->name == AT_UNNAMED && - ((!(a->flags & ATTR_IS_COMPRESSED) != !NAttrCompressed(na)) || - (!(a->flags & ATTR_IS_SPARSE) != !NAttrSparse(na)) || + ((!(a->flags & ATTR_IS_SPARSE) != !NAttrSparse(na)) || (!(a->flags & ATTR_IS_ENCRYPTED) != !NAttrEncrypted(na)))) { errno = EIO; ntfs_log_perror("Inode %lld has corrupt attribute flags " @@ -449,14 +466,15 @@ ntfs_attr *ntfs_attr_open(ntfs_inode *ni, const ATTR_TYPES type, } if (a->non_resident) { - if ((a->flags & ATTR_IS_COMPRESSED) && !a->compression_unit) { + if ((a->flags & ATTR_COMPRESSION_MASK) + && !a->compression_unit) { errno = EIO; ntfs_log_perror("Compressed inode %lld attr 0x%x has " "no compression unit", (unsigned long long)ni->mft_no, type); goto put_err_out; } - ntfs_attr_init(na, TRUE, a->flags & ATTR_IS_COMPRESSED, + ntfs_attr_init(na, TRUE, a->flags, a->flags & ATTR_IS_ENCRYPTED, a->flags & ATTR_IS_SPARSE, sle64_to_cpu(a->allocated_size), @@ -466,7 +484,7 @@ ntfs_attr *ntfs_attr_open(ntfs_inode *ni, const ATTR_TYPES type, cs ? a->compression_unit : 0); } else { s64 l = le32_to_cpu(a->value_length); - ntfs_attr_init(na, FALSE, a->flags & ATTR_IS_COMPRESSED, + ntfs_attr_init(na, FALSE, a->flags, a->flags & ATTR_IS_ENCRYPTED, a->flags & ATTR_IS_SPARSE, (l + 7) & ~7, l, l, cs ? (l + 7) & ~7 : 0, 0); @@ -794,8 +812,16 @@ static s64 ntfs_attr_pread_i(ntfs_attr *na, const s64 pos, s64 count, void *b) /* Sanity checking arguments is done in ntfs_attr_pread(). */ - if (NAttrCompressed(na) && NAttrNonResident(na)) - return ntfs_compressed_attr_pread(na, pos, count, b); + if ((na->data_flags & ATTR_COMPRESSION_MASK) && NAttrNonResident(na)) { + if ((na->data_flags & ATTR_COMPRESSION_MASK) + == ATTR_IS_COMPRESSED) + return ntfs_compressed_attr_pread(na, pos, count, b); + else { + /* compression mode not supported */ + errno = EOPNOTSUPP; + return -1; + } + } /* * Encrypted non-resident attributes are not supported. We return * access denied, which is what Windows NT4 does, too. @@ -1034,6 +1060,7 @@ static int ntfs_attr_fill_hole(ntfs_attr *na, s64 count, s64 *ofs, runlist_element **rl, VCN *update_from) { s64 to_write; + s64 need; ntfs_volume *vol = na->ni->vol; int eo, ret = -1; runlist *rlc; @@ -1067,7 +1094,16 @@ static int ntfs_attr_fill_hole(ntfs_attr *na, s64 count, s64 *ofs, while (rlc->vcn) { rlc--; if (rlc->lcn >= 0) { - lcn_seek_from = rlc->lcn + (from_vcn - rlc->vcn); + /* + * avoid fragmenting a compressed file + * Windows does not do that, and that may + * not be desirable for files which can + * be updated + */ + if (na->data_flags & ATTR_COMPRESSION_MASK) + lcn_seek_from = rlc->lcn + rlc->length; + else + lcn_seek_from = rlc->lcn + (from_vcn - rlc->vcn); break; } } @@ -1085,14 +1121,29 @@ static int ntfs_attr_fill_hole(ntfs_attr *na, s64 count, s64 *ofs, } } - rlc = ntfs_cluster_alloc(vol, from_vcn, - ((*ofs + to_write - 1) >> vol->cluster_size_bits) - + 1 + (*rl)->vcn - from_vcn, + need = ((*ofs + to_write - 1) >> vol->cluster_size_bits) + + 1 + (*rl)->vcn - from_vcn; + if ((na->data_flags & ATTR_COMPRESSION_MASK) + && (need < na->compression_block_clusters)) { + /* + * for a compressed file, be sure to allocate the full hole. + * We may need space to decompress existing compressed data. + */ + rlc = ntfs_cluster_alloc(vol, (*rl)->vcn, (*rl)->length, + lcn_seek_from, DATA_ZONE); + } else + rlc = ntfs_cluster_alloc(vol, from_vcn, need, lcn_seek_from, DATA_ZONE); if (!rlc) goto err_out; *rl = ntfs_runlists_merge(na->rl, rlc); + /* + * For a compressed attribute, we must be sure there is an + * available entry, so reserve it before it gets too late. + */ + if (*rl && (na->data_flags & ATTR_COMPRESSION_MASK)) + *rl = ntfs_rl_extend(*rl,1); if (!*rl) { eo = errno; ntfs_log_perror("Failed to merge runlists"); @@ -1177,14 +1228,19 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) s64 total = 0; VCN update_from = -1; ntfs_volume *vol; + s64 fullcount; ntfs_attr_search_ctx *ctx = NULL; runlist_element *rl; s64 hole_end; int eo; + int compressed_part; struct { unsigned int undo_initialized_size : 1; unsigned int undo_data_size : 1; } need_to = { 0, 0 }; + BOOL makingnonresident = FALSE; + BOOL wasnonresident = FALSE; + BOOL compressed; ntfs_log_enter("Entering for inode %lld, attr 0x%x, pos 0x%llx, count " "0x%llx.\n", (long long)na->ni->mft_no, na->type, @@ -1196,6 +1252,8 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) goto errno_set; } vol = na->ni->vol; + compressed = (na->data_flags & ATTR_COMPRESSION_MASK) + != const_cpu_to_le16(0); /* * Encrypted non-resident attributes are not supported. We return * access denied, which is what Windows NT4 does, too. @@ -1205,16 +1263,32 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) goto errno_set; } /* If this is a compressed attribute it needs special treatment. */ - if (NAttrCompressed(na)) { + wasnonresident = NAttrNonResident(na) != 0; + makingnonresident = wasnonresident /* yes : already changed */ + && !pos && (count == na->initialized_size); + /* + * Writing to compressed files is currently restricted + * to appending data. However we have to accept + * recursive write calls to make the attribute non resident. + * These are writing at position 0 up to initialized_size. + * Compression is also restricted to data streams. + * Only ATTR_IS_COMPRESSED compression mode is supported. + */ + if (compressed + && ((na->type != AT_DATA) + || ((na->data_flags & ATTR_COMPRESSION_MASK) + != ATTR_IS_COMPRESSED) + || ((pos != na->initialized_size) + && (pos || (count != na->initialized_size))))) { // TODO: Implement writing compressed attributes! (AIA) - // return ntfs_attr_pwrite_compressed(ntfs_attr *na, - // const s64 pos, s64 count, void *b); errno = EOPNOTSUPP; goto errno_set; } if (!count) goto out; + /* for a compressed file, get prepared to reserve a full block */ + fullcount = count; /* If the write reaches beyond the end, extend the attribute. */ old_data_size = na->data_size; if (pos + count > na->data_size) { @@ -1222,7 +1296,23 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) ntfs_log_perror("Failed to enlarge attribute"); goto errno_set; } + /* resizing may change the compression mode */ + compressed = (na->data_flags & ATTR_COMPRESSION_MASK) + != const_cpu_to_le16(0); need_to.undo_data_size = 1; + } + /* + * For compressed data, a single full block was allocated + * to deal with compression, possibly in a previous call. + * We are not able to process several blocks because + * some clusters are freed after compression and + * new allocations have to be done before proceeding, + * so truncate the requested count if needed (big buffers). + */ + if (compressed) { + fullcount = na->data_size - pos; + if (count > fullcount) + count = fullcount; } old_initialized_size = na->initialized_size; /* If it is a resident attribute, write the data to the mft record. */ @@ -1267,6 +1357,15 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) if (pos + count > na->initialized_size) { if (ntfs_attr_map_whole_runlist(na)) goto err_out; + /* + * For a compressed attribute, we must be sure there is an + * available entry, so reserve it before it gets too late. + */ + if (compressed) { + na->rl = ntfs_rl_extend(na->rl,1); + if (!na->rl) + goto err_out; + } /* Set initialized_size to @pos + @count. */ ctx = ntfs_attr_get_search_ctx(na->ni, NULL); if (!ctx) @@ -1282,6 +1381,9 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) goto err_out; ctx->attr->initialized_size = cpu_to_sle64(pos + count); + /* fix data_size for compressed files */ + if (compressed) + ctx->attr->data_size = ctx->attr->initialized_size; if (ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no, ctx->mrec)) { /* @@ -1315,16 +1417,57 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) */ if (errno == ENOENT) { errno = EIO; - ntfs_log_perror("%s: Failed to find VCN #1", __FUNCTION__); + ntfs_log_perror("%s: Failed to find VCN #3", __FUNCTION__); } goto err_out; } + ofs = pos - (rl->vcn << vol->cluster_size_bits); + /* + * Determine if there is compressed data in the current + * compression block (when appending to an existing file). + * If so, decompression will be needed, and the full block + * must be allocated to be identified as uncompressed. + * This comes in two variants, depending on whether + * compression has saved at least one cluster. + * The compressed size can never be over full size by + * more than 485 (maximum for 15 compression blocks + * compressed to 4098 and the last 3640 bytes compressed + * to 3640 + 3640/8 = 4095, with 15*2 + 4095 - 3640 = 485) + * This is less than the smallest cluster, so the hole is + * is never beyond the cluster next to the position of + * the first uncompressed byte to write. + */ + compressed_part = 0; + if (compressed) { + if ((rl->lcn >= 0) && (rl[1].lcn == (LCN)LCN_HOLE)) { + s64 xofs; + + if (wasnonresident) + compressed_part = na->compression_block_clusters + - rl[1].length; + rl++; + xofs = 0; + if (ntfs_attr_fill_hole(na, + rl->length << vol->cluster_size_bits, + &xofs, &rl, &update_from)) + goto err_out; + /* the fist allocated cluster was not merged */ + if (!xofs) + rl--; + } else + if ((rl->lcn == (LCN)LCN_HOLE) + && wasnonresident + && (rl->length < na->compression_block_clusters)) + compressed_part + = na->compression_block_clusters + - rl->length; + /* normal hole filling will do later */ + } /* * Scatter the data from the linear data buffer to the volume. Note, a * partial final vcn is taken care of by the @count capping of write * length. */ - ofs = pos - (rl->vcn << vol->cluster_size_bits); for (hole_end = 0; count; rl++, ofs = 0, hole_end = 0) { if (rl->lcn == LCN_RL_NOT_MAPPED) { rl = ntfs_attr_find_vcn(na, rl->vcn); @@ -1332,7 +1475,7 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) if (errno == ENOENT) { errno = EIO; ntfs_log_perror("%s: Failed to find VCN" - " #2", __FUNCTION__); + " #4", __FUNCTION__); } goto rl_err_out; } @@ -1345,7 +1488,6 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) goto rl_err_out; } if (rl->lcn < (LCN)0) { - hole_end = rl->vcn + rl->length; if (rl->lcn != (LCN)LCN_HOLE) { @@ -1355,10 +1497,17 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) (long long)rl->lcn); goto rl_err_out; } - - if (ntfs_attr_fill_hole(na, count, &ofs, &rl, &update_from)) + if (ntfs_attr_fill_hole(na, fullcount, &ofs, &rl, + &update_from)) goto err_out; } + if (compressed) { + while (ofs >= (rl->length << vol->cluster_size_bits)) { + ofs -= rl->length << vol->cluster_size_bits; + rl++; + } + } + /* It is a real lcn, write it to the volume. */ to_write = min(count, (rl->length << vol->cluster_size_bits) - ofs); retry: @@ -1378,6 +1527,9 @@ retry: * This will cause the kernel not to seek and read disk * blocks during write(2) to fill the end of the buffer * which increases write speed by 2-10 fold typically. + * + * This is done even for compressed files, because + * data is generally first written uncompressed. */ if (rounding && ((wend == na->initialized_size) || (wend < (hole_end << vol->cluster_size_bits)))){ @@ -1393,13 +1545,27 @@ retry: memcpy(cb, b, to_write); memset(cb + to_write, 0, rounding - to_write); - written = ntfs_pwrite(vol->dev, wpos, rounding, cb); - if (written == rounding) - written = to_write; + if (compressed) { + written = ntfs_compressed_pwrite(na, + rl, wpos, ofs, to_write, + rounding, b, compressed_part); + } else { + written = ntfs_pwrite(vol->dev, wpos, + rounding, cb); + if (written == rounding) + written = to_write; + } free(cb); - } else - written = ntfs_pwrite(vol->dev, wpos, to_write, b); + } else { + if (compressed) { + written = ntfs_compressed_pwrite(na, + rl, wpos, ofs, to_write, + to_write, b, compressed_part); + } else + written = ntfs_pwrite(vol->dev, wpos, + to_write, b); + } } else written = to_write; /* If everything ok, update progress counters and continue. */ @@ -1408,20 +1574,23 @@ retry: count -= written; b = (const u8*)b + written; } - if (written == to_write) - continue; - /* If the syscall was interrupted, try again. */ - if (written == (s64)-1 && errno == EINTR) - goto retry; - if (!written) - errno = EIO; - goto rl_err_out; + if (written != to_write) { + /* Partial write cannot be dealt with, stop there */ + /* If the syscall was interrupted, try again. */ + if (written == (s64)-1 && errno == EINTR) + goto retry; + if (!written) + errno = EIO; + goto rl_err_out; + } + compressed_part = 0; } done: if (ctx) ntfs_attr_put_search_ctx(ctx); /* Update mapping pairs if needed. */ - if (update_from != -1) + if ((update_from != -1) + || (compressed && !makingnonresident)) if (ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/)) { /* * FIXME: trying to recover by goto rl_err_out; @@ -1497,6 +1666,172 @@ errno_set: goto out; } +int ntfs_attr_pclose(ntfs_attr *na) +{ + s64 written, ofs; + BOOL ok = TRUE; + VCN update_from = -1; + ntfs_volume *vol; + ntfs_attr_search_ctx *ctx = NULL; + runlist_element *rl; + int eo; + s64 hole; + int compressed_part; + BOOL compressed; + + ntfs_log_enter("Entering for inode 0x%llx, attr 0x%x.\n", + na->ni->mft_no, na->type); + + if (!na || !na->ni || !na->ni->vol) { + errno = EINVAL; + ntfs_log_perror("%s", __FUNCTION__); + goto errno_set; + } + vol = na->ni->vol; + compressed = (na->data_flags & ATTR_COMPRESSION_MASK) + != const_cpu_to_le16(0); + /* + * Encrypted non-resident attributes are not supported. We return + * access denied, which is what Windows NT4 does, too. + */ + if (NAttrEncrypted(na) && NAttrNonResident(na)) { + errno = EACCES; + goto errno_set; + } + /* If this is not a compressed attribute get out */ + /* same if it is resident */ + if (!compressed || !NAttrNonResident(na)) + goto out; + + /* + * For a compressed attribute, we must be sure there is an + * available entry, so reserve it before it gets too late. + */ + if (ntfs_attr_map_whole_runlist(na)) + goto err_out; + na->rl = ntfs_rl_extend(na->rl,1); + if (!na->rl) + goto err_out; + /* Find the runlist element containing the terminal vcn. */ + rl = ntfs_attr_find_vcn(na, (na->initialized_size - 1) >> vol->cluster_size_bits); + if (!rl) { + /* + * If the vcn is not present it is an out of bounds write. + * However, we have already written the last byte uncompressed, + * so getting this here must be an error of some kind. + */ + if (errno == ENOENT) { + errno = EIO; + ntfs_log_perror("%s: Failed to find VCN #5", __FUNCTION__); + } + goto err_out; + } + /* + * Scatter the data from the linear data buffer to the volume. Note, a + * partial final vcn is taken care of by the @count capping of write + * length. + */ + compressed_part = 0; + if ((rl->lcn >= 0) && (rl[1].lcn == (LCN)LCN_HOLE)) + compressed_part + = na->compression_block_clusters - rl[1].length; + else + if ((rl->lcn == (LCN)LCN_HOLE) + && (rl->length < na->compression_block_clusters)) + compressed_part + = na->compression_block_clusters + - rl->length; + /* done, if the last block set was compressed */ + if (compressed_part) + goto out; + + ofs = na->initialized_size - (rl->vcn << vol->cluster_size_bits); + + if (rl->lcn == LCN_RL_NOT_MAPPED) { + rl = ntfs_attr_find_vcn(na, rl->vcn); + if (!rl) { + if (errno == ENOENT) { + errno = EIO; + ntfs_log_perror("%s: Failed to find VCN" + " #6", __FUNCTION__); + } + goto rl_err_out; + } + /* Needed for case when runs merged. */ + ofs = na->initialized_size - (rl->vcn << vol->cluster_size_bits); + } + if (!rl->length) { + errno = EIO; + ntfs_log_perror("%s: Zero run length", __FUNCTION__); + goto rl_err_out; + } + if (rl->lcn < (LCN)0) { + hole = rl->vcn + rl->length; + if (rl->lcn != (LCN)LCN_HOLE) { + errno = EIO; + ntfs_log_perror("%s: Unexpected LCN (%lld)", + __FUNCTION__, + (long long)rl->lcn); + goto rl_err_out; + } + + if (ntfs_attr_fill_hole(na, (s64)0, &ofs, &rl, &update_from)) + goto err_out; + } + while (ofs >= (rl->length << vol->cluster_size_bits)) { + ofs -= rl->length << vol->cluster_size_bits; + rl++; + } + +retry: + if (!NVolReadOnly(vol)) { + + written = ntfs_compressed_close(na, rl, ofs); + /* If everything ok, update progress counters and continue. */ + if (!written) + goto done; + } + /* If the syscall was interrupted, try again. */ + if (written == (s64)-1 && errno == EINTR) + goto retry; + if (!written) + errno = EIO; + goto rl_err_out; + +done: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + /* Update mapping pairs if needed. */ + if (ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/)) { + /* + * FIXME: trying to recover by goto rl_err_out; + * could cause driver hang by infinite looping. + */ + ok = FALSE; + goto out; + } +out: + ntfs_log_leave("\n"); + return (!ok); +rl_err_out: + /* + * need not restore old sizes, only compressed_size + * can have changed. It has been set according to + * the current runlist while updating the mapping pairs, + * and must be kept consistent with the runlists. + */ +err_out: + eo = errno; + if (ctx) + ntfs_attr_put_search_ctx(ctx); + /* Update mapping pairs if needed. */ + ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/); + errno = eo; +errno_set: + ok = FALSE; + goto out; +} + /** * ntfs_attr_mst_pread - multi sector transfer protected ntfs attribute read * @na: multi sector transfer protected ntfs attribute to read from @@ -2727,7 +3062,7 @@ int ntfs_make_room_for_attr(MFT_RECORD *m, u8 *pos, u32 size) */ int ntfs_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type, ntfschar *name, u8 name_len, u8 *val, u32 size, - ATTR_FLAGS flags) + ATTR_FLAGS data_flags) { ntfs_attr_search_ctx *ctx; u32 length; @@ -2791,8 +3126,9 @@ int ntfs_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type, a->non_resident = 0; a->name_length = name_len; a->name_offset = (name_len - ? cpu_to_le16(offsetof(ATTR_RECORD, resident_end)) : 0); - a->flags = flags; + ? cpu_to_le16(offsetof(ATTR_RECORD, resident_end)) + : const_cpu_to_le16(0)); + a->flags = data_flags; a->instance = m->next_attr_instance; a->value_length = cpu_to_le32(size); a->value_offset = cpu_to_le16(length - ((size + 7) & ~7)); @@ -2929,7 +3265,8 @@ int ntfs_non_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type, a->instance = m->next_attr_instance; a->lowest_vcn = cpu_to_sle64(lowest_vcn); a->mapping_pairs_offset = cpu_to_le16(length - dataruns_size); - a->compression_unit = (flags & ATTR_IS_COMPRESSED) ? 4 : 0; + a->compression_unit = (flags & ATTR_IS_COMPRESSED) + ? STANDARD_COMPRESSION_UNIT : 0; /* If @lowest_vcn == 0, than setup empty attribute. */ if (!lowest_vcn) { a->highest_vcn = cpu_to_sle64(-1); @@ -3255,9 +3592,17 @@ int ntfs_attr_add(ntfs_inode *ni, ATTR_TYPES type, add_attr_record: if (is_resident) { + ATTR_FLAGS data_flags; + + if ((ni->flags & FILE_ATTR_COMPRESSED) + && ((type == AT_DATA) + || ((type == AT_INDEX_ROOT) && (name == NTFS_INDEX_I30)))) + data_flags = ATTR_IS_COMPRESSED; + else + data_flags = const_cpu_to_le16(0); /* Add resident attribute. */ offset = ntfs_resident_attr_record_add(attr_ni, type, name, - name_len, val, size, 0); + name_len, val, size, data_flags); if (offset < 0) { if (errno == ENOSPC && can_be_non_resident) goto add_non_resident; @@ -3271,7 +3616,7 @@ add_attr_record: add_non_resident: /* Add non resident attribute. */ offset = ntfs_non_resident_attr_record_add(attr_ni, type, name, - name_len, 0, 8, 0); + name_len, 0, 8, const_cpu_to_le16(0)); if (offset < 0) { err = errno; ntfs_log_perror("Failed to add non resident attribute"); @@ -3318,6 +3663,34 @@ err_out: return -1; } +/* + * Change an attribute flag + */ + +int ntfs_attr_set_flags(ntfs_inode *ni, ATTR_TYPES type, + ntfschar *name, u8 name_len, ATTR_FLAGS flags, ATTR_FLAGS mask) +{ + ntfs_attr_search_ctx *ctx; + int res; + + res = -1; + /* Search for designated attribute */ + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (ctx) { + if (!ntfs_attr_lookup(type, name, name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx)) { + /* do the requested change (all small endian le16) */ + ctx->attr->flags = (ctx->attr->flags & ~mask) + | (flags & mask); + NInoSetDirty(ni); + res = 0; + } + ntfs_attr_put_search_ctx(ctx); + } + return (res); +} + + /** * ntfs_attr_rm - remove attribute from ntfs inode * @na: opened ntfs attribute to delete @@ -3724,7 +4097,6 @@ static int ntfs_attr_make_non_resident(ntfs_attr *na, * FIXME: For now just clear all of these as we don't support them when * writing. */ - NAttrClearCompressed(na); NAttrClearSparse(na); NAttrClearEncrypted(na); @@ -3751,7 +4123,10 @@ static int ntfs_attr_make_non_resident(ntfs_attr *na, goto cluster_free_err_out; } /* Calculate new offsets for the name and the mapping pairs array. */ - name_ofs = (sizeof(ATTR_REC) - sizeof(a->compressed_size) + 7) & ~7; + if (na->ni->flags & FILE_ATTR_COMPRESSED) + name_ofs = (sizeof(ATTR_REC) + 7) & ~7; + else + name_ofs = (sizeof(ATTR_REC) - sizeof(a->compressed_size) + 7) & ~7; mp_ofs = (name_ofs + a->name_length * sizeof(ntfschar) + 7) & ~7; /* * Determine the size of the resident part of the non-resident @@ -3778,10 +4153,6 @@ static int ntfs_attr_make_non_resident(ntfs_attr *na, a->name_length * sizeof(ntfschar)); a->name_offset = cpu_to_le16(name_ofs); - /* Update the flags to match the in-memory ones. */ - a->flags &= ~(ATTR_IS_SPARSE | ATTR_IS_ENCRYPTED | - ATTR_COMPRESSION_MASK); - /* Setup the fields specific to non-resident attributes. */ a->lowest_vcn = cpu_to_sle64(0); a->highest_vcn = cpu_to_sle64((new_allocated_size - 1) >> @@ -3789,7 +4160,26 @@ static int ntfs_attr_make_non_resident(ntfs_attr *na, a->mapping_pairs_offset = cpu_to_le16(mp_ofs); - a->compression_unit = 0; + /* + * Update the flags to match the in-memory ones. + * However cannot change the compression state if we had + * a fuse_file_info open with a mark for release. + * The decisions about compression can only be made when + * creating/recreating the stream, not when making non resident. + */ + a->flags &= ~(ATTR_IS_SPARSE | ATTR_IS_ENCRYPTED); + if ((a->flags & ATTR_COMPRESSION_MASK) == ATTR_IS_COMPRESSED) { + /* support only ATTR_IS_COMPRESSED compression mode */ + a->compression_unit = STANDARD_COMPRESSION_UNIT; + a->compressed_size = const_cpu_to_le64(0); + na->compression_block_size + = 1 << (a->compression_unit + vol->cluster_size_bits); + na->compression_block_clusters = 1 << STANDARD_COMPRESSION_UNIT; + } else { + a->compression_unit = 0; + a->flags &= ~ATTR_COMPRESSION_MASK; + na->data_flags = a->flags; + } memset(&a->reserved1, 0, sizeof(a->reserved1)); @@ -3886,7 +4276,8 @@ static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize) /* Update attribute size everywhere. */ na->data_size = na->initialized_size = newsize; na->allocated_size = (newsize + 7) & ~7; - if (NAttrCompressed(na) || NAttrSparse(na)) + if ((na->data_flags & ATTR_COMPRESSION_MASK) + || NAttrSparse(na)) na->compressed_size = na->allocated_size; if (na->type == AT_DATA && na->name == AT_UNNAMED) { na->ni->data_size = na->data_size; @@ -4109,8 +4500,8 @@ static int ntfs_attr_make_resident(ntfs_attr *na, ntfs_attr_search_ctx *ctx) if (ntfs_attr_can_be_resident(vol, na->type)) return -1; - if (NAttrCompressed(na) || NAttrEncrypted(na)) { - ntfs_log_trace("Making compressed or encrypted files resident is not " + if (NAttrEncrypted(na)) { + ntfs_log_trace("Making encrypted files resident is not " "implemented yet.\n"); errno = EOPNOTSUPP; return -1; @@ -4158,6 +4549,16 @@ static int ntfs_attr_make_resident(ntfs_attr *na, ntfs_attr_search_ctx *ctx) a->flags = 0; a->value_length = cpu_to_le32(na->data_size); a->value_offset = cpu_to_le16(val_ofs); + /* + * If a data stream was wiped out, adjust the compression mode + * to current state of compression flag + */ + if (!na->data_size + && (na->type == AT_DATA) + && (na->ni->flags & FILE_ATTR_COMPRESSED)) { + a->flags |= ATTR_IS_COMPRESSED; + na->data_flags = a->flags; + } /* * File names cannot be non-resident so we would never see this here * but at least it serves as a reminder that there may be attributes @@ -4211,7 +4612,6 @@ static int ntfs_attr_make_resident(ntfs_attr *na, ntfs_attr_search_ctx *ctx) /* Update in-memory struct ntfs_attr. */ NAttrClearNonResident(na); - NAttrClearCompressed(na); NAttrClearSparse(na); NAttrClearEncrypted(na); na->initialized_size = na->data_size; @@ -4279,8 +4679,8 @@ static int ntfs_attr_update_meta(ATTR_RECORD *a, ntfs_attr *na, MFT_RECORD *m, NAttrSetSparse(na); a->flags |= ATTR_IS_SPARSE; - a->compression_unit = 4; /* Windows set it so, even if attribute - is not actually compressed. */ + a->compression_unit = STANDARD_COMPRESSION_UNIT; /* Windows + set it so, even if attribute is not actually compressed. */ memmove((u8*)a + le16_to_cpu(a->name_offset) + 8, (u8*)a + le16_to_cpu(a->name_offset), @@ -4312,7 +4712,7 @@ static int ntfs_attr_update_meta(ATTR_RECORD *a, ntfs_attr *na, MFT_RECORD *m, } /* Update compressed size if required. */ - if (sparse) { + if (sparse || (na->data_flags & ATTR_COMPRESSION_MASK)) { s64 new_compr_size; new_compr_size = ntfs_rl_get_compressed_size(na->ni->vol, na->rl); @@ -4588,7 +4988,8 @@ retry: cur_max_mp_size = le32_to_cpu(m->bytes_allocated) - le32_to_cpu(m->bytes_in_use) - (offsetof(ATTR_RECORD, compressed_size) + - ((NAttrCompressed(na) || NAttrSparse(na)) ? + (((na->data_flags & ATTR_COMPRESSION_MASK) + || NAttrSparse(na)) ? sizeof(a->compressed_size) : 0)) - ((sizeof(ntfschar) * na->name_len + 7) & ~7); if (mp_size > cur_max_mp_size) @@ -5047,6 +5448,8 @@ static int ntfs_non_resident_attr_expand(ntfs_attr *na, const s64 newsize) int ntfs_attr_truncate(ntfs_attr *na, const s64 newsize) { int ret = STATUS_ERROR; + s64 fullsize; + BOOL compressed; if (!na || newsize < 0 || (na->ni->mft_no == FILE_MFT && na->type == AT_DATA)) { @@ -5075,17 +5478,41 @@ int ntfs_attr_truncate(ntfs_attr *na, const s64 newsize) } /* * TODO: Implement making handling of compressed attributes. + * Currently we can only expand the attribute or delete it, + * and only for ATTR_IS_COMPRESSED. This is however possible + * for resident attributes when there is no open fuse context + * (important case : $INDEX_ROOT:$I30) */ - if (NAttrCompressed(na)) { + compressed = (na->data_flags & ATTR_COMPRESSION_MASK) + != const_cpu_to_le16(0); + if (compressed + && NAttrNonResident(na) + && (((na->data_flags & ATTR_COMPRESSION_MASK) != ATTR_IS_COMPRESSED) + || (newsize && (newsize < na->data_size)))) { errno = EOPNOTSUPP; ntfs_log_perror("Failed to truncate compressed attribute"); goto out; } if (NAttrNonResident(na)) { - if (newsize > na->data_size) - ret = ntfs_non_resident_attr_expand(na, newsize); + /* + * For compressed data, the last block must be fully + * allocated, and we do not known the size of compression + * block until the attribute has been made non-resident. + * Moreover we can only process a single compression + * block at a time, so we silently do not allocate more. + * + * Note : do not request truncate on compressed files + * unless being able to face the consequences ! + */ + if (compressed && newsize) + fullsize = (na->data_size + | (na->compression_block_size - 1)) + 1; else - ret = ntfs_non_resident_attr_shrink(na, newsize); + fullsize = newsize; + if (fullsize > na->data_size) + ret = ntfs_non_resident_attr_expand(na, fullsize); + else + ret = ntfs_non_resident_attr_shrink(na, fullsize); } else ret = ntfs_resident_attr_resize(na, newsize); out: diff --git a/libntfs-3g/compress.c b/libntfs-3g/compress.c index 09f874bb..35fdad9e 100644 --- a/libntfs-3g/compress.c +++ b/libntfs-3g/compress.c @@ -5,6 +5,7 @@ * Copyright (c) 2004-2005 Anton Altaparmakov * Copyright (c) 2004-2006 Szabolcs Szakacsits * Copyright (c) 2005 Yura Pakhuchiy + * Copyright (c) 2009 Jean-Pierre Andre * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -46,6 +47,7 @@ #include "layout.h" #include "runlist.h" #include "compress.h" +#include "lcnalloc.h" #include "logging.h" #include "misc.h" @@ -64,6 +66,261 @@ typedef enum { NTFS_SB_IS_COMPRESSED = 0x8000, } ntfs_compression_constants; +#define THRESHOLD 3 /* minimal match length for compression */ +#define NIL NTFS_SB_SIZE /* End of tree's node */ + +struct COMPRESS_CONTEXT { + const unsigned char *inbuf; + unsigned int len; + unsigned int nbt; + int match_position; + unsigned int match_length; + u16 lson[NTFS_SB_SIZE + 1]; + u16 rson[NTFS_SB_SIZE + 257]; + u16 dad[NTFS_SB_SIZE + 1]; +} ; + +/* + * Initialize the match tree + */ + +static void ntfs_init_compress_tree(struct COMPRESS_CONTEXT *pctx) +{ + int i; + + for (i = NTFS_SB_SIZE + 1; i <= NTFS_SB_SIZE + 256; i++) + pctx->rson[i] = NIL; /* root */ + for (i = 0; i < NTFS_SB_SIZE; i++) + pctx->dad[i] = NIL; /* node */ +} + +/* + * Insert a new node into match tree for quickly locating + * further similar strings + */ + +static void ntfs_new_node (struct COMPRESS_CONTEXT *pctx, + unsigned int r) +{ + unsigned int i; + unsigned int pp; + BOOL less; + BOOL done; + const unsigned char *key; + int c; + unsigned int mxi; + unsigned int mxl; + + mxl = (1 << (16 - pctx->nbt)) + 2; + less = FALSE; + done = FALSE; + key = &pctx->inbuf[r]; + pp = NTFS_SB_SIZE + 1 + key[0]; + pctx->rson[r] = pctx->lson[r] = NIL; + pctx->match_length = 0; + do { + if (!less) { + if (pctx->rson[pp] != NIL) + pp = pctx->rson[pp]; + else { + pctx->rson[pp] = r; + pctx->dad[r] = pp; + done = TRUE; + } + } else { + if (pctx->lson[pp] != NIL) + pp = pctx->lson[pp]; + else { + pctx->lson[pp] = r; + pctx->dad[r] = pp; + done = TRUE; + } + } + if (!done) { + register const unsigned char *p1,*p2; + + i = 1; + p1 = key; + p2 = &pctx->inbuf[pp]; + mxi = NTFS_SB_SIZE - r; + do { + } while ((p1[i] == p2[i]) && (++i < mxi)); + less = (i < mxi) && (p1[i] < p2[i]); + if (i >= THRESHOLD) { + if (i > pctx->match_length) { + pctx->match_position = + r - pp + 2*NTFS_SB_SIZE - 1; + if ((pctx->match_length = i) > mxl) { + i = pctx->dad[pp]; + pctx->dad[r] = i; + pctx->lson[r] = pctx->lson[pp]; + pctx->rson[r] = pctx->rson[pp]; + pctx->dad[pctx->lson[pp]] = r; + pctx->dad[pctx->rson[pp]] = r; + if (pctx->rson[i] == pp) + pctx->rson[i] = r; + else + pctx->lson[i] = r; + pctx->dad[pp] = NIL; /* remove pp */ + done = TRUE; + pctx->match_length = mxl; + } + } else + if ((i == pctx->match_length) + && ((c = (r - pp + 2*NTFS_SB_SIZE - 1)) + < pctx->match_position)) + pctx->match_position = c; + } + } + } while (!done); +} + +/* + * Search for the longest previous string matching the + * current one + * + * Returns the end of the longest current string which matched + * or zero if there was a bug + */ + +static unsigned int ntfs_nextmatch(struct COMPRESS_CONTEXT *pctx, unsigned int rr, int dd) +{ + unsigned int bestlen = 0; + + do { + rr++; + if (pctx->match_length > 0) + pctx->match_length--; + if (!pctx->len) { + ntfs_log_error("compress bug : void run\n"); + goto bug; + } + if (--pctx->len) { + if (rr >= NTFS_SB_SIZE) { + ntfs_log_error("compress bug : buffer overflow\n"); + goto bug; + } + if (((rr + bestlen) < NTFS_SB_SIZE)) { + while ((unsigned int)(1 << pctx->nbt) <= (rr - 1)) + pctx->nbt++; + ntfs_new_node(pctx,rr); + if (pctx->match_length > bestlen) + bestlen = pctx->match_length; + } else + if (dd > 0) { + rr += dd; + if ((int)pctx->match_length > dd) + pctx->match_length -= dd; + else + pctx->match_length = 0; + if ((int)pctx->len < dd) { + ntfs_log_error("compress bug : run overflows\n"); + goto bug; + } + pctx->len -= dd; + dd = 0; + } + } + } while (dd-- > 0); + return (rr); +bug : + return (0); +} + +/* + * Compress an input block + * + * Returns the size of the compressed block (including header) + * or zero if there was an error + */ + +static unsigned int ntfs_compress_block(const char *inbuf, unsigned int size, char *outbuf) +{ + struct COMPRESS_CONTEXT *pctx; + char *ptag; + int dd; + unsigned int rr; + unsigned int last_match_length; + unsigned int q; + unsigned int xout; + unsigned int ntag; + + pctx = (struct COMPRESS_CONTEXT*)malloc(sizeof(struct COMPRESS_CONTEXT)); + if (pctx) { + pctx->inbuf = (const unsigned char*)inbuf; + ntfs_init_compress_tree(pctx); + xout = 2; + ntag = 0; + ptag = &outbuf[xout++]; + *ptag = 0; + rr = 0; + pctx->nbt = 4; + pctx->len = size; + pctx->match_length = 0; + ntfs_new_node(pctx,0); + do { + if (pctx->match_length > pctx->len) + pctx->match_length = pctx->len; + if (pctx->match_length < THRESHOLD) { + pctx->match_length = 1; + if (ntag >= 8) { + ntag = 0; + ptag = &outbuf[xout++]; + *ptag = 0; + } + outbuf[xout++] = inbuf[rr]; + ntag++; + } else { + while ((unsigned int)(1 << pctx->nbt) <= (rr - 1)) + pctx->nbt++; + q = (pctx->match_position << (16 - pctx->nbt)) + + pctx->match_length - THRESHOLD; + if (ntag >= 8) { + ntag = 0; + ptag = &outbuf[xout++]; + *ptag = 0; + } + *ptag |= 1 << ntag++; + outbuf[xout++] = q & 255; + outbuf[xout++] = (q >> 8) & 255; + } + last_match_length = pctx->match_length; + dd = last_match_length; + if (dd-- > 0) { + rr = ntfs_nextmatch(pctx,rr,dd); + if (!rr) + goto bug; + } + /* + * stop if input is exhausted or output has exceeded + * the maximum size. Two extra bytes have to be + * reserved in output buffer, as 3 bytes may be + * output in a loop. + */ + } while ((pctx->len > 0) + && (rr < size) && (xout < (NTFS_SB_SIZE + 2))); + /* uncompressed must be full size, so accept if better */ + if (xout < (NTFS_SB_SIZE + 2)) { + outbuf[0] = (xout - 3) & 255; + outbuf[1] = 0xb0 + (((xout - 3) >> 8) & 15); + } else { + memcpy(&outbuf[2],inbuf,size); + if (size < NTFS_SB_SIZE) + memset(&outbuf[size+2],0,NTFS_SB_SIZE - size); + outbuf[0] = 0xff; + outbuf[1] = 0x3f; + xout = NTFS_SB_SIZE + 2; + } + free(pctx); + } else { + xout = 0; + errno = ENOMEM; + } + return (xout); /* 0 for an error, > size if cannot compress */ +bug : + return (0); +} + /** * ntfs_decompress - decompress a compression block into an array of pages * @dest: buffer to which to write the decompressed data @@ -108,7 +365,7 @@ do_next_sb: * position in the compression block is one byte before its end so the * first two checks do not detect it. */ - if (cb == cb_end || !le16_to_cpup((u16*)cb) || dest == dest_end) { + if (cb == cb_end || !le16_to_cpup((le16*)cb) || dest == dest_end) { ntfs_log_debug("Completed. Returning success (0).\n"); return 0; } @@ -123,12 +380,12 @@ do_next_sb: goto return_overflow; /* Setup the current sub-block source pointers and validate range. */ cb_sb_start = cb; - cb_sb_end = cb_sb_start + (le16_to_cpup((u16*)cb) & NTFS_SB_SIZE_MASK) + cb_sb_end = cb_sb_start + (le16_to_cpup((le16*)cb) & NTFS_SB_SIZE_MASK) + 3; if (cb_sb_end > cb_end) goto return_overflow; /* Now, we are ready to process the current sub-block (sb). */ - if (!(le16_to_cpup((u16*)cb) & NTFS_SB_IS_COMPRESSED)) { + if (!(le16_to_cpup((le16*)cb) & NTFS_SB_IS_COMPRESSED)) { ntfs_log_debug("Found uncompressed sub-block.\n"); /* This sb is not compressed, just copy it into destination. */ /* Advance source position to first data byte. */ @@ -202,7 +459,7 @@ do_next_tag: for (i = dest - dest_sb_start - 1; i >= 0x10; i >>= 1) lg++; /* Get the phrase token into i. */ - pt = le16_to_cpup((u16*)cb); + pt = le16_to_cpup((le16*)cb); /* * Calculate starting position of the byte sequence in * the destination using the fact that p = (pt >> (12 - lg)) + 1 @@ -332,13 +589,19 @@ s64 ntfs_compressed_attr_pread(ntfs_attr *na, s64 pos, s64 count, void *b) u8 *dest, *cb, *cb_pos, *cb_end; u32 cb_size; int err; + ATTR_FLAGS data_flags; + FILE_ATTR_FLAGS compression; unsigned int nr_cbs, cb_clusters; ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, pos 0x%llx, count 0x%llx.\n", (unsigned long long)na->ni->mft_no, na->type, (long long)pos, (long long)count); - if (!na || !NAttrCompressed(na) || !na->ni || !na->ni->vol || !b || - pos < 0 || count < 0) { + data_flags = na->data_flags; + compression = na->ni->flags & FILE_ATTR_COMPRESSED; + if (!na || !na->ni || !na->ni->vol || !b + || ((data_flags & ATTR_COMPRESSION_MASK) + != ATTR_IS_COMPRESSED) + || pos < 0 || count < 0) { errno = EINVAL; return -1; } @@ -379,12 +642,12 @@ s64 ntfs_compressed_attr_pread(ntfs_attr *na, s64 pos, s64 count, void *b) cb_clusters = na->compression_block_clusters; /* Need a temporary buffer for each loaded compression block. */ - cb = ntfs_malloc(cb_size); + cb = (u8*)ntfs_malloc(cb_size); if (!cb) return -1; /* Need a temporary buffer for each uncompressed block. */ - dest = ntfs_malloc(cb_size); + dest = (u8*)ntfs_malloc(cb_size); if (!dest) { free(cb); return -1; @@ -450,6 +713,7 @@ do_next_cb: to_read = min(count, cb_size - ofs); ofs += vcn << vol->cluster_size_bits; NAttrClearCompressed(na); + na->data_flags &= ~ATTR_COMPRESSION_MASK; tdata_size = na->data_size; tinitialized_size = na->initialized_size; na->data_size = na->initialized_size = na->allocated_size; @@ -459,7 +723,8 @@ do_next_cb: err = errno; na->data_size = tdata_size; na->initialized_size = tinitialized_size; - NAttrSetCompressed(na); + na->ni->flags |= compression; + na->data_flags = data_flags; free(cb); free(dest); if (total) @@ -475,7 +740,8 @@ do_next_cb: } while (to_read > 0); na->data_size = tdata_size; na->initialized_size = tinitialized_size; - NAttrSetCompressed(na); + na->ni->flags |= compression; + na->data_flags = data_flags; ofs = 0; } else { s64 tdata_size, tinitialized_size; @@ -496,6 +762,7 @@ do_next_cb: */ to_read = cb_size; NAttrClearCompressed(na); + na->data_flags &= ~ATTR_COMPRESSION_MASK; tdata_size = na->data_size; tinitialized_size = na->initialized_size; na->data_size = na->initialized_size = na->allocated_size; @@ -507,7 +774,8 @@ do_next_cb: err = errno; na->data_size = tdata_size; na->initialized_size = tinitialized_size; - NAttrSetCompressed(na); + na->ni->flags |= compression; + na->data_flags = data_flags; free(cb); free(dest); if (total) @@ -520,7 +788,8 @@ do_next_cb: } while (to_read > 0); na->data_size = tdata_size; na->initialized_size = tinitialized_size; - NAttrSetCompressed(na); + na->ni->flags |= compression; + na->data_flags = data_flags; /* Just a precaution. */ if (cb_pos + 2 <= cb_end) *(u16*)cb_pos = 0; @@ -550,3 +819,537 @@ do_next_cb: /* Return number of bytes read. */ return total + total2; } + +/* + * Read data from a set of clusters + * + * Returns the amount of data read + */ + +static int read_clusters(ntfs_volume *vol, const runlist_element *rl, + s64 offs, int to_read, char *inbuf) +{ + int count; + int got, xgot; + s64 xpos; + BOOL first; + char *xinbuf; + const runlist_element *xrl; + + got = 0; + xrl = rl; + xinbuf = inbuf; + first = TRUE; + do { + count = xrl->length << vol->cluster_size_bits; + xpos = xrl->lcn << vol->cluster_size_bits; + if (first) { + count -= offs; + xpos += offs; + } + if ((to_read - got) < count) + count = to_read - got; + xgot = ntfs_pread(vol->dev, xpos, count, xinbuf); + if (xgot == count) { + got += count; + xpos += count; + xinbuf += count; + xrl++; + } + first = FALSE; + } while ((xgot == count) && (got < to_read)); + return (got); +} + +/* + * Write data to a set of clusters + * + * Returns the amount of data written + */ + +static int write_clusters(ntfs_volume *vol, const runlist_element *rl, + s64 offs, int to_write, const char *outbuf) +{ + int count; + int put, xput; + s64 xpos; + BOOL first; + const char *xoutbuf; + const runlist_element *xrl; + + put = 0; + xrl = rl; + xoutbuf = outbuf; + first = TRUE; + do { + count = xrl->length << vol->cluster_size_bits; + xpos = xrl->lcn << vol->cluster_size_bits; + if (first) { + count -= offs; + xpos += offs; + } + if ((to_write - put) < count) + count = to_write - put; + xput = ntfs_pwrite(vol->dev, xpos, count, xoutbuf); + if (xput == count) { + put += count; + xpos += count; + xoutbuf += count; + xrl++; + } + first = FALSE; + } while ((xput == count) && (put < to_write)); + return (put); +} + + +/* + * Compress and write a set of blocks + * + * returns the size actually written (rounded to a full cluster) + * or 0 if could not compress (nothing is written) + * or -1 if there were an irrecoverable error (errno set) + */ + +static int ntfs_comp_set(ntfs_attr *na, runlist_element *rl, + s64 offs, unsigned int insz, const char *inbuf) +{ + ntfs_volume *vol; + char *outbuf; + unsigned int compsz; + int written; + int rounded; + unsigned int clsz; + unsigned int p; + unsigned int sz; + unsigned int bsz; + BOOL fail; + + vol = na->ni->vol; + written = 0; /* default return */ + clsz = 1 << vol->cluster_size_bits; + /* may need 2 extra bytes per block and 2 more bytes */ + outbuf = (char*)ntfs_malloc(na->compression_block_size + + 2*(na->compression_block_size/NTFS_SB_SIZE) + + 2); + if (outbuf) { + fail = FALSE; + compsz = 0; + for (p=0; (p na->compression_block_size)) + fail = TRUE; + else + compsz += sz; + } + if (!fail) { + /* add a couple of null bytes, space has been checked */ + outbuf[compsz++] = 0; + outbuf[compsz++] = 0; + /* write a full cluster, to avoid partial reading */ + rounded = ((compsz - 1) | (clsz - 1)) + 1; + written = write_clusters(vol, rl, offs, rounded, outbuf); + if (written != rounded) { +// previously written text has been spoilt, should return a specific error + ntfs_log_error("error writing compressed data\n"); + errno = EIO; + written = -1; + } + } + free(outbuf); + } + return (written); +} + +/* + * Free unneeded clusters after compression + * + * This generally requires an empty slot at the end of runlist, + * but we do not want to reallocate the runlist here because + * there are many pointers to it. + * So the empty slot has to be reserved beforehand + * + * Returns zero unless some error occurred (described by errno) + */ + +static int ntfs_compress_free(ntfs_attr *na, runlist_element *rl, + s64 used, s64 reserved) +{ + int freecnt; + int usedcnt; + int res; + s64 freelcn; + s64 freevcn; + int freelength; + ntfs_volume *vol; + runlist_element *freerl; + + res = -1; /* default return */ + vol = na->ni->vol; + freecnt = (reserved - used) >> vol->cluster_size_bits; + usedcnt = (reserved >> vol->cluster_size_bits) - freecnt; + /* skip entries fully used, if any */ + while (rl->length && (rl->length < usedcnt)) { + usedcnt -= rl->length; + rl++; + } + if (rl->length) { + /* + * Splitting the current allocation block requires + * an extra runlist element to create the hole. + * The required entry has been prereserved when + * mapping the runlist. + */ + freelcn = rl->lcn + usedcnt; + freevcn = rl->vcn + usedcnt; + freelength = rl->length - usedcnt; + /* new count of allocated clusters */ + rl->length = usedcnt; + freerl = ++rl; + if (!((freevcn + freecnt) + & (na->compression_block_clusters - 1))) { + if (freelength > 0) { + /* + * move the unused part to the end. Doing so, + * the vcn will be out of order. This does + * not harm, the vcn are meaningless now, and + * only the lcn are meaningful for freeing. + */ + /* locate current end */ + while (rl->length) + rl++; + /* new terminator relocated */ + rl[1].vcn = rl->vcn; + rl[1].lcn = LCN_ENOENT; + rl[1].length = 0; + /* hole, currently allocated */ + rl->vcn = freevcn; + rl->lcn = freelcn; + rl->length = freelength; + } + /* free the hole */ + res = ntfs_cluster_free_from_rl(vol,freerl); + if (!res) { + /* mark hole as free */ + freerl->lcn = LCN_HOLE; + freerl->vcn = freevcn; + freerl->length = freecnt; + /* and set up the new end */ + freerl[1].lcn = LCN_ENOENT; + freerl[1].vcn = freevcn + freecnt; + freerl[1].length = 0; + } + } else { + ntfs_log_error("Bad end of a compression block set\n"); + errno = EIO; + } + } else { + ntfs_log_error("No cluster to free after compression\n"); + errno = EIO; + } + return (res); +} + +/* + * Read existing data, decompress and append buffer + * Do nothing if something fails + */ + +static int ntfs_read_append(ntfs_attr *na, const runlist_element *rl, + s64 offs, int compsz, int pos, + char *outbuf, s64 to_write, const void *b) +{ + int fail = 1; + char *compbuf; + int decompsz; + int got; + + compbuf = (char*)ntfs_malloc(compsz); + if (compbuf) { + /* must align to full block for decompression */ + decompsz = ((pos - 1) | (NTFS_SB_SIZE - 1)) + 1; + got = read_clusters(na->ni->vol, rl, offs, compsz, compbuf); + if ((got == compsz) + && !ntfs_decompress((u8*)outbuf,decompsz, + (u8*)compbuf,compsz)) { + memcpy(&outbuf[pos],b,to_write); + fail = 0; + } + free(compbuf); + } + return (fail); +} + +/* + * Flush a full compression block + * + * returns the size actually written (rounded to a full cluster) + * or 0 if could not compress (and written uncompressed) + * or -1 if there were an irrecoverable error (errno set) + */ + +static int ntfs_flush(ntfs_attr *na, runlist_element *rl, s64 offs, + const char *outbuf, int count, BOOL compress) +{ + int rounded; + int written; + int clsz; + + if (compress) { + written = ntfs_comp_set(na, rl, offs, count, outbuf); + if (!written) + compress = FALSE; + if ((written > 0) + && ntfs_compress_free(na,rl,written, + na->compression_block_size)) + written = -1; + } + if (!compress) { + clsz = 1 << na->ni->vol->cluster_size_bits; + rounded = ((count - 1) | (clsz - 1)) + 1; + written = write_clusters(na->ni->vol, rl, + offs, rounded, outbuf); + if (written != rounded) + written = -1; + } + return (written); +} + +/* + * Write some data to be compressed. + * Compression only occurs when a few clusters (usually 16) are + * full. When this occurs an extra runlist slot may be needed, so + * it has to be reserved beforehand. + * + * Returns the size of uncompressed data written, + * or zero if an error occurred. + * When the returned size is less than requested, new clusters have + * to be allocated before the function is called again. + */ + +s64 ntfs_compressed_pwrite(ntfs_attr *na, runlist_element *wrl, s64 wpos, + s64 offs, s64 to_write, s64 rounded, + const void *b, int compressed_part) +{ + ntfs_volume *vol; + runlist_element *brl; /* entry containing the beginning of block */ + int compression_length; + s64 written; + s64 to_read; + s64 roffs; + s64 got; + s64 start_vcn; + s64 nextblock; + int compsz; + char *inbuf; + char *outbuf; + BOOL fail; + BOOL done; + BOOL compress; + + written = 0; /* default return */ + vol = na->ni->vol; + compression_length = na->compression_block_clusters; + compress = FALSE; + done = FALSE; + /* + * Cannot accept writing beyond the current compression set + * because when compression occurs, clusters are freed + * and have to be reallocated. + * (cannot happen with standard fuse 4K buffers) + * Caller has to avoid this situation, or face consequences. + */ + nextblock = ((offs + (wrl->vcn << vol->cluster_size_bits)) + | (na->compression_block_size - 1)) + 1; + if ((offs + to_write + (wrl->vcn << vol->cluster_size_bits)) + >= nextblock) { + /* it is time to compress */ + compress = TRUE; + /* only process what we can */ + to_write = rounded = nextblock + - (offs + (wrl->vcn << vol->cluster_size_bits)); + } + start_vcn = 0; + fail = FALSE; + brl = wrl; + roffs = 0; + /* + * If we are about to compress or we need to decompress + * existing data, we have to process a full set of blocks. + * So relocate the parameters to the beginning of allocation + * containing the first byte of the set of blocks. + */ + if (compress || compressed_part) { + /* find the beginning of block */ + start_vcn = (wrl->vcn + (offs >> vol->cluster_size_bits)) + & -compression_length; + while (brl->vcn && (brl->vcn > start_vcn)) { + /* jumping back a hole means big trouble */ + if (brl->lcn == (LCN)LCN_HOLE) { + ntfs_log_error("jump back over a hole when appending\n"); + fail = TRUE; + errno = EIO; + } + brl--; + offs += brl->length << vol->cluster_size_bits; + } + roffs = (start_vcn - brl->vcn) << vol->cluster_size_bits; + } + if (compressed_part && !fail) { + /* + * The set of compression blocks contains compressed data + * (we are reopening an existing file to append to it) + * Decompress the data and append + */ + compsz = compressed_part << vol->cluster_size_bits; +// improve the needed size + outbuf = (char*)ntfs_malloc(na->compression_block_size); + if (outbuf) { + to_read = offs - roffs; + if (!ntfs_read_append(na, brl, roffs, compsz, + to_read, outbuf, to_write, b)) { + written = ntfs_flush(na, brl, roffs, + outbuf, to_read + to_write, compress); + if (written >= 0) { + written = to_write; + done = TRUE; + } + } + free(outbuf); + } + } else { + if (compress && !fail) { + /* + * we are filling up a block, read the full set of blocks + * and compress it + */ + inbuf = (char*)ntfs_malloc(na->compression_block_size); + if (inbuf) { + to_read = offs - roffs; + if (to_read) + got = read_clusters(vol, brl, roffs, + to_read, inbuf); + else + got = 0; + if (got == to_read) { + memcpy(&inbuf[to_read],b,to_write); + written = ntfs_comp_set(na, brl, roffs, + to_read + to_write, inbuf); + /* + * if compression was not successful, + * only write the part which was requested + */ + if ((written > 0) + /* free the unused clusters */ + && !ntfs_compress_free(na,brl, + written + roffs, + na->compression_block_size + + roffs)) { + done = TRUE; + written = to_write; + } + } + free(inbuf); + } + } + if (!done) { + /* + * if the compression block is not full, or + * if compression failed for whatever reason, + * write uncompressed + */ + /* check we are not overflowing current allocation */ + if ((wpos + rounded) + > ((wrl->lcn + wrl->length) + << vol->cluster_size_bits)) { + ntfs_log_error("writing on unallocated clusters\n"); + errno = EIO; + } else { + written = ntfs_pwrite(vol->dev, wpos, + rounded, b); + if (written == rounded) + written = to_write; + } + } + } + return (written); +} + +/* + * Close a file written compressed. + * This compresses the last partial compression block of the file. + * An empty runlist slot has to be reserved beforehand. + * + * Returns zero if closing is successful. + */ + +int ntfs_compressed_close(ntfs_attr *na, runlist_element *wrl, s64 offs) +{ + ntfs_volume *vol; + runlist_element *brl; /* entry containing the beginning of block */ + int compression_length; + s64 written; + s64 to_read; + s64 roffs; + s64 got; + s64 start_vcn; + char *inbuf; + BOOL fail; + BOOL done; + + vol = na->ni->vol; + compression_length = na->compression_block_clusters; + done = FALSE; + /* + * There generally is an uncompressed block at end of file, + * read the full block and compress it + */ + inbuf = (char*)ntfs_malloc(na->compression_block_size); + if (inbuf) { + start_vcn = (wrl->vcn + (offs >> vol->cluster_size_bits)) + & -compression_length; + to_read = offs + ((wrl->vcn - start_vcn) << vol->cluster_size_bits); + brl = wrl; + fail = FALSE; + while (brl->vcn && (brl->vcn > start_vcn)) { + if (brl->lcn == (LCN)LCN_HOLE) { + ntfs_log_error("jump back over a hole when closing\n"); + fail = TRUE; + errno = EIO; + } + brl--; + } + if (!fail) { + /* roffs can be an offset from another uncomp block */ + roffs = (start_vcn - brl->vcn) << vol->cluster_size_bits; + if (to_read) { + got = read_clusters(vol, brl, roffs, to_read, + inbuf); + if (got == to_read) { + written = ntfs_comp_set(na, brl, roffs, + to_read, inbuf); + if ((written > 0) + /* free the unused clusters */ + && !ntfs_compress_free(na,brl, + written + roffs, + na->compression_block_size + roffs)) { + done = TRUE; + } else + /* if compression failed, leave uncompressed */ + if (!written) + done = TRUE; + } + } else + done = TRUE; + free(inbuf); + } + } + return (!done); +} diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c index e20b44de..7933496c 100644 --- a/libntfs-3g/dir.c +++ b/libntfs-3g/dir.c @@ -1218,6 +1218,9 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, si->file_attributes = FILE_ATTR_SYSTEM; ni->flags = FILE_ATTR_SYSTEM; } + if ((dir_ni->flags & FILE_ATTR_COMPRESSED) + && (S_ISREG(type) || S_ISDIR(type))) + ni->flags |= FILE_ATTR_COMPRESSED; /* Add STANDARD_INFORMATION to inode. */ if (ntfs_attr_add(ni, AT_STANDARD_INFORMATION, AT_UNNAMED, 0, (u8*)si, si_len)) { diff --git a/libntfs-3g/runlist.c b/libntfs-3g/runlist.c index 0312e0cc..f81996bd 100644 --- a/libntfs-3g/runlist.c +++ b/libntfs-3g/runlist.c @@ -111,6 +111,27 @@ static runlist_element *ntfs_rl_realloc(runlist_element *rl, int old_size, return realloc(rl, new_size); } +/* + * Extend a runlist by some entry count + * The runlist may have to be reallocated + * + * Returns the reallocated runlist + * or NULL if reallocation was not possible (with errno set) + */ + +runlist_element *ntfs_rl_extend(runlist_element *rl, int more_entries) +{ + int last; + + last = 0; + while (rl[last].length) + last++; + rl = ntfs_rl_realloc(rl,last+1,last+more_entries+1); + if (!rl) + errno = ENOMEM; + return (rl); +} + /** * ntfs_rl_are_mergeable - test if two runlists can be joined together * @dst: original runlist diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c index 0a28fc26..c02dcd85 100644 --- a/libntfs-3g/security.c +++ b/libntfs-3g/security.c @@ -3877,6 +3877,7 @@ int ntfs_set_ntfs_attrib(const char *path __attribute__((unused)), { u32 attrib; le32 settable; + ATTR_FLAGS dirflags; int res; res = -1; @@ -3885,12 +3886,31 @@ int ntfs_set_ntfs_attrib(const char *path __attribute__((unused)), /* copy to avoid alignment problems */ memcpy(&attrib,value,sizeof(FILE_ATTR_FLAGS)); settable = FILE_ATTR_SETTABLE; - if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) - settable |= FILE_ATTR_COMPRESSED; - ni->flags = (ni->flags & ~settable) - | (cpu_to_le32(attrib) & settable); - NInoSetDirty(ni); res = 0; + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + /* + * Accept changing compression for a directory + * and set index root accordingly + */ + settable |= FILE_ATTR_COMPRESSED; + if ((ni->flags ^ cpu_to_le32(attrib)) + & FILE_ATTR_COMPRESSED) { + if (ni->flags & FILE_ATTR_COMPRESSED) + dirflags = const_cpu_to_le16(0); + else + dirflags = ATTR_IS_COMPRESSED; + res = ntfs_attr_set_flags(ni, + AT_INDEX_ROOT, + NTFS_INDEX_I30, 4, + dirflags, + ATTR_COMPRESSION_MASK); + } + } + if (!res) { + ni->flags = (ni->flags & ~settable) + | (cpu_to_le32(attrib) & settable); + NInoSetDirty(ni); + } } else errno = EEXIST; } else @@ -4461,6 +4481,7 @@ BOOL ntfs_set_file_attributes(struct SECURITY_API *scapi, { ntfs_inode *ni; le32 settable; + ATTR_FLAGS dirflags; int res; res = 0; /* default return */ @@ -4468,11 +4489,30 @@ BOOL ntfs_set_file_attributes(struct SECURITY_API *scapi, ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path); if (ni) { settable = FILE_ATTR_SETTABLE; - if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + /* + * Accept changing compression for a directory + * and set index root accordingly + */ settable |= FILE_ATTR_COMPRESSED; - ni->flags = (ni->flags & ~settable) - | (cpu_to_le32(attrib) & settable); - NInoSetDirty(ni); + if ((ni->flags ^ cpu_to_le32(attrib)) + & FILE_ATTR_COMPRESSED) { + if (ni->flags & FILE_ATTR_COMPRESSED) + dirflags = const_cpu_to_le16(0); + else + dirflags = ATTR_IS_COMPRESSED; + res = ntfs_attr_set_flags(ni, + AT_INDEX_ROOT, + NTFS_INDEX_I30, 4, + dirflags, + ATTR_COMPRESSION_MASK); + } + } + if (!res) { + ni->flags = (ni->flags & ~settable) + | (cpu_to_le32(attrib) & settable); + NInoSetDirty(ni); + } if (!ntfs_inode_close(ni)) res = -1; } else diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index 700f8c18..53ad3f99 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -880,6 +880,11 @@ static int ntfs_fuse_open(const char *org_path, if (NAttrEncrypted(na)) res = -EACCES; #endif + /* mark a future need to compress the last chunk */ + if ((res >= 0) + && (na->data_flags & ATTR_COMPRESSION_MASK) + && (fi->flags & (O_WRONLY | O_RDWR))) + fi->fh |= 1; ntfs_attr_close(na); } else res = -errno; @@ -978,10 +983,6 @@ static int ntfs_fuse_write(const char *org_path, const char *buf, size_t size, } while (size) { s64 ret = ntfs_attr_pwrite(na, offset, size, buf); - if (0 <= ret && ret < (s64)size) - ntfs_log_perror("ntfs_attr_pwrite partial write to '%s'" - " (%lld: %lld <> %lld)", path, (long long)offset, - (long long)size, (long long)ret); if (ret <= 0) { res = -errno; goto exit; @@ -1005,6 +1006,48 @@ out: return res; } +static int ntfs_fuse_release(const char *org_path, + struct fuse_file_info *fi) +{ + ntfs_inode *ni = NULL; + ntfs_attr *na = NULL; + char *path = NULL; + ntfschar *stream_name; + int stream_name_len, res; + + /* Only for marked descriptors there is something to do */ + if (!(fi->fh & 1)) { + res = 0; + goto out; + } + stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); + if (stream_name_len < 0) { + res = stream_name_len; + goto out; + } + ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); + if (!ni) { + res = -errno; + goto exit; + } + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); + if (!na) { + res = -errno; + goto exit; + } + res = ntfs_attr_pclose(na); +exit: + if (na) + ntfs_attr_close(na); + if (ntfs_inode_close(ni)) + set_fuse_error(&res); + free(path); + if (stream_name_len) + free(stream_name); +out: + return res; +} + /* * Common part for truncate() and ftruncate() */ @@ -1049,7 +1092,11 @@ static int ntfs_fuse_trunc(const char *org_path, off_t size, goto exit; } #endif - + /* for compressed files, only deleting contents is implemented */ + if (NAttrCompressed(na) && size) { + errno = EOPNOTSUPP; + goto exit; + } if (ntfs_attr_truncate(na, size)) goto exit; @@ -1211,7 +1258,7 @@ static int ntfs_fuse_access(const char *path, int type) #endif static int ntfs_fuse_create(const char *org_path, mode_t typemode, dev_t dev, - const char *target) + const char *target, struct fuse_file_info *fi) { char *name; ntfschar *uname = NULL, *utarget = NULL; @@ -1325,6 +1372,10 @@ static int ntfs_fuse_create(const char *org_path, mode_t typemode, dev_t dev, security.uid, security.gid, perm) < 0) set_fuse_error(&res); #endif + } + /* mark a need to compress the end of file */ + if (fi && (ni->flags & FILE_ATTR_COMPRESSED)) { + fi->fh |= 1; } NInoSetDirty(ni); if (ntfs_inode_close(ni)) @@ -1349,7 +1400,8 @@ exit: } static int ntfs_fuse_create_stream(const char *path, - ntfschar *stream_name, const int stream_name_len) + ntfschar *stream_name, const int stream_name_len, + struct fuse_file_info *fi) { ntfs_inode *ni; int res = 0; @@ -1361,11 +1413,13 @@ static int ntfs_fuse_create_stream(const char *path, /* * If such file does not exist, create it and try once * again to add stream to it. + * Note : no fuse_file_info for creation of main file */ - res = ntfs_fuse_create(path, S_IFREG, 0, NULL); + res = ntfs_fuse_create(path, S_IFREG, 0, NULL, + (struct fuse_file_info*)NULL); if (!res) return ntfs_fuse_create_stream(path, - stream_name, stream_name_len); + stream_name, stream_name_len,fi); else res = -errno; } @@ -1373,12 +1427,16 @@ static int ntfs_fuse_create_stream(const char *path, } if (ntfs_attr_add(ni, AT_DATA, stream_name, stream_name_len, NULL, 0)) res = -errno; + if ((res >= 0) && (ni->flags & FILE_ATTR_COMPRESSED) + && (fi->flags & (O_WRONLY | O_RDWR))) + fi->fh |= 1; if (ntfs_inode_close(ni)) set_fuse_error(&res); return res; } -static int ntfs_fuse_mknod_common(const char *org_path, mode_t mode, dev_t dev) +static int ntfs_fuse_mknod_common(const char *org_path, mode_t mode, dev_t dev, + struct fuse_file_info *fi) { char *path = NULL; ntfschar *stream_name; @@ -1393,10 +1451,11 @@ static int ntfs_fuse_mknod_common(const char *org_path, mode_t mode, dev_t dev) goto exit; } if (!stream_name_len) - res = ntfs_fuse_create(path, mode & (S_IFMT | 07777), dev, NULL); + res = ntfs_fuse_create(path, mode & (S_IFMT | 07777), dev, + NULL,fi); else res = ntfs_fuse_create_stream(path, stream_name, - stream_name_len); + stream_name_len,fi); exit: free(path); if (stream_name_len) @@ -1406,20 +1465,22 @@ exit: static int ntfs_fuse_mknod(const char *path, mode_t mode, dev_t dev) { - return ntfs_fuse_mknod_common(path, mode, dev); + return ntfs_fuse_mknod_common(path, mode, dev, + (struct fuse_file_info*)NULL); } static int ntfs_fuse_create_file(const char *path, mode_t mode, - struct fuse_file_info *fi __attribute__((unused))) + struct fuse_file_info *fi) { - return ntfs_fuse_mknod_common(path, mode, 0); + return ntfs_fuse_mknod_common(path, mode, 0, fi); } static int ntfs_fuse_symlink(const char *to, const char *from) { if (ntfs_fuse_is_named_data_stream(from)) return -EINVAL; /* n/a for named data streams. */ - return ntfs_fuse_create(from, S_IFLNK, 0, to); + return ntfs_fuse_create(from, S_IFLNK, 0, to, + (struct fuse_file_info*)NULL); } static int ntfs_fuse_link(const char *old_path, const char *new_path) @@ -1754,7 +1815,8 @@ static int ntfs_fuse_mkdir(const char *path, { if (ntfs_fuse_is_named_data_stream(path)) return -EINVAL; /* n/a for named data streams. */ - return ntfs_fuse_create(path, S_IFDIR | (mode & 07777), 0, NULL); + return ntfs_fuse_create(path, S_IFDIR | (mode & 07777), 0, NULL, + (struct fuse_file_info*)NULL); } static int ntfs_fuse_rmdir(const char *path) @@ -2357,6 +2419,7 @@ static int ntfs_fuse_setxattr(const char *path, const char *name, ntfs_attr *na = NULL; ntfschar *lename = NULL; int res, lename_len; + size_t part, total; int attr; int namespace; struct SECURITY_CONTEXT security; @@ -2522,15 +2585,25 @@ static int ntfs_fuse_setxattr(const char *path, const char *name, goto exit; } } else { - if (ntfs_attr_truncate(na, (s64)size)) { + /* currently compressed streams can only be wiped out */ + if (ntfs_attr_truncate(na, (s64)0 /* size */)) { res = -errno; goto exit; } } - res = ntfs_attr_pwrite(na, 0, size, value); - if (res != (s64) size) + total = 0; + if (size) { + do { + part = ntfs_attr_pwrite(na, total, size - total, + &value[total]); + if (part > 0) + total += part; + } while ((part > 0) && (total < size)); + if (total != size) res = -errno; else + res = ntfs_attr_pclose(na); + } else res = 0; exit: if (na) @@ -2754,6 +2827,7 @@ static struct fuse_operations ntfs_3g_ops = { .readlink = ntfs_fuse_readlink, .readdir = ntfs_fuse_readdir, .open = ntfs_fuse_open, + .release = ntfs_fuse_release, .read = ntfs_fuse_read, .write = ntfs_fuse_write, .truncate = ntfs_fuse_truncate,