소스 검색

Merge pull request #401 from thrasher8390/bugfix/thrasher8390/issue-394-lookahead-buffer-corruption

Lookahead corruption fix given an IO Error during traversal
Christopher Haster 5 년 전
부모
커밋
01e42abd10
2개의 변경된 파일99개의 추가작업 그리고 9개의 파일을 삭제
  1. 15 9
      lfs.c
  2. 84 0
      tests/test_alloc.toml

+ 15 - 9
lfs.c

@@ -437,6 +437,19 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
     return 0;
 }
 
+static void lfs_alloc_ack(lfs_t *lfs) {
+    lfs->free.ack = lfs->cfg->block_count;
+}
+
+// Invalidate the lookahead buffer. This is done during mounting and
+// failed traversals
+static void lfs_alloc_reset(lfs_t *lfs) {
+    lfs->free.off = lfs->seed % lfs->cfg->block_size;
+    lfs->free.size = 0;
+    lfs->free.i = 0;
+    lfs_alloc_ack(lfs);
+}
+
 static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
     while (true) {
         while (lfs->free.i != lfs->free.size) {
@@ -477,16 +490,12 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
         memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
         int err = lfs_fs_traverseraw(lfs, lfs_alloc_lookahead, lfs, true);
         if (err) {
+            lfs_alloc_reset(lfs);
             return err;
         }
     }
 }
 
-static void lfs_alloc_ack(lfs_t *lfs) {
-    lfs->free.ack = lfs->cfg->block_count;
-}
-
-
 /// Metadata pair and directory operations ///
 static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir,
         lfs_tag_t gmask, lfs_tag_t gtag,
@@ -3772,10 +3781,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
     lfs->gdisk = lfs->gstate;
 
     // setup free lookahead
-    lfs->free.off = lfs->seed % lfs->cfg->block_size;
-    lfs->free.size = 0;
-    lfs->free.i = 0;
-    lfs_alloc_ack(lfs);
+    lfs_alloc_reset(lfs);
 
     LFS_TRACE("lfs_mount -> %d", 0);
     return 0;

+ 84 - 0
tests/test_alloc.toml

@@ -323,6 +323,90 @@ code = '''
     lfs_unmount(&lfs) => 0;
 '''
 
+[[case]] # what if we have a bad block during an allocation scan?
+in = "lfs.c"
+define.LFS_ERASE_CYCLES = 0xffffffff
+define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_READERROR'
+code = '''
+    lfs_format(&lfs, &cfg) => 0;
+    lfs_mount(&lfs, &cfg) => 0;
+    // first fill to exhaustion to find available space
+    lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    strcpy((char*)buffer, "waka");
+    size = strlen("waka");
+    lfs_size_t filesize = 0;
+    while (true) {
+        lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
+        assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
+        if (res == LFS_ERR_NOSPC) {
+            break;
+        }
+        filesize += size;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    // now fill all but a couple of blocks of the filesystem with data
+    filesize -= 3*LFS_BLOCK_SIZE;
+    lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    strcpy((char*)buffer, "waka");
+    size = strlen("waka");
+    for (lfs_size_t i = 0; i < filesize/size; i++) {
+        lfs_file_write(&lfs, &file, buffer, size) => size;
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    // also save head of file so we can error during lookahead scan
+    lfs_block_t fileblock = file.ctz.head;
+    lfs_unmount(&lfs) => 0;
+
+    // remount to force an alloc scan
+    lfs_mount(&lfs, &cfg) => 0;
+
+    // but mark the head of our file as a "bad block", this is force our
+    // scan to bail early
+    lfs_testbd_setwear(&cfg, fileblock, 0xffffffff) => 0;
+    lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    strcpy((char*)buffer, "chomp");
+    size = strlen("chomp");
+    while (true) {
+        lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
+        assert(res == (lfs_ssize_t)size || res == LFS_ERR_CORRUPT);
+        if (res == LFS_ERR_CORRUPT) {
+            break;
+        }
+    }
+    lfs_file_close(&lfs, &file) => 0;
+
+    // now reverse the "bad block" and try to write the file again until we
+    // run out of space
+    lfs_testbd_setwear(&cfg, fileblock, 0) => 0;
+    lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
+    strcpy((char*)buffer, "chomp");
+    size = strlen("chomp");
+    while (true) {
+        lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
+        assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
+        if (res == LFS_ERR_NOSPC) {
+            break;
+        }
+    }
+    lfs_file_close(&lfs, &file) => 0;
+
+    lfs_unmount(&lfs) => 0;
+
+    // check that the disk isn't hurt
+    lfs_mount(&lfs, &cfg) => 0;
+    lfs_file_open(&lfs, &file, "pacman", LFS_O_RDONLY) => 0;
+    strcpy((char*)buffer, "waka");
+    size = strlen("waka");
+    for (lfs_size_t i = 0; i < filesize/size; i++) {
+        uint8_t rbuffer[4];
+        lfs_file_read(&lfs, &file, rbuffer, size) => size;
+        assert(memcmp(rbuffer, buffer, size) == 0);
+    }
+    lfs_file_close(&lfs, &file) => 0;
+    lfs_unmount(&lfs) => 0;
+'''
+
+
 # Below, I don't like these tests. They're fragile and depend _heavily_
 # on the geometry of the block device. But they are valuable. Eventually they
 # should be removed and replaced with generalized tests.