/* 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_struct.h"
#include "file_system_instance.h"
#include "file_system_instance_struct.h"
#include "file_system_struct.h"
#include "ro_fs.h"
#include "pfs.h"
#include "pfs_structs.h"
#include "file_table.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define is_mount_point(dir) ((dir)->file_system_instance != (dir)->master_file_system_instance)
#define get_dir_local_id(dir) (is_mount_point(dir) ? 0 : (dir)->id)
#define get_local_id(node) ((node)->is_dir ? get_dir_local_id(node) : (node)->id)
/* dot et dot-dot */
static struct pfs_dir_struct common_dir_entries;
static const char dot_name[] = ".";
static struct pfs_dir_entry_struct dot = {
.info = {
.name = dot_name
}
};
static const char dotdot_name[] = "..";
static struct pfs_dir_entry_struct dotdot = {
.info = {
.name = dotdot_name
}
};
static const unsigned int common_dir_entries_size = sizeof dot_name + sizeof dotdot_name;
extern struct file_struct *const root;
/* Utilitaires */
/* Verifie que la somme des arguments est positive et peut etre representee
* dans un long.
* Retourne un code d'erreur si l'addition n'est pas possible. */
static int check_for_addition(unsigned long pos, long offset)
{
if (offset < 0 && pos < (unsigned long)(1 - offset) - 1) /* la position resultante serait negative */
return -EINVAL;
if ((offset >= 0 && pos > (unsigned long)LONG_MAX - offset) || (offset < 0 && pos > (unsigned long)LONG_MAX + (-offset))) /* la position resultante serait trop grande */
return -EOVERFLOW;
return 0;
}
/* Operations */
static int dir_read(struct file_struct *dir, unsigned long pos, char *fs_buf, unsigned int count)
{
if (pos < common_dir_entries_size)
return pfs_read_dir(&common_dir_entries, pos, fs_buf, count);
else
return dir->file_system_instance->file_system->read_dir(dir->file_system_instance->data, get_dir_local_id(dir), pos - common_dir_entries_size, fs_buf, count);
}
static int leaf_read(struct file_struct *leaf, unsigned long pos, char *fs_buf, unsigned int count)
{
return leaf->file_system_instance->file_system->read(leaf->file_system_instance->data, leaf->id, pos, fs_buf, count);
}
static int dir_write(struct file_struct *dir, unsigned long pos, const char *fs_buf, unsigned int count)
{
return -EISDIR;
}
static int leaf_write(struct file_struct *leaf, unsigned long pos, const char *fs_buf, unsigned int count)
{
return leaf->file_system_instance->file_system->write(leaf->file_system_instance->data, leaf->id, pos, fs_buf, count);
}
static long seek(const struct file_struct *node, unsigned long pos, long offset, int whence)
{
unsigned long size;
int rv;
/* Une erreur est retournee si la position resultante ne peut pas etre representee par un long (valeur de retour de fd_seek()). */
switch (whence) {
case SEEK_SET:
if ((rv = check_for_addition(0, offset)) < 0)
return rv;
return offset;
case SEEK_CUR:
if ((rv = check_for_addition(pos, offset)) < 0)
return rv;
return pos + offset;
case SEEK_END:
/* mode accessible uniquement pour les fichiers reguliers */
if ((rv = node->file_system_instance->file_system->get_size(node->file_system_instance->data, get_local_id(node), &size)) < 0)
return rv;
if ((rv = check_for_addition(size, offset)) < 0)
return rv;
return size + offset;
default:
return -EINVAL;
}
}
static long dir_seek(const struct file_struct *dir, unsigned long pos, long offset, int whence)
{
if (whence == SEEK_END)
/* mode non accessible pour un repertoire */
return -EINVAL;
return seek(dir, pos, offset, whence);
}
static long chr_seek(const struct file_struct *chr, unsigned long pos, long offset, int whence)
{
return 0;
}
static long blk_seek(const struct file_struct *blk, unsigned long pos, long offset, int whence)
{
if (whence == SEEK_END)
/* mode non accessible pour un peripherique de type bloc */
return -EINVAL;
return seek(blk, pos, offset, whence);
}
static long reg_seek(const struct file_struct *reg, unsigned long pos, long offset, int whence)
{
return seek(reg, pos, offset, whence);
}
static int node_stat(const struct file_struct *node, struct stat_struct *fs_buf)
{
return node->file_system_instance->file_system->stat(node->file_system_instance->data, get_local_id(node), fs_buf);
}
static int spec_ioctl(const struct file_struct *spec, int request, void *fs_arg)
{
return spec->file_system_instance->file_system->ioctl(spec->file_system_instance->data, spec->id, request, fs_arg);
}
static int not_spec_ioctl(const struct file_struct *node, int request, void *fs_arg)
{
return -ENOTTY;
}
static int spec_get_device(const struct file_struct *spec, struct device_struct **spec_device)
{
return spec->file_system_instance->file_system->get_device(spec->file_system_instance->data, spec->id, spec_device);
}
static int not_spec_get_device(const struct file_struct *node, struct device_struct **spec_device)
{
return -ENOTBLK;
}
static const struct file_operations_struct dir_operations = {
.read = dir_read,
.write = dir_write,
.seek = dir_seek,
.stat = node_stat,
.ioctl = not_spec_ioctl,
.get_device = not_spec_get_device
};
static const struct file_operations_struct chr_operations = {
.read = leaf_read,
.write = leaf_write,
.seek = chr_seek,
.stat = node_stat,
.ioctl = spec_ioctl,
.get_device = spec_get_device
};
static const struct file_operations_struct blk_operations = {
.read = leaf_read,
.write = leaf_write,
.seek = blk_seek,
.stat = node_stat,
.ioctl = spec_ioctl,
.get_device = spec_get_device
};
static const struct file_operations_struct reg_operations = {
.read = leaf_read,
.write = leaf_write,
.seek = reg_seek,
.stat = node_stat,
.ioctl = not_spec_ioctl,
.get_device = not_spec_get_device
};
/* Utilitaires sur l'arboresence des fichiers */
/* Retourne le fichier precedent dans l'arborescence. */
static struct file_struct *get_previous(const struct file_struct *node)
{
struct file_struct *nd;
nd = node->parent->first_entry;
while (nd->next_entry != node)
nd = nd->next_entry;
return nd;
}
/* Connecte node2 a la place de node1. */
static void replace(const struct file_struct *node1, struct file_struct *node2)
{
if (node1->parent->first_entry == node1)
node1->parent->first_entry = node2;
else
get_previous(node1)->next_entry = node2;
}
/* Arboresence des fichiers */
static void init_node(struct file_struct *node, const struct file_operations_struct *file_operations, struct file_struct *parent, unsigned long id)
{
node->file_operations = file_operations;
node->count = 0;
node->write_access_count = 0;
mapping_init(&node->mapping);
node->parent = parent;
node->next_entry = parent->first_entry;
node->file_system_instance = parent->file_system_instance;
node->id = id;
}
static void init_dir(struct file_struct *dir, struct file_struct *parent, unsigned long id)
{
init_node(dir, &dir_operations, parent, id);
dir->is_dir = 1;
dir->first_entry = NULL;
dir->master_file_system_instance = parent->file_system_instance;
}
static void init_leaf(struct file_struct *leaf, const struct file_operations_struct *leaf_operations, struct file_struct *parent, unsigned long id)
{
init_node(leaf, leaf_operations, parent, id);
leaf->is_dir = 0;
leaf->to_remove = 0;
}
static int alloc_dir(struct file_struct *parent, unsigned long id, struct file_struct **dir)
{
if (!(*dir = alloc_file()))
return -ENOMEM;
init_dir(*dir, parent, id);
return 0;
}
static int alloc_chr(struct file_struct *parent, unsigned long id, struct file_struct **chr)
{
if (!(*chr = alloc_file()))
return -ENOMEM;
init_leaf(*chr, &chr_operations, parent, id);
return 0;
}
static int alloc_blk(struct file_struct *parent, unsigned long id, struct file_struct **blk)
{
if (!(*blk = alloc_file()))
return -ENOMEM;
init_leaf(*blk, &blk_operations, parent, id);
return 0;
}
static int alloc_reg(struct file_struct *parent, unsigned long id, struct file_struct **reg)
{
if (!(*reg = alloc_file()))
return -ENOMEM;
init_leaf(*reg, ®_operations, parent, id);
return 0;
}
static int alloc_node(struct file_struct *parent, unsigned long id, int type, struct file_struct **node)
{
switch (type) {
case FT_DIR:
return alloc_dir(parent, id, node);
case FT_CHR:
return alloc_chr(parent, id, node);
case FT_BLK:
return alloc_blk(parent, id, node);
case FT_REG:
default:
return alloc_reg(parent, id, node);
}
}
/* Retourne 1 si le parent est candidat a la liberation. */
static int destroy_node(struct file_struct *node)
{
assert(node->count == 0);
mapping_destroy(&node->mapping);
if (!node->is_dir)
if (node->to_remove)
node->file_system_instance->file_system->remove(node->file_system_instance->data, node->id, get_dir_local_id(node->parent));
if (node->parent->first_entry == node) {
node->parent->first_entry = node->next_entry;
if (!node->parent->first_entry && node->parent != root) /* la racine n'est pas liberable */
return 1;
else
return 0;
}
else {
get_previous(node)->next_entry = node->next_entry;
return 0;
}
}
/* Retourne le noeud de l'arborescence de parent parent et d'identifiant id. Si
* ce fichier n'est pas present dans la table, une entree lui est allouee et
* est initialisee avec le type type. Autrement, type est ignore.
* *node est mis a NULL si le fichier est marque a supprimer.
* Retourne un code d'erreur en cas d'echec. */
static int get_node(struct file_struct *parent, unsigned long id, int type, struct file_struct **node)
{
int rv;
/* recherche du fichier dans la liste des entrees du repertoire parent */
for (*node = parent->first_entry; *node; *node = (*node)->next_entry)
if ((*node)->id == id) {
if (!(*node)->is_dir && (*node)->to_remove)
*node = NULL; /* les fichiers marques comme `a supprimer` sont invisibles */
return 0;
}
/* fichier non present, allocation et initialisation d'une nouvelle entree */
if ((rv = alloc_node(parent, id, type, node)) < 0)
return rv;
parent->first_entry = *node;
return 0;
}
/* *node est mis a NULL si le fichier n'est pas trouve. */
static int find_node_internal(struct file_struct *dir, const char *fs_basename, struct file_struct **node)
{
unsigned long id;
int type;
int rv;
if (pfs_name_match(fs_basename, dot_name))
*node = dir;
else if (pfs_name_match(fs_basename, dotdot_name))
*node = dir->parent;
else {
/* resolution du nom de fichier */
if ((rv = dir->file_system_instance->file_system->find(dir->file_system_instance->data, get_dir_local_id(dir), fs_basename, &id, &type)) < 0)
return rv;
/* affectation d'un noeud */
if (!rv)
*node = NULL;
else
if ((rv = get_node(dir, id, type, node)) < 0)
return rv;
}
return 0;
}
static void free_node(struct file_struct *node)
{
struct file_struct *parent;
do {
parent = node->parent;
if (!destroy_node(node) || parent->count)
parent = NULL;
free_file(node);
}
while ((node = parent));
}
static int node_is_busy(const struct file_struct *node)
{
return node->count > 1; /* ne pas compter la reference courante */
}
/* Retourne une valeur non nulle si et seulement si node est dans la
* descendance de dir. */
static int is_parent_directory(const struct file_struct *dir, const struct file_struct *node)
{
for (; node != dir; node = node->parent)
if (node->parent == node)
return 0;
return 1;
}
/* src et dest sont supposes avoir le meme type.
* Ne modifie le nombre de references ni de src ni de dest. */
static void copy_node(const struct file_struct *src, struct file_struct *dest)
{
/* cas particulier ne rentrant pas dans le cas general */
if (dest == src)
return; /* rien a faire dans ce cas */
replace(dest, dest->next_entry);
dest->write_access_count = src->write_access_count;
mapping_copy(&src->mapping, &dest->mapping);
dest->parent = src->parent;
dest->next_entry = src->next_entry;
dest->file_system_instance = src->file_system_instance;
dest->id = src->id;
if (src->is_dir) {
dest->first_entry = src->first_entry;
dest->master_file_system_instance = src->master_file_system_instance;
}
else
dest->to_remove = src->to_remove;
replace(src, dest);
}
/* dummy_file_system */
static int dummy_mount(const struct device_struct *device, void **data)
{
*data = NULL;
return 0;
}
static void dummy_umount(void *data) {}
static int dummy_find(void *data, unsigned long dir_id, const char *fs_basename, unsigned long *id, int *type)
{
return 0;
}
static int dummy_stat(void *data, unsigned long id, 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 = 0;
statbuf.type = FT_DIR;
statbuf.rdev = DEV_NONE;
statbuf.size = 0;
statbuf.mtime = startup_time;
memcpy_tofs(fs_buf, &statbuf, sizeof (struct stat_struct));
return 0;
}
static int dummy_read_dir(void *data, unsigned long dir_id, unsigned long pos, char *fs_buf, unsigned int count)
{
return 0;
}
static const struct file_system_struct dummy_file_system = {
.name = "dummy",
.type = FT_NONE,
.mount = dummy_mount,
.umount = dummy_umount,
.find = dummy_find,
.get_name = NULL,
.get_device = NULL,
.get_size = NULL,
.stat = dummy_stat,
.read_dir = dummy_read_dir,
.read = NULL,
.write = NULL,
.ioctl = NULL,
.create = no_create,
.mkdir = no_mkdir,
.can_remove = NULL,
.remove = NULL,
.rmdir = NULL,
.rename = NULL
};
static struct file_system_instance_struct dummy_file_system_instance = {
.file_system = &dummy_file_system,
.device = DEV_NONE,
.count = 1 /* non demontable */
};
/* root */
/* racine de l'arborescence */
static struct file_struct _root = {
.file_operations = &dir_operations,
{
{
.count = 0,
.write_access_count = 0,
.parent = &_root,
.next_entry = NULL,
.file_system_instance = &dummy_file_system_instance,
.id = 0,
.is_dir = 1,
.first_entry = NULL,
.master_file_system_instance = &dummy_file_system_instance
}
}
};
struct file_struct *const root = &_root;
/* Fonctions exportees */
void root_init()
{
pfs_insert_dir_entry(&dot, &common_dir_entries);
pfs_insert_dir_entry(&dotdot, &common_dir_entries);
}
/* Trouve ou alloue un noeud.
* *node est mis a NULL si le fichier n'est pas trouve. */
int find_node(struct file_struct *dir, const char *fs_basename, struct file_struct **node)
{
return find_node_internal(dir, fs_basename, node);
}
/* Incremente le nombre de references sur le noeud. */
void node_hold(struct file_struct *node)
{
node->count++;
file_system_instance_hold(node->file_system_instance);
}
/* Decremente le nombre de references sur le noeud.
* Le fichier est libere s'il n'est plus reference. */
void node_release(struct file_struct *node)
{
assert(node->count > 0);
node->count--;
file_system_instance_release(node->file_system_instance);
if (!node->count && (!node->is_dir || !node->first_entry)) { /* liberable ? */
if (node == root) /* ne devrait jamais se produire, sauf si le processus init appelle exit() (apres un echec de reboot() par exemple) */
return;
free_node(node);
}
}
int node_get_write_access(struct file_struct *node)
{
if (node->write_access_count < 0)
return -ETXTBSY;
node->write_access_count++;
return 0;
}
int node_deny_write_access(struct file_struct *node)
{
if (node->write_access_count > 0)
return -ETXTBSY;
node->write_access_count--;
return 0;
}
void node_release_write_access(struct file_struct *node)
{
if (node->write_access_count < 0)
node->write_access_count++;
else if (node->write_access_count > 0)
node->write_access_count--;
}
struct mapping_struct *node_get_mapping(struct file_struct *node)
{
return &node->mapping;
}
int node_create(const struct file_struct *node, const char *fs_basename)
{
if (!node->is_dir)
return -ENOTDIR;
return node->file_system_instance->file_system->create(node->file_system_instance->data, get_dir_local_id(node), fs_basename);
}
int node_remove(struct file_struct *node)
{
int rv;
if (node->is_dir)
return -EISDIR;
if ((rv = node->file_system_instance->file_system->can_remove(node->file_system_instance->data, node->id)) < 0)
return rv;
node->to_remove = 1;
/* La suppression effective a lieu lorsque le fichier n'est plus reference. */
return 0;
}
int node_mkdir(const struct file_struct *node, const char *fs_basename)
{
if (!node->is_dir)
return -ENOTDIR;
return node->file_system_instance->file_system->mkdir(node->file_system_instance->data, get_dir_local_id(node), fs_basename);
}
int node_rmdir(const struct file_struct *node)
{
if (!node->is_dir)
return -ENOTDIR;
if (node_is_busy(node))
return -EBUSY; /* repertoire occupe */
return node->file_system_instance->file_system->rmdir(node->file_system_instance->data, node->id, get_dir_local_id(node->parent));
}
/* Renomme un fichier, en le deplacant dans un autre repertoire si necessaire,
* sans l'invalider si celui-ci est deja reference.
* Une erreur est renvoyee si l'ancien nom et le nouveau nom n'appartiennent
* pas au meme systeme de fichier.
* Cette fonction n'est pas un mv, elle ne copie pas physiquement les fichiers
* (encore moins les repertoires recursivement). En particulier, si newname
* existe (et meme si c'est un repertoire vide), une erreur est renvoyee. */
/* Fonction delicate, nombreux cas pathologiques. */
int node_rename(struct file_struct *node, struct file_struct *dir, const char *fs_basename)
{
struct file_struct *nd;
int rv;
/* verification que le repertoire de destination n'est pas un sous-repertoire du fichier */
if (node->is_dir && is_parent_directory(node, dir))
return -EINVAL;
/* verification que le fichier n'est pas un repertoire en cours d'utilisation (en particulier, n'est pas un point de montage) */
if (node->is_dir && (node_is_busy(node)))
return -EBUSY; /* repertoire occupe */
/* verification que node et dir ont le meme systeme de fichiers */
if (node->file_system_instance != dir->file_system_instance)
return -EXDEV;
/* renommage */
if ((rv = node->file_system_instance->file_system->rename(node->file_system_instance->data, node->id, get_dir_local_id(node->parent), get_dir_local_id(dir), fs_basename)) < 0)
return rv;
/* mise a jour de la table des fichiers */
/* code sans effet si le nouveau nom est identique a l'ancien, ou si node n'a pas d'autre reference (notamment si node est un repertoire). */
if ((rv = find_node_internal(dir, fs_basename, &nd)) < 0) /* ca ne serait pas de chance, mais le nouveau fichier pourrait avoir ete modifie concurrentiellement */
return rv;
copy_node(nd, node);
/* si nd a eu le temps d'avoir d'autres references, le meme fichier physique possede deux entrees dans la table des fichiers, une avec node et une autre avec nd. Cette situation est improbable mais ne devrait pas poser probleme. */
return 0;
}
/* node_mount() n'interdit pas de monter un meme peripherique a plusieurs endroits. */
int node_mount(struct file_struct *node, char *device_path, struct device_struct *device, const struct file_system_struct *file_system)
{
struct file_system_instance_struct *fsi;
int retval;
if (!node->is_dir)
return -ENOTDIR;
/* Verification que le repertoire n'est pas occupe et qu'il n'est pas deja un
point de montage.
On est oblige d'affranchir explicitement root de la premiere condition
pour pouvoir monter dessus (root est toujours occupe car c'est le
repertoire courant du processus init).
Pour les autres repertoires, la premiere condition implique toujours la
seconde. */
if ((node != root && (node_is_busy(node) || node->first_entry))
|| is_mount_point(node))
return -EBUSY; /* repertoire occupe */
if (!(fsi = kmalloc(sizeof (struct file_system_instance_struct))))
return -ENOMEM;
if ((retval = file_system_instance_init(fsi, file_system, device_path, device, node)) < 0)
goto error_free;
node_hold(node); /* incremente aussi le compteur du systeme de fichiers actuel */
file_system_instance_hold(fsi); /* node va avoir une reference sur fsi */
node->file_system_instance = fsi;
return 0;
error_free:
kfree(fsi, sizeof (struct file_system_instance_struct));
return retval;
}
/* Fait l'inverse de node_mount(). */
int node_umount(struct file_struct *node)
{
struct file_system_instance_struct *fsi;
if (!node->is_dir)
return -ENOTDIR;
/* verification qu'un systeme de fichier est effectivement monte */
if (!is_mount_point(node))
return -EINVAL;
fsi = node->file_system_instance;
if (file_system_instance_is_busy(fsi))
return -EBUSY;
node->file_system_instance = node->master_file_system_instance;
file_system_instance_release(fsi); /* node ne reference plus fsi */
node_release(node); /* plus de systeme de fichier monte - decremente le compteur du systeme de fichiers retabli */
file_system_instance_destroy(fsi);
kfree(fsi, sizeof (struct file_system_instance_struct));
return 0;
}
struct file_struct *node_get_parent(const struct file_struct *node)
{
return node->parent;
}
int node_is_dir(const struct file_struct *node)
{
return node->is_dir;
}
/* name doit etre un tableau d'au moins (NAME_MAX + 1) elements. */
int node_get_name(const struct file_struct *node, char name[])
{
return node->parent->file_system_instance->file_system->get_name(node->parent->file_system_instance->data, node->id, name);
}
int node_get_device(const struct file_struct *node, struct device_struct **device)
{
return node->file_operations->get_device(node, device);
}
/* Fonctions speciales pour le noyau */
int stat_node(const struct file_struct *node, struct stat_struct *buf)
{
unsigned short fs;
int retval;
get_fs(fs);
load_fs(KERNEL_DATA_SEL);
retval = node_stat(node, buf);
load_fs(fs);
return retval;
}
/* Lecture de fichiers pour le noyau. */
int read_node(struct file_struct *node, unsigned long pos, void *buf, unsigned int count)
{
unsigned short fs;
int retval;
get_fs(fs);
load_fs(KERNEL_DATA_SEL);
retval = node->file_operations->read(node, pos, buf, count);
load_fs(fs);
return retval;
}