mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 08:42:40 +01:00 
			
		
		
		
	This change is necessary to handle out-of-order writes found by pjsg's fuzzing work. The problem is that it is possible for (non-NOR) block devices to write pages in any order, or to even write random data in the case of a power-loss. This breaks littlefs's use of the first bit in a page to indicate the erase-state. pjsg notes this behavior is documented in the W25Q here: https://community.cypress.com/docs/DOC-10507 --- The basic idea here is to CRC the next page, and use this "erase-state CRC" to check if the next page is erased and ready to accept programs. .------------------. \ commit | metadata | | | | +---. | | | | |------------------| | | | erase-state CRC -----. | |------------------| | | | | commit CRC ---|-|-' |------------------| / | | padding | | padding (doesn't need CRC) | | | |------------------| \ | next prog | erased? | +-' | | | | | v | / | | | | '------------------' This is made a bit annoying since littlefs doesn't actually store the page (prog_size) in the superblock, since it doesn't need to know the size for any other operation. We can work around this by storing both the CRC and size of the next page when necessary. Another interesting note is that we don't need to any bit tweaking information, since we read the next page every time we would need to know how to clobber the erase-state CRC. And since we only read prog_size, this works really well with our caching, since the caches must be a multiple of prog_size. This also brings back the internal lfs_bd_crc function, in which we can use some optimizations added to lfs_bd_cmp. Needs some cleanup but the idea is passing most relevant tests.
		
			
				
	
	
		
			206 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Block device emulated in a file
 | |
|  *
 | |
|  * Copyright (c) 2017, Arm Limited. All rights reserved.
 | |
|  * SPDX-License-Identifier: BSD-3-Clause
 | |
|  */
 | |
| #include "bd/lfs_filebd.h"
 | |
| 
 | |
| #include <fcntl.h>
 | |
| #include <unistd.h>
 | |
| #include <errno.h>
 | |
| 
 | |
| int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path,
 | |
|         const struct lfs_filebd_config *bdcfg) {
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_createcfg(%p {.context=%p, "
 | |
|                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
 | |
|                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
 | |
|                 ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
 | |
|                 "\"%s\", "
 | |
|                 "%p {.erase_value=%"PRId32"})",
 | |
|             (void*)cfg, cfg->context,
 | |
|             (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
 | |
|             (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
 | |
|             cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
 | |
|             path, (void*)bdcfg, bdcfg->erase_value);
 | |
|     lfs_filebd_t *bd = cfg->context;
 | |
|     bd->cfg = bdcfg;
 | |
| 
 | |
|     // open file
 | |
|     bd->fd = open(path, O_RDWR | O_CREAT, 0666);
 | |
|     if (bd->fd < 0) {
 | |
|         int err = -errno;
 | |
|         LFS_FILEBD_TRACE("lfs_filebd_createcfg -> %d", err);
 | |
|         return err;
 | |
|     }
 | |
| 
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_createcfg -> %d", 0);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int lfs_filebd_create(const struct lfs_config *cfg, const char *path) {
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_create(%p {.context=%p, "
 | |
|                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
 | |
|                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
 | |
|                 ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
 | |
|                 "\"%s\")",
 | |
|             (void*)cfg, cfg->context,
 | |
|             (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
 | |
|             (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
 | |
|             cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
 | |
|             path);
 | |
|     static const struct lfs_filebd_config defaults = {.erase_value=-1};
 | |
|     int err = lfs_filebd_createcfg(cfg, path, &defaults);
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_create -> %d", err);
 | |
|     return err;
 | |
| }
 | |
| 
 | |
| int lfs_filebd_destroy(const struct lfs_config *cfg) {
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_destroy(%p)", (void*)cfg);
 | |
|     lfs_filebd_t *bd = cfg->context;
 | |
|     int err = close(bd->fd);
 | |
|     if (err < 0) {
 | |
|         err = -errno;
 | |
|         LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", err);
 | |
|         return err;
 | |
|     }
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", 0);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
 | |
|         lfs_off_t off, void *buffer, lfs_size_t size) {
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_read(%p, "
 | |
|                 "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
 | |
|             (void*)cfg, block, off, buffer, size);
 | |
|     lfs_filebd_t *bd = cfg->context;
 | |
| 
 | |
|     // check if read is valid
 | |
|     LFS_ASSERT(off  % cfg->read_size == 0);
 | |
|     LFS_ASSERT(size % cfg->read_size == 0);
 | |
|     LFS_ASSERT(block < cfg->block_count);
 | |
| 
 | |
|     // read
 | |
|     off_t res1 = lseek(bd->fd,
 | |
|             (off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
 | |
|     if (res1 < 0) {
 | |
|         int err = -errno;
 | |
|         LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err);
 | |
|         return err;
 | |
|     }
 | |
| 
 | |
|     ssize_t res2 = read(bd->fd, buffer, size);
 | |
|     if (res2 < 0) {
 | |
|         int err = -errno;
 | |
|         LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err);
 | |
|         return err;
 | |
|     }
 | |
| 
 | |
|     // file truncated? zero for reproducability
 | |
|     if (res2 < size) {
 | |
|         memset((uint8_t*)buffer + res2, 0, size-res2);
 | |
|     }
 | |
| 
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_read -> %d", 0);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
 | |
|         lfs_off_t off, const void *buffer, lfs_size_t size) {
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
 | |
|             (void*)cfg, block, off, buffer, size);
 | |
|     lfs_filebd_t *bd = cfg->context;
 | |
| 
 | |
|     // check if write is valid
 | |
|     LFS_ASSERT(off  % cfg->prog_size == 0);
 | |
|     LFS_ASSERT(size % cfg->prog_size == 0);
 | |
|     LFS_ASSERT(block < cfg->block_count);
 | |
| 
 | |
|     // check that data was erased? only needed for testing
 | |
|     if (bd->cfg->erase_value != -1) {
 | |
|         off_t res1 = lseek(bd->fd,
 | |
|                 (off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
 | |
|         if (res1 < 0) {
 | |
|             int err = -errno;
 | |
|             LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err);
 | |
|             return err;
 | |
|         }
 | |
| 
 | |
|         for (lfs_off_t i = 0; i < size; i++) {
 | |
|             uint8_t c;
 | |
|             ssize_t res2 = read(bd->fd, &c, 1);
 | |
|             if (res2 < 0) {
 | |
|                 int err = -errno;
 | |
|                 LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err);
 | |
|                 return err;
 | |
|             }
 | |
| 
 | |
|             LFS_ASSERT(c == bd->cfg->erase_value);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // program data
 | |
|     off_t res1 = lseek(bd->fd,
 | |
|             (off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
 | |
|     if (res1 < 0) {
 | |
|         int err = -errno;
 | |
|         LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err);
 | |
|         return err;
 | |
|     }
 | |
| 
 | |
|     ssize_t res2 = write(bd->fd, buffer, size);
 | |
|     if (res2 < 0) {
 | |
|         int err = -errno;
 | |
|         LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err);
 | |
|         return err;
 | |
|     }
 | |
| 
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", 0);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) {
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
 | |
|     lfs_filebd_t *bd = cfg->context;
 | |
| 
 | |
|     // check if erase is valid
 | |
|     LFS_ASSERT(block < cfg->block_count);
 | |
| 
 | |
|     // erase, only needed for testing
 | |
|     if (bd->cfg->erase_value != -1) {
 | |
|         off_t res1 = lseek(bd->fd, (off_t)block*cfg->block_size, SEEK_SET);
 | |
|         if (res1 < 0) {
 | |
|             int err = -errno;
 | |
|             LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", err);
 | |
|             return err;
 | |
|         }
 | |
| 
 | |
|         for (lfs_off_t i = 0; i < cfg->block_size; i++) {
 | |
|             ssize_t res2 = write(bd->fd, &(uint8_t){bd->cfg->erase_value}, 1);
 | |
|             if (res2 < 0) {
 | |
|                 int err = -errno;
 | |
|                 LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", err);
 | |
|                 return err;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", 0);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int lfs_filebd_sync(const struct lfs_config *cfg) {
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_sync(%p)", (void*)cfg);
 | |
|     // file sync
 | |
|     lfs_filebd_t *bd = cfg->context;
 | |
|     int err = fsync(bd->fd);
 | |
|     if (err) {
 | |
|         err = -errno;
 | |
|         LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0);
 | |
|         return err;
 | |
|     }
 | |
| 
 | |
|     LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0);
 | |
|     return 0;
 | |
| }
 |