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 fs_context *root_fc; |
149 | struct vfsmount *root_mnt; |
150 | struct dentry *dentry; |
151 | size_t len; |
152 | int ret; |
153 | |
154 | struct fs_parameter param = { |
155 | .key = "source" , |
156 | .type = fs_value_is_string, |
157 | .dirfd = -1, |
158 | }; |
159 | |
160 | if (IS_ERR(ptr: server)) |
161 | return PTR_ERR(ptr: server); |
162 | |
163 | root_fc = vfs_dup_fs_context(fc); |
164 | if (IS_ERR(ptr: root_fc)) { |
165 | nfs_free_server(server); |
166 | return PTR_ERR(ptr: root_fc); |
167 | } |
168 | kfree(objp: root_fc->source); |
169 | root_fc->source = NULL; |
170 | |
171 | root_ctx = nfs_fc2context(fc: root_fc); |
172 | root_ctx->internal = true; |
173 | root_ctx->server = server; |
174 | /* We leave export_path unset as it's not used to find the root. */ |
175 | |
176 | len = strlen(hostname) + 5; |
177 | param.string = kmalloc(size: len, GFP_KERNEL); |
178 | if (param.string == NULL) { |
179 | put_fs_context(fc: root_fc); |
180 | return -ENOMEM; |
181 | } |
182 | |
183 | /* Does hostname needs to be enclosed in brackets? */ |
184 | if (strchr(hostname, ':')) |
185 | param.size = snprintf(buf: param.string, size: len, fmt: "[%s]:/" , hostname); |
186 | else |
187 | param.size = snprintf(buf: param.string, size: len, fmt: "%s:/" , hostname); |
188 | ret = vfs_parse_fs_param(fc: root_fc, param: ¶m); |
189 | kfree(objp: param.string); |
190 | if (ret < 0) { |
191 | put_fs_context(fc: root_fc); |
192 | return ret; |
193 | } |
194 | root_mnt = fc_mount(fc: root_fc); |
195 | put_fs_context(fc: root_fc); |
196 | |
197 | if (IS_ERR(ptr: root_mnt)) |
198 | return PTR_ERR(ptr: root_mnt); |
199 | |
200 | ret = nfs_referral_loop_protect(); |
201 | if (ret) { |
202 | mntput(mnt: root_mnt); |
203 | return ret; |
204 | } |
205 | |
206 | dentry = mount_subtree(mnt: root_mnt, path: export_path); |
207 | nfs_referral_loop_unprotect(); |
208 | |
209 | if (IS_ERR(ptr: dentry)) |
210 | return PTR_ERR(ptr: dentry); |
211 | |
212 | fc->root = dentry; |
213 | return 0; |
214 | } |
215 | |
216 | int nfs4_try_get_tree(struct fs_context *fc) |
217 | { |
218 | struct nfs_fs_context *ctx = nfs_fc2context(fc); |
219 | int err; |
220 | |
221 | dfprintk(MOUNT, "--> nfs4_try_get_tree()\n" ); |
222 | |
223 | /* We create a mount for the server's root, walk to the requested |
224 | * location and then create another mount for that. |
225 | */ |
226 | err= do_nfs4_mount(server: nfs4_create_server(fc), |
227 | fc, hostname: ctx->nfs_server.hostname, |
228 | export_path: ctx->nfs_server.export_path); |
229 | if (err) { |
230 | nfs_ferrorf(fc, MOUNT, "NFS4: Couldn't follow remote path" ); |
231 | dfprintk(MOUNT, "<-- nfs4_try_get_tree() = %d [error]\n" , err); |
232 | } else { |
233 | dfprintk(MOUNT, "<-- nfs4_try_get_tree() = 0\n" ); |
234 | } |
235 | return err; |
236 | } |
237 | |
238 | /* |
239 | * Create an NFS4 server record on referral traversal |
240 | */ |
241 | int nfs4_get_referral_tree(struct fs_context *fc) |
242 | { |
243 | struct nfs_fs_context *ctx = nfs_fc2context(fc); |
244 | int err; |
245 | |
246 | dprintk("--> nfs4_referral_mount()\n" ); |
247 | |
248 | /* create a new volume representation */ |
249 | err = do_nfs4_mount(server: nfs4_create_referral_server(fc), |
250 | fc, hostname: ctx->nfs_server.hostname, |
251 | export_path: ctx->nfs_server.export_path); |
252 | if (err) { |
253 | nfs_ferrorf(fc, MOUNT, "NFS4: Couldn't follow remote path" ); |
254 | dfprintk(MOUNT, "<-- nfs4_get_referral_tree() = %d [error]\n" , err); |
255 | } else { |
256 | dfprintk(MOUNT, "<-- nfs4_get_referral_tree() = 0\n" ); |
257 | } |
258 | return err; |
259 | } |
260 | |
261 | static int __init init_nfs_v4(void) |
262 | { |
263 | int err; |
264 | |
265 | err = nfs_dns_resolver_init(); |
266 | if (err) |
267 | goto out; |
268 | |
269 | err = nfs_idmap_init(); |
270 | if (err) |
271 | goto out1; |
272 | |
273 | #ifdef CONFIG_NFS_V4_2 |
274 | err = nfs4_xattr_cache_init(); |
275 | if (err) |
276 | goto out2; |
277 | #endif |
278 | |
279 | err = nfs4_register_sysctl(); |
280 | if (err) |
281 | goto out2; |
282 | |
283 | #ifdef CONFIG_NFS_V4_2 |
284 | nfs42_ssc_register_ops(); |
285 | #endif |
286 | register_nfs_version(&nfs_v4); |
287 | return 0; |
288 | out2: |
289 | nfs_idmap_quit(); |
290 | out1: |
291 | nfs_dns_resolver_destroy(); |
292 | out: |
293 | return err; |
294 | } |
295 | |
296 | static void __exit exit_nfs_v4(void) |
297 | { |
298 | /* Not called in the _init(), conditionally loaded */ |
299 | nfs4_pnfs_v3_ds_connect_unload(); |
300 | |
301 | unregister_nfs_version(&nfs_v4); |
302 | #ifdef CONFIG_NFS_V4_2 |
303 | nfs4_xattr_cache_exit(); |
304 | nfs42_ssc_unregister_ops(); |
305 | #endif |
306 | nfs4_unregister_sysctl(); |
307 | nfs_idmap_quit(); |
308 | nfs_dns_resolver_destroy(); |
309 | } |
310 | |
311 | MODULE_LICENSE("GPL" ); |
312 | |
313 | module_init(init_nfs_v4); |
314 | module_exit(exit_nfs_v4); |
315 | |