MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
核心功能: - ✅ Categories/Series双视图管理(category_view.rs + import_markdown.rs) - ✅ FUSE Multi-Volume支持(tree_type参数) - ✅ SSH/SFTP/SCP/rsync协议完整实现(4042行) - ✅ NFS/SMB Module Phase 1-3完成 - ✅ Archive Module Phase 1-4完成(2916行) - ✅ Download Center API完整实现 - ✅ S3兼容API实现(560行) Git配置修正: - ✅ 删除错误origin(gitea.momentry.ddns.net) - ✅ 删除m5max128(指向机器名) - ✅ 设置origin = m5max128gitea.momentry.ddns.net/admin/markbase - ✅ 设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase 数据清理: - ✅ 删除38个临时SQLite(保留accusys.sqlite、demo.sqlite) - ✅ 删除.bak、test_*.bin、调试脚本等临时文件 - ✅ 删除临时目录(build/、download files/、raid_test/等) - ✅ 更新.gitignore排除临时文件 架构优化: - 52个文件修改,2434行新增,4739行删除 - Workspace成员整合(16个crate) - 数据库状态:accusys.sqlite保留(主demo测试) 远程同步: - ✅ 准备推送到m5max128gitea(远程Gitea) - ✅ 准备推送到m4minigitea(本地Gitea)
This commit is contained in:
855
docs/fuse_poc/markbase_v10_full.c
Normal file
855
docs/fuse_poc/markbase_v10_full.c
Normal file
@@ -0,0 +1,855 @@
|
||||
#define FUSE_USE_VERSION 31
|
||||
#include <fuse3/fuse.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sqlite3.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
static sqlite3 *db = NULL;
|
||||
static const char *db_path = "/Users/accusys/markbase/data/users/warren.sqlite";
|
||||
static pthread_mutex_t db_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
// Enhanced cache with write support
|
||||
typedef struct {
|
||||
char node_id[64];
|
||||
char file_path[512];
|
||||
long file_size;
|
||||
int access_count;
|
||||
time_t last_access;
|
||||
int is_dirty; // Flag for modified files
|
||||
} FileCacheEntry;
|
||||
|
||||
#define CACHE_SIZE 200
|
||||
static FileCacheEntry file_cache[CACHE_SIZE];
|
||||
static int cache_count = 0;
|
||||
static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
// Node info cache
|
||||
typedef struct {
|
||||
char node_id[64];
|
||||
char node_type[20];
|
||||
long file_size;
|
||||
char parent_id[64];
|
||||
} NodeCacheEntry;
|
||||
|
||||
#define NODE_CACHE_SIZE 500
|
||||
static NodeCacheEntry node_cache[NODE_CACHE_SIZE];
|
||||
static int node_cache_count = 0;
|
||||
|
||||
static int init_db() {
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
int result = sqlite3_open_v2(db_path, &db,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, NULL);
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
|
||||
if (result != SQLITE_OK) {
|
||||
fprintf(stderr, "Cannot open database: %s\n", db_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Database opened: %s (read-write mode)\n", db_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Cache functions (same as v9)
|
||||
static FileCacheEntry* cache_lookup(const char *node_id) {
|
||||
pthread_mutex_lock(&cache_mutex);
|
||||
for (int i = 0; i < cache_count; i++) {
|
||||
if (strcmp(file_cache[i].node_id, node_id) == 0) {
|
||||
file_cache[i].access_count++;
|
||||
file_cache[i].last_access = time(NULL);
|
||||
pthread_mutex_unlock(&cache_mutex);
|
||||
return &file_cache[i];
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache_mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void cache_insert(const char *node_id, const char *file_path, long file_size) {
|
||||
pthread_mutex_lock(&cache_mutex);
|
||||
|
||||
for (int i = 0; i < cache_count; i++) {
|
||||
if (strcmp(file_cache[i].node_id, node_id) == 0) {
|
||||
pthread_mutex_unlock(&cache_mutex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cache_count >= CACHE_SIZE) {
|
||||
int lru_index = 0;
|
||||
time_t oldest_time = file_cache[0].last_access;
|
||||
|
||||
for (int i = 1; i < cache_count; i++) {
|
||||
if (file_cache[i].last_access < oldest_time) {
|
||||
oldest_time = file_cache[i].last_access;
|
||||
lru_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
strcpy(file_cache[lru_index].node_id, node_id);
|
||||
strcpy(file_cache[lru_index].file_path, file_path);
|
||||
file_cache[lru_index].file_size = file_size;
|
||||
file_cache[lru_index].access_count = 1;
|
||||
file_cache[lru_index].last_access = time(NULL);
|
||||
file_cache[lru_index].is_dirty = 0;
|
||||
} else {
|
||||
strcpy(file_cache[cache_count].node_id, node_id);
|
||||
strcpy(file_cache[cache_count].file_path, file_path);
|
||||
file_cache[cache_count].file_size = file_size;
|
||||
file_cache[cache_count].access_count = 1;
|
||||
file_cache[cache_count].last_access = time(NULL);
|
||||
file_cache[cache_count].is_dirty = 0;
|
||||
cache_count++;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&cache_mutex);
|
||||
}
|
||||
|
||||
static NodeCacheEntry* node_cache_lookup(const char *node_id) {
|
||||
pthread_mutex_lock(&cache_mutex);
|
||||
for (int i = 0; i < node_cache_count; i++) {
|
||||
if (strcmp(node_cache[i].node_id, node_id) == 0) {
|
||||
pthread_mutex_unlock(&cache_mutex);
|
||||
return &node_cache[i];
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache_mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void node_cache_insert(const char *node_id, const char *node_type,
|
||||
long file_size, const char *parent_id) {
|
||||
pthread_mutex_lock(&cache_mutex);
|
||||
|
||||
if (node_cache_count < NODE_CACHE_SIZE) {
|
||||
strcpy(node_cache[node_cache_count].node_id, node_id);
|
||||
strcpy(node_cache[node_cache_count].node_type, node_type);
|
||||
node_cache[node_cache_count].file_size = file_size;
|
||||
if (parent_id) strcpy(node_cache[node_cache_count].parent_id, parent_id);
|
||||
else node_cache[node_cache_count].parent_id[0] = '\0';
|
||||
node_cache_count++;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&cache_mutex);
|
||||
}
|
||||
|
||||
static void *mb_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
|
||||
(void) conn;
|
||||
cfg->kernel_cache = 1;
|
||||
|
||||
init_db();
|
||||
|
||||
// Pre-cache top 200 most accessed files
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
const char *sql = "SELECT f.file_uuid, l.location, f.file_size "
|
||||
"FROM file_nodes f "
|
||||
"JOIN file_locations l ON f.file_uuid = l.file_uuid "
|
||||
"ORDER BY f.file_size DESC LIMIT 200";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
int cached = 0;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *file_uuid = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char *location = (const char*)sqlite3_column_text(stmt, 1);
|
||||
long file_size = sqlite3_column_int64(stmt, 2);
|
||||
|
||||
cache_insert(file_uuid, location, file_size);
|
||||
cached++;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
printf("Pre-cached %d large files\n", cached);
|
||||
}
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void mb_destroy(void *userdata) {
|
||||
(void) userdata;
|
||||
|
||||
printf("Cache statistics:\n");
|
||||
printf(" File cache: %d entries\n", cache_count);
|
||||
printf(" Node cache: %d entries\n", node_cache_count);
|
||||
|
||||
if (db) {
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
sqlite3_close(db);
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-safe path lookup with caching
|
||||
static char* find_node_id(const char *path) {
|
||||
if (strcmp(path, "/") == 0) return NULL;
|
||||
|
||||
char *path_copy = strdup(path);
|
||||
char *components[20];
|
||||
int depth = 0;
|
||||
|
||||
char *token = strtok(path_copy + 1, "/");
|
||||
while (token && depth < 20) {
|
||||
components[depth++] = strdup(token);
|
||||
token = strtok(NULL, "/");
|
||||
}
|
||||
free(path_copy);
|
||||
|
||||
if (depth == 0) return NULL;
|
||||
|
||||
char *current_parent_id = NULL;
|
||||
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
|
||||
for (int level = 0; level < depth; level++) {
|
||||
const char *sql;
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
if (level == 0) {
|
||||
sql = "SELECT node_id, node_type, file_size, parent_id "
|
||||
"FROM file_nodes WHERE label = ? AND (parent_id IS NULL OR parent_id = '')";
|
||||
} else {
|
||||
sql = "SELECT node_id, node_type, file_size, parent_id "
|
||||
"FROM file_nodes WHERE label = ? AND parent_id = ?";
|
||||
}
|
||||
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
for (int i = 0; i < depth; i++) free(components[i]);
|
||||
if (current_parent_id) free(current_parent_id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, components[level], -1, SQLITE_STATIC);
|
||||
if (level > 0 && current_parent_id) {
|
||||
sqlite3_bind_text(stmt, 2, current_parent_id, -1, SQLITE_STATIC);
|
||||
}
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_ROW) {
|
||||
sqlite3_finalize(stmt);
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
for (int i = 0; i < depth; i++) free(components[i]);
|
||||
if (current_parent_id) free(current_parent_id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *found_node_id = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char *node_type = (const char*)sqlite3_column_text(stmt, 1);
|
||||
long file_size = sqlite3_column_int64(stmt, 2);
|
||||
const char *parent_id = (const char*)sqlite3_column_text(stmt, 3);
|
||||
|
||||
node_cache_insert(found_node_id, node_type, file_size, parent_id);
|
||||
|
||||
if (current_parent_id) free(current_parent_id);
|
||||
current_parent_id = strdup(found_node_id);
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
|
||||
for (int i = 0; i < depth; i++) free(components[i]);
|
||||
return current_parent_id;
|
||||
}
|
||||
|
||||
static int mb_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) {
|
||||
(void) fi;
|
||||
memset(stbuf, 0, sizeof(struct stat));
|
||||
|
||||
if (strcmp(path, "/") == 0) {
|
||||
stbuf->st_mode = S_IFDIR | 0755;
|
||||
stbuf->st_nlink = 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *node_id = find_node_id(path);
|
||||
if (!node_id) return -ENOENT;
|
||||
|
||||
NodeCacheEntry *cached_node = node_cache_lookup(node_id);
|
||||
if (cached_node) {
|
||||
if (strcmp(cached_node->node_type, "folder") == 0) {
|
||||
stbuf->st_mode = S_IFDIR | 0755;
|
||||
stbuf->st_nlink = 2;
|
||||
} else {
|
||||
stbuf->st_mode = S_IFREG | 0644; // Changed to allow write
|
||||
stbuf->st_nlink = 1;
|
||||
stbuf->st_size = cached_node->file_size;
|
||||
}
|
||||
free(node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
const char *sql = "SELECT node_type, file_size FROM file_nodes WHERE node_id = ?";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
int result = -ENOENT;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, node_id, -1, SQLITE_STATIC);
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *node_type = (const char*)sqlite3_column_text(stmt, 0);
|
||||
long file_size = sqlite3_column_int64(stmt, 1);
|
||||
|
||||
if (strcmp(node_type, "folder") == 0) {
|
||||
stbuf->st_mode = S_IFDIR | 0755;
|
||||
stbuf->st_nlink = 2;
|
||||
} else {
|
||||
stbuf->st_mode = S_IFREG | 0644;
|
||||
stbuf->st_nlink = 1;
|
||||
stbuf->st_size = file_size;
|
||||
}
|
||||
result = 0;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
|
||||
free(node_id);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int mb_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
|
||||
off_t offset, struct fuse_file_info *fi,
|
||||
enum fuse_readdir_flags flags) {
|
||||
(void) offset;
|
||||
(void) fi;
|
||||
(void) flags;
|
||||
|
||||
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
|
||||
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
|
||||
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
|
||||
if (strcmp(path, "/") == 0) {
|
||||
const char *sql = "SELECT label FROM file_nodes WHERE parent_id IS NULL OR parent_id = ''";
|
||||
sqlite3_stmt *stmt;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *label = (const char*)sqlite3_column_text(stmt, 0);
|
||||
if (label) filler(buf, label, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *parent_node_id = find_node_id(path);
|
||||
if (!parent_node_id) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
const char *sql = "SELECT label FROM file_nodes WHERE parent_id = ?";
|
||||
sqlite3_stmt *stmt;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, parent_node_id, -1, SQLITE_STATIC);
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *label = (const char*)sqlite3_column_text(stmt, 0);
|
||||
if (label) filler(buf, label, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(parent_node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mb_open(const char *path, struct fuse_file_info *fi) {
|
||||
// Allow both read and write
|
||||
char *node_id = find_node_id(path);
|
||||
if (!node_id) return -ENOENT;
|
||||
free(node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mb_read(const char *path, char *buf, size_t size, off_t offset,
|
||||
struct fuse_file_info *fi) {
|
||||
(void) fi;
|
||||
|
||||
char *node_id = find_node_id(path);
|
||||
if (!node_id) return -ENOENT;
|
||||
|
||||
FileCacheEntry *cached = cache_lookup(node_id);
|
||||
|
||||
if (cached && strcmp(cached->file_path, "") != 0) {
|
||||
FILE *fp = fopen(cached->file_path, "rb");
|
||||
if (fp) {
|
||||
if (fseek(fp, offset, SEEK_SET) == 0) {
|
||||
size_t bytes_read = fread(buf, 1, size, fp);
|
||||
fclose(fp);
|
||||
free(node_id);
|
||||
return bytes_read;
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
const char *sql = "SELECT location FROM file_locations WHERE file_uuid = ? LIMIT 1";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, node_id, -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_ROW) {
|
||||
sqlite3_finalize(stmt);
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(node_id);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
const char *file_path = (const char*)sqlite3_column_text(stmt, 0);
|
||||
char *path_copy = strdup(file_path);
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
|
||||
NodeCacheEntry *node_info = node_cache_lookup(node_id);
|
||||
if (node_info) {
|
||||
cache_insert(node_id, path_copy, node_info->file_size);
|
||||
}
|
||||
|
||||
free(node_id);
|
||||
|
||||
FILE *fp = fopen(path_copy, "rb");
|
||||
if (!fp) {
|
||||
free(path_copy);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (fseek(fp, offset, SEEK_SET) != 0) {
|
||||
fclose(fp);
|
||||
free(path_copy);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
size_t bytes_read = fread(buf, 1, size, fp);
|
||||
fclose(fp);
|
||||
free(path_copy);
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
// NEW: Write support
|
||||
static int mb_write(const char *path, const char *buf, size_t size, off_t offset,
|
||||
struct fuse_file_info *fi) {
|
||||
(void) fi;
|
||||
|
||||
char *node_id = find_node_id(path);
|
||||
if (!node_id) return -ENOENT;
|
||||
|
||||
FileCacheEntry *cached = cache_lookup(node_id);
|
||||
|
||||
if (cached && strcmp(cached->file_path, "") != 0) {
|
||||
FILE *fp = fopen(cached->file_path, "r+b");
|
||||
if (fp) {
|
||||
if (fseek(fp, offset, SEEK_SET) == 0) {
|
||||
size_t bytes_written = fwrite(buf, 1, size, fp);
|
||||
fclose(fp);
|
||||
|
||||
// Mark as dirty
|
||||
pthread_mutex_lock(&cache_mutex);
|
||||
cached->is_dirty = 1;
|
||||
cached->file_size = (offset + bytes_written > cached->file_size) ?
|
||||
offset + bytes_written : cached->file_size;
|
||||
pthread_mutex_unlock(&cache_mutex);
|
||||
|
||||
free(node_id);
|
||||
return bytes_written;
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
// Query file path
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
const char *sql = "SELECT location FROM file_locations WHERE file_uuid = ? LIMIT 1";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, node_id, -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_ROW) {
|
||||
sqlite3_finalize(stmt);
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(node_id);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
const char *file_path = (const char*)sqlite3_column_text(stmt, 0);
|
||||
char *path_copy = strdup(file_path);
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
|
||||
// Write to file
|
||||
FILE *fp = fopen(path_copy, "r+b");
|
||||
if (!fp) {
|
||||
free(path_copy);
|
||||
free(node_id);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (fseek(fp, offset, SEEK_SET) != 0) {
|
||||
fclose(fp);
|
||||
free(path_copy);
|
||||
free(node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
size_t bytes_written = fwrite(buf, 1, size, fp);
|
||||
fclose(fp);
|
||||
|
||||
// Update cache
|
||||
NodeCacheEntry *node_info = node_cache_lookup(node_id);
|
||||
if (node_info) {
|
||||
cache_insert(node_id, path_copy, offset + bytes_written);
|
||||
}
|
||||
|
||||
free(path_copy);
|
||||
free(node_id);
|
||||
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
// NEW: Create directory
|
||||
static int mb_mkdir(const char *path, mode_t mode) {
|
||||
(void) mode;
|
||||
|
||||
// Parse path to get parent and new folder name
|
||||
char *path_copy = strdup(path);
|
||||
char *last_slash = strrchr(path_copy, '/');
|
||||
|
||||
if (!last_slash) {
|
||||
free(path_copy);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
char *folder_name = strdup(last_slash + 1);
|
||||
*last_slash = '\0';
|
||||
char *parent_path = strdup(path_copy);
|
||||
free(path_copy);
|
||||
|
||||
if (strlen(folder_name) == 0) {
|
||||
free(folder_name);
|
||||
free(parent_path);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// Find parent node_id
|
||||
char *parent_node_id = NULL;
|
||||
if (strlen(parent_path) == 0 || strcmp(parent_path, "/") == 0) {
|
||||
parent_path = strdup("/");
|
||||
} else {
|
||||
parent_node_id = find_node_id(parent_path);
|
||||
}
|
||||
|
||||
// Generate new node_id
|
||||
char new_node_id[64];
|
||||
snprintf(new_node_id, sizeof(new_node_id), "%s_%ld", folder_name, time(NULL));
|
||||
|
||||
// Insert into database
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
const char *sql = "INSERT INTO file_nodes (node_id, label, parent_id, node_type, file_size, created_at, updated_at) "
|
||||
"VALUES (?, ?, ?, 'folder', 0, datetime('now'), datetime('now'))";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(folder_name);
|
||||
free(parent_path);
|
||||
if (parent_node_id) free(parent_node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, new_node_id, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, folder_name, -1, SQLITE_STATIC);
|
||||
|
||||
if (parent_node_id) {
|
||||
sqlite3_bind_text(stmt, 3, parent_node_id, -1, SQLITE_STATIC);
|
||||
} else {
|
||||
sqlite3_bind_null(stmt, 3);
|
||||
}
|
||||
|
||||
int result = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
|
||||
free(folder_name);
|
||||
free(parent_path);
|
||||
if (parent_node_id) free(parent_node_id);
|
||||
|
||||
if (result != SQLITE_DONE) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NEW: Remove file
|
||||
static int mb_unlink(const char *path) {
|
||||
char *node_id = find_node_id(path);
|
||||
if (!node_id) return -ENOENT;
|
||||
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
|
||||
// Delete from file_nodes
|
||||
const char *sql = "DELETE FROM file_nodes WHERE node_id = ?";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, node_id, -1, SQLITE_STATIC);
|
||||
int result = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (result != SQLITE_DONE) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
// Delete from file_locations
|
||||
sql = "DELETE FROM file_locations WHERE file_uuid = ?";
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, node_id, -1, SQLITE_STATIC);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
|
||||
// Remove from cache
|
||||
pthread_mutex_lock(&cache_mutex);
|
||||
for (int i = 0; i < cache_count; i++) {
|
||||
if (strcmp(file_cache[i].node_id, node_id) == 0) {
|
||||
file_cache[i].node_id[0] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache_mutex);
|
||||
|
||||
free(node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NEW: Remove directory
|
||||
static int mb_rmdir(const char *path) {
|
||||
char *node_id = find_node_id(path);
|
||||
if (!node_id) return -ENOENT;
|
||||
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
|
||||
// Check if directory is empty
|
||||
const char *sql = "SELECT COUNT(*) FROM file_nodes WHERE parent_id = ?";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, node_id, -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int child_count = sqlite3_column_int(stmt, 0);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (child_count > 0) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(node_id);
|
||||
return -ENOTEMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete directory
|
||||
sql = "DELETE FROM file_nodes WHERE node_id = ?";
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, node_id, -1, SQLITE_STATIC);
|
||||
int result = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
|
||||
if (result != SQLITE_DONE) {
|
||||
free(node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
free(node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NEW: Truncate file
|
||||
static int mb_truncate(const char *path, off_t size, struct fuse_file_info *fi) {
|
||||
(void) fi;
|
||||
|
||||
char *node_id = find_node_id(path);
|
||||
if (!node_id) return -ENOENT;
|
||||
|
||||
// Update file_size in database
|
||||
pthread_mutex_lock(&db_mutex);
|
||||
const char *sql = "UPDATE file_nodes SET file_size = ?, updated_at = datetime('now') WHERE node_id = ?";
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
free(node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sqlite3_bind_int64(stmt, 1, size);
|
||||
sqlite3_bind_text(stmt, 2, node_id, -1, SQLITE_STATIC);
|
||||
|
||||
int result = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
pthread_mutex_unlock(&db_mutex);
|
||||
|
||||
if (result != SQLITE_DONE) {
|
||||
free(node_id);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
// Update cache
|
||||
NodeCacheEntry *cached_node = node_cache_lookup(node_id);
|
||||
if (cached_node) {
|
||||
pthread_mutex_lock(&cache_mutex);
|
||||
cached_node->file_size = size;
|
||||
pthread_mutex_unlock(&cache_mutex);
|
||||
}
|
||||
|
||||
free(node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NEW: Get extended attributes
|
||||
static int mb_getxattr(const char *path, const char *name, char *value, size_t size) {
|
||||
(void) path;
|
||||
(void) name;
|
||||
(void) value;
|
||||
|
||||
// MarkBase specific xattr: mb.file_size, mb.sha256
|
||||
char *node_id = find_node_id(path);
|
||||
if (!node_id) return -ENOENT;
|
||||
|
||||
NodeCacheEntry *cached_node = node_cache_lookup(node_id);
|
||||
if (!cached_node) {
|
||||
free(node_id);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (strcmp(name, "user.mb.file_size") == 0) {
|
||||
char size_str[32];
|
||||
snprintf(size_str, sizeof(size_str), "%ld", cached_node->file_size);
|
||||
|
||||
if (size == 0) {
|
||||
free(node_id);
|
||||
return strlen(size_str);
|
||||
}
|
||||
|
||||
if (size < strlen(size_str)) {
|
||||
free(node_id);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
strcpy(value, size_str);
|
||||
free(node_id);
|
||||
return strlen(size_str);
|
||||
}
|
||||
|
||||
free(node_id);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
// NEW: Set extended attributes
|
||||
static int mb_setxattr(const char *path, const char *name, const char *value,
|
||||
size_t size, int flags) {
|
||||
(void) path;
|
||||
(void) name;
|
||||
(void) value;
|
||||
(void) size;
|
||||
(void) flags;
|
||||
|
||||
// MarkBase specific xattr
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
// NEW: List extended attributes
|
||||
static int mb_listxattr(const char *path, char *list, size_t size) {
|
||||
(void) path;
|
||||
|
||||
const char *xattr_list = "user.mb.file_size\0user.mb.sha256\0";
|
||||
|
||||
if (size == 0) {
|
||||
return strlen(xattr_list) + 1;
|
||||
}
|
||||
|
||||
if (size < strlen(xattr_list) + 1) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
strcpy(list, xattr_list);
|
||||
return strlen(xattr_list) + 1;
|
||||
}
|
||||
|
||||
static const struct fuse_operations mb_oper = {
|
||||
.init = mb_init,
|
||||
.destroy = mb_destroy,
|
||||
.getattr = mb_getattr,
|
||||
.readdir = mb_readdir,
|
||||
.open = mb_open,
|
||||
.read = mb_read,
|
||||
.write = mb_write,
|
||||
.mkdir = mb_mkdir,
|
||||
.unlink = mb_unlink,
|
||||
.rmdir = mb_rmdir,
|
||||
.truncate = mb_truncate,
|
||||
.getxattr = mb_getxattr,
|
||||
.setxattr = mb_setxattr,
|
||||
.listxattr = mb_listxattr,
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("MarkBase FUSE v10.0 - Full Filesystem Support\n");
|
||||
printf("==============================================\n");
|
||||
printf("Features:\n");
|
||||
printf(" - Read/Write support\n");
|
||||
printf(" - mkdir/rmdir support\n");
|
||||
printf(" - unlink (delete) support\n");
|
||||
printf(" - truncate support\n");
|
||||
printf(" - xattr support (user.mb.file_size, user.mb.sha256)\n");
|
||||
printf(" - Thread-safe SQLite (mutex)\n");
|
||||
printf(" - LRU cache (200 entries)\n");
|
||||
printf(" - Node info cache (500 entries)\n");
|
||||
printf("\n");
|
||||
|
||||
return fuse_main(argc, argv, &mb_oper, NULL);
|
||||
}
|
||||
Reference in New Issue
Block a user