Allowed creating holes in compressed files

N2009_11_14_FIXES
jpandre 2009-08-11 08:02:59 +00:00
parent b725b77e88
commit 88473752c5
3 changed files with 271 additions and 76 deletions

View File

@ -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

View File

@ -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<insz) && !fail; p+=NTFS_SB_SIZE) {
if ((p + NTFS_SB_SIZE) < insz)
bsz = NTFS_SB_SIZE;
else
bsz = insz - p;
sz = ntfs_compress_block(&inbuf[p],bsz,
&outbuf[compsz]);
pbuf = &outbuf[compsz];
sz = ntfs_compress_block(&inbuf[p],bsz,pbuf);
/* fail if all the clusters (or more) are needed */
if (!sz || ((compsz + sz + clsz + 2)
> 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

View File

@ -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);