From 88473752c5722ed337677dd7f6848ddd8486a2e8 Mon Sep 17 00:00:00 2001 From: jpandre Date: Tue, 11 Aug 2009 08:02:59 +0000 Subject: [PATCH] Allowed creating holes in compressed files --- libntfs-3g/attrib.c | 181 +++++++++++++++++++++++++++++++++++------- libntfs-3g/compress.c | 146 ++++++++++++++++++++++++---------- src/ntfs-3g.c | 20 ++++- 3 files changed, 271 insertions(+), 76 deletions(-) diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c index 774796ce..37192f8d 100644 --- a/libntfs-3g/attrib.c +++ b/libntfs-3g/attrib.c @@ -1249,6 +1249,8 @@ err_out: return ret; } +static int stuff_hole(ntfs_attr *na, const s64 pos); + /** * ntfs_attr_pwrite - positioned write to an ntfs attribute * @na: ntfs attribute to write to @@ -1310,6 +1312,15 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) errno = EACCES; goto errno_set; } + /* + * Fill the gap, when writing beyond the end of a compressed + * file. This will make recursive calls + */ + if (compressed + && (na->type == AT_DATA) + && (pos > na->initialized_size) + && stuff_hole(na,pos)) + goto errno_set; /* If this is a compressed attribute it needs special treatment. */ wasnonresident = NAttrNonResident(na) != 0; makingnonresident = wasnonresident /* yes : already changed */ @@ -1407,10 +1418,12 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) goto err_out; /* * For a compressed attribute, we must be sure there is an - * available entry, so reserve it before it gets too late. + * available entry, and, when reopening a compressed file, + * we may need to split a hole. So reserve the entries + * before it gets too late. */ if (compressed) { - na->rl = ntfs_rl_extend(na->rl,1); + na->rl = ntfs_rl_extend(na->rl,2); if (!na->rl) goto err_out; } @@ -1487,29 +1500,45 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) */ 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)) + if ((rl->lcn == (LCN)LCN_HOLE) + && wasnonresident) { + if (rl->length < na->compression_block_clusters) compressed_part - = na->compression_block_clusters - - rl->length; + = na->compression_block_clusters + - rl->length; + else { + compressed_part + = na->compression_block_clusters; + if (rl->length > na->compression_block_clusters) { + rl[2].lcn = rl[1].lcn; + rl[2].vcn = rl[1].vcn; + rl[2].length = rl[1].length; + rl[1].vcn -= compressed_part; + rl[1].lcn = LCN_HOLE; + rl[1].length = compressed_part; + rl[0].length -= compressed_part; + ofs -= rl->length << vol->cluster_size_bits; + rl++; + } + } /* normal hole filling will do later */ + } else + 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--; + } } /* * Scatter the data from the linear data buffer to the volume. Note, a @@ -1785,11 +1814,15 @@ int ntfs_attr_pclose(ntfs_attr *na) 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; + if (rl->lcn == (LCN)LCN_HOLE) { + if (rl->length < na->compression_block_clusters) + compressed_part + = na->compression_block_clusters + - rl->length; + else + compressed_part + = na->compression_block_clusters; + } /* done, if the last block set was compressed */ if (compressed_part) goto out; @@ -5552,13 +5585,14 @@ int ntfs_attr_truncate(ntfs_attr *na, const s64 newsize) * 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. + * block at a time (from where we are about to write), + * 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 + fullsize = (na->initialized_size | (na->compression_block_size - 1)) + 1; else fullsize = newsize; @@ -5573,6 +5607,93 @@ out: return ret; } +/* + * Stuff a hole in a compressed file + * + * An unallocated hole must be aligned on compression block size. + * If needed current block and target block are stuffed with zeroes. + * + * Returns 0 if succeeded, + * -1 if it failed (as explained in errno) + */ + +static int stuff_hole(ntfs_attr *na, const s64 pos) +{ + s64 size; + s64 begin_size; + s64 end_size; + char *buf; + int ret; + + ret = 0; + /* + * If the attribute is resident, the compression block size + * is not defined yet and we can make no decision. + * So we first try resizing to the target and if the + * attribute is still resident, we're done + */ + if (!NAttrNonResident(na)) { + ret = ntfs_resident_attr_resize(na, pos); + if (!ret && !NAttrNonResident(na)) + na->initialized_size = na->data_size = pos; + } + if (!ret && NAttrNonResident(na)) { + /* does the hole span over several compression block ? */ + if ((pos ^ na->initialized_size) + & ~(na->compression_block_size - 1)) { + begin_size = ((na->initialized_size - 1) + | (na->compression_block_size - 1)) + + 1 - na->initialized_size; + end_size = pos & (na->compression_block_size - 1); + size = (begin_size > end_size ? begin_size : end_size); + } else { + /* short stuffing in a single compression block */ + begin_size = size = pos - na->initialized_size; + end_size = 0; + } + if (size) + buf = (char*)ntfs_malloc(size); + else + buf = (char*)NULL; + if (buf || !size) { + memset(buf,0,size); + /* stuff into current block */ + if (begin_size + && (ntfs_attr_pwrite(na, + na->initialized_size, begin_size, buf) + != begin_size)) + ret = -1; + /* create an unstuffed hole */ + if (!ret + && ((na->initialized_size + end_size) < pos) + && ntfs_non_resident_attr_expand(na, + pos - end_size)) + ret = -1; + else + na->initialized_size + = na->data_size = pos - end_size; + /* stuff into the target block */ + if (!ret && end_size + && (ntfs_attr_pwrite(na, + na->initialized_size, end_size, buf) + != end_size)) + ret = -1; + if (buf) + free(buf); + } else + ret = -1; + } + /* make absolutely sure we have reached the target */ + if (!ret && (na->initialized_size != pos)) { + ntfs_log_error("Failed to stuff a compressed file" + "target %lld reached %lld\n", + (long long)pos, (long long)na->initialized_size); + errno = EIO; + ret = -1; + } + return (ret); +} + /** * ntfs_attr_readall - read the entire data from an ntfs attribute * @ni: open ntfs inode in which the ntfs attribute resides diff --git a/libntfs-3g/compress.c b/libntfs-3g/compress.c index f7fb4311..4db1826d 100644 --- a/libntfs-3g/compress.c +++ b/libntfs-3g/compress.c @@ -839,11 +839,12 @@ do_next_cb: * Returns the amount of data read */ -static int read_clusters(ntfs_volume *vol, const runlist_element *rl, - s64 offs, int to_read, char *inbuf) +static u32 read_clusters(ntfs_volume *vol, const runlist_element *rl, + s64 offs, u32 to_read, char *inbuf) { - int count; - int got, xgot; + u32 count; + int xgot; + u32 got; s64 xpos; BOOL first; char *xinbuf; @@ -863,14 +864,14 @@ static int read_clusters(ntfs_volume *vol, const runlist_element *rl, if ((to_read - got) < count) count = to_read - got; xgot = ntfs_pread(vol->dev, xpos, count, xinbuf); - if (xgot == count) { + if (xgot == (int)count) { got += count; xpos += count; xinbuf += count; xrl++; } first = FALSE; - } while ((xgot == count) && (got < to_read)); + } while ((xgot == (int)count) && (got < to_read)); return (got); } @@ -920,8 +921,9 @@ static int write_clusters(ntfs_volume *vol, const runlist_element *rl, * 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) + * or 0 if all zeroes (nothing is written) + * or -1 if could not compress (nothing is written) + * or -2 if there were an irrecoverable error (errno set) */ static int ntfs_comp_set(ntfs_attr *na, runlist_element *rl, @@ -929,6 +931,7 @@ static int ntfs_comp_set(ntfs_attr *na, runlist_element *rl, { ntfs_volume *vol; char *outbuf; + char *pbuf; unsigned int compsz; int written; int rounded; @@ -937,9 +940,16 @@ static int ntfs_comp_set(ntfs_attr *na, runlist_element *rl, unsigned int sz; unsigned int bsz; BOOL fail; + BOOL allzeroes; + /* a single compressed zero */ + static char onezero[] = { 0x01, 0xb0, 0x00, 0x00 } ; + /* a couple of compressed zeroes */ + static char twozeroes[] = { 0x02, 0xb0, 0x00, 0x00, 0x00 } ; + /* more compressed zeroes, to be followed by some count */ + static char morezeroes[] = { 0x03, 0xb0, 0x02, 0x00 } ; vol = na->ni->vol; - written = 0; /* default return */ + written = -1; /* 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 @@ -948,21 +958,43 @@ static int ntfs_comp_set(ntfs_attr *na, runlist_element *rl, if (outbuf) { fail = FALSE; compsz = 0; + allzeroes = TRUE; for (p=0; (p na->compression_block_size)) fail = TRUE; - else - compsz += sz; + else { + if (allzeroes) { + /* check whether this is all zeroes */ + switch (sz) { + case 4 : + allzeroes = !memcmp( + pbuf,onezero,4); + break; + case 5 : + allzeroes = !memcmp( + pbuf,twozeroes,5); + break; + case 6 : + allzeroes = !memcmp( + pbuf,morezeroes,4); + break; + default : + allzeroes = FALSE; + break; + } + } + compsz += sz; + } } - if (!fail) { + if (!fail && !allzeroes) { /* add a couple of null bytes, space has been checked */ outbuf[compsz++] = 0; outbuf[compsz++] = 0; @@ -973,9 +1005,11 @@ static int ntfs_comp_set(ntfs_attr *na, runlist_element *rl, // previously written text has been spoilt, should return a specific error ntfs_log_error("error writing compressed data\n"); errno = EIO; - written = -1; + written = -2; } - } + } else + if (!fail) + written = 0; free(outbuf); } return (written); @@ -1001,6 +1035,8 @@ static int ntfs_compress_free(ntfs_attr *na, runlist_element *rl, s64 freelcn; s64 freevcn; int freelength; + BOOL mergeholes; + BOOL beginhole; ntfs_volume *vol; runlist_element *freerl; @@ -1024,11 +1060,21 @@ static int ntfs_compress_free(ntfs_attr *na, runlist_element *rl, freevcn = rl->vcn + usedcnt; freelength = rl->length - usedcnt; /* new count of allocated clusters */ - rl->length = usedcnt; - freerl = ++rl; + rl->length = usedcnt; /* warning : can be zero */ if (!((freevcn + freecnt) & (na->compression_block_clusters - 1))) { - if (freelength > 0) { + beginhole = !usedcnt && !rl->vcn; + mergeholes = !usedcnt + && rl[0].vcn + && (rl[-1].lcn == LCN_HOLE); + if (mergeholes) { + freerl = rl; + freerl->length = freecnt; + } else + freerl = ++rl; + if ((freelength > 0) + && !mergeholes + && (usedcnt || beginhole)) { /* * move the unused part to the end. Doing so, * the vcn will be out of order. This does @@ -1050,10 +1096,18 @@ static int ntfs_compress_free(ntfs_attr *na, runlist_element *rl, /* 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; + if (mergeholes) { + /* merge with adjacent hole */ + freerl--; + freerl->length += freecnt; + } else { + if (beginhole) + freerl--; + /* 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; @@ -1076,26 +1130,34 @@ static int ntfs_compress_free(ntfs_attr *na, runlist_element *rl, */ static int ntfs_read_append(ntfs_attr *na, const runlist_element *rl, - s64 offs, int compsz, int pos, + s64 offs, u32 compsz, int pos, char *outbuf, s64 to_write, const void *b) { int fail = 1; char *compbuf; - int decompsz; - int got; + u32 decompsz; + u32 got; - compbuf = (char*)ntfs_malloc(compsz); - if (compbuf) { + if (compsz == na->compression_block_size) { + /* if the full block was requested, it was a hole */ + memset(outbuf,0,compsz); + memcpy(&outbuf[pos],b,to_write); + fail = 0; + } else { + 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; + 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); } - free(compbuf); } return (fail); } @@ -1117,9 +1179,9 @@ static int ntfs_flush(ntfs_attr *na, runlist_element *rl, s64 offs, if (compress) { written = ntfs_comp_set(na, rl, offs, count, outbuf); - if (!written) + if (written == -1) compress = FALSE; - if ((written > 0) + if ((written >= 0) && ntfs_compress_free(na,rl,offs + written, offs + na->compression_block_size)) written = -1; @@ -1160,7 +1222,7 @@ s64 ntfs_compressed_pwrite(ntfs_attr *na, runlist_element *wrl, s64 wpos, s64 got; s64 start_vcn; s64 nextblock; - int compsz; + u32 compsz; char *inbuf; char *outbuf; BOOL fail; @@ -1259,7 +1321,7 @@ s64 ntfs_compressed_pwrite(ntfs_attr *na, runlist_element *wrl, s64 wpos, * if compression was not successful, * only write the part which was requested */ - if ((written > 0) + if ((written >= 0) /* free the unused clusters */ && !ntfs_compress_free(na,brl, written + roffs, @@ -1348,7 +1410,7 @@ int ntfs_compressed_close(ntfs_attr *na, runlist_element *wrl, s64 offs) if (got == to_read) { written = ntfs_comp_set(na, brl, roffs, to_read, inbuf); - if ((written > 0) + if ((written >= 0) /* free the unused clusters */ && !ntfs_compress_free(na,brl, written + roffs, @@ -1356,7 +1418,7 @@ int ntfs_compressed_close(ntfs_attr *na, runlist_element *wrl, s64 offs) done = TRUE; } else /* if compression failed, leave uncompressed */ - if (!written) + if (written == -1) done = TRUE; } } else diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index 6f1ed55f..9fbfdab2 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -1156,14 +1156,26 @@ 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) { + /* + * for compressed files, only deleting contents and expanding + * are implemented. Expanding is done by inserting a final + * zero, which is optimized as creating a hole when possible. + */ + if ((na->data_flags & ATTR_COMPRESSION_MASK) + && size + && (size < na->initialized_size)) { errno = EOPNOTSUPP; goto exit; } oldsize = na->data_size; - if (ntfs_attr_truncate(na, size)) - goto exit; + if ((na->data_flags & ATTR_COMPRESSION_MASK) + && (size > na->initialized_size)) { + char zero = 0; + if (ntfs_attr_pwrite(na, size - 1, 1, &zero) <= 0) + goto exit; + } else + if (ntfs_attr_truncate(na, size)) + goto exit; if (oldsize != size) set_archive(ni);