Files
luban-lite-t3e-pro/packages/third-party/mklittlefs/main.cpp
刘可亮 7bbc029dae v1.0.0
2023-08-30 16:21:18 +08:00

813 lines
23 KiB
C++

//
// main.cpp
// make_littlefs
//
// Created by Earle F. Philhower, III on December 15, 2018
// Derived from mkspiffs:
// | Created by Ivan Grokhotkov on 13/05/15.
// | Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
//
#define TCLAP_SETBASE_ZERO 1
#include <iostream>
#include <vector>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <utime.h>
#include <unistd.h>
#include <cstring>
#include <string>
#include <memory>
#include <cstdlib>
#include "tclap/CmdLine.h"
#include "tclap/UnlabeledValueArg.h"
extern "C" {
#ifndef LFS_NAME_MAX
#define LFS_NAME_MAX 32
#endif
#include "littlefs/lfs.h"
}
#ifndef PATH_MAX
#define PATH_MAX 512
#endif
static std::vector<uint8_t> s_flashmem;
static std::string s_dirName;
static std::string s_imageName;
static uint32_t s_imageSize;
static uint32_t s_pageSize;
static uint32_t s_blockSize;
enum Action { ACTION_NONE, ACTION_PACK, ACTION_UNPACK, ACTION_LIST };
static Action s_action = ACTION_NONE;
static int s_debugLevel = 0;
static bool s_addAllFiles;
// Unless -a flag is given, these files/directories will not be included into the image
static const char* ignored_file_names[] = {
".DS_Store",
".git",
".gitignore",
".gitmodules"
};
int lfs_flash_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
{
memcpy(buffer, &s_flashmem[0] + c->block_size * block + off, size);
return 0;
}
int lfs_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
memcpy(&s_flashmem[0] + block * c->block_size + off, buffer, size);
return 0;
}
int lfs_flash_erase(const struct lfs_config *c, lfs_block_t block)
{
memset(&s_flashmem[0] + block * c->block_size, 0, c->block_size);
return 0;
}
int lfs_flash_sync(const struct lfs_config *c) {
(void) c;
return 0;
}
// Implementation
static lfs_t s_fs;
static lfs_config s_cfg;
bool s_mounted = false;
void setLfsConfig()
{
memset(&s_fs, 0, sizeof(s_fs));
memset(&s_cfg, 0, sizeof(s_cfg));
s_cfg.read = lfs_flash_read;
s_cfg.prog = lfs_flash_prog;
s_cfg.erase = lfs_flash_erase;
s_cfg.sync = lfs_flash_sync;
s_cfg.read_size = 64;
s_cfg.prog_size = 64;
s_cfg.block_size = s_blockSize;
s_cfg.block_count = s_flashmem.size() / s_blockSize;
s_cfg.block_cycles = 16; // TODO - need better explanation
s_cfg.cache_size = 64;
s_cfg.lookahead_size = 64;
s_cfg.read_buffer = nullptr;
s_cfg.prog_buffer = nullptr;
s_cfg.lookahead_buffer = nullptr;
s_cfg.name_max = 0;
s_cfg.file_max = 0;
s_cfg.attr_max = 0;
}
int littlefsTryMount() {
setLfsConfig();
int ret = lfs_mount(&s_fs, &s_cfg);
if (ret) {
s_mounted = false;
return -1;
}
s_mounted = true;
return 0;
}
bool littlefsMount(){
if (s_mounted)
return true;
int res = littlefsTryMount();
return (res == 0);
}
void littlefsUnmount() {
if (s_mounted) {
lfs_unmount(&s_fs);
s_mounted = false;
}
}
bool littlefsFormat(){
littlefsUnmount();
setLfsConfig();
int formated = lfs_format(&s_fs, &s_cfg);
if(formated != 0)
return false;
return (littlefsTryMount() == 0);
}
int addFile(char* name, const char* path) {
FILE* src = fopen(path, "rb");
if (!src) {
std::cerr << "error: failed to open " << path << " for reading" << std::endl;
return 1;
}
struct stat sbuf;
// Make any subdirs required to place this file
char pathStr[PATH_MAX+1];
strcpy(pathStr, name); // Already know path length < LFS_NAME_MAX
// Make dirs up to the final fnamepart
char *ptr = strchr(pathStr, '/');
while (ptr) {
*ptr = 0;
lfs_mkdir(&s_fs, pathStr); // Ignore error, we'll catch later if it's fatal
// Add time metadata 't'
if (!stat(path, &sbuf)) {
uint32_t ftime = sbuf.st_mtime;
lfs_setattr(&s_fs, pathStr, 't', (const void *)&ftime, sizeof(ftime));
// There is no portable way to get creation time via stat, so simply call it identical to the last write in this case
lfs_setattr(&s_fs, pathStr, 'c', (const void *)&ftime, sizeof(ftime));
}
*ptr = '/';
ptr = strchr(ptr+1, '/');
}
lfs_file_t dst;
int ret = lfs_file_open(&s_fs, &dst, name, LFS_O_CREAT | LFS_O_TRUNC | LFS_O_WRONLY);
if (ret < 0) {
std::cerr << "unable to open '" << name << "." << std::endl;
return 1;
}
// read file size
fseek(src, 0, SEEK_END);
size_t size = ftell(src);
fseek(src, 0, SEEK_SET);
if (s_debugLevel > 0) {
std::cout << "file size: " << size << std::endl;
}
size_t left = size;
uint8_t data_byte;
while (left > 0){
if (1 != fread(&data_byte, 1, 1, src)) {
std::cerr << "fread error!" << std::endl;
fclose(src);
lfs_file_close(&s_fs, &dst);
return 1;
}
int res = lfs_file_write(&s_fs, &dst, &data_byte, 1);
if (res < 0) {
std::cerr << "lfs_write error(" << res << "): ";
if (res == LFS_ERR_NOSPC) {
std::cerr << "File system is full." << std::endl;
} else {
std::cerr << "unknown";
}
std::cerr << std::endl;
if (s_debugLevel > 0) {
std::cout << "data left: " << left << std::endl;
}
fclose(src);
lfs_file_close(&s_fs, &dst);
return 1;
}
left -= 1;
}
lfs_file_close(&s_fs, &dst);
fclose(src);
// Add time metadata 't'
if (!stat(path, &sbuf)) {
uint32_t ftime = sbuf.st_mtime;
lfs_setattr(&s_fs, name, 't', (const void *)&ftime, sizeof(ftime));
// There is no portable way to get creation time via stat, so simply call it identical to the last write in this case
lfs_setattr(&s_fs, name, 'c', (const void *)&ftime, sizeof(ftime));
}
return 0;
}
int addFiles(const char* dirname, const char* subPath) {
DIR *dir;
struct dirent *ent;
bool error = false;
std::string dirPath = dirname;
dirPath += subPath;
// Open directory
if ((dir = opendir (dirPath.c_str())) != NULL) {
// Read files from directory.
while ((ent = readdir (dir)) != NULL) {
// Ignore dir itself.
if ((strcmp(ent->d_name, ".") == 0) || (strcmp(ent->d_name, "..") == 0)) {
continue;
}
#if !defined(_WIN32)
{
struct stat path_stat;
std::string name = dirPath + ent->d_name;
int loopcount = 10; // where is SYMLOOP_MAX?
bool skipentry = false;
std::string target = name;
// follow a chain of softlinks
lstat(name.c_str(), &path_stat);
while (S_ISLNK(path_stat.st_mode) && loopcount > 0) {
char buf[PATH_MAX];
ssize_t size = readlink(target.c_str(), buf, sizeof buf);
if (size < 0) {
perror(("readlink " + target).c_str());
skipentry = true;
break;
}
if (buf[0] == '/') {
target = std::string(buf, size);
} else {
target = dirPath + std::string(buf, size);
}
char rpath[PATH_MAX];
const char *ptr = realpath(target.c_str(), rpath);
if (ptr == NULL) {
perror(("realpath " + target).c_str());
skipentry = true;
continue;
}
target = rpath;
lstat(target.c_str(), &path_stat);
// if it points to a directory, skip that entry
if (S_ISDIR(path_stat.st_mode)) {
std::cerr << "symlink " << name << " points to directory " << target << " - skipping" << std::endl;
skipentry = true;
}
}
// also skip links pointing to themselves
if (S_ISLNK(path_stat.st_mode) && name.compare(target) == 0) {
std::cerr << "symlink " << name << " loops back to itself - skipping"
<< std::endl;
skipentry = true;
break;
}
name = target;
loopcount--;
if (loopcount == 0) {
std::cerr << "symlink " << name
<< " - too many redirections, skipping" << std::endl;
continue;
}
if (skipentry)
continue;
}
#endif
if (!s_addAllFiles) {
bool skip = false;
size_t ignored_file_names_count = sizeof(ignored_file_names) / sizeof(ignored_file_names[0]);
for (size_t i = 0; i < ignored_file_names_count; ++i) {
if (strcmp(ent->d_name, ignored_file_names[i]) == 0) {
std::cerr << "skipping " << ent->d_name << std::endl;
skip = true;
break;
}
}
if (skip) {
continue;
}
}
std::string fullpath = dirPath;
fullpath += ent->d_name;
struct stat path_stat;
stat (fullpath.c_str(), &path_stat);
if (!S_ISREG(path_stat.st_mode)) {
// Check if path is a directory.
if (S_ISDIR(path_stat.st_mode)) {
// Prepare new sub path.
std::string newSubPath = subPath;
newSubPath += ent->d_name;
newSubPath += "/";
if (addFiles(dirname, newSubPath.c_str()) != 0)
{
std::cerr << "Error for adding content from " << ent->d_name << "!" << std::endl;
}
continue;
}
else
{
std::cerr << "skipping " << ent->d_name << std::endl;
continue;
}
}
// Filepath with dirname as root folder.
std::string filepath = subPath;
filepath += ent->d_name;
std::cout << filepath << std::endl;
// Add File to image.
if (addFile((char*)filepath.c_str(), fullpath.c_str()) != 0) {
std::cerr << "error adding file!" << std::endl;
error = true;
if (s_debugLevel > 0) {
std::cout << std::endl;
}
break;
}
} // end while
closedir (dir);
} else {
std::cerr << "warning: can't read source directory" << std::endl;
return 1;
}
return (error) ? 1 : 0;
}
void listFiles(const char *path) {
int ret;
lfs_dir_t dir;
lfs_info it;
ret = lfs_dir_open(&s_fs, &dir, path);
if (ret < 0) {
std::cerr << "unable to open directory '" << path << "'" << std::endl;
return;
}
while (true) {
int res = lfs_dir_read(&s_fs, &dir, &it);
if (res <= 0)
break;
// Ignore special dir entries
if ((strcmp(it.name, ".") == 0) || (strcmp(it.name, "..") == 0)) {
continue;
}
uint32_t ftime;
time_t t;
if (it.type == LFS_TYPE_DIR) {
char newpath[PATH_MAX];
sprintf(newpath, "%s/%s", path, it.name);
if (lfs_getattr(&s_fs, newpath, 't', (uint8_t *)&ftime, sizeof(ftime)) >= 0) { // and/or check 'c' as well?
t = (time_t)ftime;
std::cout << "<dir>" << '\t' << path << "/" << it.name << '\t' << asctime(gmtime(&t));
} else {
std::cout << "<dir>" << '\t' << path << "/" << it.name << std::endl;
}
listFiles(newpath);
} else {
char buff[PATH_MAX];
snprintf(buff, sizeof(buff), "%s/%s", path, it.name);
if (lfs_getattr(&s_fs, buff, 't', (uint8_t *)&ftime, sizeof(ftime)) >= 0) { // and/or check 'c' as well?
t = (time_t)ftime;
std::cout << it.size << '\t' << path << "/" << it.name << '\t' << asctime(gmtime(&t));
} else {
std::cout << it.size << '\t' << path << "/" << it.name << std::endl;
}
}
}
lfs_dir_close(&s_fs, &dir);
}
/**
* @brief Check if directory exists.
* @param path Directory path.
* @return True if exists otherwise false.
*
* @author Pascal Gollor (http://www.pgollor.de/cms/)
*/
bool dirExists(const char* path) {
DIR *d = opendir(path);
if (d) {
closedir(d);
return true;
}
return false;
}
/**
* @brief Create directory if it not exists.
* @param path Directory path.
* @return True or false.
*
* @author Pascal Gollor (http://www.pgollor.de/cms/)
*/
bool dirCreate(const char* path) {
// Check if directory also exists.
if (dirExists(path)) {
return false;
}
// platform stuff...
#if defined(_WIN32)
if (mkdir(path) != 0) {
#else
if (mkdir(path, S_IRWXU | S_IXGRP | S_IRGRP | S_IROTH | S_IXOTH) != 0) {
#endif
std::cerr << "Can not create directory!!!" << std::endl;
return false;
}
return true;
}
/**
* @brief Unpack file from file system.
* @param littlefsFile SPIFFS dir entry pointer.
* @param destPath Destination file path path.
* @return True or false.
*
* @author Pascal Gollor (http://www.pgollor.de/cms/)
*/
bool unpackFile(const char *lfsDir, lfs_info *littlefsFile, const char *destPath) {
uint8_t buffer[littlefsFile->size];
std::string filename = lfsDir + std::string("/") + littlefsFile->name;
// Open file from littlefs file system.
lfs_file_t src;
int ret = lfs_file_open(&s_fs, &src, (char *)(filename.c_str()), LFS_O_RDONLY);
if (ret < 0) {
std::cerr << "unable to open '" << filename.c_str() << "." << std::endl;
return false;
}
// read content into buffer
lfs_file_read(&s_fs, &src, buffer, littlefsFile->size);
// Close littlefs file.
lfs_file_close(&s_fs, &src);
// Open file.
FILE* dst = fopen(destPath, "wb");
if (!dst) return false;
// Write content into file.
fwrite(buffer, sizeof(uint8_t), sizeof(buffer), dst);
// Close file.
fclose(dst);
// Adjust time, if present
uint32_t ftime;
if (lfs_getattr(&s_fs, (char *)(filename.c_str()), 't', (uint8_t *)&ftime, sizeof(ftime)) >= 0) {
struct utimbuf ut;
ut.actime = ftime;
ut.modtime = ftime;
utime(destPath, &ut);
}
return true;
}
bool unpackLFSDirFiles(std::string sDest, const char *lfsDir) {
lfs_dir_t dir;
lfs_info ent;
// Check if directory exists. If it does not then try to create it with permissions 755.
if (! dirExists(sDest.c_str())) {
std::cout << "Directory " << sDest << " does not exists. Try to create it." << std::endl;
// Try to create directory.
if (! dirCreate(sDest.c_str())) {
return false;
}
}
// Open directory.
lfs_dir_open(&s_fs, &dir, lfsDir);
// Read content from directory.
while (lfs_dir_read(&s_fs, &dir, &ent)==1) {
// Ignore special dir entries
if ((strcmp(ent.name, ".") == 0) || (strcmp(ent.name, "..") == 0)) {
continue;
}
// Check if content is a file.
if ((int)(ent.type) == LFS_TYPE_REG) {
std::string name = (const char*)(ent.name);
std::string sDestFilePath = sDest + name;
// Unpack file to destination directory.
if (! unpackFile(lfsDir, &ent, sDestFilePath.c_str()) ) {
std::cout << "Can not unpack " << ent.name << "!" << std::endl;
return false;
}
// Output stuff.
std::cout
<< lfsDir
<< ent.name
<< '\t'
<< " > " << sDestFilePath
<< '\t'
<< "size: " << ent.size << " Bytes"
<< std::endl;
} else if (ent.type == LFS_TYPE_DIR) {
char newPath[PATH_MAX];
if (lfsDir[0]) {
sprintf(newPath, "%s/%s/", lfsDir, ent.name);
} else {
sprintf(newPath, "%s/", ent.name);
}
std::string newDest = sDest + ent.name + "/";
dirCreate(newDest.c_str());
unpackLFSDirFiles(newDest, newPath);
}
// Get next file handle.
} // end while
// Close directory.
lfs_dir_close(&s_fs, &dir);
return true;
}
/**
* @brief Unpack files from file system.
* @param sDest Directory path as std::string.
* @return True or false.
*
* @author Pascal Gollor (http://www.pgollor.de/cms/)
*
* todo: Do unpack stuff for directories.
*/
bool unpackFiles(std::string sDest) {
// Add "./" to path if is not given.
if (sDest.find("./") == std::string::npos && sDest.find("/") == std::string::npos) {
sDest = "./" + sDest;
}
if (sDest.back() != '/') {
sDest += "/";
}
// Check if directory exists. If it does not then try to create it with permissions 755.
if (! dirExists(sDest.c_str())) {
std::cout << "Directory " << sDest << " does not exists. Try to create it." << std::endl;
// Try to create directory.
if (! dirCreate(sDest.c_str())) {
return false;
}
}
return unpackLFSDirFiles(sDest, "");
}
// Actions
int actionPack() {
if (!s_imageSize) {
std::cerr << "error: image size not specified, can't create filesystem" << std::endl;
return 1;
}
s_flashmem.resize(s_imageSize, 0xff);
FILE* fdres = fopen(s_imageName.c_str(), "wb");
if (!fdres) {
std::cerr << "error: failed to open image file" << std::endl;
return 1;
}
littlefsFormat();
int result = addFiles(s_dirName.c_str(), "/");
// Set creation/modification time of volume on root
time_t ct = time(NULL);
lfs_setattr(&s_fs, "/", 't', &ct, sizeof(ct));
lfs_setattr(&s_fs, "/", 'c', &ct, sizeof(ct));
littlefsUnmount();
fwrite(&s_flashmem[0], 4, s_flashmem.size()/4, fdres);
fclose(fdres);
return result;
}
/**
* @brief Unpack action.
* @return 0 success, 1 error
*
* @author Pascal Gollor (http://www.pgollor.de/cms/)
*/
int actionUnpack(void) {
int ret = 0;
// open littlefs image
FILE* fdsrc = fopen(s_imageName.c_str(), "rb");
if (!fdsrc) {
std::cerr << "error: failed to open image file" << std::endl;
return 1;
}
fseek(fdsrc, 0L, SEEK_END);
int filesize = s_imageSize ? s_imageSize : ftell(fdsrc);
fseek(fdsrc, 0L, SEEK_SET);
s_flashmem.resize(filesize, 0xff);
// read content into s_flashmem
if (s_flashmem.size()/4 != fread(&s_flashmem[0], 4, s_flashmem.size()/4, fdsrc)) {
std::cerr << "error: couldn't read image file" << std::endl;
fclose(fdsrc);
return 1;
}
// close fiel handle
fclose(fdsrc);
// mount file system
littlefsMount();
// unpack files
if (! unpackFiles(s_dirName)) {
ret = 1;
}
// unmount file system
littlefsUnmount();
return ret;
}
int actionList() {
FILE* fdsrc = fopen(s_imageName.c_str(), "rb");
if (!fdsrc) {
std::cerr << "error: failed to open image file" << std::endl;
return 1;
}
fseek(fdsrc, 0L, SEEK_END);
int filesize = s_imageSize ? s_imageSize : ftell(fdsrc);
fseek(fdsrc, 0L, SEEK_SET);
s_flashmem.resize(filesize, 0xff);
if (s_flashmem.size()/4 != fread(&s_flashmem[0], 4, s_flashmem.size()/4, fdsrc)) {
std::cerr << "error: couldn't read image file" << std::endl;
fclose(fdsrc);
return 1;
}
fclose(fdsrc);
littlefsMount();
listFiles("");
time_t ct;
if (lfs_getattr(&s_fs, "/", 't', &ct, sizeof(ct)) >= 0) { // and/or check 'c' as well?
std::cout << "Creation time:" << '\t' << asctime(gmtime(&ct));
}
littlefsUnmount();
return 0;
}
#define PRINT_INT_MACRO(def_name) \
std::cout << " " # def_name ": " << def_name << std::endl;
class CustomOutput : public TCLAP::StdOutput
{
public:
virtual void version(TCLAP::CmdLineInterface& c)
{
std::cout << "mklittlefs ver. " VERSION << std::endl;
const char* configName = BUILD_CONFIG_NAME;
if (configName[0] == '-') {
configName += 1;
}
std::cout << "Build configuration name: " << configName << std::endl;
std::cout << "LittleFS ver. " << LITTLEFS_VERSION << std::endl;
const char* buildConfig = BUILD_CONFIG;
std::cout << "Extra build flags: " << (strlen(buildConfig) ? buildConfig : "(none)") << std::endl;
std::cout << "LittleFS configuration:" << std::endl;
PRINT_INT_MACRO(LFS_NAME_MAX);
PRINT_INT_MACRO(LFS_FILE_MAX);
PRINT_INT_MACRO(LFS_ATTR_MAX);
}
};
#undef PRINT_INT_MACRO
void processArgs(int argc, const char** argv) {
TCLAP::CmdLine cmd("", ' ', VERSION);
CustomOutput output;
cmd.setOutput(&output);
TCLAP::ValueArg<std::string> packArg( "c", "create", "create littlefs image from a directory", true, "", "pack_dir");
TCLAP::ValueArg<std::string> unpackArg( "u", "unpack", "unpack littlefs image to a directory", true, "", "dest_dir");
TCLAP::SwitchArg listArg( "l", "list", "list files in littlefs image", false);
TCLAP::UnlabeledValueArg<std::string> outNameArg( "image_file", "littlefs image file", true, "", "image_file" );
TCLAP::ValueArg<int> imageSizeArg( "s", "size", "fs image size, in bytes", false, 0, "number" );
TCLAP::ValueArg<int> pageSizeArg( "p", "page", "fs page size, in bytes", false, 256, "number" );
TCLAP::ValueArg<int> blockSizeArg( "b", "block", "fs block size, in bytes", false, 4096, "number" );
TCLAP::SwitchArg addAllFilesArg( "a", "all-files", "when creating an image, include files which are normally ignored; currently only applies to '.DS_Store' files and '.git' directories", false);
TCLAP::ValueArg<int> debugArg( "d", "debug", "Debug level. 0 means no debug output.", false, 0, "0-5" );
cmd.add( imageSizeArg );
cmd.add( pageSizeArg );
cmd.add( blockSizeArg );
cmd.add( addAllFilesArg );
cmd.add( debugArg );
std::vector<TCLAP::Arg*> args = {&packArg, &unpackArg, &listArg};
cmd.xorAdd( args );
cmd.add( outNameArg );
cmd.parse( argc, argv );
if (debugArg.getValue() > 0) {
std::cout << "Debug output enabled" << std::endl;
s_debugLevel = debugArg.getValue();
}
if (packArg.isSet()) {
s_dirName = packArg.getValue();
s_action = ACTION_PACK;
} else if (unpackArg.isSet()) {
s_dirName = unpackArg.getValue();
s_action = ACTION_UNPACK;
} else if (listArg.isSet()) {
s_action = ACTION_LIST;
}
s_imageName = outNameArg.getValue();
s_imageSize = imageSizeArg.getValue();
s_pageSize = pageSizeArg.getValue();
s_blockSize = blockSizeArg.getValue();
s_addAllFiles = addAllFilesArg.isSet();
}
int main(int argc, const char * argv[]) {
try {
processArgs(argc, argv);
} catch(...) {
std::cerr << "Invalid arguments" << std::endl;
return 1;
}
switch (s_action) {
case ACTION_PACK:
return actionPack();
break;
case ACTION_UNPACK:
return actionUnpack();
break;
case ACTION_LIST:
return actionList();
break;
default:
break;
}
return 1;
}