mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-11-01 00:38:29 +01:00 
			
		
		
		
	This implements the second step of full dynamic wear-leveling, block allocation randomization. This is the key part the uniformly distributes wear across the filesystem, even through reboots. The entropy actually comes from the filesystem itself, by xoring together all of the CRCs in the metadata-pairs on the filesystem. While this sounds like a ridiculous operation, it's easy to do when we already scan the metadata-pairs at mount time. This gives us a random number we can use for block allocation. Unfortunately it's not a great general purpose random generator as the output only changes every filesystem write. Fortunately that's exactly when we need our allocator. --- Additionally, the randomization created a mess for the testing framework. Fortunately, this method of randomization is deterministic. A very useful property for reproducing bugs.
		
			
				
	
	
		
			325 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Block device emulated on standard files
 | |
|  *
 | |
|  * Copyright (c) 2017, Arm Limited. All rights reserved.
 | |
|  * SPDX-License-Identifier: BSD-3-Clause
 | |
|  */
 | |
| #include "emubd/lfs_emubd.h"
 | |
| 
 | |
| #include <errno.h>
 | |
| #include <string.h>
 | |
| #include <stdlib.h>
 | |
| #include <stdio.h>
 | |
| #include <limits.h>
 | |
| #include <dirent.h>
 | |
| #include <sys/stat.h>
 | |
| #include <unistd.h>
 | |
| #include <assert.h>
 | |
| #include <stdbool.h>
 | |
| #include <inttypes.h>
 | |
| 
 | |
| 
 | |
| // Emulated block device utils
 | |
| static inline void lfs_emubd_tole32(lfs_emubd_t *emu) {
 | |
|     emu->cfg.read_size     = lfs_tole32(emu->cfg.read_size);
 | |
|     emu->cfg.prog_size     = lfs_tole32(emu->cfg.prog_size);
 | |
|     emu->cfg.block_size    = lfs_tole32(emu->cfg.block_size);
 | |
|     emu->cfg.block_count   = lfs_tole32(emu->cfg.block_count);
 | |
| 
 | |
|     emu->stats.read_count  = lfs_tole32(emu->stats.read_count);
 | |
|     emu->stats.prog_count  = lfs_tole32(emu->stats.prog_count);
 | |
|     emu->stats.erase_count = lfs_tole32(emu->stats.erase_count);
 | |
| 
 | |
|     for (int i = 0; i < sizeof(emu->history.blocks) /
 | |
|             sizeof(emu->history.blocks[0]); i++) {
 | |
|         emu->history.blocks[i] = lfs_tole32(emu->history.blocks[i]);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static inline void lfs_emubd_fromle32(lfs_emubd_t *emu) {
 | |
|     emu->cfg.read_size     = lfs_fromle32(emu->cfg.read_size);
 | |
|     emu->cfg.prog_size     = lfs_fromle32(emu->cfg.prog_size);
 | |
|     emu->cfg.block_size    = lfs_fromle32(emu->cfg.block_size);
 | |
|     emu->cfg.block_count   = lfs_fromle32(emu->cfg.block_count);
 | |
| 
 | |
|     emu->stats.read_count  = lfs_fromle32(emu->stats.read_count);
 | |
|     emu->stats.prog_count  = lfs_fromle32(emu->stats.prog_count);
 | |
|     emu->stats.erase_count = lfs_fromle32(emu->stats.erase_count);
 | |
| 
 | |
|     for (int i = 0; i < sizeof(emu->history.blocks) /
 | |
|             sizeof(emu->history.blocks[0]); i++) {
 | |
|         emu->history.blocks[i] = lfs_fromle32(emu->history.blocks[i]);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| // Block device emulated on existing filesystem
 | |
| int lfs_emubd_create(const struct lfs_config *cfg, const char *path) {
 | |
|     lfs_emubd_t *emu = cfg->context;
 | |
|     emu->cfg.read_size   = cfg->read_size;
 | |
|     emu->cfg.prog_size   = cfg->prog_size;
 | |
|     emu->cfg.block_size  = cfg->block_size;
 | |
|     emu->cfg.block_count = cfg->block_count;
 | |
| 
 | |
|     // Allocate buffer for creating children files
 | |
|     size_t pathlen = strlen(path);
 | |
|     emu->path = malloc(pathlen + 1 + LFS_NAME_MAX + 1);
 | |
|     if (!emu->path) {
 | |
|         return -ENOMEM;
 | |
|     }
 | |
| 
 | |
|     strcpy(emu->path, path);
 | |
|     emu->path[pathlen] = '/';
 | |
|     emu->child = &emu->path[pathlen+1];
 | |
|     memset(emu->child, '\0', LFS_NAME_MAX+1);
 | |
| 
 | |
|     // Create directory if it doesn't exist
 | |
|     int err = mkdir(path, 0777);
 | |
|     if (err && errno != EEXIST) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     // Load stats to continue incrementing
 | |
|     snprintf(emu->child, LFS_NAME_MAX, ".stats");
 | |
|     FILE *f = fopen(emu->path, "r");
 | |
|     if (!f) {
 | |
|         memset(&emu->stats, 0, sizeof(emu->stats));
 | |
|     } else {
 | |
|         size_t res = fread(&emu->stats, sizeof(emu->stats), 1, f);
 | |
|         lfs_emubd_fromle32(emu);
 | |
|         if (res < 1) {
 | |
|             return -errno;
 | |
|         }
 | |
| 
 | |
|         err = fclose(f);
 | |
|         if (err) {
 | |
|             return -errno;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Load history
 | |
|     snprintf(emu->child, LFS_NAME_MAX, ".history");
 | |
|     f = fopen(emu->path, "r");
 | |
|     if (!f) {
 | |
|         memset(&emu->history, 0, sizeof(emu->history));
 | |
|     } else {
 | |
|         size_t res = fread(&emu->history, sizeof(emu->history), 1, f);
 | |
|         lfs_emubd_fromle32(emu);
 | |
|         if (res < 1) {
 | |
|             return -errno;
 | |
|         }
 | |
| 
 | |
|         err = fclose(f);
 | |
|         if (err) {
 | |
|             return -errno;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| void lfs_emubd_destroy(const struct lfs_config *cfg) {
 | |
|     lfs_emubd_sync(cfg);
 | |
| 
 | |
|     lfs_emubd_t *emu = cfg->context;
 | |
|     free(emu->path);
 | |
| }
 | |
| 
 | |
| int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block,
 | |
|         lfs_off_t off, void *buffer, lfs_size_t size) {
 | |
|     lfs_emubd_t *emu = cfg->context;
 | |
|     uint8_t *data = buffer;
 | |
| 
 | |
|     // Check if read is valid
 | |
|     assert(off  % cfg->read_size == 0);
 | |
|     assert(size % cfg->read_size == 0);
 | |
|     assert(block < cfg->block_count);
 | |
| 
 | |
|     // Zero out buffer for debugging
 | |
|     memset(data, 0, size);
 | |
| 
 | |
|     // Read data
 | |
|     snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block);
 | |
| 
 | |
|     FILE *f = fopen(emu->path, "rb");
 | |
|     if (!f && errno != ENOENT) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     if (f) {
 | |
|         int err = fseek(f, off, SEEK_SET);
 | |
|         if (err) {
 | |
|             return -errno;
 | |
|         }
 | |
| 
 | |
|         size_t res = fread(data, 1, size, f);
 | |
|         if (res < size && !feof(f)) {
 | |
|             return -errno;
 | |
|         }
 | |
| 
 | |
|         err = fclose(f);
 | |
|         if (err) {
 | |
|             return -errno;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     emu->stats.read_count += 1;
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block,
 | |
|         lfs_off_t off, const void *buffer, lfs_size_t size) {
 | |
|     lfs_emubd_t *emu = cfg->context;
 | |
|     const uint8_t *data = buffer;
 | |
| 
 | |
|     // Check if write is valid
 | |
|     assert(off  % cfg->prog_size == 0);
 | |
|     assert(size % cfg->prog_size == 0);
 | |
|     assert(block < cfg->block_count);
 | |
| 
 | |
|     // Program data
 | |
|     snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block);
 | |
| 
 | |
|     FILE *f = fopen(emu->path, "r+b");
 | |
|     if (!f) {
 | |
|         return (errno == EACCES) ? 0 : -errno;
 | |
|     }
 | |
| 
 | |
|     // Check that file was erased
 | |
|     assert(f);
 | |
| 
 | |
|     int err = fseek(f, off, SEEK_SET);
 | |
|     if (err) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     size_t res = fwrite(data, 1, size, f);
 | |
|     if (res < size) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     err = fseek(f, off, SEEK_SET);
 | |
|     if (err) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     uint8_t dat;
 | |
|     res = fread(&dat, 1, 1, f);
 | |
|     if (res < 1) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     err = fclose(f);
 | |
|     if (err) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     // update history and stats
 | |
|     if (block != emu->history.blocks[0]) {
 | |
|         memcpy(&emu->history.blocks[1], &emu->history.blocks[0],
 | |
|                 sizeof(emu->history) - sizeof(emu->history.blocks[0]));
 | |
|         emu->history.blocks[0] = block;
 | |
|     }
 | |
| 
 | |
|     emu->stats.prog_count += 1;
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) {
 | |
|     lfs_emubd_t *emu = cfg->context;
 | |
| 
 | |
|     // Check if erase is valid
 | |
|     assert(block < cfg->block_count);
 | |
| 
 | |
|     // Erase the block
 | |
|     snprintf(emu->child, LFS_NAME_MAX, "%" PRIx32, block);
 | |
|     struct stat st;
 | |
|     int err = stat(emu->path, &st);
 | |
|     if (err && errno != ENOENT) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     if (!err && S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode)) {
 | |
|         err = unlink(emu->path);
 | |
|         if (err) {
 | |
|             return -errno;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (err || (S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode))) {
 | |
|         FILE *f = fopen(emu->path, "w");
 | |
|         if (!f) {
 | |
|             return -errno;
 | |
|         }
 | |
| 
 | |
|         err = fclose(f);
 | |
|         if (err) {
 | |
|             return -errno;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     emu->stats.erase_count += 1;
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int lfs_emubd_sync(const struct lfs_config *cfg) {
 | |
|     lfs_emubd_t *emu = cfg->context;
 | |
| 
 | |
|     // Just write out info/stats for later lookup
 | |
|     snprintf(emu->child, LFS_NAME_MAX, ".config");
 | |
|     FILE *f = fopen(emu->path, "w");
 | |
|     if (!f) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     lfs_emubd_tole32(emu);
 | |
|     size_t res = fwrite(&emu->cfg, sizeof(emu->cfg), 1, f);
 | |
|     lfs_emubd_fromle32(emu);
 | |
|     if (res < 1) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     int err = fclose(f);
 | |
|     if (err) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     snprintf(emu->child, LFS_NAME_MAX, ".stats");
 | |
|     f = fopen(emu->path, "w");
 | |
|     if (!f) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     lfs_emubd_tole32(emu);
 | |
|     res = fwrite(&emu->stats, sizeof(emu->stats), 1, f);
 | |
|     lfs_emubd_fromle32(emu);
 | |
|     if (res < 1) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     err = fclose(f);
 | |
|     if (err) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     snprintf(emu->child, LFS_NAME_MAX, ".history");
 | |
|     f = fopen(emu->path, "w");
 | |
|     if (!f) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     lfs_emubd_tole32(emu);
 | |
|     res = fwrite(&emu->history, sizeof(emu->history), 1, f);
 | |
|     lfs_emubd_fromle32(emu);
 | |
|     if (res < 1) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     err = fclose(f);
 | |
|     if (err) {
 | |
|         return -errno;
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 |