[RFC][2.4 PATCH] A restricted /dev filesystem.

From: Tetsuo Handa
Date: Mon Nov 01 2004 - 07:18:36 EST


Purpose:

Ensure that device files in /dev directory are never faked.
For example, the cracker who got root privileges can't do like
'rm -f /dev/null; mknod /dev/null b 8 0'.

Making root-fs read-only will improve security.
But since some device files in /dev needs to be chown()/chmod()-able,
some special technique is required to make root-fs read-only.

Devfs is a nice choice, but devfs can't ensure the bindings between
filenames and their device numbers (i.e. /dev/null may be 'block-8-0',
which should be 'char-1-3'.)

So I want a filesystem that can ensure the bindings.
And here is my work, but since I'm a newbie developing a new filesystem,
I'm afraid there are some bugs, especially locking related bugs.
So, could someone please check the code?
I made this code based on /usr/src/linux-2.4.27/fs/ramfs/inode.c .

Code Name:

SYAORAN (Simple Yet All-important Object Realizing Abiding Nexus)

Specifications:

No regular files allowed. (since I think /dev needn't to keep regular files.)
No hardlinks allowed. (since I think hardlinks are unused for /dev .)
"Files which are created at mount time" and "files which can be created/deleted
after mount time" are given via a config file using "mount -o" option.
Modified part starts with '/***** SYAORAN start. *****/'
and ends with '/***** SYAORAN end. *****/' .

Config File Format:

There are 10 elements that describes an entry.

'filename' is a fullpath seen from the mount point, but no leading / .
'permission' is specified in an octal format, between 000 and 777.
'uid' and 'gid' are specified in a decimal format.
'may_create' is a boolean flag that controls whether the entry can be created after mount.
'may_delete' is a boolean flag that controls whether the entry can be deleted after mount.
'type' is a character that specifies the device type, one of 'd' (dir), 'f' (fifo), 's' (socket), 'c' (char-dev), 'b' (blk-dev), 'l' (symlink).
'symlink_data' is an initial content that is used when creating a symlink at mount time.
'major' and 'minor' are device's major/minor number specified in a decimal format.

Lines are separeted by '\n', and all chars that matches "(unsigned char) c <= ' '" are treated as delimiters.
The permission, uid, gid are ignored for symlinks.

Usage:

gcc -Wall -O3 -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4.27/include/ -c syaoran.c
insmod syaoran.o
mkdir -p /mnt/test/
mount -t syaoran -o /root/syaoran.conf none /mnt/test/

Regards....

---
Tetsuo Handa


---------- Example of config file ( /root/syaoran.conf ) ----------

#filename permission uid gid may_create may_delete type [ symlink_data | major minor ]
pts 755 0 0 0 0 d
shm 755 0 0 0 0 d
fd 777 0 0 0 0 l /proc/self/fd
stdin 777 0 0 0 0 l /proc/self/fd/0
stdout 777 0 0 0 0 l /proc/self/fd/1
stderr 777 0 0 0 0 l /proc/self/fd/2
null 666 0 0 0 0 c 1 3
zero 666 0 0 0 0 c 1 5
random 644 0 0 0 0 c 1 8
urandom 644 0 0 0 0 c 1 9
tty 666 0 0 0 0 c 5 0
tty0 600 0 0 0 0 c 4 0
tty1 600 0 0 0 0 c 4 1
tty2 600 0 0 0 0 c 4 2
tty3 600 0 0 0 0 c 4 3
tty4 600 0 0 0 0 c 4 4
tty5 600 0 0 0 0 c 4 5
tty6 600 0 0 0 0 c 4 6
tty7 600 0 0 0 0 c 4 7
tty8 600 0 0 0 0 c 4 8
cdrom 777 0 0 1 1 l /dev/scd0
mouse 777 0 0 1 1 l psaux
console 600 0 0 1 0 c 5 1
fd0 660 0 19 0 0 b 2 0
fd1 660 0 19 0 0 b 2 1
fd2 660 0 19 0 0 b 2 2
fd3 660 0 19 0 0 b 2 3
hda 660 0 6 0 0 b 3 0
hda1 660 0 6 0 0 b 3 1
hda2 660 0 6 0 0 b 3 2
hda3 660 0 6 0 0 b 3 3
hda5 660 0 6 0 0 b 3 5
hda6 660 0 6 0 0 b 3 6
hda7 660 0 6 0 0 b 3 7
hda8 660 0 6 0 0 b 3 8
hda9 660 0 6 0 0 b 3 9
hda10 660 0 6 0 0 b 3 10
hda11 660 0 6 0 0 b 3 11
initctl 600 0 0 1 1 p
log 666 0 0 1 1 s
rtc 644 0 0 0 0 c 10 135
ptmx 666 0 0 0 0 c 5 2
ram 777 0 0 1 1 l /dev/ram0
ram0 660 0 6 0 0 b 1 0
ram1 660 0 6 0 0 b 1 1
sda 660 0 6 0 0 b 8 0
initrd 660 0 6 1 0 b 1 250
psaux 600 0 0 0 0 c 10 1
apm_bios 600 0 0 0 0 c 10 134
cpu 755 0 0 0 0 d
cpu/0 755 0 0 0 0 d
cpu/0/microcode 600 0 0 0 0 c 10 184
ttyS0 660 0 14 0 0 c 4 64
ttyS1 660 0 14 0 0 c 4 65
ttyS2 660 0 14 0 0 c 4 66
ttyS3 660 0 14 0 0 c 4 67
ptal-printd 777 0 0 1 1 l /var/run/ptal-printd
gpmctl 700 0 0 1 1 s
scd0 660 0 6 0 0 b 11 0

---------- Source Code ----------

/*
* Restricted /dev filesystem. [EXPERIMENTAL]
*
* based on /usr/src/linux-2.4.27/fs/ramfs/inode.c
*
*/

/*
* Resizable simple ram filesystem for Linux.
*
* Copyright (C) 2000 Linus Torvalds.
* 2000 Transmeta Corp.
*
* Usage limits added by David Gibson, Linuxcare Australia.
* This file is released under the GPL.
*/

/*
* NOTE! This filesystem is probably most useful
* not as a real filesystem, but as an example of
* how virtual filesystems can be written.
*
* It doesn't get much simpler than this. Consider
* that this file implements the full semantics of
* a POSIX-compliant read-write filesystem.
*
* Note in particular how the filesystem does not
* need to implement any data structures of its own
* to keep track of the virtual data: using the VFS
* caches is sufficient.
*/

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/locks.h>

#include <asm/uaccess.h>

/***** SYAORAN start. *****/
static void syaoran_put_super(struct super_block *sb);
static int Syaoran_Initialize(struct super_block *sb, void *data);
static void MakeInitialNodes(struct super_block *sb);
static int MayCreateNode(struct dentry *dentry, int mode, int dev);
static int MayDeleteNode(struct dentry *dentry);
/***** SYAORAN end. *****/

/* some random number */
#define SYAORAN_MAGIC 0x2F646576 /* = '/dev' */

static struct super_operations syaoran_ops;
static struct address_space_operations syaoran_aops;
static struct file_operations syaoran_file_operations;
static struct inode_operations syaoran_dir_inode_operations;

static int syaoran_statfs(struct super_block *sb, struct statfs *buf)
{
buf->f_type = SYAORAN_MAGIC;
buf->f_bsize = PAGE_CACHE_SIZE;
buf->f_namelen = NAME_MAX;
return 0;
}

/*
* Lookup the data. This is trivial - if the dentry didn't already
* exist, we know it is negative.
*/
static struct dentry * syaoran_lookup(struct inode *dir, struct dentry *dentry)
{
if (dentry->d_name.len > NAME_MAX)
return ERR_PTR(-ENAMETOOLONG);
d_add(dentry, NULL);
return NULL;
}

/*
* Read a page. Again trivial. If it didn't already exist
* in the page cache, it is zero-filled.
*/
static int syaoran_readpage(struct file *file, struct page * page)
{
if (!Page_Uptodate(page)) {
memset(kmap(page), 0, PAGE_CACHE_SIZE);
kunmap(page);
flush_dcache_page(page);
SetPageUptodate(page);
}
UnlockPage(page);
return 0;
}

static int syaoran_prepare_write(struct file *file, struct page *page, unsigned offset, unsigned to)
{
void *addr = kmap(page);
if (!Page_Uptodate(page)) {
memset(addr, 0, PAGE_CACHE_SIZE);
flush_dcache_page(page);
SetPageUptodate(page);
}
SetPageDirty(page);
return 0;
}

static int syaoran_commit_write(struct file *file, struct page *page, unsigned offset, unsigned to)
{
struct inode *inode = page->mapping->host;
loff_t pos = ((loff_t)page->index << PAGE_CACHE_SHIFT) + to;

kunmap(page);
if (pos > inode->i_size)
inode->i_size = pos;
return 0;
}

struct inode *syaoran_get_inode(struct super_block *sb, int mode, int dev)
{
struct inode * inode = new_inode(sb);

if (inode) {
inode->i_mode = mode;
inode->i_uid = current->fsuid;
inode->i_gid = current->fsgid;
inode->i_blksize = PAGE_CACHE_SIZE;
inode->i_blocks = 0;
inode->i_rdev = NODEV;
inode->i_mapping->a_ops = &syaoran_aops;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
switch (mode & S_IFMT) {
default:
init_special_inode(inode, mode, dev);
break;
case S_IFREG:
inode->i_fop = &syaoran_file_operations;
break;
case S_IFDIR:
inode->i_op = &syaoran_dir_inode_operations;
inode->i_fop = &dcache_dir_ops;
break;
case S_IFLNK:
inode->i_op = &page_symlink_inode_operations;
break;
}
}
return inode;
}

/*
* File creation. Allocate an inode, and we're done..
*/
static int syaoran_mknod(struct inode *dir, struct dentry *dentry, int mode, int dev)
{
/***** SYAORAN start. *****/
if (MayCreateNode(dentry, mode, dev) < 0) return -EPERM;
/***** SYAORAN end. *****/
struct inode * inode = syaoran_get_inode(dir->i_sb, mode, dev);
int error = -ENOSPC;

if (inode) {
if (dir->i_mode & S_ISGID) {
inode->i_gid = dir->i_gid;
if (S_ISDIR(mode))
inode->i_mode |= S_ISGID;
}
d_instantiate(dentry, inode);
dget(dentry); /* Extra count - pin the dentry in core */
error = 0;
}
return error;
}

static int syaoran_mkdir(struct inode * dir, struct dentry * dentry, int mode)
{
return syaoran_mknod(dir, dentry, mode | S_IFDIR, 0);
}

static int syaoran_create(struct inode *dir, struct dentry *dentry, int mode)
{
return syaoran_mknod(dir, dentry, mode | S_IFREG, 0);
}

/*
* Link a file..
*/
static int syaoran_link(struct dentry *old_dentry, struct inode * dir, struct dentry * dentry)
{
/***** SYAORAN start. *****/
return -EPERM; /* No hardlinks allowed. */
/***** SYAORAN end. *****/
struct inode *inode = old_dentry->d_inode;

if (S_ISDIR(inode->i_mode))
return -EPERM;

inode->i_nlink++;
atomic_inc(&inode->i_count); /* New dentry reference */
dget(dentry); /* Extra pinning count for the created dentry */
d_instantiate(dentry, inode);
return 0;
}

static inline int syaoran_positive(struct dentry *dentry)
{
return dentry->d_inode && !d_unhashed(dentry);
}

/*
* Check that a directory is empty (this works
* for regular files too, they'll just always be
* considered empty..).
*
* Note that an empty directory can still have
* children, they just all have to be negative..
*/
static int syaoran_empty(struct dentry *dentry)
{
struct list_head *list;

spin_lock(&dcache_lock);
list = dentry->d_subdirs.next;

while (list != &dentry->d_subdirs) {
struct dentry *de = list_entry(list, struct dentry, d_child);

if (syaoran_positive(de)) {
spin_unlock(&dcache_lock);
return 0;
}
list = list->next;
}
spin_unlock(&dcache_lock);
return 1;
}

/*
* This works for both directories and regular files.
* (non-directories will always have empty subdirs)
*/
static int syaoran_unlink(struct inode * dir, struct dentry *dentry)
{
/***** SYAORAN start. *****/
if (MayDeleteNode(dentry) < 0) return -EPERM;
/***** SYAORAN end. *****/
int retval = -ENOTEMPTY;
if (syaoran_empty(dentry)) {
struct inode *inode = dentry->d_inode;

inode->i_nlink--;
dput(dentry); /* Undo the count from "create" - this does all the work */
retval = 0;
}
return retval;
}

#define syaoran_rmdir syaoran_unlink

/*
* The VFS layer already does all the dentry stuff for rename,
* we just have to decrement the usage count for the target if
* it exists so that the VFS layer correctly free's it when it
* gets overwritten.
*/
static int syaoran_rename(struct inode * old_dir, struct dentry *old_dentry, struct inode * new_dir,struct dentry *new_dentry)
{
/***** SYAORAN start. *****/
struct inode *inode = old_dentry->d_inode;
if (!inode || MayDeleteNode(old_dentry) < 0 || MayCreateNode(new_dentry, inode->i_mode, inode->i_rdev) < 0) return -EPERM;
/***** SYAORAN end. *****/
int error = -ENOTEMPTY;

if (syaoran_empty(new_dentry)) {
struct inode *inode = new_dentry->d_inode;
if (inode) {
inode->i_nlink--;
dput(new_dentry);
}
error = 0;
}
return error;
}

static int syaoran_symlink(struct inode * dir, struct dentry *dentry, const char * symname)
{
int error;

error = syaoran_mknod(dir, dentry, S_IFLNK | S_IRWXUGO, 0);
if (!error) {
int l = strlen(symname)+1;
struct inode *inode = dentry->d_inode;
error = block_symlink(inode, symname, l);
}
return error;
}

static int syaoran_sync_file(struct file * file, struct dentry *dentry, int datasync)
{
return 0;
}

static struct address_space_operations syaoran_aops = {
readpage: syaoran_readpage,
writepage: fail_writepage,
prepare_write: syaoran_prepare_write,
commit_write: syaoran_commit_write
};

static struct file_operations syaoran_file_operations = {
read: generic_file_read,
write: generic_file_write,
mmap: generic_file_mmap,
fsync: syaoran_sync_file,
};

static struct inode_operations syaoran_dir_inode_operations = {
create: syaoran_create,
lookup: syaoran_lookup,
link: syaoran_link,
unlink: syaoran_unlink,
symlink: syaoran_symlink,
mkdir: syaoran_mkdir,
rmdir: syaoran_rmdir,
mknod: syaoran_mknod,
rename: syaoran_rename,
};

static struct super_operations syaoran_ops = {
statfs: syaoran_statfs,
put_inode: force_delete,
/***** SYAORAN start. *****/
put_super: syaoran_put_super,
/***** SYAORAN end. *****/
};

static struct super_block *syaoran_read_super(struct super_block * sb, void * data, int silent)
{
struct inode * inode;
struct dentry * root;

sb->s_blocksize = PAGE_CACHE_SIZE;
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
sb->s_magic = SYAORAN_MAGIC;
sb->s_op = &syaoran_ops;
/***** SYAORAN start. *****/
if (Syaoran_Initialize(sb, data) < 0) return NULL;
/***** SYAORAN end. *****/
inode = syaoran_get_inode(sb, S_IFDIR | 0755, 0);
if (!inode)
return NULL;

root = d_alloc_root(inode);
if (!root) {
iput(inode);
return NULL;
}
sb->s_root = root;
/***** SYAORAN start. *****/
MakeInitialNodes(sb);
/***** SYAORAN end. *****/
return sb;
}

static DECLARE_FSTYPE(syaoran_fs_type, "syaoran", syaoran_read_super, FS_LITTER);

static int __init init_syaoran_fs(void)
{
return register_filesystem(&syaoran_fs_type);
}

static void __exit exit_syaoran_fs(void)
{
unregister_filesystem(&syaoran_fs_type);
}

module_init(init_syaoran_fs)
module_exit(exit_syaoran_fs)

MODULE_LICENSE("GPL");

/***** SYAORAN start. *****/

#if !defined(lock_kernel)
#include <asm/smplock.h>
#endif
extern void *kmalloc(size_t, int);
extern void kfree(const void *);

/* Similar to lookup_create(). */
static struct dentry *lookup_create2(const char *name, struct dentry *base, int is_dir) {
unsigned long hash;
struct qstr this;
unsigned int c;
struct dentry *dentry = ERR_PTR(-EACCES);
int len = name ? strlen(name) : 0;
this.name = name;
this.len = len;
down(&base->d_inode->i_sem);
if (!len) goto fail;
hash = init_name_hash();
while (len--) {
c = * (const unsigned char *) name++;
if (c == '/' || c == '\0') goto fail;
hash = partial_name_hash(c, hash);
}
this.hash = end_name_hash(hash);
dentry = lookup_hash(&this, base);
if (IS_ERR(dentry)) goto fail;
if (!is_dir && this.name[this.len] && !dentry->d_inode) goto enoent;
return dentry;
enoent:
dput(dentry);
dentry = ERR_PTR(-ENOENT);
fail:
return dentry;
}

/* mkdir() used at mount time. */
static int fs_mkdir(const char *pathname, struct dentry *base, int mode, uid_t user, gid_t group) {
struct dentry *dentry = lookup_create2(pathname, base, 1);
int error = PTR_ERR(dentry);
if (!IS_ERR(dentry)) {
error = vfs_mkdir(base->d_inode, dentry, mode);
if (!error) {
lock_kernel();
dentry->d_inode->i_uid = user;
dentry->d_inode->i_gid = group;
unlock_kernel();
}
dput(dentry);
}
up(&base->d_inode->i_sem);
return error;
}

/* mknod() used at mount time. */
static int fs_mknod(const char *filename, struct dentry *base, int mode, dev_t dev, uid_t user, gid_t group) {
switch (mode & S_IFMT) {
case S_IFCHR: case S_IFBLK: case S_IFIFO: case S_IFSOCK:
break;
default:
return -EPERM;
}
struct dentry *dentry = lookup_create2(filename, base, 0);
int error = PTR_ERR(dentry);
if (!IS_ERR(dentry)) {
error = vfs_mknod(base->d_inode, dentry, mode, dev);
if (!error) {
lock_kernel();
dentry->d_inode->i_uid = user;
dentry->d_inode->i_gid = group;
unlock_kernel();
}
dput(dentry);
}
up(&base->d_inode->i_sem);
return error;
}

/* symlink() used at mount time. */
static int fs_symlink(const char *pathname, struct dentry *base, char *oldname) {
struct dentry *dentry = lookup_create2(pathname, base, 0);
int error = PTR_ERR(dentry);
if (!IS_ERR(dentry)) {
error = vfs_symlink(base->d_inode, dentry, oldname);
dput(dentry);
}
up(&base->d_inode->i_sem);
return error;
}

/* Format a line. Compress multiple delimiters. */
static void NormalizeLine(unsigned char *buffer) {
unsigned char *sp = buffer, *dp = buffer;
int first = 1;
while (*sp && *sp <= ' ') sp++;
while (*sp) {
if (!first) *dp++ = ' ';
first = 0;
while (*sp > ' ') *dp++ = *sp++;
while (*sp && *sp <= ' ') sp++;
}
*dp = '\0';
}

/* strdup() */
static char *strdup(const char *str) {
char *cp;
int len = str ? strlen(str) + 1 : 0;
if ((cp = kmalloc(len, GFP_KERNEL)) != NULL) memmove(cp, str, len);
return cp;
}

typedef struct {
char *name; /* Pathname under the mount point, excluding the beggining / . */
mode_t mode; /* Mode and permission */
uid_t uid; /* User */
gid_t gid; /* Group */
kdev_t kdev; /* Device number */
char *symlink_data; /* Symlink's content */
int may_create; /* Can this file created after mount? */
int may_delete; /* Can this file deleted after mount? */
} DEVLIST;

struct syaoran_sb_info {
DEVLIST *entry; /* entry's list. */
unsigned int entry_count; /* entry[0] ... entry[entry_count - 1] */
unsigned int buffer_size; /* kmalloc()'ed size for entry[] */
int initialize_done; /* mount operation finished? */
};

/* Add file info to the entry[]. */
static int RegisterNodeInfo(char *buffer, struct super_block *sb) {
enum {
ARG_FILENAME = 0,
ARG_PERMISSION = 1,
ARG_UID = 2,
ARG_GID = 3,
ARG_MAY_CREATE = 4,
ARG_MAY_DELETE = 5,
ARG_DEV_TYPE = 6,
ARG_SYMLINK_DATA = 7,
ARG_DEV_MAJOR = 7,
ARG_DEV_MINOR = 8,
MAX_ARG = 9
};
char *args[MAX_ARG];
int i;
int error = -EINVAL;
memset(args, 0, sizeof(args));
args[0] = buffer;
for (i = 1; i < MAX_ARG; i++) {
args[i] = strchr(args[i - 1] + 1, ' ');
if (!args[i]) break;
*args[i]++ = '\0';
}
/*
printk("<%s> <%s> <%s> <%s> <%s> <%s> <%s> <%s> <%s>\n", args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
*/
if (!args[ARG_FILENAME] || !args[ARG_PERMISSION] || !args[ARG_UID] || !args[ARG_GID]
|| !args[ARG_DEV_TYPE] || !args[ARG_MAY_CREATE] || !args[ARG_MAY_DELETE]) goto out;
mode_t perm; uid_t uid; gid_t gid; unsigned short int major = 0, minor = 0; int may_create, may_delete;
if (sscanf(args[ARG_PERMISSION], "%ho", &perm) != 1 || !(perm <= 0777) || sscanf(args[ARG_UID], "%u", &uid) != 1
|| sscanf(args[ARG_GID], "%u", &gid) != 1 || sscanf(args[ARG_MAY_CREATE], "%d", &may_create) != 1
|| sscanf(args[ARG_MAY_DELETE], "%d", &may_delete) != 1 || *(args[ARG_DEV_TYPE] + 1)) goto out;
switch (*args[ARG_DEV_TYPE]) {
case 'c':
perm |= S_IFCHR;
if (!args[ARG_DEV_MAJOR] || sscanf(args[ARG_DEV_MAJOR], "%hu", &major) != 1
|| !args[ARG_DEV_MINOR] || sscanf(args[ARG_DEV_MINOR], "%hu", &minor) != 1) goto out;
break;
case 'b':
perm |= S_IFBLK;
if (!args[ARG_DEV_MAJOR] || sscanf(args[ARG_DEV_MAJOR], "%hu", &major) != 1
|| !args[ARG_DEV_MINOR] || sscanf(args[ARG_DEV_MINOR], "%hu", &minor) != 1) goto out;
break;
case 'l':
perm |= S_IFLNK;
if (!args[ARG_SYMLINK_DATA]) goto out;
break;
case 'd':
perm |= S_IFDIR;
break;
case 's':
perm |= S_IFSOCK;
break;
case 'p':
perm |= S_IFIFO;
break;
default:
goto out;
}
error = -ENOMEM;
struct syaoran_sb_info *info = (struct syaoran_sb_info *) sb->u.generic_sbp;
if (!info->entry) {
if ((info->entry = kmalloc(PAGE_SIZE, GFP_KERNEL)) == NULL) goto out;
info->buffer_size = PAGE_SIZE;
info->entry_count = 0;
}
if ((info->entry_count + 1) * sizeof(DEVLIST) > info->buffer_size) {
if ((buffer = kmalloc(info->buffer_size * 2, GFP_KERNEL)) == NULL) goto out;
info->buffer_size *= 2;
memmove(buffer, info->entry, info->entry_count * sizeof(DEVLIST));
info->entry = (DEVLIST *) buffer;
}
DEVLIST *entry = &info->entry[info->entry_count];
memset(entry, 0, sizeof(*entry));
entry->symlink_data = NULL;
if (S_ISLNK(perm)) {
if ((entry->symlink_data = strdup(args[ARG_SYMLINK_DATA])) == NULL) goto out;
}
if ((entry->name = strdup(args[ARG_FILENAME])) == NULL) {
kfree(entry->symlink_data);
goto out;
}
entry->mode = perm;
entry->uid = uid;
entry->gid = gid;
entry->kdev = S_ISCHR(perm) || S_ISBLK(perm) ? MKDEV(major, minor) : 0;
entry->may_create = may_create;
entry->may_delete = may_delete;
info->entry_count++;
/*
printk("Entry added.\n");
*/
error = 0;
out: ;
return error;
}

/* Free the entry[]. */
static void syaoran_put_super(struct super_block *sb) {
if (!sb) return;
struct syaoran_sb_info *info = (struct syaoran_sb_info *) sb->u.generic_sbp;
if (!info) return;
int i;
for (i = 0; i < info->entry_count; i++) {
DEVLIST *entry = &info->entry[i];
kfree(entry->name); entry->name = NULL;
kfree(entry->symlink_data); entry->symlink_data = NULL;
/*
printk("Entry removed.\n");
*/
}
kfree(info->entry); info->entry = NULL;
info->buffer_size = 0;
info->entry_count = 0;
kfree(info);
sb->u.generic_sbp = NULL;
printk("syaoran_put_super: Unused memory freed.\n");
}

/* Read a config file. */
static int ReadConfigFile(struct file *file, struct super_block *sb) {
char *buffer;
int error = -ENOMEM;
if (!file) return -EINVAL;
if ((buffer = kmalloc(PAGE_SIZE, GFP_KERNEL)) != NULL) {
int avail_len = 0;
int len;
unsigned long offset = 0;
memset(buffer, 0, PAGE_SIZE);
while ((len = kernel_read(file, offset, buffer + avail_len, PAGE_SIZE - avail_len - 1)) > 0) {
char *cp;
avail_len += len;
offset += len;
while ((cp = memchr(buffer, '\n', avail_len)) != NULL) {
*cp = '\0';
NormalizeLine((unsigned char *) buffer);
if (RegisterNodeInfo(buffer, sb) == -ENOMEM) goto out;
cp++; len = cp - buffer;
avail_len -= len;
memmove(buffer, cp, avail_len);
}
}
kfree(buffer);
error = 0;
}
out: ;
return 0;
}

/* Create a file at mount time. */
static void MakeNode(DEVLIST *entry, struct dentry *root) {
struct dentry *base = dget(root);
char *filename = entry->name;
unsigned long hash;
struct qstr this;
unsigned int c;
goto start;
while ((c = * (unsigned char *) filename) != '\0') {
if (c == '/') {
this.len = filename - (char *) this.name;
*filename = '\0'; filename++;
this.hash = end_name_hash(hash);
struct dentry *new_base;
down(&base->d_inode->i_sem);
new_base = lookup_hash(&this, base);
up(&base->d_inode->i_sem);
dput(base);
if (IS_ERR(new_base)) {
/* printk("'%s' not found. (1)\n", this.name); */
return;
} else if (!new_base->d_inode) {
dput(new_base);
/* printk("'%s' not found. (2)\n", this.name); */
return;
}
/* printk("'%s' found.\n", this.name); */
base = new_base;
start: ;
this.name = filename;
hash = init_name_hash();
} else {
filename++;
hash = partial_name_hash(c, hash);
}
}
filename = (char *) this.name;
mode_t perm = entry->mode;
uid_t uid = entry->uid;
gid_t gid = entry->gid;
if (S_ISLNK(perm)) {
fs_symlink(filename, base, entry->symlink_data);
} else if (S_ISDIR(perm)) {
fs_mkdir(filename, base, perm ^ S_IFDIR, uid, gid);
} else if (S_ISSOCK(perm) || S_ISFIFO(perm)) {
fs_mknod(filename, base, perm, 0, uid, gid);
} else if (S_ISCHR(perm) || S_ISBLK(perm)) {
fs_mknod(filename, base, perm, entry->kdev, uid, gid);
}
dput(base);
}

/* Create files at mount time according to entry[]. */
static void MakeInitialNodes(struct super_block *sb) {
if (!sb) return;
struct syaoran_sb_info *info = (struct syaoran_sb_info *) sb->u.generic_sbp;
if (!info) return;
int i;
for (i = 0; i < info->entry_count; i++) {
MakeNode(&info->entry[i], sb->s_root);
}
info->initialize_done = 1;
}

/* Open a config file and read. */
static int Syaoran_Initialize(struct super_block *sb, void *data) {
int error = -EINVAL;
if (data) {
struct file *f = filp_open((char *) data, O_RDONLY, 0600);
if (!IS_ERR(f)) {
if (f->f_dentry->d_inode->i_size > 0) {
if ((sb->u.generic_sbp = kmalloc(sizeof(struct syaoran_sb_info), GFP_KERNEL)) != NULL) {
memset(sb->u.generic_sbp, 0, sizeof(struct syaoran_sb_info));
printk("Reading '%s'\n", (char *) data);
if (ReadConfigFile(f, sb) == 0) error = 0;
}
}
filp_close(f, NULL);
}
}
return error;
}

/* Based on __d_path(), but doesn't go over mount point. */
static char *GetAbsolutePath(struct dentry *dentry, char *buffer, int buflen) {
char * end = buffer+buflen;
char * retval;
int namelen;

*--end = '\0';
buflen--;

/* Get '/' right */
retval = end-1;
*retval = '/';

for (;;) {
if (IS_ROOT(dentry)) break;
struct dentry *parent = dentry->d_parent;
namelen = dentry->d_name.len;
buflen -= namelen + 1;
if (buflen < 0) return ERR_PTR(-ENAMETOOLONG);
end -= namelen;
memcpy(end, dentry->d_name.name, namelen);
*--end = '/';
retval = end;
dentry = parent;
}
namelen = dentry->d_name.len;
buflen -= namelen;
if (buflen >= 0) {
retval -= namelen-1;/* hit the slash */
memcpy(retval, dentry->d_name.name, namelen);
} else
retval = ERR_PTR(-ENAMETOOLONG);
return retval;
}

/* Based on sys_getcwd(). Get an absolute pathname. */
static int realpath_from_dentry(struct dentry *dentry, char *newname, int newname_len) {
int error;
char *page;
char *cwd = NULL;
struct dentry *d_dentry;
if (!dentry || !newname || newname_len <= 0) return -EINVAL;
if (!current->fs) {
printk("realpath: current->fs == NULL for pid=%d\n", current->pid);
return -ENOENT;
}
page = (char *) kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!page) return -ENOMEM;
d_dentry = dget(dentry);
error = -ENOENT;
/* CRITICAL SECTION START */
spin_lock(&dcache_lock);
if (IS_ROOT(d_dentry) || !list_empty(&d_dentry->d_hash)) cwd = GetAbsolutePath(d_dentry, page, PAGE_SIZE);
spin_unlock(&dcache_lock);
/* CRITICAL SECTION END */
if (cwd) {
if (!IS_ERR(cwd)) {
unsigned long len = PAGE_SIZE + page - cwd;
if (len <= newname_len) {
memmove(newname, cwd, len);
error = 0;
} else {
error = -ERANGE;
}
} else {
error = PTR_ERR(cwd);
}
}
dput(d_dentry);
kfree(page);
return error;
}

/* Check whether a file with specified attributes are permitted to create. */
static int MayCreateNode(struct dentry *dentry, int mode, int dev) {
switch (mode & S_IFMT) {
case S_IFCHR: case S_IFBLK: case S_IFIFO: case S_IFSOCK: case S_IFDIR: case S_IFLNK:
break;
default:
return -EPERM;
}
struct syaoran_sb_info *info = (struct syaoran_sb_info *) dentry->d_sb->u.generic_sbp;
if (!info) {
printk("MayCreateNode: dentry->d_sb->u.generic_sbp == NULL\n");
return -EPERM;
}
if (!info->initialize_done) return 0;
int error = -EPERM;
char *filename = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!filename) return -ENOMEM;
memset(filename, 0, PAGE_SIZE);
if (realpath_from_dentry(dentry, filename, PAGE_SIZE - 1) == 0) {
int i;
for (i = 0; i < info->entry_count; i++) {
DEVLIST *entry = &info->entry[i];
if ((mode & S_IFMT) != (entry->mode & S_IFMT)) continue;
if ((S_ISBLK(mode) || S_ISCHR(mode)) && dev != entry->kdev) continue;
if (strcmp(entry->name, filename + 1)) continue;
if (entry->may_create) error = 0;
break;
}
}
kfree(filename);
return error;
}

/* Check whether a file with specified attributes are permitted to delete. */
static int MayDeleteNode(struct dentry *dentry) {
struct syaoran_sb_info *info = (struct syaoran_sb_info *) dentry->d_sb->u.generic_sbp;
if (!info) {
printk("MayDeleteNode: dentry->d_sb->u.generic_sbp == NULL\n");
return -EPERM;
}
if (!dentry->d_inode) return -ENOENT;
const mode_t mode = dentry->d_inode->i_mode;
const kdev_t dev = dentry->d_inode->i_rdev;
int error = -EPERM;
char *filename = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!filename) return -ENOMEM;
memset(filename, 0, PAGE_SIZE);
if (realpath_from_dentry(dentry, filename, PAGE_SIZE - 1) == 0) {
int i;
for (i = 0; i < info->entry_count; i++) {
DEVLIST *entry = &info->entry[i];
if ((mode & S_IFMT) != (entry->mode & S_IFMT)) continue;
if ((S_ISBLK(mode) || S_ISCHR(mode)) && dev != entry->kdev) continue;
if (strcmp(entry->name, filename + 1)) continue;
if (entry->may_delete) error = 0;
break;
}
}
kfree(filename);
return error;
}

/***** SYAORAN end. *****/
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/