From bdd4f5857f7de0142637197f8d46e71a60bd3818 Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Tue, 9 Feb 2010 08:12:37 +0000 Subject: [PATCH] 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 --- servers/mfs/link.c | 111 ++++---- servers/mfs/proto.h | 1 - servers/mfs/write.c | 13 +- servers/vfs/link.c | 6 +- servers/vfs/misc.c | 19 +- test/Makefile | 3 +- test/run | 4 +- test/test50.c | 635 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 729 insertions(+), 63 deletions(-) create mode 100644 test/test50.c diff --git a/servers/mfs/link.c b/servers/mfs/link.c index 5ac8558d2..55094dfc1 100644 --- a/servers/mfs/link.c +++ b/servers/mfs/link.c @@ -11,14 +11,16 @@ #define SAME 1000 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, - struct inode *rip, char file_name[NAME_MAX]) ); -FORWARD _PROTOTYPE( off_t nextblock, (off_t pos, int zonesize) ); -FORWARD _PROTOTYPE( void zeroblock_half, (struct inode *i, off_t p, int l)); -FORWARD _PROTOTYPE( void zeroblock_range, (struct inode *i, off_t p, off_t h)); - -/* Args to zeroblock_half() */ + struct inode *rip, char file_name[NAME_MAX]) ); +FORWARD _PROTOTYPE( off_t nextblock, (off_t pos, int zone_size) ); +FORWARD _PROTOTYPE( void zerozone_half, (struct inode *rip, off_t pos, + int half, int zone_size) ); +FORWARD _PROTOTYPE( void zerozone_range, (struct inode *rip, off_t pos, + off_t len) ); + +/* Args to zerozone_half() */ #define FIRST_HALF 0 #define LAST_HALF 1 @@ -513,9 +515,12 @@ off_t newsize; /* inode must become this size */ scale = rip->i_sp->s_log_zone_size; 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); + /* 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. */ rip->i_size = newsize; rip->i_update |= CTIME | MTIME; @@ -545,6 +550,7 @@ off_t start, end; /* range of bytes to free (end uninclusive) */ */ off_t p, e; int zone_size, dev; + int zero_last, zero_first; if(end > rip->i_size) /* freeing beyond end makes no sense */ end = rip->i_size; @@ -555,28 +561,30 @@ off_t start, end; /* range of bytes to free (end uninclusive) */ dev = rip->i_dev; /* device on which inode resides */ /* 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) { - zeroblock_range(rip, start, end-start); + zero_last = start % zone_size; + 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 { - /* First zero unused part of partly used blocks. */ - if(start%zone_size) - zeroblock_half(rip, start, LAST_HALF); - if(end%zone_size && end < rip->i_size) - zeroblock_half(rip, end, FIRST_HALF); + /* First zero unused part of partly used zones. */ + if(zero_last) + zerozone_half(rip, start, LAST_HALF, zone_size); + if(zero_first) + zerozone_half(rip, end, FIRST_HALF, zone_size); + + /* Now completely free the completely unused zones. + * write_map() will free unused (double) indirect + * blocks too. Converting the range to zone numbers avoids + * overflow on p when doing e.g. 'p += zone_size'. + */ + e = end/zone_size; + if(end == rip->i_size && (end % zone_size)) e++; + for(p = nextblock(start, zone_size)/zone_size; p < e; p ++) + write_map(rip, p*zone_size, NO_ZONE, WMAP_FREE); } - /* Now completely free the completely unused blocks. - * write_map() will free unused (double) indirect - * blocks too. Converting the range to zone numbers avoids - * overflow on p when doing e.g. 'p += zone_size'. - */ - e = end/zone_size; - if(end == rip->i_size && (end % zone_size)) e++; - for(p = nextblock(start, zone_size)/zone_size; p < e; p ++) - write_map(rip, p*zone_size, NO_ZONE, WMAP_FREE); - rip->i_update |= CTIME | MTIME; 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; off_t pos; 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. * * 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; /* Offset of zeroing boundary. */ - offset = pos % rip->i_sp->s_block_size; + offset = pos % zone_size; if(half == LAST_HALF) { - len = rip->i_sp->s_block_size - offset; + len = zone_size - offset; } else { len = 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; off_t pos; off_t len; { -/* Zero a range in a block. - * This function is used to zero a segment of a block, either - * FIRST_HALF of LAST_HALF. - * +/* Zero an arbitrary byte range in a zone, possibly spanning multiple blocks. */ block_t b; struct buf *bp; off_t offset; + int bytes, block_size; + + block_size = rip->i_sp->s_block_size; if(!len) return; /* no zeroing to be done. */ if( (b = read_map(rip, pos)) == NO_BLOCK) return; - if( (bp = get_block(rip->i_dev, b, NORMAL)) == NIL_BUF) - panic(__FILE__, "zeroblock_range: no block", NO_NUM); - offset = pos % rip->i_sp->s_block_size; - if(offset + len > rip->i_sp->s_block_size) - panic(__FILE__, "zeroblock_range: len too long", len); - memset(bp->b_data + offset, 0, len); - bp->b_dirt = DIRTY; - put_block(bp, FULL_DATA_BLOCK); + while (len > 0) { + if( (bp = get_block(rip->i_dev, b, NORMAL)) == NIL_BUF) + panic(__FILE__, "zerozone_range: no block", NO_NUM); + offset = pos % block_size; + bytes = block_size - offset; + if (bytes > len) + bytes = len; + memset(bp->b_data + offset, 0, bytes); + bp->b_dirt = DIRTY; + put_block(bp, FULL_DATA_BLOCK); + + pos += bytes; + len -= bytes; + b++; + } } diff --git a/servers/mfs/proto.h b/servers/mfs/proto.h index cb4a403d8..18ece47b1 100644 --- a/servers/mfs/proto.h +++ b/servers/mfs/proto.h @@ -94,7 +94,6 @@ _PROTOTYPE( struct buf *rahead, (struct inode *rip, block_t baseblock, u64_t position, unsigned bytes_ahead) ); _PROTOTYPE( void read_ahead, (void) ); _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) ); /* stadir.c */ diff --git a/servers/mfs/write.c b/servers/mfs/write.c index 3bac78cb9..b2e90b2a1 100644 --- a/servers/mfs/write.c +++ b/servers/mfs/write.c @@ -1,5 +1,5 @@ /* 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 * 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 - * Free the indirect block. + * free the indirect block. */ if(empty_indir(bp, rip->i_sp)) { free_zone(rip->i_dev, z1); @@ -161,15 +161,18 @@ int op; /* special actions */ } else { 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); } /* If the single indirect block isn't there (or was just freed), * 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 && empty_indir(bp_dindir, rip->i_sp)) { + bp_dindir->b_dirt = CLEAN; free_zone(rip->i_dev, z2); 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) register struct inode *rip; /* inode 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 * 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; diff --git a/servers/vfs/link.c b/servers/vfs/link.c index a7d28a65d..4d44aea76 100644 --- a/servers/vfs/link.c +++ b/servers/vfs/link.c @@ -197,6 +197,8 @@ PUBLIC int do_truncate() struct vnode *vp; int r; + if ((off_t) m_in.flength < 0) return(EINVAL); + /* Temporarily open file */ 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); @@ -219,10 +221,12 @@ PUBLIC int do_ftruncate() int r; struct filp *rfilp; + if ((off_t) m_in.flength < 0) return(EINVAL); + /* 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->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); } diff --git a/servers/vfs/misc.c b/servers/vfs/misc.c index 4178f736a..529610ae4 100644 --- a/servers/vfs/misc.c +++ b/servers/vfs/misc.c @@ -220,7 +220,7 @@ PUBLIC int do_fcntl() /* Figure out starting position base. */ 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: if (ex64hi(f->filp_pos) != 0) 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; 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; if(end <= start) return EINVAL; + if(end > f->filp_vno->v_size) end = f->filp_vno->v_size; } else { end = 0; } - - return req_ftrunc(f->filp_vno->v_fs_e, f->filp_vno->v_inode_nr, start, - end); + + r = req_ftrunc(f->filp_vno->v_fs_e, f->filp_vno->v_inode_nr, start, + end); + + if(r == OK && flock_arg.l_len == 0) + f->filp_vno->v_size = start; + + return(r); } default: diff --git a/test/Makefile b/test/Makefile index ded683ffc..a6973ba52 100644 --- a/test/Makefile +++ b/test/Makefile @@ -10,7 +10,7 @@ OBJ= test1 test2 test3 test4 test5 test6 test7 test8 test9 \ test21 test22 test23 test25 test26 test27 test28 test29 \ test30 test31 test32 test34 test35 test36 test37 test38 \ 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 ROOTOBJ= test11 test33 test43 test46 @@ -98,4 +98,5 @@ test47: test47.c test48: test48.c test49: test49.c test49-gcc: test49.c +test50: test50.c diff --git a/test/run b/test/run index 5267264f1..11cb0a2cc 100755 --- a/test/run +++ b/test/run @@ -13,13 +13,13 @@ badones= # list of tests that failed # Print test welcome message 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 " " # 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 \ 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 if [ -x ./test$i ] then diff --git a/test/test50.c b/test/test50.c new file mode 100644 index 000000000..7da55f016 --- /dev/null +++ b/test/test50.c @@ -0,0 +1,635 @@ +/* Tests for truncate(2) call family - by D.C. van Moolenbroek */ +#define _POSIX_SOURCE 1 +#include +#include +#include +#include + +#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); + } + } + } + } +} -- 2.44.0