View of xos/fs/ramfs.c


XOS | Parent Directory | View | Download

/* 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 <http://www.gnu.org/licenses/>. */
 
#include "file_system_struct.h"
#include "pfs.h"
#include "pfs_structs.h"
 
#include <time.h>
#include <stat_struct.h>
#include <kmalloc.h>
#include <verify_area.h>
#include <segment.h>
#include <consts.h>
#include <enums.h>
#include <errno.h>
#include <string.h>
#include <i386.h>
#include <util.h>
#include <limits.h>
 
/* 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 = &reg_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
};