Fixes for truncate system calls:

- VFS: check for negative sizes in all truncate calls
- VFS: update file size after truncating with fcntl(F_FREESP)
- VFS: move pos/len checks for F_FREESP with l_len!=0 from FS to VFS
- MFS: do not zero data block for small files when fully truncating
- MFS: do not write out freed indirect blocks after freeing space
- MFS: make truncate work correctly with differing zone/block sizes
- tests: add new test50 for truncate call family
This commit is contained in:
David van Moolenbroek 2010-02-09 08:12:37 +00:00
parent f029b0e0b1
commit bdd4f5857f
8 changed files with 728 additions and 62 deletions

View file

@ -11,14 +11,16 @@
#define SAME 1000 #define SAME 1000
FORWARD _PROTOTYPE( int remove_dir, (struct inode *rldirp, FORWARD _PROTOTYPE( int remove_dir, (struct inode *rldirp,
struct inode *rip, char dir_name[NAME_MAX]) ); struct inode *rip, char dir_name[NAME_MAX]) );
FORWARD _PROTOTYPE( int unlink_file, (struct inode *dirp, FORWARD _PROTOTYPE( int unlink_file, (struct inode *dirp,
struct inode *rip, char file_name[NAME_MAX]) ); struct inode *rip, char file_name[NAME_MAX]) );
FORWARD _PROTOTYPE( off_t nextblock, (off_t pos, int zonesize) ); FORWARD _PROTOTYPE( off_t nextblock, (off_t pos, int zone_size) );
FORWARD _PROTOTYPE( void zeroblock_half, (struct inode *i, off_t p, int l)); FORWARD _PROTOTYPE( void zerozone_half, (struct inode *rip, off_t pos,
FORWARD _PROTOTYPE( void zeroblock_range, (struct inode *i, off_t p, off_t h)); int half, int zone_size) );
FORWARD _PROTOTYPE( void zerozone_range, (struct inode *rip, off_t pos,
off_t len) );
/* Args to zeroblock_half() */ /* Args to zerozone_half() */
#define FIRST_HALF 0 #define FIRST_HALF 0
#define LAST_HALF 1 #define LAST_HALF 1
@ -513,9 +515,12 @@ off_t newsize; /* inode must become this size */
scale = rip->i_sp->s_log_zone_size; scale = rip->i_sp->s_log_zone_size;
zone_size = (zone_t) rip->i_sp->s_block_size << scale; zone_size = (zone_t) rip->i_sp->s_block_size << scale;
/* Free the actual space if relevant. */ /* Free the actual space if truncating. */
if(newsize < rip->i_size) freesp_inode(rip, newsize, rip->i_size); if(newsize < rip->i_size) freesp_inode(rip, newsize, rip->i_size);
/* Clear the rest of the last zone if expanding. */
if(newsize > rip->i_size) clear_zone(rip, rip->i_size, 0);
/* Next correct the inode size. */ /* Next correct the inode size. */
rip->i_size = newsize; rip->i_size = newsize;
rip->i_update |= CTIME | MTIME; rip->i_update |= CTIME | MTIME;
@ -545,6 +550,7 @@ off_t start, end; /* range of bytes to free (end uninclusive) */
*/ */
off_t p, e; off_t p, e;
int zone_size, dev; int zone_size, dev;
int zero_last, zero_first;
if(end > rip->i_size) /* freeing beyond end makes no sense */ if(end > rip->i_size) /* freeing beyond end makes no sense */
end = rip->i_size; end = rip->i_size;
@ -555,27 +561,29 @@ off_t start, end; /* range of bytes to free (end uninclusive) */
dev = rip->i_dev; /* device on which inode resides */ dev = rip->i_dev; /* device on which inode resides */
/* If freeing doesn't cross a zone boundary, then we may only zero /* If freeing doesn't cross a zone boundary, then we may only zero
* a range of the block. * a range of the zone, unless we are freeing up that entire zone.
*/ */
if(start/zone_size == (end-1)/zone_size) { zero_last = start % zone_size;
zeroblock_range(rip, start, end-start); zero_first = end % zone_size && end < rip->i_size;
if(start/zone_size == (end-1)/zone_size && (zero_last || zero_first)) {
zerozone_range(rip, start, end-start);
} else { } else {
/* First zero unused part of partly used blocks. */ /* First zero unused part of partly used zones. */
if(start%zone_size) if(zero_last)
zeroblock_half(rip, start, LAST_HALF); zerozone_half(rip, start, LAST_HALF, zone_size);
if(end%zone_size && end < rip->i_size) if(zero_first)
zeroblock_half(rip, end, FIRST_HALF); zerozone_half(rip, end, FIRST_HALF, zone_size);
}
/* Now completely free the completely unused blocks. /* Now completely free the completely unused zones.
* write_map() will free unused (double) indirect * write_map() will free unused (double) indirect
* blocks too. Converting the range to zone numbers avoids * blocks too. Converting the range to zone numbers avoids
* overflow on p when doing e.g. 'p += zone_size'. * overflow on p when doing e.g. 'p += zone_size'.
*/ */
e = end/zone_size; e = end/zone_size;
if(end == rip->i_size && (end % zone_size)) e++; if(end == rip->i_size && (end % zone_size)) e++;
for(p = nextblock(start, zone_size)/zone_size; p < e; p ++) for(p = nextblock(start, zone_size)/zone_size; p < e; p ++)
write_map(rip, p*zone_size, NO_ZONE, WMAP_FREE); write_map(rip, p*zone_size, NO_ZONE, WMAP_FREE);
}
rip->i_update |= CTIME | MTIME; rip->i_update |= CTIME | MTIME;
rip->i_dirt = DIRTY; rip->i_dirt = DIRTY;
@ -603,62 +611,69 @@ int zone_size;
/*===========================================================================* /*===========================================================================*
* zeroblock_half * * zerozone_half *
*===========================================================================*/ *===========================================================================*/
PRIVATE void zeroblock_half(rip, pos, half) PRIVATE void zerozone_half(rip, pos, half, zone_size)
struct inode *rip; struct inode *rip;
off_t pos; off_t pos;
int half; int half;
int zone_size;
{ {
/* Zero the upper or lower 'half' of a block that holds position 'pos'. /* Zero the upper or lower 'half' of a zone that holds position 'pos'.
* half can be FIRST_HALF or LAST_HALF. * half can be FIRST_HALF or LAST_HALF.
* *
* FIRST_HALF: 0..pos-1 will be zeroed * FIRST_HALF: 0..pos-1 will be zeroed
* LAST_HALF: pos..blocksize-1 will be zeroed * LAST_HALF: pos..zone_size-1 will be zeroed
*/ */
int offset, len; int offset, len;
/* Offset of zeroing boundary. */ /* Offset of zeroing boundary. */
offset = pos % rip->i_sp->s_block_size; offset = pos % zone_size;
if(half == LAST_HALF) { if(half == LAST_HALF) {
len = rip->i_sp->s_block_size - offset; len = zone_size - offset;
} else { } else {
len = offset; len = offset;
pos -= offset; pos -= offset;
offset = 0;
} }
zeroblock_range(rip, pos, len); zerozone_range(rip, pos, len);
} }
/*===========================================================================* /*===========================================================================*
* zeroblock_range * * zerozone_range *
*===========================================================================*/ *===========================================================================*/
PRIVATE void zeroblock_range(rip, pos, len) PRIVATE void zerozone_range(rip, pos, len)
struct inode *rip; struct inode *rip;
off_t pos; off_t pos;
off_t len; off_t len;
{ {
/* Zero a range in a block. /* Zero an arbitrary byte range in a zone, possibly spanning multiple blocks.
* This function is used to zero a segment of a block, either
* FIRST_HALF of LAST_HALF.
*
*/ */
block_t b; block_t b;
struct buf *bp; struct buf *bp;
off_t offset; off_t offset;
int bytes, block_size;
block_size = rip->i_sp->s_block_size;
if(!len) return; /* no zeroing to be done. */ if(!len) return; /* no zeroing to be done. */
if( (b = read_map(rip, pos)) == NO_BLOCK) return; if( (b = read_map(rip, pos)) == NO_BLOCK) return;
if( (bp = get_block(rip->i_dev, b, NORMAL)) == NIL_BUF) while (len > 0) {
panic(__FILE__, "zeroblock_range: no block", NO_NUM); if( (bp = get_block(rip->i_dev, b, NORMAL)) == NIL_BUF)
offset = pos % rip->i_sp->s_block_size; panic(__FILE__, "zerozone_range: no block", NO_NUM);
if(offset + len > rip->i_sp->s_block_size) offset = pos % block_size;
panic(__FILE__, "zeroblock_range: len too long", len); bytes = block_size - offset;
memset(bp->b_data + offset, 0, len); if (bytes > len)
bp->b_dirt = DIRTY; bytes = len;
put_block(bp, FULL_DATA_BLOCK); memset(bp->b_data + offset, 0, bytes);
bp->b_dirt = DIRTY;
put_block(bp, FULL_DATA_BLOCK);
pos += bytes;
len -= bytes;
b++;
}
} }

View file

@ -94,7 +94,6 @@ _PROTOTYPE( struct buf *rahead, (struct inode *rip, block_t baseblock,
u64_t position, unsigned bytes_ahead) ); u64_t position, unsigned bytes_ahead) );
_PROTOTYPE( void read_ahead, (void) ); _PROTOTYPE( void read_ahead, (void) );
_PROTOTYPE( block_t read_map, (struct inode *rip, off_t pos) ); _PROTOTYPE( block_t read_map, (struct inode *rip, off_t pos) );
_PROTOTYPE( int read_write, (int rw_flag) );
_PROTOTYPE( zone_t rd_indir, (struct buf *bp, int index) ); _PROTOTYPE( zone_t rd_indir, (struct buf *bp, int index) );
/* stadir.c */ /* stadir.c */

View file

@ -1,5 +1,5 @@
/* This file is the counterpart of "read.c". It contains the code for writing /* This file is the counterpart of "read.c". It contains the code for writing
* insofar as this is not contained in read_write(). * insofar as this is not contained in fs_readwrite().
* *
* The entry points into this file are * The entry points into this file are
* write_map: write a new zone into an inode * write_map: write a new zone into an inode
@ -142,7 +142,7 @@ int op; /* special actions */
} }
/* Last reference in the indirect block gone? Then /* Last reference in the indirect block gone? Then
* Free the indirect block. * free the indirect block.
*/ */
if(empty_indir(bp, rip->i_sp)) { if(empty_indir(bp, rip->i_sp)) {
free_zone(rip->i_dev, z1); free_zone(rip->i_dev, z1);
@ -161,15 +161,18 @@ int op; /* special actions */
} else { } else {
wr_indir(bp, ex, new_zone); wr_indir(bp, ex, new_zone);
} }
bp->b_dirt = DIRTY; /* z1 equals NO_ZONE only when we are freeing up the indirect block. */
bp->b_dirt = (z1 == NO_ZONE) ? CLEAN : DIRTY;
put_block(bp, INDIRECT_BLOCK); put_block(bp, INDIRECT_BLOCK);
} }
/* If the single indirect block isn't there (or was just freed), /* If the single indirect block isn't there (or was just freed),
* see if we have to keep the double indirect block, if any. * see if we have to keep the double indirect block, if any.
* If we don't have to keep it, don't bother writing it out.
*/ */
if(z1 == NO_ZONE && !single && z2 != NO_ZONE && if(z1 == NO_ZONE && !single && z2 != NO_ZONE &&
empty_indir(bp_dindir, rip->i_sp)) { empty_indir(bp_dindir, rip->i_sp)) {
bp_dindir->b_dirt = CLEAN;
free_zone(rip->i_dev, z2); free_zone(rip->i_dev, z2);
rip->i_zone[zones+1] = NO_ZONE; rip->i_zone[zones+1] = NO_ZONE;
} }
@ -236,11 +239,11 @@ struct super_block *sb; /* superblock of device block resides on */
PUBLIC void clear_zone(rip, pos, flag) PUBLIC void clear_zone(rip, pos, flag)
register struct inode *rip; /* inode to clear */ register struct inode *rip; /* inode to clear */
off_t pos; /* points to block to clear */ off_t pos; /* points to block to clear */
int flag; /* 0 if called by read_write, 1 by new_block */ int flag; /* 1 if called by new_block, 0 otherwise */
{ {
/* Zero a zone, possibly starting in the middle. The parameter 'pos' gives /* Zero a zone, possibly starting in the middle. The parameter 'pos' gives
* a byte in the first block to be zeroed. Clearzone() is called from * a byte in the first block to be zeroed. Clearzone() is called from
* read_write and new_block(). * fs_readwrite(), truncate_inode(), and new_block().
*/ */
register struct buf *bp; register struct buf *bp;

View file

@ -197,6 +197,8 @@ PUBLIC int do_truncate()
struct vnode *vp; struct vnode *vp;
int r; int r;
if ((off_t) m_in.flength < 0) return(EINVAL);
/* Temporarily open file */ /* Temporarily open file */
if (fetch_name(m_in.m2_p1, m_in.m2_i1, M1) != OK) return(err_code); if (fetch_name(m_in.m2_p1, m_in.m2_i1, M1) != OK) return(err_code);
if ((vp = eat_path(PATH_NOFLAGS)) == NIL_VNODE) return(err_code); if ((vp = eat_path(PATH_NOFLAGS)) == NIL_VNODE) return(err_code);
@ -219,10 +221,12 @@ PUBLIC int do_ftruncate()
int r; int r;
struct filp *rfilp; struct filp *rfilp;
if ((off_t) m_in.flength < 0) return(EINVAL);
/* File is already opened; get a vnode pointer from filp */ /* File is already opened; get a vnode pointer from filp */
if ((rfilp = get_filp(m_in.m2_i1)) == NIL_FILP) return(err_code); if ((rfilp = get_filp(m_in.m2_i1)) == NIL_FILP) return(err_code);
if (!(rfilp->filp_mode & W_BIT)) return(EBADF); if (!(rfilp->filp_mode & W_BIT)) return(EBADF);
return truncate_vnode(rfilp->filp_vno, m_in.m2_l1); return truncate_vnode(rfilp->filp_vno, m_in.flength);
} }

View file

@ -220,7 +220,7 @@ PUBLIC int do_fcntl()
/* Figure out starting position base. */ /* Figure out starting position base. */
switch(flock_arg.l_whence) { switch(flock_arg.l_whence) {
case SEEK_SET: start = 0; if(offset < 0) return EINVAL; break; case SEEK_SET: start = 0; break;
case SEEK_CUR: case SEEK_CUR:
if (ex64hi(f->filp_pos) != 0) if (ex64hi(f->filp_pos) != 0)
panic(__FILE__, "do_fcntl: position in file too high", panic(__FILE__, "do_fcntl: position in file too high",
@ -236,15 +236,24 @@ PUBLIC int do_fcntl()
if(offset > 0 && start + offset < start) return EINVAL; if(offset > 0 && start + offset < start) return EINVAL;
if(offset < 0 && start + offset > start) return EINVAL; if(offset < 0 && start + offset > start) return EINVAL;
start += offset; start += offset;
if(flock_arg.l_len > 0) { if(start < 0) return EINVAL;
if(flock_arg.l_len != 0) {
if(start >= f->filp_vno->v_size) return EINVAL;
end = start + flock_arg.l_len; end = start + flock_arg.l_len;
if(end <= start) return EINVAL; if(end <= start) return EINVAL;
if(end > f->filp_vno->v_size) end = f->filp_vno->v_size;
} else { } else {
end = 0; end = 0;
} }
return req_ftrunc(f->filp_vno->v_fs_e, f->filp_vno->v_inode_nr, start, r = req_ftrunc(f->filp_vno->v_fs_e, f->filp_vno->v_inode_nr, start,
end); end);
if(r == OK && flock_arg.l_len == 0)
f->filp_vno->v_size = start;
return(r);
} }
default: default:

View file

@ -10,7 +10,7 @@ OBJ= test1 test2 test3 test4 test5 test6 test7 test8 test9 \
test21 test22 test23 test25 test26 test27 test28 test29 \ test21 test22 test23 test25 test26 test27 test28 test29 \
test30 test31 test32 test34 test35 test36 test37 test38 \ test30 test31 test32 test34 test35 test36 test37 test38 \
test39 t10a t11a t11b test40 t40a t40b t40c t40d t40e t40f test41 \ test39 t10a t11a t11b test40 t40a t40b t40c t40d t40e t40f test41 \
test42 test44 test45 test47 test48 test49 test42 test44 test45 test47 test48 test49 test50
BIGOBJ= test20 test24 BIGOBJ= test20 test24
ROOTOBJ= test11 test33 test43 test46 ROOTOBJ= test11 test33 test43 test46
@ -98,4 +98,5 @@ test47: test47.c
test48: test48.c test48: test48.c
test49: test49.c test49: test49.c
test49-gcc: test49.c test49-gcc: test49.c
test50: test50.c

View file

@ -13,13 +13,13 @@ badones= # list of tests that failed
# Print test welcome message # Print test welcome message
clr clr
echo "Running POSIX compliance test suite. There are 53 tests in total." echo "Running POSIX compliance test suite. There are 54 tests in total."
echo " " echo " "
# Run all the tests, keeping track of who failed. # Run all the tests, keeping track of who failed.
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \
41 42 43 44 45 45-gcc 46 47 48 49 49-gcc sh1.sh sh2.sh 41 42 43 44 45 45-gcc 46 47 48 49 49-gcc 50 sh1.sh sh2.sh
do do
if [ -x ./test$i ] if [ -x ./test$i ]
then then

635
test/test50.c Normal file
View file

@ -0,0 +1,635 @@
/* Tests for truncate(2) call family - by D.C. van Moolenbroek */
#define _POSIX_SOURCE 1
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#define ITERATIONS 1
#define MAX_ERROR 4
#define TESTFILE "testfile"
#define TESTSIZE 4096
#define THRESHOLD 1048576
#ifndef MIN
#define MIN(x,y) (((x)<(y))?(x):(y))
#endif
#include "common.c"
_PROTOTYPE(int main, (int argc, char *argv[]));
_PROTOTYPE(void prepare, (void));
_PROTOTYPE(int make_file, (off_t size));
_PROTOTYPE(void check_file, (int fd, off_t size, off_t hole_start,
off_t hole_end));
_PROTOTYPE(void all_sizes,
(_PROTOTYPE(void (*call), (off_t osize, off_t nsize))));
_PROTOTYPE(void test50a, (void));
_PROTOTYPE(void test50b, (void));
_PROTOTYPE(void test50c, (void));
_PROTOTYPE(void test50d, (void));
_PROTOTYPE(void sub50e, (off_t osize, off_t nsize));
_PROTOTYPE(void test50e, (void));
_PROTOTYPE(void sub50f, (off_t osize, off_t nsize));
_PROTOTYPE(void test50f, (void));
_PROTOTYPE(void sub50g, (off_t osize, off_t nsize));
_PROTOTYPE(void test50g, (void));
_PROTOTYPE(void sub50h, (off_t osize, off_t nsize));
_PROTOTYPE(void test50h, (void));
_PROTOTYPE(void sub50i, (off_t size, off_t off, size_t len, int type));
_PROTOTYPE(void test50i, (void));
/* Some of the sizes have been chosen in such a way that they should be on the
* edge of direct/single indirect/double indirect switchovers for a MINIX
* file system with 4K block size.
*/
static off_t sizes[] = {
0L, 1L, 511L, 512L, 513L, 1023L, 1024L, 1025L, 2047L, 2048L, 2049L, 3071L,
3072L, 3073L, 4095L, 4096L, 4097L, 16383L, 16384L, 16385L, 28671L, 28672L,
28673L, 65535L, 65536L, 65537L, 4222975L, 4222976L, 4222977L
};
static unsigned char *data;
int main(argc, argv)
int argc;
char *argv[];
{
int i, j, m = 0xFFFF;
start(50);
prepare();
if (argc == 2) m = atoi(argv[1]);
for (j = 0; j < ITERATIONS; j++) {
if (m & 00001) test50a();
if (m & 00002) test50b();
if (m & 00004) test50c();
if (m & 00010) test50d();
if (m & 00020) test50e();
if (m & 00040) test50f();
if (m & 00100) test50g();
if (m & 00200) test50h();
if (m & 00400) test50i();
}
quit();
return(-1); /* impossible */
}
void prepare()
{
size_t largest;
int i;
largest = 0;
for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++)
if (largest < sizes[i]) largest = sizes[i];
/* internal integrity check: this is needed for early tests */
assert(largest >= TESTSIZE);
data = malloc(largest);
if (data == NULL) e(1000);
srand(1);
for (i = 0; i < largest; i++)
data[i] = (unsigned char) (rand() % 255 + 1);
}
void all_sizes(call)
_PROTOTYPE(void (*call), (off_t osize, off_t nsize));
{
int i, j;
for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++)
for (j = 0; j < sizeof(sizes) / sizeof(sizes[0]); j++)
call(sizes[i], sizes[j]);
}
int make_file(size)
off_t size;
{
off_t off;
int i, fd, r;
if ((fd = open(TESTFILE, O_RDWR|O_CREAT|O_EXCL, 0600)) < 0) e(1001);
off = 0;
while (off < size) {
r = write(fd, data + off, size - off);
if (r != size - off) e(1002);
off += r;
}
return fd;
}
void check_file(fd, hole_start, hole_end, size)
int fd;
off_t hole_start;
off_t hole_end;
off_t size;
{
static unsigned char buf[16384];
struct stat statbuf;
off_t off;
int i, chunk;
/* The size must match. */
if (fstat(fd, &statbuf) != 0) e(1003);
if (statbuf.st_size != size) e(1004);
if (lseek(fd, 0L, SEEK_SET) != 0L) e(1005);
/* All bytes in the file must be equal to what we wrote, except for the bytes
* in the hole, which must be zero.
*/
for (off = 0; off < size; off += chunk) {
chunk = MIN(sizeof(buf), size - off);
if (read(fd, buf, chunk) != chunk) e(1006);
for (i = 0; i < chunk; i++) {
if (off + i >= hole_start && off + i < hole_end) {
if (buf[i] != 0) e(1007);
}
else {
if (buf[i] != data[off+i]) e(1008);
}
}
}
/* We must get back EOF at the end. */
if (read(fd, buf, sizeof(buf)) != 0) e(1009);
}
void test50a()
{
struct stat statbuf;
int fd;
subtest = 1;
if ((fd = open(TESTFILE, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) e(1);
if (write(fd, data, TESTSIZE) != TESTSIZE) e(2);
/* Negative sizes should result in EINVAL. */
if (truncate(TESTFILE, -1) != -1) e(3);
if (errno != EINVAL) e(4);
/* Make sure the file size did not change. */
if (fstat(fd, &statbuf) != 0) e(5);
if (statbuf.st_size != TESTSIZE) e(6);
close(fd);
if (unlink(TESTFILE) != 0) e(7);
/* An empty path should result in ENOENT. */
if (truncate("", 0) != -1) e(8);
if (errno != ENOENT) e(9);
/* A non-existing file name should result in ENOENT. */
if (truncate(TESTFILE"2", 0) != -1) e(10);
if (errno != ENOENT) e(11);
}
void test50b()
{
struct stat statbuf;
int fd;
subtest = 2;
if ((fd = open(TESTFILE, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) e(1);
if (write(fd, data, TESTSIZE) != TESTSIZE) e(2);
/* Negative sizes should result in EINVAL. */
if (ftruncate(fd, -1) != -1) e(3);
if (errno != EINVAL) e(4);
/* Make sure the file size did not change. */
if (fstat(fd, &statbuf) != 0) e(5);
if (statbuf.st_size != TESTSIZE) e(6);
close(fd);
/* Calls on an invalid file descriptor should return EBADF or EINVAL. */
if (ftruncate(fd, 0) != -1) e(7);
if (errno != EBADF && errno != EINVAL) e(8);
if ((fd = open(TESTFILE, O_RDONLY)) < 0) e(9);
/* Calls on a file opened read-only should return EBADF or EINVAL. */
if (ftruncate(fd, 0) != -1) e(10);
if (errno != EBADF && errno != EINVAL) e(11);
close(fd);
if (unlink(TESTFILE) != 0) e(12);
}
void test50c()
{
struct stat statbuf;
struct flock flock;
off_t off;
int fd;
subtest = 3;
if ((fd = open(TESTFILE, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) e(1);
if (write(fd, data, TESTSIZE) != TESTSIZE) e(2);
off = TESTSIZE / 2;
if (lseek(fd, off, SEEK_SET) != off) e(3);
flock.l_len = 0;
/* Negative sizes should result in EINVAL. */
flock.l_whence = SEEK_SET;
flock.l_start = -1;
if (fcntl(fd, F_FREESP, &flock) != -1) e(4);
if (errno != EINVAL) e(5);
flock.l_whence = SEEK_CUR;
flock.l_start = -off - 1;
if (fcntl(fd, F_FREESP, &flock) != -1) e(6);
if (errno != EINVAL) e(7);
flock.l_whence = SEEK_END;
flock.l_start = -TESTSIZE - 1;
if (fcntl(fd, F_FREESP, &flock) != -1) e(8);
if (errno != EINVAL) e(9);
/* Make sure the file size did not change. */
if (fstat(fd, &statbuf) != 0) e(10);
if (statbuf.st_size != TESTSIZE) e(11);
/* Proper negative values should work, however. */
flock.l_whence = SEEK_CUR;
flock.l_start = -1;
if (fcntl(fd, F_FREESP, &flock) != 0) e(12);
if (fstat(fd, &statbuf) != 0) e(13);
if (statbuf.st_size != off - 1) e(14);
flock.l_whence = SEEK_END;
flock.l_start = -off + 1;
if (fcntl(fd, F_FREESP, &flock) != 0) e(15);
if (fstat(fd, &statbuf) != 0) e(16);
if (statbuf.st_size != 0L) e(17);
close(fd);
/* Calls on an invalid file descriptor should return EBADF or EINVAL. */
flock.l_whence = SEEK_SET;
flock.l_start = 0;
if (fcntl(fd, F_FREESP, &flock) != -1) e(18);
if (errno != EBADF && errno != EINVAL) e(19);
if ((fd = open(TESTFILE, O_RDONLY)) < 0) e(20);
/* Calls on a file opened read-only should return EBADF or EINVAL. */
if (fcntl(fd, F_FREESP, &flock) != -1) e(21);
if (errno != EBADF && errno != EINVAL) e(22);
close(fd);
if (unlink(TESTFILE) != 0) e(23);
}
void test50d()
{
struct stat statbuf;
struct flock flock;
off_t off;
int fd;
subtest = 4;
if ((fd = open(TESTFILE, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) e(1);
if (write(fd, data, TESTSIZE) != TESTSIZE) e(2);
off = TESTSIZE / 2;
if (lseek(fd, off, SEEK_SET) != off) e(3);
/* The given length must be positive. */
flock.l_whence = SEEK_CUR;
flock.l_start = 0;
flock.l_len = -1;
if (fcntl(fd, F_FREESP, &flock) != -1) e(4);
if (errno != EINVAL) e(5);
/* Negative start positions are not allowed. */
flock.l_whence = SEEK_SET;
flock.l_start = -1;
flock.l_len = 1;
if (fcntl(fd, F_FREESP, &flock) != -1) e(6);
if (errno != EINVAL) e(7);
flock.l_whence = SEEK_CUR;
flock.l_start = -off - 1;
if (fcntl(fd, F_FREESP, &flock) != -1) e(8);
if (errno != EINVAL) e(9);
flock.l_whence = SEEK_END;
flock.l_start = -TESTSIZE - 1;
if (fcntl(fd, F_FREESP, &flock) != -1) e(10);
if (errno != EINVAL) e(11);
/* Start positions at or beyond the end of the file are no good, either. */
flock.l_whence = SEEK_SET;
flock.l_start = TESTSIZE;
if (fcntl(fd, F_FREESP, &flock) != -1) e(12);
if (errno != EINVAL) e(13);
flock.l_start = TESTSIZE + 1;
if (fcntl(fd, F_FREESP, &flock) != -1) e(13);
if (errno != EINVAL) e(14);
flock.l_whence = SEEK_CUR;
flock.l_start = TESTSIZE - off;
if (fcntl(fd, F_FREESP, &flock) != -1) e(15);
if (errno != EINVAL) e(16);
flock.l_whence = SEEK_END;
flock.l_start = 1;
if (fcntl(fd, F_FREESP, &flock) != -1) e(17);
if (errno != EINVAL) e(18);
/* End positions beyond the end of the file may be silently bounded. */
flock.l_whence = SEEK_SET;
flock.l_start = 0;
flock.l_len = TESTSIZE + 1;
if (fcntl(fd, F_FREESP, &flock) != 0) e(19);
flock.l_whence = SEEK_CUR;
flock.l_len = TESTSIZE - off + 1;
if (fcntl(fd, F_FREESP, &flock) != 0) e(20);
flock.l_whence = SEEK_END;
flock.l_start = -1;
flock.l_len = 2;
if (fcntl(fd, F_FREESP, &flock) != 0) e(21);
/* However, this must never cause the file size to change. */
if (fstat(fd, &statbuf) != 0) e(22);
if (statbuf.st_size != TESTSIZE) e(23);
close(fd);
/* Calls on an invalid file descriptor should return EBADF or EINVAL. */
flock.l_whence = SEEK_SET;
flock.l_start = 0;
if (fcntl(fd, F_FREESP, &flock) != -1) e(24);
if (errno != EBADF && errno != EINVAL) e(25);
if ((fd = open(TESTFILE, O_RDONLY)) < 0) e(26);
/* Calls on a file opened read-only should return EBADF or EINVAL. */
if (fcntl(fd, F_FREESP, &flock) != -1) e(27);
if (errno != EBADF && errno != EINVAL) e(28);
close(fd);
if (unlink(TESTFILE) != 0) e(29);
}
void sub50e(osize, nsize)
off_t osize;
off_t nsize;
{
int fd;
fd = make_file(osize);
if (truncate(TESTFILE, nsize) != 0) e(1);
check_file(fd, osize, nsize, nsize);
if (nsize < osize) {
if (truncate(TESTFILE, osize) != 0) e(2);
check_file(fd, nsize, osize, osize);
}
close(fd);
if (unlink(TESTFILE) != 0) e(3);
}
void test50e()
{
subtest = 5;
/* truncate(2) on a file that is open. */
all_sizes(sub50e);
}
void sub50f(osize, nsize)
off_t osize;
off_t nsize;
{
int fd;
fd = make_file(osize);
close(fd);
if (truncate(TESTFILE, nsize) != 0) e(1);
if ((fd = open(TESTFILE, O_RDONLY)) < 0) e(2);
check_file(fd, osize, nsize, nsize);
if (nsize < osize) {
close(fd);
if (truncate(TESTFILE, osize) != 0) e(3);
if ((fd = open(TESTFILE, O_RDONLY)) < 0) e(4);
check_file(fd, nsize, osize, osize);
}
close(fd);
if (unlink(TESTFILE) != 0) e(5);
}
void test50f()
{
subtest = 6;
/* truncate(2) on a file that is not open. */
all_sizes(sub50f);
}
void sub50g(osize, nsize)
off_t osize;
off_t nsize;
{
int fd, r;
fd = make_file(osize);
if (ftruncate(fd, nsize) != 0) e(1);
check_file(fd, osize, nsize, nsize);
if (nsize < osize) {
if (ftruncate(fd, osize) != 0) e(2);
check_file(fd, nsize, osize, osize);
}
close(fd);
if (unlink(TESTFILE) != 0) e(3);
}
void test50g()
{
subtest = 7;
/* ftruncate(2) on an open file. */
all_sizes(sub50g);
}
void sub50h(osize, nsize)
off_t osize;
off_t nsize;
{
struct flock flock;
int fd;
fd = make_file(osize);
flock.l_whence = SEEK_SET;
flock.l_start = nsize;
flock.l_len = 0;
if (fcntl(fd, F_FREESP, &flock) != 0) e(1);
check_file(fd, osize, nsize, nsize);
if (nsize < osize) {
flock.l_whence = SEEK_SET;
flock.l_start = osize;
flock.l_len = 0;
if (fcntl(fd, F_FREESP, &flock) != 0) e(2);
check_file(fd, nsize, osize, osize);
}
close(fd);
if (unlink(TESTFILE) != 0) e(3);
}
void test50h()
{
subtest = 8;
/* fcntl(2) with F_FREESP and l_len=0. */
all_sizes(sub50h);
}
void sub50i(size, off, len, type)
off_t size;
off_t off;
size_t len;
int type;
{
struct flock flock;
int fd;
fd = make_file(size);
switch (type) {
case 0:
flock.l_whence = SEEK_SET;
flock.l_start = off;
break;
case 1:
if (lseek(fd, off, SEEK_SET) != off) e(1);
flock.l_whence = SEEK_CUR;
flock.l_start = 0;
break;
case 2:
flock.l_whence = SEEK_END;
flock.l_start = off - size;
break;
default:
e(1);
}
flock.l_len = len;
if (fcntl(fd, F_FREESP, &flock) != 0) e(2);
check_file(fd, off, off + len, size);
/* Repeat the call in order to see whether the file system can handle holes
* while freeing up. If not, the server would typically crash; we need not
* check the results again.
*/
flock.l_whence = SEEK_SET;
flock.l_start = off;
if (fcntl(fd, F_FREESP, &flock) != 0) e(3);
close(fd);
if (unlink(TESTFILE) != 0) e(4);
}
void test50i()
{
off_t off;
int i, j, k, l;
subtest = 9;
/* fcntl(2) with F_FREESP and l_len>0. */
/* This loop determines the size of the test file. */
for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) {
/* Big files simply take too long. We have to compromise here. */
if (sizes[i] >= THRESHOLD) continue;
/* This loop determines one of the two values for the offset. */
for (j = 0; j < sizeof(sizes) / sizeof(sizes[0]); j++) {
if (sizes[j] >= sizes[i]) continue;
/* This loop determines the other. */
for (k = 0; k < sizeof(sizes) / sizeof(sizes[0]); k++) {
if (sizes[k] > sizes[j]) continue;
/* Construct an offset by adding the two sizes. */
off = sizes[j] + sizes[k];
if (j + 1 < sizeof(sizes) / sizeof(sizes[0]) &&
off >= sizes[j + 1]) continue;
/* This loop determines the length of the hole. */
for (l = 0; l < sizeof(sizes) / sizeof(sizes[0]); l++) {
if (sizes[l] == 0 || off + sizes[l] > sizes[i])
continue;
/* This could have been a loop, too! */
sub50i(sizes[i], off, sizes[l], 0);
sub50i(sizes[i], off, sizes[l], 1);
sub50i(sizes[i], off, sizes[l], 2);
}
}
}
}
}