瀏覽代碼

Added lfs_file_truncate

As a copy-on-write filesystem, the truncate function is a very nice
function to have, as it can take advantage of reusing the data already
written out to disk.
Christopher Haster 8 年之前
父節點
當前提交
d88f0ac02f
共有 4 個文件被更改,包括 195 次插入2 次删除
  1. 1 1
      Makefile
  2. 56 1
      lfs.c
  3. 5 0
      lfs.h
  4. 133 0
      tests/test_truncate.sh

+ 1 - 1
Makefile

@@ -33,7 +33,7 @@ size: $(OBJ)
 	$(SIZE) -t $^
 
 .SUFFIXES:
-test: test_format test_dirs test_files test_seek test_parallel \
+test: test_format test_dirs test_files test_seek test_truncate test_parallel \
 	test_alloc test_paths test_orphan test_move test_corrupt
 test_%: tests/test_%.sh
 ifdef QUIET

+ 56 - 1
lfs.c

@@ -1664,6 +1664,57 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
     return file->pos;
 }
 
+int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
+    if ((file->flags & 3) == LFS_O_RDONLY) {
+        return LFS_ERR_INVAL;
+    }
+
+    if (size < lfs_file_size(lfs, file)) {
+        // need to flush since directly changing metadata
+        int err = lfs_file_flush(lfs, file);
+        if (err) {
+            return err;
+        }
+
+        // lookup new head in ctz skip list
+        err = lfs_ctz_find(lfs, &file->cache, NULL,
+                file->head, file->size,
+                size, &file->head, &(lfs_off_t){0});
+        if (err) {
+            return err;
+        }
+
+        file->size = size;
+        file->flags |= LFS_F_DIRTY;
+    } else if (size > lfs_file_size(lfs, file)) {
+        lfs_off_t pos = file->pos;
+
+        // flush+seek if not already at end
+        if (file->pos != lfs_file_size(lfs, file)) {
+            int err = lfs_file_seek(lfs, file, 0, SEEK_END);
+            if (err) {
+                return err;
+            }
+        }
+
+        // fill with zeros
+        while (file->pos < size) {
+            lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
+            if (res < 0) {
+                return res;
+            }
+        }
+
+        // restore pos
+        int err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET);
+        if (err < 0) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+
 lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) {
     return file->pos;
 }
@@ -1678,7 +1729,11 @@ int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) {
 }
 
 lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
-    return lfs_max(file->pos, file->size);
+    if (file->flags & LFS_F_WRITING) {
+        return lfs_max(file->pos, file->size);
+    } else {
+        return file->size;
+    }
 }
 
 

+ 5 - 0
lfs.h

@@ -364,6 +364,11 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
 lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
         lfs_soff_t off, int whence);
 
+// Truncates the size of the file to the specified size
+//
+// Returns a negative error code on failure.
+int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size);
+
 // Return the position of the file
 //
 // Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)

+ 133 - 0
tests/test_truncate.sh

@@ -0,0 +1,133 @@
+#!/bin/bash
+set -eu
+
+SMALLSIZE=32
+MEDIUMSIZE=2048
+LARGESIZE=8192
+
+echo "=== Truncate tests ==="
+rm -rf blocks
+tests/test.py << TEST
+    lfs_format(&lfs, &cfg) => 0;
+TEST
+
+truncate_test() {
+STARTSIZES="$1"
+HOTSIZES="$2"
+COLDSIZES="$3"
+tests/test.py << TEST
+    static const lfs_off_t startsizes[] = {$STARTSIZES};
+    static const lfs_off_t hotsizes[]   = {$HOTSIZES};
+
+    lfs_mount(&lfs, &cfg) => 0;
+
+    for (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
+        sprintf((char*)buffer, "hairyhead%d", i);
+        lfs_file_open(&lfs, &file[0], (const char*)buffer,
+                LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
+
+        strcpy((char*)buffer, "hair");
+        size = strlen((char*)buffer);
+        for (int j = 0; j < startsizes[i]; j += size) {
+            lfs_file_write(&lfs, &file[0], buffer, size) => size;
+        }
+        lfs_file_size(&lfs, &file[0]) => startsizes[i];
+
+        lfs_file_truncate(&lfs, &file[0], hotsizes[i]) => 0;
+        lfs_file_size(&lfs, &file[0]) => hotsizes[i];
+
+        lfs_file_close(&lfs, &file[0]) => 0;
+    }
+
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    static const lfs_off_t startsizes[] = {$STARTSIZES};
+    static const lfs_off_t hotsizes[]   = {$HOTSIZES};
+    static const lfs_off_t coldsizes[]  = {$COLDSIZES};
+
+    lfs_mount(&lfs, &cfg) => 0;
+
+    for (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
+        sprintf((char*)buffer, "hairyhead%d", i);
+        lfs_file_open(&lfs, &file[0], (const char*)buffer, LFS_O_RDWR) => 0;
+        lfs_file_size(&lfs, &file[0]) => hotsizes[i];
+
+        size = strlen("hair");
+        int j = 0;
+        for (; j < startsizes[i] && j < hotsizes[i]; j += size) {
+            lfs_file_read(&lfs, &file[0], buffer, size) => size;
+            memcmp(buffer, "hair", size) => 0;
+        }
+
+        for (; j < hotsizes[i]; j += size) {
+            lfs_file_read(&lfs, &file[0], buffer, size) => size;
+            memcmp(buffer, "\0\0\0\0", size) => 0;
+        }
+
+        lfs_file_truncate(&lfs, &file[0], coldsizes[i]) => 0;
+        lfs_file_size(&lfs, &file[0]) => coldsizes[i];
+
+        lfs_file_close(&lfs, &file[0]) => 0;
+    }
+
+    lfs_unmount(&lfs) => 0;
+TEST
+tests/test.py << TEST
+    static const lfs_off_t startsizes[] = {$STARTSIZES};
+    static const lfs_off_t hotsizes[]   = {$HOTSIZES};
+    static const lfs_off_t coldsizes[]  = {$COLDSIZES};
+
+    lfs_mount(&lfs, &cfg) => 0;
+
+    for (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
+        sprintf((char*)buffer, "hairyhead%d", i);
+        lfs_file_open(&lfs, &file[0], (const char*)buffer, LFS_O_RDONLY) => 0;
+        lfs_file_size(&lfs, &file[0]) => coldsizes[i];
+
+        size = strlen("hair");
+        int j = 0;
+        for (; j < startsizes[i] && j < hotsizes[i] && j < coldsizes[i];
+                j += size) {
+            lfs_file_read(&lfs, &file[0], buffer, size) => size;
+            memcmp(buffer, "hair", size) => 0;
+        }
+
+        for (; j < coldsizes[i]; j += size) {
+            lfs_file_read(&lfs, &file[0], buffer, size) => size;
+            memcmp(buffer, "\0\0\0\0", size) => 0;
+        }
+
+        lfs_file_close(&lfs, &file[0]) => 0;
+    }
+
+    lfs_unmount(&lfs) => 0;
+TEST
+}
+
+echo "--- Cold shrinking truncate ---"
+truncate_test \
+    "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
+    "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
+    "           0,   $SMALLSIZE,  $MEDIUMSIZE,   $LARGESIZE, 2*$LARGESIZE"
+
+echo "--- Cold expanding truncate ---"
+truncate_test \
+    "           0,   $SMALLSIZE,  $MEDIUMSIZE,   $LARGESIZE, 2*$LARGESIZE" \
+    "           0,   $SMALLSIZE,  $MEDIUMSIZE,   $LARGESIZE, 2*$LARGESIZE" \
+    "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE"
+
+echo "--- Warm shrinking truncate ---"
+truncate_test \
+    "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
+    "           0,   $SMALLSIZE,  $MEDIUMSIZE,   $LARGESIZE, 2*$LARGESIZE" \
+    "           0,            0,            0,            0,            0"
+
+echo "--- Warm expanding truncate ---"
+truncate_test \
+    "           0,   $SMALLSIZE,  $MEDIUMSIZE,   $LARGESIZE, 2*$LARGESIZE" \
+    "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
+    "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE"
+
+echo "--- Results ---"
+tests/stats.py