#define FUSE_USE_VERSION 31 #include #include #include #include #include #include #include #include #include #include #include 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); }