/* Copyright (C) 2008 Emmanuel Varoquaux This file is part of XOS. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "file_system_struct.h" #include "pfs.h" #include "pfs_structs.h" #include #include #include #include #include #include #include #include #include #include #include #include /* taille d'un bloc de donnees */ #define RAMFS_BLOCK_SIZE PAGE_SIZE /* taille maximale d'un fichier regulier */ #define MAX_SIZE LONG_MAX #define BLOCK_TABLE_SIZE (RAMFS_BLOCK_SIZE / sizeof (void *)) struct ramfs_file_struct; struct ramfs_file_operations_struct { void (*destroy)(struct ramfs_file_struct *); }; struct ramfs_file_struct { struct pfs_dir_entry_struct dir_entry; /* entree de repertoire */ /* metadonnees */ long mtime; /* date de derniere modification */ /* donnees */ union { /* FT_DIR */ struct pfs_dir_struct dir; /* repertoire */ /* FT_REG */ struct { unsigned long size; /* taille du fichier */ void *data; /* blocs de donnees */ }; }; const struct ramfs_file_operations_struct *operations; /* operations */ }; static void *alloc_block() { void *block; if (!(block = kmalloc(RAMFS_BLOCK_SIZE))) return NULL; memset(block, 0, RAMFS_BLOCK_SIZE); return block; } static void free_block(void *block) { kfree(block, RAMFS_BLOCK_SIZE); } /* new_size > old_size > 0 */ static void *create_block_tables(void *data, unsigned long old_size, unsigned long new_size) { unsigned long new_size_sav; void **block_table; old_size = (old_size - 1) / RAMFS_BLOCK_SIZE; new_size = (new_size - 1) / RAMFS_BLOCK_SIZE; while (old_size) { old_size /= BLOCK_TABLE_SIZE; new_size /= BLOCK_TABLE_SIZE; } new_size_sav = new_size; while (new_size) { if (!(block_table = alloc_block())) goto error; block_table[0] = data; data = block_table; new_size /= BLOCK_TABLE_SIZE; } return data; error: while (new_size < new_size_sav) { block_table = data; data = block_table[0]; free_block(block_table); new_size *= BLOCK_TABLE_SIZE; } return NULL; } static int grow_reg(struct ramfs_file_struct *reg, unsigned long new_size) { void *data; if (new_size > RAMFS_BLOCK_SIZE && reg->size > 0) { /* indexation des blocs dans des blocs de niveau superieur */ if (!(data = create_block_tables(reg->data, reg->size, new_size))) return 0; reg->data = data; } reg->size = new_size; return 1; } static inline unsigned long get_entry_size(unsigned long size) { unsigned long entry_size; size = (size - 1) / RAMFS_BLOCK_SIZE; entry_size = RAMFS_BLOCK_SIZE; while (size /= BLOCK_TABLE_SIZE) entry_size *= BLOCK_TABLE_SIZE; return entry_size; } static const void *get_block(const struct ramfs_file_struct *reg, unsigned long pos) { const void *data; unsigned long entry_size; void *const *block_table; void *const *ent; if (reg->size == 0) return NULL; if (!reg->data) return NULL; if (reg->size <= RAMFS_BLOCK_SIZE) return reg->data; data = reg->data; entry_size = get_entry_size(reg->size); while (1) { block_table = data; ent = &block_table[pos / entry_size]; if (!*ent) return NULL; data = *ent; pos = pos & (entry_size - 1); if (entry_size <= RAMFS_BLOCK_SIZE) break; entry_size /= BLOCK_TABLE_SIZE; } return data; } static void *get_alloc_block(struct ramfs_file_struct *reg, unsigned long pos) { void *data; unsigned long entry_size; void **block_table; void **ent; if (reg->size == 0) return NULL; if (!reg->data) if (!(reg->data = alloc_block())) return NULL; if (reg->size <= RAMFS_BLOCK_SIZE) return reg->data; data = reg->data; entry_size = get_entry_size(reg->size); while (1) { block_table = data; ent = &block_table[pos / entry_size]; if (!*ent) if (!(*ent = alloc_block())) return NULL; data = *ent; pos = pos & (entry_size - 1); if (entry_size <= RAMFS_BLOCK_SIZE) break; entry_size /= BLOCK_TABLE_SIZE; } return data; } static void free_block_table(void **block_table, unsigned long entry_size) { void **ent; for (ent = &block_table[0]; ent < &block_table[BLOCK_TABLE_SIZE]; ent++) if (*ent) { if (entry_size > RAMFS_BLOCK_SIZE) free_block_table(*ent, entry_size / BLOCK_TABLE_SIZE); else free_block(*ent); } free_block(block_table); } static void free_blocks(struct ramfs_file_struct *reg) { if (reg->size == 0) return; if (!reg->data) return; if (reg->size <= RAMFS_BLOCK_SIZE) { free_block(reg->data); return; } free_block_table(reg->data, get_entry_size(reg->size)); } static void init_file_common(struct ramfs_file_struct *file, unsigned long id, char *name, int type) { file->dir_entry.info.id = id; pfs_set_dir_entry_info_name(&file->dir_entry.info, name); file->dir_entry.info.type = type; file->mtime = get_time(); } static void destroy_file_common(struct ramfs_file_struct *file) { pfs_free_dir_entry_info_name(&file->dir_entry.info); } static void rename_file(struct ramfs_file_struct *file, char *name) { if (file->dir_entry.info.name) pfs_free_dir_entry_info_name(&file->dir_entry.info); return pfs_set_dir_entry_info_name(&file->dir_entry.info, name); } static void dir_destroy(struct ramfs_file_struct *dir) { destroy_file_common(dir); } static const struct ramfs_file_operations_struct dir_operations = { .destroy = dir_destroy }; static void init_dir(struct ramfs_file_struct *dir, unsigned long id, char *name) { init_file_common(dir, id, name, FT_DIR); pfs_init_dir(&dir->dir); dir->operations = &dir_operations; } static void reg_destroy(struct ramfs_file_struct *file) { free_blocks(file); destroy_file_common(file); } static const struct ramfs_file_operations_struct reg_operations = { .destroy = reg_destroy }; static void init_reg(struct ramfs_file_struct *reg, unsigned long id, char *name) { init_file_common(reg, id, name, FT_REG); reg->size = 0; reg->data = NULL; reg->operations = ®_operations; } /* Operations sur les fichiers */ static int find(const struct ramfs_file_struct *dir, const char *fs_basename, unsigned long *id, int *type) { return pfs_find(&dir->dir, fs_basename, id, type); } static int get_name(const struct ramfs_file_struct *file, char name[]) { strcpy(name, file->dir_entry.info.name); return 0; } static int get_size(const struct ramfs_file_struct *reg, unsigned long *size) { *size = reg->size; return 0; } static int stat(const struct ramfs_file_struct *file, struct stat_struct *fs_buf) { struct stat_struct statbuf; if (!verify_area(fs_buf, sizeof (struct stat_struct), PF_WRITE)) return -EFAULT; statbuf.dev = DEV_NONE; statbuf.id = file->dir_entry.info.id; statbuf.type = file->dir_entry.info.type; statbuf.rdev = DEV_NONE; if (file->dir_entry.info.type == FT_REG) get_size(file, &statbuf.size); else statbuf.size = 0; statbuf.mtime = file->mtime; memcpy_tofs(fs_buf, &statbuf, sizeof (struct stat_struct)); return 0; } static int read_dir(const struct ramfs_file_struct *dir, unsigned long pos, char *fs_buf, unsigned int count) { return pfs_read_dir(&dir->dir, pos, fs_buf, count); } static int read_reg(const struct ramfs_file_struct *reg, unsigned long pos, char *fs_buf, unsigned int count) { int n; const void *block; unsigned long off; unsigned int k; /* ajustement du nombre d'octets a lire */ if (!count) return 0; if (pos >= reg->size) return 0; if (count > reg->size - pos) count = reg->size - pos; if (!verify_area(fs_buf, count, PF_WRITE)) return -EFAULT; /* lecture des donnees */ n = 0; /* nombre de caracteres lus */ do { /* lecture d'un bloc */ block = get_block(reg, pos); off = pos & (RAMFS_BLOCK_SIZE - 1); k = min(RAMFS_BLOCK_SIZE - off, count); if (block) memcpy_tofs(fs_buf, block + off, k); else memset_tofs(fs_buf, 0, k); n += k; pos += k; fs_buf += k; count -= k; } while (count); return n; } static int write_reg(struct ramfs_file_struct *reg, unsigned long pos, const char *fs_buf, unsigned int count) { unsigned long new_size; int n; void *block; unsigned long off; unsigned int k; /* ajustement du nombre d'octets a ecrire */ if (!count) return 0; if (pos >= MAX_SIZE) return 0; if (count > MAX_SIZE - pos) count = MAX_SIZE - pos; if (!verify_area(fs_buf, count, PF_READ)) return -EFAULT; /* ajustement de la taille du fichier */ if ((new_size = pos + count) > reg->size) /* augmentation de la taille du fichier */ if (!grow_reg(reg, new_size)) return 0; /* ecriture des donnees */ n = 0; /* nombre de caracteres ecrits */ do { /* ecriture d'un bloc */ if (!(block = get_alloc_block(reg, pos))) break; off = pos & (RAMFS_BLOCK_SIZE - 1); k = min(RAMFS_BLOCK_SIZE - off, count); memcpy_fromfs(block + off, fs_buf, k); n += k; pos += k; fs_buf += k; count -= k; } while (count); reg->mtime = get_time(); return n; } static int create_file_common(struct ramfs_file_struct *dir, const char *fs_basename, void (*init_file)(struct ramfs_file_struct *, unsigned long, char *)) { struct ramfs_file_struct *new_file; char *name; int retval; /* parcours du repertoire */ if (pfs_file_exists(&dir->dir, fs_basename)) return -EEXIST; /* allocation et initialisation du nouveau fichier */ if (!(new_file = kmalloc(sizeof (struct ramfs_file_struct)))) return -ENOMEM; if (!(name = pfs_create_name(fs_basename))) { retval = -ENOMEM; goto error_free_new_file; } init_file(new_file, (unsigned long)new_file, name); /* insertion dans le repertoire */ pfs_insert_dir_entry(&new_file->dir_entry, &dir->dir); dir->mtime = get_time(); return 0; error_free_new_file: kfree(new_file, sizeof (struct ramfs_file_struct)); return retval; } static int create_reg(struct ramfs_file_struct *dir, const char *fs_basename) { return create_file_common(dir, fs_basename, init_reg); } static int create_dir(struct ramfs_file_struct *dir, const char *fs_basename) { return create_file_common(dir, fs_basename, init_dir); } static int remove_file_common(struct ramfs_file_struct *file, struct ramfs_file_struct *parent_dir) { /* suppression de l'entree de repertoire */ pfs_remove_dir_entry(&file->dir_entry, &parent_dir->dir); /* destruction du fichier */ file->operations->destroy(file); kfree(file, sizeof (struct ramfs_file_struct)); return 0; } static int remove_reg(struct ramfs_file_struct *reg, struct ramfs_file_struct *parent_dir) { return remove_file_common(reg, parent_dir); } static int remove_dir(struct ramfs_file_struct *dir, struct ramfs_file_struct *parent_dir) { /* verification que le repertoire est vide */ if (dir->dir.first_entry) return -ENOTEMPTY; return remove_file_common(dir, parent_dir); } static int rename(struct ramfs_file_struct *file, struct ramfs_file_struct *old_parent_dir, struct ramfs_file_struct *new_parent_dir, const char *fs_basename) { char *new_name; /* parcours du nouveau repertoire */ if (new_parent_dir != old_parent_dir) if (pfs_file_exists(&new_parent_dir->dir, fs_basename)) return -EEXIST; /* creation du nouveau nom */ if (!pfs_name_match(fs_basename, file->dir_entry.info.name)) { if (!(new_name = pfs_create_name(fs_basename))) return -ENOMEM; } else new_name = NULL; /* point de non-retour */ if (new_parent_dir != old_parent_dir) { /* suppression du fichier dans le repertoire actuel */ pfs_remove_dir_entry(&file->dir_entry, &old_parent_dir->dir); old_parent_dir->mtime = get_time(); } if (new_name) /* modification de l'entree de repertoire */ rename_file(file, new_name); if (new_parent_dir != old_parent_dir) /* insertion du fichier dans le nouveau repertoire */ pfs_insert_dir_entry(&file->dir_entry, &new_parent_dir->dir); if (new_parent_dir != old_parent_dir || new_name) new_parent_dir->mtime = get_time(); return 0; } /* ramfs_instance */ struct ramfs_instance_struct { struct ramfs_file_struct root_dir; }; static struct ramfs_file_struct *get_file(struct ramfs_instance_struct *instance, unsigned long id) { return id == 0 ? &instance->root_dir : (struct ramfs_file_struct *)id; } static int ramfs_instance_init(struct ramfs_instance_struct *ramfs_instance) { init_dir(&ramfs_instance->root_dir, 0, NULL); return 0; } static void empty_dir(struct ramfs_file_struct *dir) { struct pfs_dir_entry_struct *dir_entry; struct ramfs_file_struct *file; for (dir_entry = dir->dir.first_entry; dir_entry; dir_entry = dir_entry->next) { file = downcast(struct ramfs_file_struct, dir_entry, dir_entry); if (file->dir_entry.info.type == FT_DIR) empty_dir(file); file->operations->destroy(file); kfree(file, sizeof (struct ramfs_file_struct)); } } static void ramfs_instance_destroy(struct ramfs_instance_struct *ramfs_instance) { empty_dir(&ramfs_instance->root_dir); ramfs_instance->root_dir.operations->destroy(&ramfs_instance->root_dir); } /* ramfs */ static int ramfs_mount(const struct device_struct *device, void **data) { struct ramfs_instance_struct *ramfs_instance; int retval; if (!(ramfs_instance = kmalloc(sizeof (struct ramfs_instance_struct)))) return -ENOMEM; if ((retval = ramfs_instance_init(ramfs_instance)) < 0) goto error_free; *data = ramfs_instance; return 0; error_free: kfree(ramfs_instance, sizeof (struct ramfs_instance_struct)); return retval; } static void ramfs_umount(void *data) { ramfs_instance_destroy(data); kfree(data, sizeof (struct ramfs_instance_struct)); } static int ramfs_find(void *data, unsigned long dir_id, const char *fs_basename, unsigned long *id, int *type) { return find(get_file(data, dir_id), fs_basename, id, type); } static int ramfs_get_name(void *data, unsigned long id, char name[]) { return get_name(get_file(data, id), name); } static int ramfs_get_size(void *data, unsigned long reg_id, unsigned long *size) { return get_size(get_file(data, reg_id), size); } static int ramfs_stat(void *data, unsigned long id, struct stat_struct *fs_buf) { return stat(get_file(data, id), fs_buf); } static int ramfs_read_dir(void *data, unsigned long dir_id, unsigned long pos, char *fs_buf, unsigned int count) { return read_dir(get_file(data, dir_id), pos, fs_buf, count); } static int ramfs_read(void *data, unsigned long leaf_id, unsigned long pos, char *fs_buf, unsigned int count) { return read_reg(get_file(data, leaf_id), pos, fs_buf, count); } static int ramfs_write(void *data, unsigned long leaf_id, unsigned long pos, const char *fs_buf, unsigned int count) { return write_reg(get_file(data, leaf_id), pos, fs_buf, count); } static int ramfs_create(void *data, unsigned long dir_id, const char *fs_basename) { return create_reg(get_file(data, dir_id), fs_basename); } static int ramfs_mkdir(void *data, unsigned long dir_id, const char *fs_basename) { return create_dir(get_file(data, dir_id), fs_basename); } static int ramfs_can_remove(void *data, unsigned long leaf_id) { return 0; } static int ramfs_remove(void *data, unsigned long leaf_id, unsigned long parent_dir_id) { return remove_reg(get_file(data, leaf_id), get_file(data, parent_dir_id)); } static int ramfs_rmdir(void *data, unsigned long dir_id, unsigned long parent_dir_id) { return remove_dir(get_file(data, dir_id), get_file(data, parent_dir_id)); } static int ramfs_rename(void *data, unsigned long old_id, unsigned long old_parent_dir_id, unsigned long new_dir_id, const char *fs_basename) { return rename(get_file(data, old_id), get_file(data, old_parent_dir_id), get_file(data, new_dir_id), fs_basename); } const struct file_system_struct ramfs = { .name = "ramfs", .type = FT_NONE, .mount = ramfs_mount, .umount = ramfs_umount, .find = ramfs_find, .get_name = ramfs_get_name, .get_device = NULL, .get_size = ramfs_get_size, .stat = ramfs_stat, .read_dir = ramfs_read_dir, .read = ramfs_read, .write = ramfs_write, .ioctl = NULL, .create = ramfs_create, .mkdir = ramfs_mkdir, .can_remove = ramfs_can_remove, .remove = ramfs_remove, .rmdir = ramfs_rmdir, .rename = ramfs_rename };