1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2019 Hammerspace Inc |
4 | */ |
5 | |
6 | #include <linux/module.h> |
7 | #include <linux/kobject.h> |
8 | #include <linux/sysfs.h> |
9 | #include <linux/fs.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/netdevice.h> |
12 | #include <linux/string.h> |
13 | #include <linux/nfs_fs.h> |
14 | #include <linux/rcupdate.h> |
15 | #include <linux/lockd/lockd.h> |
16 | |
17 | #include "nfs4_fs.h" |
18 | #include "netns.h" |
19 | #include "sysfs.h" |
20 | |
21 | static struct kset *nfs_kset; |
22 | |
23 | static void nfs_kset_release(struct kobject *kobj) |
24 | { |
25 | struct kset *kset = container_of(kobj, struct kset, kobj); |
26 | kfree(objp: kset); |
27 | } |
28 | |
29 | static const struct kobj_ns_type_operations *nfs_netns_object_child_ns_type( |
30 | const struct kobject *kobj) |
31 | { |
32 | return &net_ns_type_operations; |
33 | } |
34 | |
35 | static struct kobj_type nfs_kset_type = { |
36 | .release = nfs_kset_release, |
37 | .sysfs_ops = &kobj_sysfs_ops, |
38 | .child_ns_type = nfs_netns_object_child_ns_type, |
39 | }; |
40 | |
41 | int nfs_sysfs_init(void) |
42 | { |
43 | int ret; |
44 | |
45 | nfs_kset = kzalloc(size: sizeof(*nfs_kset), GFP_KERNEL); |
46 | if (!nfs_kset) |
47 | return -ENOMEM; |
48 | |
49 | ret = kobject_set_name(kobj: &nfs_kset->kobj, name: "nfs" ); |
50 | if (ret) { |
51 | kfree(objp: nfs_kset); |
52 | return ret; |
53 | } |
54 | |
55 | nfs_kset->kobj.parent = fs_kobj; |
56 | nfs_kset->kobj.ktype = &nfs_kset_type; |
57 | nfs_kset->kobj.kset = NULL; |
58 | |
59 | ret = kset_register(kset: nfs_kset); |
60 | if (ret) { |
61 | kfree(objp: nfs_kset); |
62 | return ret; |
63 | } |
64 | |
65 | return 0; |
66 | } |
67 | |
68 | void nfs_sysfs_exit(void) |
69 | { |
70 | kset_unregister(kset: nfs_kset); |
71 | } |
72 | |
73 | static ssize_t nfs_netns_identifier_show(struct kobject *kobj, |
74 | struct kobj_attribute *attr, char *buf) |
75 | { |
76 | struct nfs_netns_client *c = container_of(kobj, |
77 | struct nfs_netns_client, |
78 | kobject); |
79 | ssize_t ret; |
80 | |
81 | rcu_read_lock(); |
82 | ret = sysfs_emit(buf, fmt: "%s\n" , rcu_dereference(c->identifier)); |
83 | rcu_read_unlock(); |
84 | return ret; |
85 | } |
86 | |
87 | /* Strip trailing '\n' */ |
88 | static size_t nfs_string_strip(const char *c, size_t len) |
89 | { |
90 | while (len > 0 && c[len-1] == '\n') |
91 | --len; |
92 | return len; |
93 | } |
94 | |
95 | static ssize_t nfs_netns_identifier_store(struct kobject *kobj, |
96 | struct kobj_attribute *attr, |
97 | const char *buf, size_t count) |
98 | { |
99 | struct nfs_netns_client *c = container_of(kobj, |
100 | struct nfs_netns_client, |
101 | kobject); |
102 | const char *old; |
103 | char *p; |
104 | size_t len; |
105 | |
106 | len = nfs_string_strip(c: buf, min_t(size_t, count, CONTAINER_ID_MAXLEN)); |
107 | if (!len) |
108 | return 0; |
109 | p = kmemdup_nul(s: buf, len, GFP_KERNEL); |
110 | if (!p) |
111 | return -ENOMEM; |
112 | old = rcu_dereference_protected(xchg(&c->identifier, (char __rcu *)p), 1); |
113 | if (old) { |
114 | synchronize_rcu(); |
115 | kfree(objp: old); |
116 | } |
117 | return count; |
118 | } |
119 | |
120 | static void nfs_netns_client_release(struct kobject *kobj) |
121 | { |
122 | struct nfs_netns_client *c = container_of(kobj, |
123 | struct nfs_netns_client, |
124 | kobject); |
125 | |
126 | kfree(rcu_dereference_raw(c->identifier)); |
127 | } |
128 | |
129 | static const void *nfs_netns_client_namespace(const struct kobject *kobj) |
130 | { |
131 | return container_of(kobj, struct nfs_netns_client, kobject)->net; |
132 | } |
133 | |
134 | static struct kobj_attribute nfs_netns_client_id = __ATTR(identifier, |
135 | 0644, nfs_netns_identifier_show, nfs_netns_identifier_store); |
136 | |
137 | static struct attribute *nfs_netns_client_attrs[] = { |
138 | &nfs_netns_client_id.attr, |
139 | NULL, |
140 | }; |
141 | ATTRIBUTE_GROUPS(nfs_netns_client); |
142 | |
143 | static struct kobj_type nfs_netns_client_type = { |
144 | .release = nfs_netns_client_release, |
145 | .default_groups = nfs_netns_client_groups, |
146 | .sysfs_ops = &kobj_sysfs_ops, |
147 | .namespace = nfs_netns_client_namespace, |
148 | }; |
149 | |
150 | static void nfs_netns_object_release(struct kobject *kobj) |
151 | { |
152 | struct nfs_netns_client *c = container_of(kobj, |
153 | struct nfs_netns_client, |
154 | nfs_net_kobj); |
155 | kfree(objp: c); |
156 | } |
157 | |
158 | static const void *nfs_netns_namespace(const struct kobject *kobj) |
159 | { |
160 | return container_of(kobj, struct nfs_netns_client, nfs_net_kobj)->net; |
161 | } |
162 | |
163 | static struct kobj_type nfs_netns_object_type = { |
164 | .release = nfs_netns_object_release, |
165 | .sysfs_ops = &kobj_sysfs_ops, |
166 | .namespace = nfs_netns_namespace, |
167 | }; |
168 | |
169 | static struct nfs_netns_client *nfs_netns_client_alloc(struct kobject *parent, |
170 | struct net *net) |
171 | { |
172 | struct nfs_netns_client *p; |
173 | |
174 | p = kzalloc(size: sizeof(*p), GFP_KERNEL); |
175 | if (p) { |
176 | p->net = net; |
177 | p->kobject.kset = nfs_kset; |
178 | p->nfs_net_kobj.kset = nfs_kset; |
179 | |
180 | if (kobject_init_and_add(kobj: &p->nfs_net_kobj, ktype: &nfs_netns_object_type, |
181 | parent, fmt: "net" ) != 0) { |
182 | kobject_put(kobj: &p->nfs_net_kobj); |
183 | return NULL; |
184 | } |
185 | |
186 | if (kobject_init_and_add(kobj: &p->kobject, ktype: &nfs_netns_client_type, |
187 | parent: &p->nfs_net_kobj, fmt: "nfs_client" ) == 0) |
188 | return p; |
189 | |
190 | kobject_put(kobj: &p->kobject); |
191 | } |
192 | return NULL; |
193 | } |
194 | |
195 | void nfs_netns_sysfs_setup(struct nfs_net *netns, struct net *net) |
196 | { |
197 | struct nfs_netns_client *clp; |
198 | |
199 | clp = nfs_netns_client_alloc(parent: &nfs_kset->kobj, net); |
200 | if (clp) { |
201 | netns->nfs_client = clp; |
202 | kobject_uevent(kobj: &clp->kobject, action: KOBJ_ADD); |
203 | } |
204 | } |
205 | |
206 | void nfs_netns_sysfs_destroy(struct nfs_net *netns) |
207 | { |
208 | struct nfs_netns_client *clp = netns->nfs_client; |
209 | |
210 | if (clp) { |
211 | kobject_uevent(kobj: &clp->kobject, action: KOBJ_REMOVE); |
212 | kobject_del(kobj: &clp->kobject); |
213 | kobject_put(kobj: &clp->kobject); |
214 | kobject_del(kobj: &clp->nfs_net_kobj); |
215 | kobject_put(kobj: &clp->nfs_net_kobj); |
216 | netns->nfs_client = NULL; |
217 | } |
218 | } |
219 | |
220 | static bool shutdown_match_client(const struct rpc_task *task, const void *data) |
221 | { |
222 | return true; |
223 | } |
224 | |
225 | static void shutdown_client(struct rpc_clnt *clnt) |
226 | { |
227 | clnt->cl_shutdown = 1; |
228 | rpc_cancel_tasks(clnt, error: -EIO, fnmatch: shutdown_match_client, NULL); |
229 | } |
230 | |
231 | static ssize_t |
232 | shutdown_show(struct kobject *kobj, struct kobj_attribute *attr, |
233 | char *buf) |
234 | { |
235 | struct nfs_server *server = container_of(kobj, struct nfs_server, kobj); |
236 | bool shutdown = server->flags & NFS_MOUNT_SHUTDOWN; |
237 | return sysfs_emit(buf, fmt: "%d\n" , shutdown); |
238 | } |
239 | |
240 | static ssize_t |
241 | shutdown_store(struct kobject *kobj, struct kobj_attribute *attr, |
242 | const char *buf, size_t count) |
243 | { |
244 | struct nfs_server *server; |
245 | int ret, val; |
246 | |
247 | server = container_of(kobj, struct nfs_server, kobj); |
248 | |
249 | ret = kstrtoint(s: buf, base: 0, res: &val); |
250 | if (ret) |
251 | return ret; |
252 | |
253 | if (val != 1) |
254 | return -EINVAL; |
255 | |
256 | /* already shut down? */ |
257 | if (server->flags & NFS_MOUNT_SHUTDOWN) |
258 | goto out; |
259 | |
260 | server->flags |= NFS_MOUNT_SHUTDOWN; |
261 | shutdown_client(clnt: server->client); |
262 | shutdown_client(clnt: server->nfs_client->cl_rpcclient); |
263 | |
264 | if (!IS_ERR(ptr: server->client_acl)) |
265 | shutdown_client(clnt: server->client_acl); |
266 | |
267 | if (server->nlm_host) |
268 | shutdown_client(clnt: server->nlm_host->h_rpcclnt); |
269 | out: |
270 | return count; |
271 | } |
272 | |
273 | static struct kobj_attribute nfs_sysfs_attr_shutdown = __ATTR_RW(shutdown); |
274 | |
275 | #define RPC_CLIENT_NAME_SIZE 64 |
276 | |
277 | void nfs_sysfs_link_rpc_client(struct nfs_server *server, |
278 | struct rpc_clnt *clnt, const char *uniq) |
279 | { |
280 | char name[RPC_CLIENT_NAME_SIZE]; |
281 | int ret; |
282 | |
283 | strcpy(p: name, q: clnt->cl_program->name); |
284 | strcat(p: name, q: uniq ? uniq : "" ); |
285 | strcat(p: name, q: "_client" ); |
286 | |
287 | ret = sysfs_create_link_nowarn(kobj: &server->kobj, |
288 | target: &clnt->cl_sysfs->kobject, name); |
289 | if (ret < 0) |
290 | pr_warn("NFS: can't create link to %s in sysfs (%d)\n" , |
291 | name, ret); |
292 | } |
293 | EXPORT_SYMBOL_GPL(nfs_sysfs_link_rpc_client); |
294 | |
295 | static void nfs_sysfs_sb_release(struct kobject *kobj) |
296 | { |
297 | /* no-op: why? see lib/kobject.c kobject_cleanup() */ |
298 | } |
299 | |
300 | static const void *nfs_netns_server_namespace(const struct kobject *kobj) |
301 | { |
302 | return container_of(kobj, struct nfs_server, kobj)->nfs_client->cl_net; |
303 | } |
304 | |
305 | static struct kobj_type nfs_sb_ktype = { |
306 | .release = nfs_sysfs_sb_release, |
307 | .sysfs_ops = &kobj_sysfs_ops, |
308 | .namespace = nfs_netns_server_namespace, |
309 | .child_ns_type = nfs_netns_object_child_ns_type, |
310 | }; |
311 | |
312 | void nfs_sysfs_add_server(struct nfs_server *server) |
313 | { |
314 | int ret; |
315 | |
316 | ret = kobject_init_and_add(kobj: &server->kobj, ktype: &nfs_sb_ktype, |
317 | parent: &nfs_kset->kobj, fmt: "server-%d" , server->s_sysfs_id); |
318 | if (ret < 0) { |
319 | pr_warn("NFS: nfs sysfs add server-%d failed (%d)\n" , |
320 | server->s_sysfs_id, ret); |
321 | return; |
322 | } |
323 | ret = sysfs_create_file_ns(kobj: &server->kobj, attr: &nfs_sysfs_attr_shutdown.attr, |
324 | ns: nfs_netns_server_namespace(kobj: &server->kobj)); |
325 | if (ret < 0) |
326 | pr_warn("NFS: sysfs_create_file_ns for server-%d failed (%d)\n" , |
327 | server->s_sysfs_id, ret); |
328 | } |
329 | EXPORT_SYMBOL_GPL(nfs_sysfs_add_server); |
330 | |
331 | void nfs_sysfs_move_server_to_sb(struct super_block *s) |
332 | { |
333 | struct nfs_server *server = s->s_fs_info; |
334 | int ret; |
335 | |
336 | ret = kobject_rename(&server->kobj, new_name: s->s_id); |
337 | if (ret < 0) |
338 | pr_warn("NFS: rename sysfs %s failed (%d)\n" , |
339 | server->kobj.name, ret); |
340 | } |
341 | |
342 | void nfs_sysfs_move_sb_to_server(struct nfs_server *server) |
343 | { |
344 | const char *s; |
345 | int ret = -ENOMEM; |
346 | |
347 | s = kasprintf(GFP_KERNEL, fmt: "server-%d" , server->s_sysfs_id); |
348 | if (s) { |
349 | ret = kobject_rename(&server->kobj, new_name: s); |
350 | kfree(objp: s); |
351 | } |
352 | if (ret < 0) |
353 | pr_warn("NFS: rename sysfs %s failed (%d)\n" , |
354 | server->kobj.name, ret); |
355 | } |
356 | |
357 | /* unlink, not dec-ref */ |
358 | void nfs_sysfs_remove_server(struct nfs_server *server) |
359 | { |
360 | kobject_del(kobj: &server->kobj); |
361 | } |
362 | |