Line 0
Link Here
|
|
|
1 |
/* |
2 |
* subfs.c |
3 |
* |
4 |
* Copyright (C) 2003-2004 Eugene S. Weiss <eweiss@sbclobal.net> |
5 |
* |
6 |
* Distributed under the terms of the GNU General Public License version 2 |
7 |
* or above. |
8 |
*/ |
9 |
|
10 |
#include <linux/init.h> |
11 |
#include <linux/module.h> |
12 |
#include <linux/kernel.h> |
13 |
#include <linux/moduleparam.h> |
14 |
#include <linux/pagemap.h> |
15 |
#include <linux/fs.h> |
16 |
#include <asm/atomic.h> |
17 |
#include <asm/uaccess.h> |
18 |
#include <linux/list.h> |
19 |
#include <linux/mount.h> |
20 |
#include <linux/mnt_namespace.h> |
21 |
#include <linux/namei.h> |
22 |
#include <linux/dcache.h> |
23 |
#include <linux/sysfs.h> |
24 |
#include <asm/semaphore.h> |
25 |
#include <asm/signal.h> |
26 |
#include <linux/signal.h> |
27 |
#include <linux/sched.h> |
28 |
#include <linux/version.h> |
29 |
#include <linux/rcupdate.h> |
30 |
|
31 |
#include "subfs.h" |
32 |
|
33 |
MODULE_LICENSE("GPL"); |
34 |
MODULE_AUTHOR("Eugene S. Weiss"); |
35 |
|
36 |
|
37 |
/* get_subfs_vfsmount tries to find the vfsmount structure assigned to |
38 |
* the pending mount. It looks for a vfsmount structure matching the |
39 |
* superblock pointer sent. This is not ideal, but I don't know of |
40 |
* another way to find the structure without altering the code in the |
41 |
* mount routines. |
42 |
*/ |
43 |
static struct vfsmount *get_subfs_vfsmount(struct super_block *sb) |
44 |
{ |
45 |
struct fs_struct *init_fs; |
46 |
struct list_head *entry, *head, *lh; |
47 |
struct vfsmount *mnt; |
48 |
|
49 |
/* Get the head of the global mount list from the init process. */ |
50 |
/* Is there a better way? */ |
51 |
init_fs = init_task.fs; |
52 |
head = &init_fs->root.mnt->mnt_list; |
53 |
|
54 |
/* Go through the list and look for a superblock pointer match. */ |
55 |
list_for_each_safe(entry, lh, head) { |
56 |
mnt = list_entry(entry, struct vfsmount, mnt_list); |
57 |
if (mnt->mnt_sb == sb) |
58 |
return mnt; |
59 |
} |
60 |
ERR("subfs: Unable to find mount structure for superblock.\n"); |
61 |
return NULL; /* If there was no match */ |
62 |
} |
63 |
|
64 |
|
65 |
/* Same as set_fs_pwd from namespace.c. There's a problem with the |
66 |
* symbol. When it is fixed, discard this. |
67 |
* Replace the fs->{pwdmnt,pwd} with {mnt,dentry}. Put the old values. |
68 |
* It can block. Requires the big lock held. |
69 |
*/ |
70 |
static void subfs_set_fs_pwd(struct fs_struct *fs, struct vfsmount *mnt, |
71 |
struct dentry *dentry) |
72 |
{ |
73 |
struct dentry *old_pwd; |
74 |
struct vfsmount *old_pwdmnt; |
75 |
|
76 |
write_lock(&fs->lock); |
77 |
old_pwd = fs->pwd.dentry; |
78 |
old_pwdmnt = fs->pwd.mnt; |
79 |
fs->pwd.mnt = mntget(mnt); |
80 |
fs->pwd.dentry = dget(dentry); |
81 |
write_unlock(&fs->lock); |
82 |
|
83 |
if (old_pwd) { |
84 |
dput(old_pwd); |
85 |
mntput(old_pwdmnt); |
86 |
} |
87 |
} |
88 |
|
89 |
|
90 |
/* Quickly sends an ignored signal to the signal handling system. This |
91 |
* causes the system to restart the system call when it receives the |
92 |
* -ERESTARTSYS error. |
93 |
*/ |
94 |
static void subfs_send_signal(void) |
95 |
{ |
96 |
struct task_struct *task = current; |
97 |
int signal = SIGCONT; |
98 |
|
99 |
rcu_read_lock(); |
100 |
spin_lock_irq(&task->sighand->siglock); |
101 |
sigaddset(&task->pending.signal, signal); |
102 |
spin_unlock_irq(&task->sighand->siglock); |
103 |
rcu_read_unlock(); |
104 |
set_tsk_thread_flag(task, TIF_SIGPENDING); |
105 |
return; |
106 |
} |
107 |
|
108 |
|
109 |
/* If the option "procuid" is chosen when subfs is mounted, the uid |
110 |
* and gid numbers for the current process will bea dded to the mount |
111 |
* option line. Hence, non-unix filesystems will be mounted with |
112 |
* that ownership. |
113 |
*/ |
114 |
static void add_procuid(struct subfs_mount *sfs_mnt) |
115 |
{ |
116 |
struct task_struct *task = current; |
117 |
char *o = kmalloc(strlen(sfs_mnt->options) + 1 + 32 + 1, GFP_KERNEL); |
118 |
|
119 |
if (sfs_mnt->options[0] == '\0') |
120 |
sprintf(o, "uid=%d,gid=%d", task->uid, task->gid); |
121 |
else |
122 |
sprintf(o, "%s,uid=%d,gid=%d", sfs_mnt->options, task->uid, task->gid); |
123 |
|
124 |
kfree(sfs_mnt->options); |
125 |
sfs_mnt->options = o; |
126 |
} |
127 |
|
128 |
|
129 |
/* This routine calls the /sbin/submountd program to mount the |
130 |
* appropriate filesystem on top of the subfs mount. Returns |
131 |
* 0 if the userspace program exited normally, or an error if |
132 |
* it did not. |
133 |
*/ |
134 |
static int mount_real_fs(struct subfs_mount *sfs_mnt) |
135 |
{ |
136 |
char *argv[7] = |
137 |
{ sfs_mnt->helper_prog, NULL, NULL, NULL, NULL, NULL, NULL }; |
138 |
char *envp[2] = { "HOME=/", NULL }; |
139 |
char *path_buf; |
140 |
int result, len = 0; |
141 |
struct path p; |
142 |
|
143 |
argv[1] = sfs_mnt->device; |
144 |
path_buf = (char *) __get_free_page(GFP_KERNEL); |
145 |
if (!path_buf) |
146 |
return -ENOMEM; |
147 |
p.mnt = sfs_mnt->mount; |
148 |
p.dentry = p.mnt->mnt_root; |
149 |
argv[2] = d_path(&p, path_buf, PAGE_SIZE); |
150 |
argv[3] = sfs_mnt->req_fs; |
151 |
if (!(argv[4] = kmalloc(17, GFP_KERNEL))) { |
152 |
free_page((unsigned long) path_buf); |
153 |
return -ENOMEM; /* 64 bits on some platforms */ |
154 |
} |
155 |
sprintf(argv[4], "%lx", sfs_mnt->flags); |
156 |
len = strlen(sfs_mnt->options); |
157 |
if (sfs_mnt->procuid) |
158 |
add_procuid(sfs_mnt); |
159 |
argv[5] = sfs_mnt->options; |
160 |
result = call_usermodehelper(sfs_mnt->helper_prog, argv, envp, 1); |
161 |
free_page((unsigned long) path_buf); |
162 |
kfree(argv[4]); |
163 |
if (sfs_mnt->procuid) |
164 |
sfs_mnt->options[len] = '\0'; |
165 |
return result; |
166 |
} |
167 |
|
168 |
|
169 |
/* This routine returns a pointer to the filesystem mounted on top |
170 |
* of the subfs mountpoint, or an error pointer if it was unable to. |
171 |
*/ |
172 |
static struct vfsmount *get_child_mount(struct subfs_mount *sfs_mnt) |
173 |
{ |
174 |
struct vfsmount *child; |
175 |
int result; |
176 |
/* First time: find the vfsmount structure that matches sfs_mnt. */ |
177 |
if (!sfs_mnt->mount) { |
178 |
if(!(sfs_mnt->mount = get_subfs_vfsmount(sfs_mnt->sb))) |
179 |
return ERR_PTR(-ENOMEDIUM); |
180 |
sfs_mnt->flags = sfs_mnt->sb->s_flags; |
181 |
if (sfs_mnt->mount->mnt_flags & MNT_NOSUID) |
182 |
sfs_mnt->flags |= MS_NOSUID; |
183 |
if (sfs_mnt->mount->mnt_flags & MNT_NODEV) |
184 |
sfs_mnt->flags |= MS_NODEV; |
185 |
if (sfs_mnt->mount->mnt_flags & MNT_NOEXEC) |
186 |
sfs_mnt->flags |= MS_NOEXEC; |
187 |
} |
188 |
/* Check to see if a child mount does not already exist. */ |
189 |
if (&sfs_mnt->mount->mnt_mounts == sfs_mnt->mount->mnt_mounts.next) { |
190 |
result = mount_real_fs(sfs_mnt); |
191 |
if (result) { |
192 |
ERR("subfs: "SUBMOUNTD_PATH" unsuccessful attempt to mount media (%d)\n", |
193 |
result); |
194 |
/* Workaround for call_usermodehelper return value bug. */ |
195 |
if(result < 0) |
196 |
return ERR_PTR(result); |
197 |
return ERR_PTR(-ENOMEDIUM); |
198 |
} |
199 |
if (&sfs_mnt->mount->mnt_mounts |
200 |
== sfs_mnt->mount->mnt_mounts.next) { |
201 |
ERR("subfs: submountd mount failure.\n"); |
202 |
return ERR_PTR(-ENOMEDIUM); |
203 |
} |
204 |
} |
205 |
child = list_entry(sfs_mnt->mount->mnt_mounts.next, |
206 |
struct vfsmount, mnt_child); |
207 |
return child; |
208 |
} |
209 |
|
210 |
|
211 |
/* Implements the lookup method for subfs. Tries to get the child |
212 |
* mount. If it succeeds, it emits a signal and returns |
213 |
* -ERESTARSYS. If it receives an error, it passes it on to the |
214 |
* system. It raises the semaphore in the directory inode before mounting |
215 |
* because the mount routine also calls lookup, and hence a function is |
216 |
* calling itself from within semaphore protected code. Only the semaphore |
217 |
* on the subfs pseudo-directory is effected, so this isn't deadly. |
218 |
*/ |
219 |
static struct dentry *subfs_lookup(struct inode *dir, |
220 |
struct dentry *dentry, struct nameidata *nd) |
221 |
{ |
222 |
struct subfs_mount *sfs_mnt = dir->i_sb->s_fs_info; |
223 |
struct vfsmount *child; |
224 |
|
225 |
/* This is ugly, but prevents a lockup during mount. */ |
226 |
mutex_unlock(&dir->i_mutex); |
227 |
if (down_interruptible(&sfs_mnt->sem)) { |
228 |
mutex_lock(&dir->i_mutex); /* put the dir sem back down if interrupted */ |
229 |
return ERR_PTR(-ERESTARTSYS); |
230 |
} |
231 |
child = get_child_mount(sfs_mnt); |
232 |
up(&sfs_mnt->sem); |
233 |
mutex_lock(&dir->i_mutex); |
234 |
if (IS_ERR(child)) |
235 |
return (void *) child; |
236 |
subfs_send_signal(); |
237 |
if (sfs_mnt->mount == current->fs->pwd.mnt) |
238 |
subfs_set_fs_pwd(current->fs, child, child->mnt_root); |
239 |
return ERR_PTR(-ERESTARTSYS); |
240 |
} |
241 |
|
242 |
|
243 |
/* Implements the open method for subfs. Tries to get the child mount |
244 |
* for the subfs mountpoint which is being opened. Returns -ERESTARTSYS |
245 |
* and emits an ignored signal to the calling process if it succeeds, |
246 |
* or passes the error message received if it fails. |
247 |
*/ |
248 |
static int subfs_open(struct inode *inode, struct file *filp) |
249 |
{ |
250 |
struct subfs_mount *sfs_mnt = filp->f_dentry->d_sb->s_fs_info; |
251 |
struct vfsmount *child; |
252 |
if (down_interruptible(&sfs_mnt->sem)) |
253 |
return -ERESTARTSYS; |
254 |
child = get_child_mount(sfs_mnt); |
255 |
up(&sfs_mnt->sem); |
256 |
if (IS_ERR(child)) |
257 |
return PTR_ERR(child); |
258 |
subfs_send_signal(); |
259 |
if (sfs_mnt->mount == current->fs->pwd.mnt) |
260 |
subfs_set_fs_pwd(current->fs, child, child->mnt_root); |
261 |
return -ERESTARTSYS; |
262 |
} |
263 |
|
264 |
|
265 |
/* Implements the statfs method so df and such will work on the mountpoint. |
266 |
*/ |
267 |
static int subfs_statfs(struct dentry *dentry, struct kstatfs *buf) |
268 |
{ |
269 |
struct subfs_mount *sfs_mnt = dentry->d_sb->s_fs_info; |
270 |
struct vfsmount *child; |
271 |
if (down_interruptible(&sfs_mnt->sem)) |
272 |
return -ERESTARTSYS; |
273 |
child = get_child_mount(sfs_mnt); |
274 |
up(&sfs_mnt->sem); |
275 |
if (IS_ERR(child)) |
276 |
return PTR_ERR(child); |
277 |
subfs_send_signal(); |
278 |
return -ERESTARTSYS; |
279 |
} |
280 |
|
281 |
|
282 |
/* Creates the inodes for subfs superblocks. |
283 |
*/ |
284 |
static struct inode *subfs_make_inode(struct super_block *sb, int mode) |
285 |
{ |
286 |
struct inode *ret = new_inode(sb); |
287 |
|
288 |
if (ret) { |
289 |
ret->i_mode = mode; |
290 |
ret->i_uid = ret->i_gid = 0; |
291 |
ret->i_blocks = 0; |
292 |
ret->i_atime = ret->i_mtime = ret->i_ctime = CURRENT_TIME; |
293 |
ret->i_fop = &subfs_file_ops; |
294 |
} |
295 |
return ret; |
296 |
} |
297 |
|
298 |
|
299 |
/* Fills the fields for the superblock created when subfs is mounted. |
300 |
*/ |
301 |
static int subfs_fill_super(struct super_block *sb, void *data, int silent) |
302 |
{ |
303 |
struct inode *root; |
304 |
struct dentry *root_dentry; |
305 |
|
306 |
sb->s_blocksize = PAGE_CACHE_SIZE; |
307 |
sb->s_blocksize_bits = PAGE_CACHE_SHIFT; |
308 |
sb->s_magic = SUBFS_MAGIC; |
309 |
sb->s_op = &subfs_s_ops; |
310 |
root = subfs_make_inode(sb, S_IFDIR|ROOT_MODE); |
311 |
if (!root) |
312 |
goto out; |
313 |
root->i_op = &subfs_dir_inode_operations; |
314 |
root_dentry = d_alloc_root(root); |
315 |
if (!root_dentry) |
316 |
goto out_iput; |
317 |
sb->s_root = root_dentry; |
318 |
return 0; |
319 |
out_iput: |
320 |
iput(root); |
321 |
out: |
322 |
return -ENOMEM; |
323 |
} |
324 |
|
325 |
|
326 |
/* Parse the options string and remove submount specific options |
327 |
* and store the appropriate data. |
328 |
*/ |
329 |
static int proc_opts(struct subfs_mount *sfs_mnt, void *data) |
330 |
{ |
331 |
char *opts = data, *opt, *ptr, *fs = NULL, *prog; |
332 |
int len; |
333 |
|
334 |
if (!opts) { |
335 |
if (!(data = opts = kmalloc(PAGE_SIZE, GFP_KERNEL))) |
336 |
return -EINVAL; |
337 |
strcat(opts, "fs=auto"); |
338 |
} |
339 |
len = strnlen(opts, PAGE_SIZE - 1) + 1; |
340 |
if (strstr(opts, "procuid")) |
341 |
sfs_mnt->procuid = 1; |
342 |
if (!(sfs_mnt->options = kmalloc(len, GFP_KERNEL))) |
343 |
return -ENOMEM; |
344 |
sfs_mnt->options[0] = '\0'; |
345 |
while ((opt = strsep(&opts, ","))) { |
346 |
if ((ptr = strstr(opt, "program="))) { |
347 |
if (!(prog = kmalloc((strlen(ptr) - 7), GFP_KERNEL))) |
348 |
return -ENOMEM; |
349 |
strcpy(prog, (ptr + 8)); |
350 |
kfree(sfs_mnt->helper_prog); |
351 |
sfs_mnt->helper_prog = prog; |
352 |
} |
353 |
else if ((ptr = strstr(opt, "fs="))) { |
354 |
if (!(fs = kmalloc((strlen(ptr) - 2), GFP_KERNEL))) |
355 |
return -ENOMEM; |
356 |
strcpy(fs, (ptr + 3)); |
357 |
sfs_mnt->req_fs = fs; |
358 |
} |
359 |
else if ((ptr = strstr(opt, "procuid"))) { |
360 |
} else { |
361 |
strncat(sfs_mnt->options, opt, 32); |
362 |
strcat(sfs_mnt->options, ","); |
363 |
} |
364 |
} |
365 |
if((len = strlen(sfs_mnt->options)) && (sfs_mnt->options[len-1] == ',')) |
366 |
sfs_mnt->options[len-1] = '\0'; |
367 |
if ( !sfs_mnt->req_fs ){ |
368 |
if (!(sfs_mnt->req_fs = kmalloc(5, GFP_KERNEL))) |
369 |
return -ENOMEM; /* 64 bits on some platforms */ |
370 |
strcpy(sfs_mnt->req_fs, "auto" ); |
371 |
} |
372 |
return 0; |
373 |
} |
374 |
|
375 |
|
376 |
|
377 |
/* subfs_get_super is the subfs implementation of the get_sb method on |
378 |
* the file_system_type structure. It should only be called in the |
379 |
* case of a mount. It creates a new subfs_mount structure, fills |
380 |
* the fields of the structure, except for the mount structure, and then |
381 |
* calls a generic get_sb function. The superblock pointer is stored on |
382 |
* the subfs_mount structure, and returned to the calling function. The |
383 |
* subfs_mount structure is pointed to by the s_fs_info field of the |
384 |
* superblock structure. |
385 |
*/ |
386 |
static int subfs_get_super(struct file_system_type *fst, |
387 |
int flags, const char *devname, void *data, |
388 |
struct vfsmount *mnt) |
389 |
{ |
390 |
char *device; |
391 |
struct subfs_mount *newmount; |
392 |
int ret; |
393 |
|
394 |
if (!(newmount = kmalloc(sizeof(struct subfs_mount), GFP_KERNEL))) { |
395 |
ret = -ENOMEM; |
396 |
goto err; |
397 |
} |
398 |
newmount->req_fs = NULL; |
399 |
newmount->sb = NULL; |
400 |
newmount->mount = NULL; |
401 |
newmount->procuid = 0; |
402 |
sema_init(&newmount->sem, 1); |
403 |
if (!(device = kmalloc((strlen(devname) + 1), GFP_KERNEL))) { |
404 |
ret = -ENOMEM; |
405 |
goto err; |
406 |
} |
407 |
strcpy(device, devname); |
408 |
newmount->device = device; |
409 |
if (!(newmount->helper_prog = |
410 |
kmalloc(sizeof(SUBMOUNTD_PATH), GFP_KERNEL))) { |
411 |
ret = -ENOMEM; |
412 |
goto err; |
413 |
} |
414 |
strcpy(newmount->helper_prog, SUBMOUNTD_PATH); |
415 |
if ((ret = proc_opts(newmount, data))) |
416 |
goto err; |
417 |
ret = get_sb_nodev(fst, flags, data, subfs_fill_super, mnt); |
418 |
if (ret) |
419 |
goto err; |
420 |
newmount->sb = mnt->mnt_sb; |
421 |
newmount->sb->s_fs_info = newmount; |
422 |
return ret; |
423 |
|
424 |
err: |
425 |
return ret; |
426 |
} |
427 |
|
428 |
|
429 |
/* subfs_kill_super is the subfs implementation of the kill_sb method. |
430 |
* It should be called only on umount. It cleans up the appropriate |
431 |
* subfs_mount structure and then calls a generic function to actually |
432 |
* clean up the superblock structure. |
433 |
*/ |
434 |
static void subfs_kill_super(struct super_block *sb) |
435 |
{ |
436 |
struct subfs_mount *sfs_mnt = sb->s_fs_info; |
437 |
|
438 |
if(sfs_mnt) { |
439 |
if (sfs_mnt->device) |
440 |
kfree(sfs_mnt->device); |
441 |
if (sfs_mnt->options) |
442 |
kfree(sfs_mnt->options); |
443 |
if (sfs_mnt->req_fs) |
444 |
kfree(sfs_mnt->req_fs); |
445 |
if (sfs_mnt->helper_prog) |
446 |
kfree(sfs_mnt->helper_prog); |
447 |
kfree(sfs_mnt); |
448 |
sb->s_fs_info = NULL; |
449 |
} |
450 |
kill_litter_super(sb); |
451 |
return; |
452 |
} |
453 |
|
454 |
|
455 |
static int __init subfs_init(void) |
456 |
{ |
457 |
printk(KERN_INFO "subfs %s\n", SUBFS_VER); |
458 |
return register_filesystem(&subfs_type); |
459 |
} |
460 |
|
461 |
static void __exit subfs_exit(void) |
462 |
{ |
463 |
printk(KERN_INFO "subfs exiting.\n"); |
464 |
unregister_filesystem(&subfs_type); |
465 |
} |
466 |
|
467 |
module_init(subfs_init); |
468 |
module_exit(subfs_exit); |