]> Zhao Yanbai Git Server - minix.git/commitdiff
Fixes for truncate system calls:
authorDavid van Moolenbroek <david@minix3.org>
Tue, 9 Feb 2010 08:12:37 +0000 (08:12 +0000)
committerDavid van Moolenbroek <david@minix3.org>
Tue, 9 Feb 2010 08:12:37 +0000 (08:12 +0000)
- 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
servers/mfs/proto.h
servers/mfs/write.c
servers/vfs/link.c
servers/vfs/misc.c
test/Makefile
test/run
test/test50.c [new file with mode: 0644]

index 5ac8558d23b2bda9161723e2c46b10e57dc9d5db..55094dfc143b26be06c9f3de5f5f3f3d080c2285 100644 (file)
 #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++;
+  }
 }
 
index cb4a403d861f5737e2feab1ea9d9bff8f071528d..18ece47b16c5ec78a1cf8f55ce62f867288b9be4 100644 (file)
@@ -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 */
index 3bac78cb926135060cd9f748860ca7d1b2c15df7..b2e90b2a135bc056f6950a8cd98f549fe2d20bb5 100644 (file)
@@ -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;
index a7d28a65de48605ec1da3e76bd822f716b5d00b6..4d44aea760cf63d3f4ee80c8821a3ab91fdad952 100644 (file)
@@ -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);
 }
 
 
index 4178f736a736bf03767758ceee29567b02c5480d..529610ae4cb4af35a4c3cc799653c0ff5ff40bda 100644 (file)
@@ -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:
index ded683ffc01dc307c0d31b859ac46b59c30e789a..a6973ba52b39e45e864fb87d615316f9cc49b1ab 100644 (file)
@@ -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
 
index 5267264f1b4d7c1bd173d71548141db661aabcd1..11cb0a2ccfb4b9b5f7c8dc2939b5569eff889d22 100755 (executable)
--- 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 (file)
index 0000000..7da55f0
--- /dev/null
@@ -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);
+                       }
+               }
+       }
+  }
+}