1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * This file contains functions assisting in mapping VFS to 9P2000 |
4 | * |
5 | * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com> |
6 | * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> |
7 | */ |
8 | |
9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/errno.h> |
13 | #include <linux/fs.h> |
14 | #include <linux/sched.h> |
15 | #include <linux/cred.h> |
16 | #include <linux/parser.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/seq_file.h> |
19 | #include <net/9p/9p.h> |
20 | #include <net/9p/client.h> |
21 | #include <net/9p/transport.h> |
22 | #include "v9fs.h" |
23 | #include "v9fs_vfs.h" |
24 | #include "cache.h" |
25 | |
26 | static DEFINE_SPINLOCK(v9fs_sessionlist_lock); |
27 | static LIST_HEAD(v9fs_sessionlist); |
28 | struct kmem_cache *v9fs_inode_cache; |
29 | |
30 | /* |
31 | * Option Parsing (code inspired by NFS code) |
32 | * NOTE: each transport will parse its own options |
33 | */ |
34 | |
35 | enum { |
36 | /* Options that take integer arguments */ |
37 | Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid, |
38 | /* String options */ |
39 | Opt_uname, Opt_remotename, Opt_cache, Opt_cachetag, |
40 | /* Options that take no arguments */ |
41 | Opt_nodevmap, Opt_noxattr, Opt_directio, Opt_ignoreqv, |
42 | /* Access options */ |
43 | Opt_access, Opt_posixacl, |
44 | /* Lock timeout option */ |
45 | Opt_locktimeout, |
46 | /* Error token */ |
47 | Opt_err |
48 | }; |
49 | |
50 | static const match_table_t tokens = { |
51 | {Opt_debug, "debug=%x" }, |
52 | {Opt_dfltuid, "dfltuid=%u" }, |
53 | {Opt_dfltgid, "dfltgid=%u" }, |
54 | {Opt_afid, "afid=%u" }, |
55 | {Opt_uname, "uname=%s" }, |
56 | {Opt_remotename, "aname=%s" }, |
57 | {Opt_nodevmap, "nodevmap" }, |
58 | {Opt_noxattr, "noxattr" }, |
59 | {Opt_directio, "directio" }, |
60 | {Opt_ignoreqv, "ignoreqv" }, |
61 | {Opt_cache, "cache=%s" }, |
62 | {Opt_cachetag, "cachetag=%s" }, |
63 | {Opt_access, "access=%s" }, |
64 | {Opt_posixacl, "posixacl" }, |
65 | {Opt_locktimeout, "locktimeout=%u" }, |
66 | {Opt_err, NULL} |
67 | }; |
68 | |
69 | /* Interpret mount options for cache mode */ |
70 | static int get_cache_mode(char *s) |
71 | { |
72 | int version = -EINVAL; |
73 | |
74 | if (!strcmp(s, "loose" )) { |
75 | version = CACHE_SC_LOOSE; |
76 | p9_debug(P9_DEBUG_9P, "Cache mode: loose\n" ); |
77 | } else if (!strcmp(s, "fscache" )) { |
78 | version = CACHE_SC_FSCACHE; |
79 | p9_debug(P9_DEBUG_9P, "Cache mode: fscache\n" ); |
80 | } else if (!strcmp(s, "mmap" )) { |
81 | version = CACHE_SC_MMAP; |
82 | p9_debug(P9_DEBUG_9P, "Cache mode: mmap\n" ); |
83 | } else if (!strcmp(s, "readahead" )) { |
84 | version = CACHE_SC_READAHEAD; |
85 | p9_debug(P9_DEBUG_9P, "Cache mode: readahead\n" ); |
86 | } else if (!strcmp(s, "none" )) { |
87 | version = CACHE_SC_NONE; |
88 | p9_debug(P9_DEBUG_9P, "Cache mode: none\n" ); |
89 | } else if (kstrtoint(s, base: 0, res: &version) != 0) { |
90 | version = -EINVAL; |
91 | pr_info("Unknown Cache mode or invalid value %s\n" , s); |
92 | } |
93 | return version; |
94 | } |
95 | |
96 | /* |
97 | * Display the mount options in /proc/mounts. |
98 | */ |
99 | int v9fs_show_options(struct seq_file *m, struct dentry *root) |
100 | { |
101 | struct v9fs_session_info *v9ses = root->d_sb->s_fs_info; |
102 | |
103 | if (v9ses->debug) |
104 | seq_printf(m, fmt: ",debug=%x" , v9ses->debug); |
105 | if (!uid_eq(left: v9ses->dfltuid, V9FS_DEFUID)) |
106 | seq_printf(m, fmt: ",dfltuid=%u" , |
107 | from_kuid_munged(to: &init_user_ns, uid: v9ses->dfltuid)); |
108 | if (!gid_eq(left: v9ses->dfltgid, V9FS_DEFGID)) |
109 | seq_printf(m, fmt: ",dfltgid=%u" , |
110 | from_kgid_munged(to: &init_user_ns, gid: v9ses->dfltgid)); |
111 | if (v9ses->afid != ~0) |
112 | seq_printf(m, fmt: ",afid=%u" , v9ses->afid); |
113 | if (strcmp(v9ses->uname, V9FS_DEFUSER) != 0) |
114 | seq_printf(m, fmt: ",uname=%s" , v9ses->uname); |
115 | if (strcmp(v9ses->aname, V9FS_DEFANAME) != 0) |
116 | seq_printf(m, fmt: ",aname=%s" , v9ses->aname); |
117 | if (v9ses->nodev) |
118 | seq_puts(m, s: ",nodevmap" ); |
119 | if (v9ses->cache) |
120 | seq_printf(m, fmt: ",cache=%x" , v9ses->cache); |
121 | #ifdef CONFIG_9P_FSCACHE |
122 | if (v9ses->cachetag && (v9ses->cache & CACHE_FSCACHE)) |
123 | seq_printf(m, fmt: ",cachetag=%s" , v9ses->cachetag); |
124 | #endif |
125 | |
126 | switch (v9ses->flags & V9FS_ACCESS_MASK) { |
127 | case V9FS_ACCESS_USER: |
128 | seq_puts(m, s: ",access=user" ); |
129 | break; |
130 | case V9FS_ACCESS_ANY: |
131 | seq_puts(m, s: ",access=any" ); |
132 | break; |
133 | case V9FS_ACCESS_CLIENT: |
134 | seq_puts(m, s: ",access=client" ); |
135 | break; |
136 | case V9FS_ACCESS_SINGLE: |
137 | seq_printf(m, fmt: ",access=%u" , |
138 | from_kuid_munged(to: &init_user_ns, uid: v9ses->uid)); |
139 | break; |
140 | } |
141 | |
142 | if (v9ses->flags & V9FS_IGNORE_QV) |
143 | seq_puts(m, s: ",ignoreqv" ); |
144 | if (v9ses->flags & V9FS_DIRECT_IO) |
145 | seq_puts(m, s: ",directio" ); |
146 | if (v9ses->flags & V9FS_POSIX_ACL) |
147 | seq_puts(m, s: ",posixacl" ); |
148 | |
149 | if (v9ses->flags & V9FS_NO_XATTR) |
150 | seq_puts(m, s: ",noxattr" ); |
151 | |
152 | return p9_show_client_options(m, clnt: v9ses->clnt); |
153 | } |
154 | |
155 | /** |
156 | * v9fs_parse_options - parse mount options into session structure |
157 | * @v9ses: existing v9fs session information |
158 | * @opts: The mount option string |
159 | * |
160 | * Return 0 upon success, -ERRNO upon failure. |
161 | */ |
162 | |
163 | static int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts) |
164 | { |
165 | char *options, *tmp_options; |
166 | substring_t args[MAX_OPT_ARGS]; |
167 | char *p; |
168 | int option = 0; |
169 | char *s; |
170 | int ret = 0; |
171 | |
172 | /* setup defaults */ |
173 | v9ses->afid = ~0; |
174 | v9ses->debug = 0; |
175 | v9ses->cache = CACHE_NONE; |
176 | #ifdef CONFIG_9P_FSCACHE |
177 | v9ses->cachetag = NULL; |
178 | #endif |
179 | v9ses->session_lock_timeout = P9_LOCK_TIMEOUT; |
180 | |
181 | if (!opts) |
182 | return 0; |
183 | |
184 | tmp_options = kstrdup(s: opts, GFP_KERNEL); |
185 | if (!tmp_options) { |
186 | ret = -ENOMEM; |
187 | goto fail_option_alloc; |
188 | } |
189 | options = tmp_options; |
190 | |
191 | while ((p = strsep(&options, "," )) != NULL) { |
192 | int token, r; |
193 | |
194 | if (!*p) |
195 | continue; |
196 | |
197 | token = match_token(p, table: tokens, args); |
198 | switch (token) { |
199 | case Opt_debug: |
200 | r = match_int(&args[0], result: &option); |
201 | if (r < 0) { |
202 | p9_debug(P9_DEBUG_ERROR, |
203 | "integer field, but no integer?\n" ); |
204 | ret = r; |
205 | } else { |
206 | v9ses->debug = option; |
207 | #ifdef CONFIG_NET_9P_DEBUG |
208 | p9_debug_level = option; |
209 | #endif |
210 | } |
211 | break; |
212 | |
213 | case Opt_dfltuid: |
214 | r = match_int(&args[0], result: &option); |
215 | if (r < 0) { |
216 | p9_debug(P9_DEBUG_ERROR, |
217 | "integer field, but no integer?\n" ); |
218 | ret = r; |
219 | continue; |
220 | } |
221 | v9ses->dfltuid = make_kuid(current_user_ns(), uid: option); |
222 | if (!uid_valid(uid: v9ses->dfltuid)) { |
223 | p9_debug(P9_DEBUG_ERROR, |
224 | "uid field, but not a uid?\n" ); |
225 | ret = -EINVAL; |
226 | } |
227 | break; |
228 | case Opt_dfltgid: |
229 | r = match_int(&args[0], result: &option); |
230 | if (r < 0) { |
231 | p9_debug(P9_DEBUG_ERROR, |
232 | "integer field, but no integer?\n" ); |
233 | ret = r; |
234 | continue; |
235 | } |
236 | v9ses->dfltgid = make_kgid(current_user_ns(), gid: option); |
237 | if (!gid_valid(gid: v9ses->dfltgid)) { |
238 | p9_debug(P9_DEBUG_ERROR, |
239 | "gid field, but not a gid?\n" ); |
240 | ret = -EINVAL; |
241 | } |
242 | break; |
243 | case Opt_afid: |
244 | r = match_int(&args[0], result: &option); |
245 | if (r < 0) { |
246 | p9_debug(P9_DEBUG_ERROR, |
247 | "integer field, but no integer?\n" ); |
248 | ret = r; |
249 | } else { |
250 | v9ses->afid = option; |
251 | } |
252 | break; |
253 | case Opt_uname: |
254 | kfree(objp: v9ses->uname); |
255 | v9ses->uname = match_strdup(&args[0]); |
256 | if (!v9ses->uname) { |
257 | ret = -ENOMEM; |
258 | goto free_and_return; |
259 | } |
260 | break; |
261 | case Opt_remotename: |
262 | kfree(objp: v9ses->aname); |
263 | v9ses->aname = match_strdup(&args[0]); |
264 | if (!v9ses->aname) { |
265 | ret = -ENOMEM; |
266 | goto free_and_return; |
267 | } |
268 | break; |
269 | case Opt_nodevmap: |
270 | v9ses->nodev = 1; |
271 | break; |
272 | case Opt_noxattr: |
273 | v9ses->flags |= V9FS_NO_XATTR; |
274 | break; |
275 | case Opt_directio: |
276 | v9ses->flags |= V9FS_DIRECT_IO; |
277 | break; |
278 | case Opt_ignoreqv: |
279 | v9ses->flags |= V9FS_IGNORE_QV; |
280 | break; |
281 | case Opt_cachetag: |
282 | #ifdef CONFIG_9P_FSCACHE |
283 | kfree(objp: v9ses->cachetag); |
284 | v9ses->cachetag = match_strdup(&args[0]); |
285 | if (!v9ses->cachetag) { |
286 | ret = -ENOMEM; |
287 | goto free_and_return; |
288 | } |
289 | #endif |
290 | break; |
291 | case Opt_cache: |
292 | s = match_strdup(&args[0]); |
293 | if (!s) { |
294 | ret = -ENOMEM; |
295 | p9_debug(P9_DEBUG_ERROR, |
296 | "problem allocating copy of cache arg\n" ); |
297 | goto free_and_return; |
298 | } |
299 | r = get_cache_mode(s); |
300 | if (r < 0) |
301 | ret = r; |
302 | else |
303 | v9ses->cache = r; |
304 | |
305 | kfree(objp: s); |
306 | break; |
307 | |
308 | case Opt_access: |
309 | s = match_strdup(&args[0]); |
310 | if (!s) { |
311 | ret = -ENOMEM; |
312 | p9_debug(P9_DEBUG_ERROR, |
313 | "problem allocating copy of access arg\n" ); |
314 | goto free_and_return; |
315 | } |
316 | |
317 | v9ses->flags &= ~V9FS_ACCESS_MASK; |
318 | if (strcmp(s, "user" ) == 0) |
319 | v9ses->flags |= V9FS_ACCESS_USER; |
320 | else if (strcmp(s, "any" ) == 0) |
321 | v9ses->flags |= V9FS_ACCESS_ANY; |
322 | else if (strcmp(s, "client" ) == 0) { |
323 | v9ses->flags |= V9FS_ACCESS_CLIENT; |
324 | } else { |
325 | uid_t uid; |
326 | |
327 | v9ses->flags |= V9FS_ACCESS_SINGLE; |
328 | r = kstrtouint(s, base: 10, res: &uid); |
329 | if (r) { |
330 | ret = r; |
331 | pr_info("Unknown access argument %s: %d\n" , |
332 | s, r); |
333 | kfree(objp: s); |
334 | continue; |
335 | } |
336 | v9ses->uid = make_kuid(current_user_ns(), uid); |
337 | if (!uid_valid(uid: v9ses->uid)) { |
338 | ret = -EINVAL; |
339 | pr_info("Unknown uid %s\n" , s); |
340 | } |
341 | } |
342 | |
343 | kfree(objp: s); |
344 | break; |
345 | |
346 | case Opt_posixacl: |
347 | #ifdef CONFIG_9P_FS_POSIX_ACL |
348 | v9ses->flags |= V9FS_POSIX_ACL; |
349 | #else |
350 | p9_debug(P9_DEBUG_ERROR, |
351 | "Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n" ); |
352 | #endif |
353 | break; |
354 | |
355 | case Opt_locktimeout: |
356 | r = match_int(&args[0], result: &option); |
357 | if (r < 0) { |
358 | p9_debug(P9_DEBUG_ERROR, |
359 | "integer field, but no integer?\n" ); |
360 | ret = r; |
361 | continue; |
362 | } |
363 | if (option < 1) { |
364 | p9_debug(P9_DEBUG_ERROR, |
365 | "locktimeout must be a greater than zero integer.\n" ); |
366 | ret = -EINVAL; |
367 | continue; |
368 | } |
369 | v9ses->session_lock_timeout = (long)option * HZ; |
370 | break; |
371 | |
372 | default: |
373 | continue; |
374 | } |
375 | } |
376 | |
377 | free_and_return: |
378 | kfree(objp: tmp_options); |
379 | fail_option_alloc: |
380 | return ret; |
381 | } |
382 | |
383 | /** |
384 | * v9fs_session_init - initialize session |
385 | * @v9ses: session information structure |
386 | * @dev_name: device being mounted |
387 | * @data: options |
388 | * |
389 | */ |
390 | |
391 | struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, |
392 | const char *dev_name, char *data) |
393 | { |
394 | struct p9_fid *fid; |
395 | int rc = -ENOMEM; |
396 | |
397 | v9ses->uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL); |
398 | if (!v9ses->uname) |
399 | goto err_names; |
400 | |
401 | v9ses->aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL); |
402 | if (!v9ses->aname) |
403 | goto err_names; |
404 | init_rwsem(&v9ses->rename_sem); |
405 | |
406 | v9ses->uid = INVALID_UID; |
407 | v9ses->dfltuid = V9FS_DEFUID; |
408 | v9ses->dfltgid = V9FS_DEFGID; |
409 | |
410 | v9ses->clnt = p9_client_create(dev_name, options: data); |
411 | if (IS_ERR(ptr: v9ses->clnt)) { |
412 | rc = PTR_ERR(ptr: v9ses->clnt); |
413 | p9_debug(P9_DEBUG_ERROR, "problem initializing 9p client\n" ); |
414 | goto err_names; |
415 | } |
416 | |
417 | v9ses->flags = V9FS_ACCESS_USER; |
418 | |
419 | if (p9_is_proto_dotl(clnt: v9ses->clnt)) { |
420 | v9ses->flags = V9FS_ACCESS_CLIENT; |
421 | v9ses->flags |= V9FS_PROTO_2000L; |
422 | } else if (p9_is_proto_dotu(clnt: v9ses->clnt)) { |
423 | v9ses->flags |= V9FS_PROTO_2000U; |
424 | } |
425 | |
426 | rc = v9fs_parse_options(v9ses, opts: data); |
427 | if (rc < 0) |
428 | goto err_clnt; |
429 | |
430 | v9ses->maxdata = v9ses->clnt->msize - P9_IOHDRSZ; |
431 | |
432 | if (!v9fs_proto_dotl(v9ses) && |
433 | ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) { |
434 | /* |
435 | * We support ACCESS_CLIENT only for dotl. |
436 | * Fall back to ACCESS_USER |
437 | */ |
438 | v9ses->flags &= ~V9FS_ACCESS_MASK; |
439 | v9ses->flags |= V9FS_ACCESS_USER; |
440 | } |
441 | /*FIXME !! */ |
442 | /* for legacy mode, fall back to V9FS_ACCESS_ANY */ |
443 | if (!(v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) && |
444 | ((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) { |
445 | |
446 | v9ses->flags &= ~V9FS_ACCESS_MASK; |
447 | v9ses->flags |= V9FS_ACCESS_ANY; |
448 | v9ses->uid = INVALID_UID; |
449 | } |
450 | if (!v9fs_proto_dotl(v9ses) || |
451 | !((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) { |
452 | /* |
453 | * We support ACL checks on clinet only if the protocol is |
454 | * 9P2000.L and access is V9FS_ACCESS_CLIENT. |
455 | */ |
456 | v9ses->flags &= ~V9FS_ACL_MASK; |
457 | } |
458 | |
459 | fid = p9_client_attach(clnt: v9ses->clnt, NULL, uname: v9ses->uname, INVALID_UID, |
460 | aname: v9ses->aname); |
461 | if (IS_ERR(ptr: fid)) { |
462 | rc = PTR_ERR(ptr: fid); |
463 | p9_debug(P9_DEBUG_ERROR, "cannot attach\n" ); |
464 | goto err_clnt; |
465 | } |
466 | |
467 | if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE) |
468 | fid->uid = v9ses->uid; |
469 | else |
470 | fid->uid = INVALID_UID; |
471 | |
472 | #ifdef CONFIG_9P_FSCACHE |
473 | /* register the session for caching */ |
474 | if (v9ses->cache & CACHE_FSCACHE) { |
475 | rc = v9fs_cache_session_get_cookie(v9ses, dev_name); |
476 | if (rc < 0) |
477 | goto err_clnt; |
478 | } |
479 | #endif |
480 | spin_lock(lock: &v9fs_sessionlist_lock); |
481 | list_add(new: &v9ses->slist, head: &v9fs_sessionlist); |
482 | spin_unlock(lock: &v9fs_sessionlist_lock); |
483 | |
484 | return fid; |
485 | |
486 | err_clnt: |
487 | #ifdef CONFIG_9P_FSCACHE |
488 | kfree(objp: v9ses->cachetag); |
489 | #endif |
490 | p9_client_destroy(clnt: v9ses->clnt); |
491 | err_names: |
492 | kfree(objp: v9ses->uname); |
493 | kfree(objp: v9ses->aname); |
494 | return ERR_PTR(error: rc); |
495 | } |
496 | |
497 | /** |
498 | * v9fs_session_close - shutdown a session |
499 | * @v9ses: session information structure |
500 | * |
501 | */ |
502 | |
503 | void v9fs_session_close(struct v9fs_session_info *v9ses) |
504 | { |
505 | if (v9ses->clnt) { |
506 | p9_client_destroy(clnt: v9ses->clnt); |
507 | v9ses->clnt = NULL; |
508 | } |
509 | |
510 | #ifdef CONFIG_9P_FSCACHE |
511 | fscache_relinquish_volume(volume: v9fs_session_cache(v9ses), NULL, invalidate: false); |
512 | kfree(objp: v9ses->cachetag); |
513 | #endif |
514 | kfree(objp: v9ses->uname); |
515 | kfree(objp: v9ses->aname); |
516 | |
517 | spin_lock(lock: &v9fs_sessionlist_lock); |
518 | list_del(entry: &v9ses->slist); |
519 | spin_unlock(lock: &v9fs_sessionlist_lock); |
520 | } |
521 | |
522 | /** |
523 | * v9fs_session_cancel - terminate a session |
524 | * @v9ses: session to terminate |
525 | * |
526 | * mark transport as disconnected and cancel all pending requests. |
527 | */ |
528 | |
529 | void v9fs_session_cancel(struct v9fs_session_info *v9ses) |
530 | { |
531 | p9_debug(P9_DEBUG_ERROR, "cancel session %p\n" , v9ses); |
532 | p9_client_disconnect(clnt: v9ses->clnt); |
533 | } |
534 | |
535 | /** |
536 | * v9fs_session_begin_cancel - Begin terminate of a session |
537 | * @v9ses: session to terminate |
538 | * |
539 | * After this call we don't allow any request other than clunk. |
540 | */ |
541 | |
542 | void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses) |
543 | { |
544 | p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n" , v9ses); |
545 | p9_client_begin_disconnect(clnt: v9ses->clnt); |
546 | } |
547 | |
548 | static struct kobject *v9fs_kobj; |
549 | |
550 | #ifdef CONFIG_9P_FSCACHE |
551 | /* |
552 | * List caches associated with a session |
553 | */ |
554 | static ssize_t caches_show(struct kobject *kobj, |
555 | struct kobj_attribute *attr, |
556 | char *buf) |
557 | { |
558 | ssize_t n = 0, count = 0, limit = PAGE_SIZE; |
559 | struct v9fs_session_info *v9ses; |
560 | |
561 | spin_lock(lock: &v9fs_sessionlist_lock); |
562 | list_for_each_entry(v9ses, &v9fs_sessionlist, slist) { |
563 | if (v9ses->cachetag) { |
564 | n = snprintf(buf, size: limit, fmt: "%s\n" , v9ses->cachetag); |
565 | if (n < 0) { |
566 | count = n; |
567 | break; |
568 | } |
569 | |
570 | count += n; |
571 | limit -= n; |
572 | } |
573 | } |
574 | |
575 | spin_unlock(lock: &v9fs_sessionlist_lock); |
576 | return count; |
577 | } |
578 | |
579 | static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches); |
580 | #endif /* CONFIG_9P_FSCACHE */ |
581 | |
582 | static struct attribute *v9fs_attrs[] = { |
583 | #ifdef CONFIG_9P_FSCACHE |
584 | &v9fs_attr_cache.attr, |
585 | #endif |
586 | NULL, |
587 | }; |
588 | |
589 | static const struct attribute_group v9fs_attr_group = { |
590 | .attrs = v9fs_attrs, |
591 | }; |
592 | |
593 | /** |
594 | * v9fs_sysfs_init - Initialize the v9fs sysfs interface |
595 | * |
596 | */ |
597 | |
598 | static int __init v9fs_sysfs_init(void) |
599 | { |
600 | v9fs_kobj = kobject_create_and_add(name: "9p" , parent: fs_kobj); |
601 | if (!v9fs_kobj) |
602 | return -ENOMEM; |
603 | |
604 | if (sysfs_create_group(kobj: v9fs_kobj, grp: &v9fs_attr_group)) { |
605 | kobject_put(kobj: v9fs_kobj); |
606 | return -ENOMEM; |
607 | } |
608 | |
609 | return 0; |
610 | } |
611 | |
612 | /** |
613 | * v9fs_sysfs_cleanup - Unregister the v9fs sysfs interface |
614 | * |
615 | */ |
616 | |
617 | static void v9fs_sysfs_cleanup(void) |
618 | { |
619 | sysfs_remove_group(kobj: v9fs_kobj, grp: &v9fs_attr_group); |
620 | kobject_put(kobj: v9fs_kobj); |
621 | } |
622 | |
623 | static void v9fs_inode_init_once(void *foo) |
624 | { |
625 | struct v9fs_inode *v9inode = (struct v9fs_inode *)foo; |
626 | |
627 | memset(&v9inode->qid, 0, sizeof(v9inode->qid)); |
628 | inode_init_once(&v9inode->netfs.inode); |
629 | } |
630 | |
631 | /** |
632 | * v9fs_init_inode_cache - initialize a cache for 9P |
633 | * Returns 0 on success. |
634 | */ |
635 | static int v9fs_init_inode_cache(void) |
636 | { |
637 | v9fs_inode_cache = kmem_cache_create(name: "v9fs_inode_cache" , |
638 | size: sizeof(struct v9fs_inode), |
639 | align: 0, flags: (SLAB_RECLAIM_ACCOUNT| |
640 | SLAB_MEM_SPREAD|SLAB_ACCOUNT), |
641 | ctor: v9fs_inode_init_once); |
642 | if (!v9fs_inode_cache) |
643 | return -ENOMEM; |
644 | |
645 | return 0; |
646 | } |
647 | |
648 | /** |
649 | * v9fs_destroy_inode_cache - destroy the cache of 9P inode |
650 | * |
651 | */ |
652 | static void v9fs_destroy_inode_cache(void) |
653 | { |
654 | /* |
655 | * Make sure all delayed rcu free inodes are flushed before we |
656 | * destroy cache. |
657 | */ |
658 | rcu_barrier(); |
659 | kmem_cache_destroy(s: v9fs_inode_cache); |
660 | } |
661 | |
662 | static int v9fs_cache_register(void) |
663 | { |
664 | int ret; |
665 | |
666 | ret = v9fs_init_inode_cache(); |
667 | if (ret < 0) |
668 | return ret; |
669 | return ret; |
670 | } |
671 | |
672 | static void v9fs_cache_unregister(void) |
673 | { |
674 | v9fs_destroy_inode_cache(); |
675 | } |
676 | |
677 | /** |
678 | * init_v9fs - Initialize module |
679 | * |
680 | */ |
681 | |
682 | static int __init init_v9fs(void) |
683 | { |
684 | int err; |
685 | |
686 | pr_info("Installing v9fs 9p2000 file system support\n" ); |
687 | /* TODO: Setup list of registered trasnport modules */ |
688 | |
689 | err = v9fs_cache_register(); |
690 | if (err < 0) { |
691 | pr_err("Failed to register v9fs for caching\n" ); |
692 | return err; |
693 | } |
694 | |
695 | err = v9fs_sysfs_init(); |
696 | if (err < 0) { |
697 | pr_err("Failed to register with sysfs\n" ); |
698 | goto out_cache; |
699 | } |
700 | err = register_filesystem(&v9fs_fs_type); |
701 | if (err < 0) { |
702 | pr_err("Failed to register filesystem\n" ); |
703 | goto out_sysfs_cleanup; |
704 | } |
705 | |
706 | return 0; |
707 | |
708 | out_sysfs_cleanup: |
709 | v9fs_sysfs_cleanup(); |
710 | |
711 | out_cache: |
712 | v9fs_cache_unregister(); |
713 | |
714 | return err; |
715 | } |
716 | |
717 | /** |
718 | * exit_v9fs - shutdown module |
719 | * |
720 | */ |
721 | |
722 | static void __exit exit_v9fs(void) |
723 | { |
724 | v9fs_sysfs_cleanup(); |
725 | v9fs_cache_unregister(); |
726 | unregister_filesystem(&v9fs_fs_type); |
727 | } |
728 | |
729 | module_init(init_v9fs) |
730 | module_exit(exit_v9fs) |
731 | |
732 | MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>" ); |
733 | MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>" ); |
734 | MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>" ); |
735 | MODULE_DESCRIPTION("9P Client File System" ); |
736 | MODULE_LICENSE("GPL" ); |
737 | |