1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2012 Bryan Schumaker <bjschuma@netapp.com> |
4 | */ |
5 | #include <linux/init.h> |
6 | #include <linux/module.h> |
7 | #include <linux/mount.h> |
8 | #include <linux/nfs4_mount.h> |
9 | #include <linux/nfs_fs.h> |
10 | #include <linux/nfs_ssc.h> |
11 | #include "delegation.h" |
12 | #include "internal.h" |
13 | #include "nfs4_fs.h" |
14 | #include "nfs4idmap.h" |
15 | #include "dns_resolve.h" |
16 | #include "pnfs.h" |
17 | #include "nfs.h" |
18 | |
19 | #define NFSDBG_FACILITY NFSDBG_VFS |
20 | |
21 | static int nfs4_write_inode(struct inode *inode, struct writeback_control *wbc); |
22 | static void nfs4_evict_inode(struct inode *inode); |
23 | |
24 | static const struct super_operations nfs4_sops = { |
25 | .alloc_inode = nfs_alloc_inode, |
26 | .free_inode = nfs_free_inode, |
27 | .write_inode = nfs4_write_inode, |
28 | .drop_inode = nfs_drop_inode, |
29 | .statfs = nfs_statfs, |
30 | .evict_inode = nfs4_evict_inode, |
31 | .umount_begin = nfs_umount_begin, |
32 | .show_options = nfs_show_options, |
33 | .show_devname = nfs_show_devname, |
34 | .show_path = nfs_show_path, |
35 | .show_stats = nfs_show_stats, |
36 | }; |
37 | |
38 | struct nfs_subversion nfs_v4 = { |
39 | .owner = THIS_MODULE, |
40 | .nfs_fs = &nfs4_fs_type, |
41 | .rpc_vers = &nfs_version4, |
42 | .rpc_ops = &nfs_v4_clientops, |
43 | .sops = &nfs4_sops, |
44 | .xattr = nfs4_xattr_handlers, |
45 | }; |
46 | |
47 | static int nfs4_write_inode(struct inode *inode, struct writeback_control *wbc) |
48 | { |
49 | int ret = nfs_write_inode(inode, wbc); |
50 | |
51 | if (ret == 0) |
52 | ret = pnfs_layoutcommit_inode(inode, |
53 | sync: wbc->sync_mode == WB_SYNC_ALL); |
54 | return ret; |
55 | } |
56 | |
57 | /* |
58 | * Clean out any remaining NFSv4 state that might be left over due |
59 | * to open() calls that passed nfs_atomic_lookup, but failed to call |
60 | * nfs_open(). |
61 | */ |
62 | static void nfs4_evict_inode(struct inode *inode) |
63 | { |
64 | truncate_inode_pages_final(&inode->i_data); |
65 | clear_inode(inode); |
66 | /* If we are holding a delegation, return and free it */ |
67 | nfs_inode_evict_delegation(inode); |
68 | /* Note that above delegreturn would trigger pnfs return-on-close */ |
69 | pnfs_return_layout(ino: inode); |
70 | pnfs_destroy_layout_final(NFS_I(inode)); |
71 | /* First call standard NFS clear_inode() code */ |
72 | nfs_clear_inode(inode); |
73 | nfs4_xattr_cache_zap(inode); |
74 | } |
75 | |
76 | struct nfs_referral_count { |
77 | struct list_head list; |
78 | const struct task_struct *task; |
79 | unsigned int referral_count; |
80 | }; |
81 | |
82 | static LIST_HEAD(nfs_referral_count_list); |
83 | static DEFINE_SPINLOCK(nfs_referral_count_list_lock); |
84 | |
85 | static struct nfs_referral_count *nfs_find_referral_count(void) |
86 | { |
87 | struct nfs_referral_count *p; |
88 | |
89 | list_for_each_entry(p, &nfs_referral_count_list, list) { |
90 | if (p->task == current) |
91 | return p; |
92 | } |
93 | return NULL; |
94 | } |
95 | |
96 | #define NFS_MAX_NESTED_REFERRALS 2 |
97 | |
98 | static int nfs_referral_loop_protect(void) |
99 | { |
100 | struct nfs_referral_count *p, *new; |
101 | int ret = -ENOMEM; |
102 | |
103 | new = kmalloc(size: sizeof(*new), GFP_KERNEL); |
104 | if (!new) |
105 | goto out; |
106 | new->task = current; |
107 | new->referral_count = 1; |
108 | |
109 | ret = 0; |
110 | spin_lock(lock: &nfs_referral_count_list_lock); |
111 | p = nfs_find_referral_count(); |
112 | if (p != NULL) { |
113 | if (p->referral_count >= NFS_MAX_NESTED_REFERRALS) |
114 | ret = -ELOOP; |
115 | else |
116 | p->referral_count++; |
117 | } else { |
118 | list_add(new: &new->list, head: &nfs_referral_count_list); |
119 | new = NULL; |
120 | } |
121 | spin_unlock(lock: &nfs_referral_count_list_lock); |
122 | kfree(objp: new); |
123 | out: |
124 | return ret; |
125 | } |
126 | |
127 | static void nfs_referral_loop_unprotect(void) |
128 | { |
129 | struct nfs_referral_count *p; |
130 | |
131 | spin_lock(lock: &nfs_referral_count_list_lock); |
132 | p = nfs_find_referral_count(); |
133 | p->referral_count--; |
134 | if (p->referral_count == 0) |
135 | list_del(entry: &p->list); |
136 | else |
137 | p = NULL; |
138 | spin_unlock(lock: &nfs_referral_count_list_lock); |
139 | kfree(objp: p); |
140 | } |
141 | |
142 | static int do_nfs4_mount(struct nfs_server *server, |
143 | struct fs_context *fc, |
144 | const char *hostname, |
145 | const char *export_path) |
146 | { |
147 | struct nfs_fs_context *root_ctx; |
148 | struct nfs_fs_context *ctx; |
149 | struct fs_context *root_fc; |
150 | struct vfsmount *root_mnt; |
151 | struct dentry *dentry; |
152 | size_t len; |
153 | int ret; |
154 | |
155 | struct fs_parameter param = { |
156 | .key = "source" , |
157 | .type = fs_value_is_string, |
158 | .dirfd = -1, |
159 | }; |
160 | |
161 | struct fs_parameter param_fsc = { |
162 | .key = "fsc" , |
163 | .type = fs_value_is_string, |
164 | .dirfd = -1, |
165 | }; |
166 | |
167 | if (IS_ERR(ptr: server)) |
168 | return PTR_ERR(ptr: server); |
169 | |
170 | root_fc = vfs_dup_fs_context(fc); |
171 | if (IS_ERR(ptr: root_fc)) { |
172 | nfs_free_server(server); |
173 | return PTR_ERR(ptr: root_fc); |
174 | } |
175 | kfree(objp: root_fc->source); |
176 | root_fc->source = NULL; |
177 | |
178 | ctx = nfs_fc2context(fc); |
179 | root_ctx = nfs_fc2context(fc: root_fc); |
180 | root_ctx->internal = true; |
181 | root_ctx->server = server; |
182 | |
183 | if (ctx->fscache_uniq) { |
184 | len = strlen(ctx->fscache_uniq); |
185 | param_fsc.size = len; |
186 | param_fsc.string = kmemdup_nul(s: ctx->fscache_uniq, len, GFP_KERNEL); |
187 | if (param_fsc.string == NULL) { |
188 | put_fs_context(fc: root_fc); |
189 | return -ENOMEM; |
190 | } |
191 | ret = vfs_parse_fs_param(fc: root_fc, param: ¶m_fsc); |
192 | kfree(objp: param_fsc.string); |
193 | if (ret < 0) { |
194 | put_fs_context(fc: root_fc); |
195 | return ret; |
196 | } |
197 | } |
198 | /* We leave export_path unset as it's not used to find the root. */ |
199 | |
200 | len = strlen(hostname) + 5; |
201 | param.string = kmalloc(size: len, GFP_KERNEL); |
202 | if (param.string == NULL) { |
203 | put_fs_context(fc: root_fc); |
204 | return -ENOMEM; |
205 | } |
206 | |
207 | /* Does hostname needs to be enclosed in brackets? */ |
208 | if (strchr(hostname, ':')) |
209 | param.size = snprintf(buf: param.string, size: len, fmt: "[%s]:/" , hostname); |
210 | else |
211 | param.size = snprintf(buf: param.string, size: len, fmt: "%s:/" , hostname); |
212 | ret = vfs_parse_fs_param(fc: root_fc, param: ¶m); |
213 | kfree(objp: param.string); |
214 | if (ret < 0) { |
215 | put_fs_context(fc: root_fc); |
216 | return ret; |
217 | } |
218 | root_mnt = fc_mount(fc: root_fc); |
219 | put_fs_context(fc: root_fc); |
220 | |
221 | if (IS_ERR(ptr: root_mnt)) |
222 | return PTR_ERR(ptr: root_mnt); |
223 | |
224 | ret = nfs_referral_loop_protect(); |
225 | if (ret) { |
226 | mntput(mnt: root_mnt); |
227 | return ret; |
228 | } |
229 | |
230 | dentry = mount_subtree(mnt: root_mnt, path: export_path); |
231 | nfs_referral_loop_unprotect(); |
232 | |
233 | if (IS_ERR(ptr: dentry)) |
234 | return PTR_ERR(ptr: dentry); |
235 | |
236 | fc->root = dentry; |
237 | return 0; |
238 | } |
239 | |
240 | int nfs4_try_get_tree(struct fs_context *fc) |
241 | { |
242 | struct nfs_fs_context *ctx = nfs_fc2context(fc); |
243 | int err; |
244 | |
245 | dfprintk(MOUNT, "--> nfs4_try_get_tree()\n" ); |
246 | |
247 | /* We create a mount for the server's root, walk to the requested |
248 | * location and then create another mount for that. |
249 | */ |
250 | err= do_nfs4_mount(server: nfs4_create_server(fc), |
251 | fc, hostname: ctx->nfs_server.hostname, |
252 | export_path: ctx->nfs_server.export_path); |
253 | if (err) { |
254 | nfs_ferrorf(fc, MOUNT, "NFS4: Couldn't follow remote path" ); |
255 | dfprintk(MOUNT, "<-- nfs4_try_get_tree() = %d [error]\n" , err); |
256 | } else { |
257 | dfprintk(MOUNT, "<-- nfs4_try_get_tree() = 0\n" ); |
258 | } |
259 | return err; |
260 | } |
261 | |
262 | /* |
263 | * Create an NFS4 server record on referral traversal |
264 | */ |
265 | int nfs4_get_referral_tree(struct fs_context *fc) |
266 | { |
267 | struct nfs_fs_context *ctx = nfs_fc2context(fc); |
268 | int err; |
269 | |
270 | dprintk("--> nfs4_referral_mount()\n" ); |
271 | |
272 | /* create a new volume representation */ |
273 | err = do_nfs4_mount(server: nfs4_create_referral_server(fc), |
274 | fc, hostname: ctx->nfs_server.hostname, |
275 | export_path: ctx->nfs_server.export_path); |
276 | if (err) { |
277 | nfs_ferrorf(fc, MOUNT, "NFS4: Couldn't follow remote path" ); |
278 | dfprintk(MOUNT, "<-- nfs4_get_referral_tree() = %d [error]\n" , err); |
279 | } else { |
280 | dfprintk(MOUNT, "<-- nfs4_get_referral_tree() = 0\n" ); |
281 | } |
282 | return err; |
283 | } |
284 | |
285 | static int __init init_nfs_v4(void) |
286 | { |
287 | int err; |
288 | |
289 | err = nfs_dns_resolver_init(); |
290 | if (err) |
291 | goto out; |
292 | |
293 | err = nfs_idmap_init(); |
294 | if (err) |
295 | goto out1; |
296 | |
297 | #ifdef CONFIG_NFS_V4_2 |
298 | err = nfs4_xattr_cache_init(); |
299 | if (err) |
300 | goto out2; |
301 | #endif |
302 | |
303 | err = nfs4_register_sysctl(); |
304 | if (err) |
305 | goto out2; |
306 | |
307 | #ifdef CONFIG_NFS_V4_2 |
308 | nfs42_ssc_register_ops(); |
309 | #endif |
310 | register_nfs_version(&nfs_v4); |
311 | return 0; |
312 | out2: |
313 | nfs_idmap_quit(); |
314 | out1: |
315 | nfs_dns_resolver_destroy(); |
316 | out: |
317 | return err; |
318 | } |
319 | |
320 | static void __exit exit_nfs_v4(void) |
321 | { |
322 | /* Not called in the _init(), conditionally loaded */ |
323 | nfs4_pnfs_v3_ds_connect_unload(); |
324 | |
325 | unregister_nfs_version(&nfs_v4); |
326 | #ifdef CONFIG_NFS_V4_2 |
327 | nfs4_xattr_cache_exit(); |
328 | nfs42_ssc_unregister_ops(); |
329 | #endif |
330 | nfs4_unregister_sysctl(); |
331 | nfs_idmap_quit(); |
332 | nfs_dns_resolver_destroy(); |
333 | } |
334 | |
335 | MODULE_LICENSE("GPL" ); |
336 | |
337 | module_init(init_nfs_v4); |
338 | module_exit(exit_nfs_v4); |
339 | |