/* * ramfs.c - a malloc based filesystem * * Copyright (c) 2007 Sascha Hauer , Pengutronix * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * 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. * */ #include #include #include #include #include #include #include #include #include #define CHUNK_SIZE (4096 * 2) struct ramfs_chunk { char *data; struct ramfs_chunk *next; }; struct ramfs_inode { char *name; struct ramfs_inode *parent; struct ramfs_inode *next; struct ramfs_inode *child; char *symlink; ulong mode; struct handle_d *handle; ulong size; struct ramfs_chunk *data; /* Points to recently used chunk */ int recent_chunk; struct ramfs_chunk *recent_chunkp; }; struct ramfs_priv { struct ramfs_inode root; }; /* ---------------------------------------------------------------*/ static struct ramfs_inode * lookup(struct ramfs_inode *node, const char *name) { debug("lookup: %s in %p\n",name, node); if(!S_ISDIR(node->mode)) return NULL; node = node->child; if (!node) return NULL; while (node) { debug("lookup: %s\n", node->name); if (!strcmp(node->name, name)) { debug("lookup: found: 0x%p\n",node); return node; } node = node->next; } return NULL; } static struct ramfs_inode* rlookup(struct ramfs_priv *priv, const char *path) { struct ramfs_inode *node = &priv->root; static char *buf; char *part; debug("rlookup %s in %p\n",path, node); buf = strdup(path); part = strtok(buf, "/"); if (!part) goto out; do { node = lookup(node, part); if (!node) goto out; part = strtok(NULL, "/"); } while(part); out: free(buf); return node; } static struct ramfs_inode* rlookup_parent(struct ramfs_priv *priv, const char *pathname, char **file) { char *path; struct ramfs_inode *node; pathname++; path = strdup(pathname); if ((*file = strrchr((char *) pathname, '/'))) { char *tmp; (*file)++; tmp = strrchr(path, '/'); *tmp = 0; node = rlookup(priv, path); if (!node) { errno = -ENOENT; goto out; } } else { *file = (char *)pathname; node = &priv->root; } out: free(path); return node; } static int chunks = 0; static struct ramfs_chunk *ramfs_get_chunk(void) { struct ramfs_chunk *data = malloc(sizeof(struct ramfs_chunk)); if (!data) return NULL; data->data = malloc(CHUNK_SIZE); if (!data->data) { free(data); return NULL; } data->next = NULL; chunks++; return data; } static void ramfs_put_chunk(struct ramfs_chunk *data) { free(data->data); free(data); chunks--; } static struct ramfs_inode* ramfs_get_inode(void) { struct ramfs_inode *node = xzalloc(sizeof(struct ramfs_inode)); return node; } static void ramfs_put_inode(struct ramfs_inode *node) { struct ramfs_chunk *data = node->data; while (data) { struct ramfs_chunk *tmp = data->next; ramfs_put_chunk(data); data = tmp; } free(node->symlink); free(node->name); free(node); } static struct ramfs_inode* node_insert(struct ramfs_inode *parent_node, const char *filename, ulong mode) { struct ramfs_inode *node, *new_node = ramfs_get_inode(); new_node->name = strdup(filename); new_node->mode = mode; node = parent_node->child; if (S_ISDIR(mode)) { struct ramfs_inode *n = ramfs_get_inode(); n->name = strdup("."); n->mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; n->child = n; n->parent = new_node; new_node->child = n; n = ramfs_get_inode(); n->name = strdup(".."); n->mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; n->parent = new_node; n->child = parent_node->child; new_node->child->next = n; } while (node->next) node = node->next; node->next = new_node; return new_node; } /* ---------------------------------------------------------------*/ static int __ramfs_create(struct device_d *dev, const char *pathname, mode_t mode, const char *symlink) { struct ramfs_priv *priv = dev->priv; struct ramfs_inode *node; char *file; char *__symlink = NULL; node = rlookup_parent(priv, pathname, &file); if (!node) return -ENOENT; if (symlink) { __symlink = strdup(symlink); if (!__symlink) return -ENOMEM; } node = node_insert(node, file, mode); if (!node) { free(__symlink); return -ENOMEM; } node->symlink = __symlink; return 0; } static int ramfs_create(struct device_d *dev, const char *pathname, mode_t mode) { return __ramfs_create(dev, pathname, mode, NULL); } static int ramfs_unlink(struct device_d *dev, const char *pathname) { struct ramfs_priv *priv = dev->priv; struct ramfs_inode *node, *lastnode; char *file; node = rlookup_parent(priv, pathname, &file); lastnode = node->child->next; node = lastnode->next; while (node) { if (!strcmp(node->name, file)) { struct ramfs_inode *tmp; tmp = node; lastnode->next = node->next; ramfs_put_inode(tmp); return 0; } lastnode = node; node = node->next; }; return -ENOENT; } static int ramfs_mkdir(struct device_d *dev, const char *pathname) { return ramfs_create(dev, pathname, S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO); } static int ramfs_rmdir(struct device_d *dev, const char *pathname) { struct ramfs_priv *priv = dev->priv; struct ramfs_inode *node, *lastnode; char *file; node = rlookup_parent(priv, pathname, &file); lastnode = node->child->next; node = lastnode->next; while (node) { if (!strcmp(node->name, file)) { struct ramfs_inode *tmp; tmp = node; lastnode->next = node->next; ramfs_put_inode(tmp->child->next); ramfs_put_inode(tmp->child); ramfs_put_inode(tmp); return 0; } lastnode = node; node = node->next; }; return -ENOENT; } static int ramfs_open(struct device_d *dev, FILE *file, const char *filename) { struct ramfs_priv *priv = dev->priv; struct ramfs_inode *node = rlookup(priv, filename); if (!node) return -ENOENT; file->size = node->size; file->priv = node; return 0; } static int ramfs_close(struct device_d *dev, FILE *f) { return 0; } static struct ramfs_chunk *ramfs_find_chunk(struct ramfs_inode *node, int chunk) { struct ramfs_chunk *data; int left = chunk; if (chunk == 0) return node->data; if (node->recent_chunk == chunk) return node->recent_chunkp; if (node->recent_chunk < chunk && node->recent_chunk != 0) { /* Start at last known chunk */ data = node->recent_chunkp; left -= node->recent_chunk; } else { /* Start at first chunk */ data = node->data; } while (left--) data = data->next; node->recent_chunkp = data; node->recent_chunk = chunk; return data; } static int ramfs_read(struct device_d *_dev, FILE *f, void *buf, size_t insize) { struct ramfs_inode *node = f->priv; int chunk; struct ramfs_chunk *data; int ofs; int now; int pos = f->pos; int size = insize; chunk = f->pos / CHUNK_SIZE; debug("%s: reading from chunk %d\n", __FUNCTION__, chunk); /* Position ourself in stream */ data = ramfs_find_chunk(node, chunk); ofs = f->pos % CHUNK_SIZE; /* Read till end of current chunk */ if (ofs) { now = min(size, CHUNK_SIZE - ofs); debug("Reading till end of node. size: %d\n", size); memcpy(buf, data->data + ofs, now); size -= now; pos += now; buf += now; if (pos > node->size) node->size = now; data = data->next; } /* Do full chunks */ while (size >= CHUNK_SIZE) { debug("do full chunk. size: %d\n", size); memcpy(buf, data->data, CHUNK_SIZE); data = data->next; size -= CHUNK_SIZE; pos += CHUNK_SIZE; buf += CHUNK_SIZE; } /* And the rest */ if (size) { debug("do rest. size: %d\n", size); memcpy(buf, data->data, size); } return insize; } static int ramfs_write(struct device_d *_dev, FILE *f, const void *buf, size_t insize) { struct ramfs_inode *node = f->priv; int chunk; struct ramfs_chunk *data; int ofs; int now; int pos = f->pos; int size = insize; chunk = f->pos / CHUNK_SIZE; debug("%s: writing to chunk %d\n", __FUNCTION__, chunk); /* Position ourself in stream */ data = ramfs_find_chunk(node, chunk); ofs = f->pos % CHUNK_SIZE; /* Write till end of current chunk */ if (ofs) { now = min(size, CHUNK_SIZE - ofs); debug("writing till end of node. size: %d\n", size); memcpy(data->data + ofs, buf, now); size -= now; pos += now; buf += now; if (pos > node->size) node->size = now; data = data->next; } /* Do full chunks */ while (size >= CHUNK_SIZE) { debug("do full chunk. size: %d\n", size); memcpy(data->data, buf, CHUNK_SIZE); data = data->next; size -= CHUNK_SIZE; pos += CHUNK_SIZE; buf += CHUNK_SIZE; } /* And the rest */ if (size) { debug("do rest. size: %d\n", size); memcpy(data->data, buf, size); } return insize; } static loff_t ramfs_lseek(struct device_d *dev, FILE *f, loff_t pos) { f->pos = pos; return f->pos; } static int ramfs_truncate(struct device_d *dev, FILE *f, ulong size) { struct ramfs_inode *node = f->priv; int oldchunks, newchunks; struct ramfs_chunk *data = node->data; newchunks = (size + CHUNK_SIZE - 1) / CHUNK_SIZE; oldchunks = (node->size + CHUNK_SIZE - 1) / CHUNK_SIZE; if (newchunks < oldchunks) { if (!newchunks) node->data = NULL; while (newchunks--) data = data->next; while (data) { struct ramfs_chunk *tmp; tmp = data->next; ramfs_put_chunk(data); data = tmp; } if (node->recent_chunk > newchunks) node->recent_chunk = 0; } if (newchunks > oldchunks) { if (!data) { node->data = ramfs_get_chunk(); if (!node->data) return -ENOMEM; data = node->data; } newchunks--; while (data->next) { newchunks--; data = data->next; } while (newchunks--) { data->next = ramfs_get_chunk(); if (!data->next) return -ENOMEM; data = data->next; } } node->size = size; return 0; } static DIR* ramfs_opendir(struct device_d *dev, const char *pathname) { DIR *dir; struct ramfs_priv *priv = dev->priv; struct ramfs_inode *node; debug("opendir: %s\n", pathname); node = rlookup(priv, pathname); if (!node) return NULL; if (!S_ISDIR(node->mode)) return NULL; dir = xmalloc(sizeof(DIR)); dir->priv = node->child; return dir; } static struct dirent* ramfs_readdir(struct device_d *dev, DIR *dir) { struct ramfs_inode *node = dir->priv; if (node) { strcpy(dir->d.d_name, node->name); dir->priv = node->next; return &dir->d; } return NULL; } static int ramfs_closedir(struct device_d *dev, DIR *dir) { free(dir); return 0; } static int ramfs_stat(struct device_d *dev, const char *filename, struct stat *s) { struct ramfs_priv *priv = dev->priv; struct ramfs_inode *node = rlookup(priv, filename); if (!node) return -ENOENT; s->st_size = node->symlink ? strlen(node->symlink) : node->size; s->st_mode = node->mode; return 0; } static int ramfs_symlink(struct device_d *dev, const char *pathname, const char *newpath) { mode_t mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO; return __ramfs_create(dev, newpath, mode, pathname); } static int ramfs_readlink(struct device_d *dev, const char *pathname, char *buf, size_t bufsiz) { struct ramfs_priv *priv = dev->priv; struct ramfs_inode *node = rlookup(priv, pathname); int len; if (!node || !node->symlink) return -ENOENT; len = min(bufsiz, strlen(node->symlink)); memcpy(buf, node->symlink, len); return 0; } static int ramfs_probe(struct device_d *dev) { struct ramfs_inode *n; struct ramfs_priv *priv = xzalloc(sizeof(struct ramfs_priv)); dev->priv = priv; priv->root.name = "/"; priv->root.mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; priv->root.parent = &priv->root; n = ramfs_get_inode(); n->name = strdup("."); n->mode = S_IFDIR; n->parent = &priv->root; n->child = n; priv->root.child = n; n = ramfs_get_inode(); n->name = strdup(".."); n->mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; n->parent = &priv->root; n->child = priv->root.child; priv->root.child->next = n; return 0; } static void ramfs_remove(struct device_d *dev) { free(dev->priv); } static struct fs_driver_d ramfs_driver = { .create = ramfs_create, .unlink = ramfs_unlink, .open = ramfs_open, .close = ramfs_close, .truncate = ramfs_truncate, .read = ramfs_read, .write = ramfs_write, .lseek = ramfs_lseek, .mkdir = ramfs_mkdir, .rmdir = ramfs_rmdir, .opendir = ramfs_opendir, .readdir = ramfs_readdir, .closedir = ramfs_closedir, .stat = ramfs_stat, .symlink = ramfs_symlink, .readlink = ramfs_readlink, .flags = FS_DRIVER_NO_DEV, .drv = { .probe = ramfs_probe, .remove = ramfs_remove, .name = "ramfs", } }; static int ramfs_init(void) { return register_fs_driver(&ramfs_driver); } coredevice_initcall(ramfs_init);