[sisyphus] std-def, un-def и hibernate

Michael Shigorin mike на osdn.org.ua
Сб Сен 3 10:41:55 UTC 2011


On Sat, Sep 03, 2011 at 02:16:59PM +0400, Michael A. Kangin wrote:
> >Поскольку led@ прикрутил subfs к ядрам>=2.6.39,
> Good news everyone!  А для P6 будет?  Надо попробовать
> скрестить его с udisks и закопать уже hal-mount-subfs.

Бери: http://fly.osdn.org.ua/~mike/kernel/subfs-0.9.2/
(и на всякий аттачу) -- кто первый успеет, там и будет :)

Собирается с 2.6.37+; в 2.6.39 выкинули старый интерфейс,
который использовался subfs ранее.

Там у него ещё юзерспейс свой с graceful fallback'ом по опциям,
неподходящим для конкретной ФС.

-- 
 ---- WBR, Michael Shigorin <mike на altlinux.ru>
  ------ Linux.Kiev http://www.linux.kiev.ua/
----------- следующая часть -----------
/*
 *  subfs.c
 *
 *  Copyright (C) 2003-2004 Eugene S. Weiss <eweiss на sbclobal.net>
 *  Copyright (C) 2011 Led <led на massivesolutions.co.uk>
 *
 *  Distributed under the terms of the GNU General Public License version 2
 *  or above.
 */

#include <linux/module.h>
#include <linux/pagemap.h>
#include <linux/fs.h>
#include <linux/mount.h>
#include <linux/fs_struct.h>
#include <linux/spinlock_types.h>
#include <linux/namei.h>
#include <linux/dcache.h>
#include <linux/sysfs.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
#include <linux/nsproxy.h>
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
#include <linux/mnt_namespace.h>
#else
#include <linux/namespace.h>
#endif

#ifndef DEFINE_MUTEX
#define i_mutex i_sem
#define mutex_lock(x) down(x)
#define mutex_unlock(x) up(x)
#endif

#ifndef pr_err
#define pr_err(args...) printk(KERN_ERR args)
#endif

#ifndef current_uid
#define current_uid() current->uid
#endif

#ifndef current_gid
#define current_gid() current->gid
#endif


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Eugene S. Weiss, Led");

#define SUBFS_VER "0.9.2"

#define SUBFS_MAGIC 0x2c791058

#define SUBMOUNTD_PATH "/sbin/submountd"

#define ROOT_MODE 0777


struct subfs_mount {
	char *device;
	char *options;
	char *req_fs;
	char *helper_prog;
	unsigned long flags;
	struct super_block *sb;
	struct vfsmount *mount;
	struct semaphore sem;
	int procuid;
};


static inline struct vfsmount *get_root_mnt(struct fs_struct *fs)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
	return fs->root.mnt;
#else
	return fs->rootmnt;
#endif
}


/* get_subfs_vfsmount tries to find the vfsmount structure assigned to
 * the pending mount.  It looks for a vfsmount structure matching the
 * superblock pointer sent.  This is not ideal, but I don't know of
 * another way to find the structure without altering the code in the
 * mount routines.
 */
static struct vfsmount *get_subfs_vfsmount(struct super_block *sb)
{
	struct list_head *entry, *lh;
	/* Get the head of the global mount list from the init process. */
	/* Is there a better way? */
	struct list_head *head = &(get_root_mnt(init_task.fs))->mnt_list;

	/* Go through the list and look for a superblock pointer match. */
	list_for_each_safe(entry, lh, head) {
		struct vfsmount *mnt = list_entry(entry, struct vfsmount, mnt_list);
		if (mnt->mnt_sb == sb)
			return mnt;
	}
	pr_err("subfs: Unable to find mount structure for superblock.\n");
	return NULL;		/* If there was no match */
}


/* Same as set_fs_pwd from namespace.c.  There's a problem with the
 * symbol.  When it is fixed, discard this.
 * Replace the fs->{pwdmnt,pwd} with {mnt,dentry}. Put the old values.
 * It can block. Requires the big lock held.
 */
static void subfs_set_fs_pwd(struct fs_struct *fs, struct vfsmount *mnt, struct dentry *dentry)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
	struct path path = {.mnt = mnt, .dentry = dentry};
#if defined(MODULE) && !defined(HAVE_SET_FS_PWD)
	struct path old_pwd;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
	spin_lock(&fs->lock);
#else
	write_lock(&fs->lock);
#endif
	old_pwd = fs->pwd;
	fs->pwd = path;
	path_get(&path);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
	spin_unlock(&fs->lock);
#else
	write_unlock(&fs->lock);
#endif

	if (old_pwd.dentry)
		path_put(&old_pwd);
#else
	set_fs_pwd(current->fs, &path);
#endif
#else
#if defined(MODULE) && !defined(HAVE_SET_FS_PWD)
	struct dentry *old_pwd;
	struct vfsmount *old_pwdmnt;

	write_lock(&fs->lock);
	old_pwd = fs->pwd;
	old_pwdmnt = fs->pwdmnt;
	fs->pwdmnt = mntget(mnt);
	fs->pwd = dget(dentry);
	write_unlock(&fs->lock);

	if (old_pwd) {
		dput(old_pwd);
		mntput(old_pwdmnt);
	}
#else
	set_fs_pwd(fs, mnt, dentry);
#endif
#endif
}


/* Quickly sends an ignored signal to the signal handling system. This
 * causes the system to restart the system call when it receives the
 * -ERESTARTSYS error.
 */
static void subfs_send_signal(void)
{
	struct task_struct *task = current;

	rcu_read_lock();
	spin_lock_irq(&task->sighand->siglock);
	sigaddset(&task->pending.signal, SIGCONT);
	spin_unlock_irq(&task->sighand->siglock);
	rcu_read_unlock();
	set_tsk_thread_flag(task, TIF_SIGPENDING);
}


/* If the option "procuid" is chosen when subfs is mounted, the uid
 * and gid numbers for the current process will  bea dded to the mount
 * option line.  Hence, non-unix filesystems will be mounted with
 * that ownership.
 */
static void add_procuid(struct subfs_mount *sfs_mnt)
{
	if (sfs_mnt->procuid) {
		uid_t uid = current_uid();
		gid_t gid = current_gid();
		char *o = kmalloc(strlen(sfs_mnt->options) + 1 + 32 + 1, GFP_KERNEL);

		if (sfs_mnt->options[0] == '\0')
			sprintf(o, "uid=%d,gid=%d", uid, gid);
		else
			sprintf(o, "%s,uid=%d,gid=%d", sfs_mnt->options, uid, gid);

		kfree(sfs_mnt->options);
		sfs_mnt->options = o;
	}
}


static char *subfs_d_path(struct vfsmount *mount, char *buf, int size)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
	struct path path;

	path.mnt = mount;
	path.dentry = path.mnt->mnt_root;

	return d_path(&path, buf, size);
#else
	return d_path(mount->mnt_mountpoint, mount->mnt_parent, buf, size);
#endif
}


/* This routine calls the /sbin/submountd program to mount the
 * appropriate filesystem on top of the subfs mount.  Returns
 * 0 if the userspace program exited normally, or an error if
 * it did not.
 */
static int mount_real_fs(struct subfs_mount *sfs_mnt)
{
	int result = -ENOMEM;
	char *path_buf = (char *)__get_free_page(GFP_KERNEL);

	if (path_buf) {
		char *argv[7];
		argv[4] = kmalloc(17, GFP_KERNEL);
		if (argv[4]) {
			char *envp[2] = {"HOME=/", NULL};

			argv[0] = sfs_mnt->helper_prog;
			argv[1] = sfs_mnt->device;
			argv[2] = subfs_d_path(sfs_mnt->mount, path_buf, PAGE_SIZE);
			argv[3] = sfs_mnt->req_fs;
			sprintf(argv[4], "%lx", sfs_mnt->flags);
			add_procuid(sfs_mnt);
			argv[5] = sfs_mnt->options;
			argv[6] = NULL;
			result = call_usermodehelper(sfs_mnt->helper_prog, argv, envp, 1);
			kfree(argv[4]);
			if (sfs_mnt->procuid)
				sfs_mnt->options[strlen(sfs_mnt->options)] = '\0';
		}
		free_page((unsigned long)path_buf);	/* 64 bits on some platforms */
	}
	return result;
}


/*  This routine returns a pointer to the filesystem mounted on top
 *	of the subfs mountpoint, or an error pointer if it was unable to.
 */
static struct vfsmount *get_child_mount(struct subfs_mount *sfs_mnt)
{
	/* First time: find the vfsmount structure that matches sfs_mnt. */
	if (!sfs_mnt->mount) {
		sfs_mnt->mount = get_subfs_vfsmount(sfs_mnt->sb);
		if(!sfs_mnt->mount)
			return ERR_PTR(-ENOMEDIUM);
		sfs_mnt->flags = sfs_mnt->sb->s_flags;
		if (sfs_mnt->mount->mnt_flags & MNT_NOSUID)
			sfs_mnt->flags |= MS_NOSUID;
		if (sfs_mnt->mount->mnt_flags & MNT_NODEV)
			sfs_mnt->flags |= MS_NODEV;
		if (sfs_mnt->mount->mnt_flags & MNT_NOEXEC)
			sfs_mnt->flags |= MS_NOEXEC;
	}
	/* Check to see if a child mount does not already exist. */
	if (&sfs_mnt->mount->mnt_mounts == sfs_mnt->mount->mnt_mounts.next) {
		int result = mount_real_fs(sfs_mnt);
		if (result) {
			pr_err("subfs: " SUBMOUNTD_PATH " unsuccessful attempt to mount media (%d)\n", result);
			/* Workaround for call_usermodehelper return value bug. */
			return ERR_PTR((result < 0) ? result : -ENOMEDIUM);
		}
		if (&sfs_mnt->mount->mnt_mounts == sfs_mnt->mount->mnt_mounts.next) {
			pr_err("subfs: submountd mount failure.\n");
			return ERR_PTR(-ENOMEDIUM);
		}
	}
	return list_entry(sfs_mnt->mount->mnt_mounts.next, struct vfsmount, mnt_child);
}


/* Implements the lookup method for subfs.  Tries to get the child
 * mount.  If it succeeds, it emits a signal and returns
 * -ERESTARSYS.  If it receives an error, it passes it on to the
 * system. It raises the semaphore in the directory inode before mounting
 * because the mount routine also calls lookup, and hence a function is
 * calling itself from within semaphore protected code.  Only the semaphore
 * on the subfs pseudo-directory is effected, so this isn't deadly.
 */
static struct dentry *subfs_lookup(struct inode *dir,
				struct dentry *dentry, struct nameidata *nd)
{
	struct subfs_mount *sfs_mnt = dir->i_sb->s_fs_info;

	/* This is ugly, but prevents a lockup during mount. */
	mutex_unlock(&dir->i_mutex);
	if (down_interruptible(&sfs_mnt->sem))
		/*put the dir sem back down if interrupted*/
		mutex_lock(&dir->i_mutex);
	else {
		struct vfsmount *child = get_child_mount(sfs_mnt);
		up(&sfs_mnt->sem);
		mutex_lock(&dir->i_mutex);
		if (IS_ERR(child))
			return (struct dentry*)child;
		subfs_send_signal();
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
		if (sfs_mnt->mount == current->fs->pwd.mnt)
#else
		if (sfs_mnt->mount == current->fs->pwdmnt)
#endif
			subfs_set_fs_pwd(current->fs, child, child->mnt_root);
	}
	return ERR_PTR(-ERESTARTSYS);
}


/* Implements the open method for subfs.  Tries to get the child mount
 * for the subfs mountpoint which is being opened.  Returns -ERESTARTSYS
 * and emits an ignored signal to the calling process if it succeeds,
 * or passes the error message received if it fails.
 */
static int subfs_open(struct inode *inode, struct file *filp)
{
	struct subfs_mount *sfs_mnt = filp->f_dentry->d_sb->s_fs_info;

	if (!down_interruptible(&sfs_mnt->sem)) {
		struct vfsmount *child = get_child_mount(sfs_mnt);
		up(&sfs_mnt->sem);
		if (IS_ERR(child))
			return PTR_ERR(child);
		subfs_send_signal();
		if (sfs_mnt->mount == current->fs->pwd.mnt)
			subfs_set_fs_pwd(current->fs, child, child->mnt_root);
	}
	return -ERESTARTSYS;
}


static struct file_operations subfs_file_ops = {
	.open = subfs_open,
};


/*  Implements the statfs method so df and such will work on the mountpoint.
 */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)
static int subfs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
	struct subfs_mount *sfs_mnt = dentry->d_sb->s_fs_info;
#else
static int subfs_statfs(struct super_block *sb, struct kstatfs *buf)
{
	struct subfs_mount *sfs_mnt = sb->s_fs_info;
#endif

	if (!down_interruptible(&sfs_mnt->sem)) {
		struct vfsmount *child = get_child_mount(sfs_mnt);
		up(&sfs_mnt->sem);
		if (IS_ERR(child))
			return PTR_ERR(child);
		subfs_send_signal();
	}
	return -ERESTARTSYS;
}


/*  Creates the inodes for subfs superblocks.
 */
static struct inode *subfs_make_inode(struct super_block *sb, int mode)
{
	struct inode *ret = new_inode(sb);

	if (ret) {
		ret->i_mode = mode;
		ret->i_uid = 0;
		ret->i_gid = 0;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
		ret->i_blksize = PAGE_CACHE_SIZE;
#endif
		ret->i_blocks = 0;
		ret->i_atime = CURRENT_TIME;
		ret->i_mtime = CURRENT_TIME;
		ret->i_ctime = CURRENT_TIME;
		ret->i_fop = &subfs_file_ops;
	}
	return ret;
}


static struct super_operations subfs_s_ops = {
	.statfs = subfs_statfs,
	.drop_inode = generic_delete_inode,
};


static struct inode_operations subfs_dir_inode_operations = {
	.lookup = subfs_lookup,
};


/*  Fills the fields for the superblock created when subfs is mounted.
 */
static int subfs_fill_super(struct super_block *sb, void *data, int silent)
{
	struct inode *root;

	sb->s_blocksize = PAGE_CACHE_SIZE;
	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
	sb->s_magic = SUBFS_MAGIC;
	sb->s_op = &subfs_s_ops;
	root = subfs_make_inode(sb, S_IFDIR|ROOT_MODE);
	if (root) {
		struct dentry *root_dentry;
		root->i_op = &subfs_dir_inode_operations;
		root_dentry = d_alloc_root(root);
		if (root_dentry) {
			sb->s_root = root_dentry;
			return 0;
		}
		iput(root);
	}
	return -ENOMEM;
}


/* Parse the options string and remove submount specific options
 * and store the appropriate data.
 */
static int proc_opts(struct subfs_mount *sfs_mnt, void *data)
{
	char *opt;
	int len;

	if (!data) {
		data = kmalloc(PAGE_SIZE, GFP_KERNEL);
		if (!data)
			return -EINVAL;
		strcpy(data, "fs=auto");
	}
	len = strnlen(data, PAGE_SIZE - 1) + 1;
	if (strstr(data, "procuid"))
		sfs_mnt->procuid = 1;
	sfs_mnt->options = kmalloc(len, GFP_KERNEL);
	if (!sfs_mnt->options)
		return -ENOMEM;
	sfs_mnt->options[0] = '\0';
	while ((opt = strsep((char**)(&data), ","))) {
		char *ptr = strstr(opt, "program=");
		if (ptr) {
			kfree(sfs_mnt->helper_prog);
			sfs_mnt->helper_prog = kstrdup(ptr + 8, GFP_KERNEL);
			if (!sfs_mnt->helper_prog)
				return -ENOMEM;
		} else {
			ptr = strstr(opt, "fs=");
			if (ptr) {
				sfs_mnt->req_fs = kstrdup(ptr + 3, GFP_KERNEL);;
				if (!sfs_mnt->req_fs)
					return -ENOMEM;
			} else {
				strncat(sfs_mnt->options, opt, 32);
				strcat(sfs_mnt->options, ",");
			}
		}
	}
	len = strlen(sfs_mnt->options) - 1;
	if (len >= 0 && sfs_mnt->options[len] == ',')
		sfs_mnt->options[len] = '\0';
	if (!sfs_mnt->req_fs) {
		sfs_mnt->req_fs = kstrdup("auto", GFP_KERNEL);
		if (!sfs_mnt->req_fs)
			return -ENOMEM;	/* 64 bits on some platforms */
	}
	return 0;
}



/* subfs_get_super is the subfs implementation of the get_sb method on
 * the file_system_type structure.  It should only be called in the
 * case of a mount.  It creates a new subfs_mount structure, fills
 * the fields of the structure, except for the mount structure, and then
 * calls a generic get_sb function.  The superblock pointer is stored on
 * the subfs_mount structure, and returned to the calling function.  The
 * subfs_mount structure is pointed to by the s_fs_info field of the
 * superblock structure.
 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)
static struct dentry *subfs_mount(struct file_system_type *fst, int flags,
				  const char *devname, void *data)
{
	int ret = -ENOMEM;
	struct subfs_mount *newmount = kzalloc(sizeof(struct subfs_mount), GFP_KERNEL);

	if (newmount) {
		newmount->device = kstrdup(devname, GFP_KERNEL);
		if (!newmount->device)
			kfree(newmount);
		else {
			sema_init(&newmount->sem, 1);
			newmount->helper_prog = kstrdup(SUBMOUNTD_PATH, GFP_KERNEL);
			if (newmount->helper_prog) {
				ret = proc_opts(newmount, data);
				if (!ret) {
					struct dentry *root = mount_nodev(fst, flags, data, subfs_fill_super);
					if (!IS_ERR(root)) {
						newmount->sb = root->d_sb;
						newmount->sb->s_fs_info = newmount;
					}
					return root;
				}
			}
		}
	}
	return ERR_PTR(ret);
}
#elif LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)
static int subfs_get_super(struct file_system_type *fst, int flags,
			   const char *devname, void *data, struct vfsmount *mnt)
{
	int ret = -ENOMEM;
	struct subfs_mount *newmount = kzalloc(sizeof(struct subfs_mount), GFP_KERNEL);

	if (newmount) {
		newmount->device = kstrdup(devname, GFP_KERNEL);
		if (!newmount->device)
			kfree(newmount);
		else {
			sema_init(&newmount->sem, 1);
			newmount->helper_prog = kstrdup(SUBMOUNTD_PATH, GFP_KERNEL);
			if (newmount->helper_prog) {
				ret = proc_opts(newmount, data);
				if (!ret) {
					ret = get_sb_nodev(fst, flags, data, subfs_fill_super, mnt);
					if (!ret) {
						newmount->sb = mnt->mnt_sb;
						newmount->sb->s_fs_info = newmount;
					}
				}
			}
		}
	}
	return ret;
}
#else
static struct super_block *subfs_get_super(struct file_system_type *fst, int flags,
					   const char *devname, void *data)
{
	int ret = -ENOMEM;
	struct subfs_mount *newmount = kzalloc(sizeof(struct subfs_mount), GFP_KERNEL);

	if (newmount) {
		newmount->device = kstrdup(devname, GFP_KERNEL);
		if (!newmount->device)
			kfree(newmount);
		else {
			sema_init(&newmount->sem, 1);
			newmount->helper_prog = kstrdup(SUBMOUNTD_PATH, GFP_KERNEL);
			if (newmount->helper_prog) {
				ret = proc_opts(newmount, data);
				if (!ret) {
					newmount->sb = get_sb_nodev(fst, flags, data, subfs_fill_super);
					if (!IS_ERR(newmount->sb))
						newmount->sb->s_fs_info = newmount;
					return newmount->sb;
				}
			}
		}
	}
	return ERR_PTR(ret)
}
#endif


static void subfs_kfree(void *p)
{
	if (p)
		kfree(p);
}


/* subfs_kill_super is the subfs implementation of the kill_sb method.
 * It should be called only on umount.  It cleans up the appropriate
 * subfs_mount structure and then calls a generic function to actually
 * clean up the superblock structure.
 */
static void subfs_kill_super(struct super_block *sb)
{
	struct subfs_mount *sfs_mnt = sb->s_fs_info;

	if(sfs_mnt) {
		subfs_kfree(sfs_mnt->device);
		subfs_kfree(sfs_mnt->options);
		subfs_kfree(sfs_mnt->req_fs);
		subfs_kfree(sfs_mnt->helper_prog);
		kfree(sfs_mnt);
		sb->s_fs_info = NULL;
	}
	kill_litter_super(sb);
}


static struct file_system_type subfs_type = {
	.owner = THIS_MODULE,
	.name = "subfs",
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)
	.mount = subfs_mount,
#else
	.get_sb = subfs_get_super,
#endif
	.kill_sb = subfs_kill_super,
};


static int __init subfs_init(void)
{
	printk(KERN_INFO "subfs " SUBFS_VER "\n");
	return register_filesystem(&subfs_type);
}

static void __exit subfs_exit(void)
{
	printk(KERN_INFO "subfs exiting.\n");
	unregister_filesystem(&subfs_type);
}


module_init(subfs_init);
module_exit(subfs_exit);
----------- следующая часть -----------
EXTRA_CFLAGS := -Os
obj-m        := subfs.o


Подробная информация о списке рассылки Sisyphus