Commit | Line | Data |
---|---|---|
b4d0d230 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
79ddbfa5 DH |
2 | /* AFS silly rename handling |
3 | * | |
4 | * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved. | |
5 | * Written by David Howells (dhowells@redhat.com) | |
6 | * - Derived from NFS's sillyrename. | |
79ddbfa5 DH |
7 | */ |
8 | ||
9 | #include <linux/kernel.h> | |
10 | #include <linux/fs.h> | |
11 | #include <linux/namei.h> | |
12 | #include <linux/fsnotify.h> | |
13 | #include "internal.h" | |
14 | ||
15 | /* | |
16 | * Actually perform the silly rename step. | |
17 | */ | |
18 | static int afs_do_silly_rename(struct afs_vnode *dvnode, struct afs_vnode *vnode, | |
19 | struct dentry *old, struct dentry *new, | |
20 | struct key *key) | |
21 | { | |
22 | struct afs_fs_cursor fc; | |
a58823ac | 23 | struct afs_status_cb *scb; |
79ddbfa5 DH |
24 | int ret = -ERESTARTSYS; |
25 | ||
26 | _enter("%pd,%pd", old, new); | |
27 | ||
a58823ac DH |
28 | scb = kzalloc(sizeof(struct afs_status_cb), GFP_KERNEL); |
29 | if (!scb) | |
30 | return -ENOMEM; | |
31 | ||
79ddbfa5 | 32 | trace_afs_silly_rename(vnode, false); |
20b8391f | 33 | if (afs_begin_vnode_operation(&fc, dvnode, key, true)) { |
a58823ac DH |
34 | afs_dataversion_t dir_data_version = dvnode->status.data_version + 1; |
35 | ||
79ddbfa5 DH |
36 | while (afs_select_fileserver(&fc)) { |
37 | fc.cb_break = afs_calc_vnode_cb_break(dvnode); | |
38 | afs_fs_rename(&fc, old->d_name.name, | |
39 | dvnode, new->d_name.name, | |
a58823ac | 40 | scb, scb); |
79ddbfa5 DH |
41 | } |
42 | ||
a58823ac DH |
43 | afs_vnode_commit_status(&fc, dvnode, fc.cb_break, |
44 | &dir_data_version, scb); | |
79ddbfa5 DH |
45 | ret = afs_end_vnode_operation(&fc); |
46 | } | |
47 | ||
48 | if (ret == 0) { | |
49 | spin_lock(&old->d_lock); | |
50 | old->d_flags |= DCACHE_NFSFS_RENAMED; | |
51 | spin_unlock(&old->d_lock); | |
52 | if (dvnode->silly_key != key) { | |
53 | key_put(dvnode->silly_key); | |
54 | dvnode->silly_key = key_get(key); | |
55 | } | |
56 | ||
57 | if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags)) | |
58 | afs_edit_dir_remove(dvnode, &old->d_name, | |
59 | afs_edit_dir_for_silly_0); | |
60 | if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags)) | |
61 | afs_edit_dir_add(dvnode, &new->d_name, | |
62 | &vnode->fid, afs_edit_dir_for_silly_1); | |
79ddbfa5 DH |
63 | } |
64 | ||
a58823ac | 65 | kfree(scb); |
79ddbfa5 DH |
66 | _leave(" = %d", ret); |
67 | return ret; | |
68 | } | |
69 | ||
70 | /** | |
71 | * afs_sillyrename - Perform a silly-rename of a dentry | |
72 | * | |
73 | * AFS is stateless and the server doesn't know when the client is holding a | |
74 | * file open. To prevent application problems when a file is unlinked while | |
75 | * it's still open, the client performs a "silly-rename". That is, it renames | |
76 | * the file to a hidden file in the same directory, and only performs the | |
77 | * unlink once the last reference to it is put. | |
78 | * | |
79 | * The final cleanup is done during dentry_iput. | |
80 | */ | |
81 | int afs_sillyrename(struct afs_vnode *dvnode, struct afs_vnode *vnode, | |
82 | struct dentry *dentry, struct key *key) | |
83 | { | |
84 | static unsigned int sillycounter; | |
85 | struct dentry *sdentry = NULL; | |
86 | unsigned char silly[16]; | |
87 | int ret = -EBUSY; | |
88 | ||
89 | _enter(""); | |
90 | ||
91 | /* We don't allow a dentry to be silly-renamed twice. */ | |
92 | if (dentry->d_flags & DCACHE_NFSFS_RENAMED) | |
93 | return -EBUSY; | |
94 | ||
95 | sdentry = NULL; | |
96 | do { | |
97 | int slen; | |
98 | ||
99 | dput(sdentry); | |
100 | sillycounter++; | |
101 | ||
102 | /* Create a silly name. Note that the ".__afs" prefix is | |
103 | * understood by the salvager and must not be changed. | |
104 | */ | |
105 | slen = scnprintf(silly, sizeof(silly), ".__afs%04X", sillycounter); | |
106 | sdentry = lookup_one_len(silly, dentry->d_parent, slen); | |
107 | ||
108 | /* N.B. Better to return EBUSY here ... it could be dangerous | |
109 | * to delete the file while it's in use. | |
110 | */ | |
111 | if (IS_ERR(sdentry)) | |
112 | goto out; | |
113 | } while (!d_is_negative(sdentry)); | |
114 | ||
115 | ihold(&vnode->vfs_inode); | |
116 | ||
117 | ret = afs_do_silly_rename(dvnode, vnode, dentry, sdentry, key); | |
118 | switch (ret) { | |
119 | case 0: | |
120 | /* The rename succeeded. */ | |
121 | d_move(dentry, sdentry); | |
122 | break; | |
123 | case -ERESTARTSYS: | |
124 | /* The result of the rename is unknown. Play it safe by forcing | |
125 | * a new lookup. | |
126 | */ | |
127 | d_drop(dentry); | |
128 | d_drop(sdentry); | |
129 | } | |
130 | ||
131 | iput(&vnode->vfs_inode); | |
132 | dput(sdentry); | |
133 | out: | |
134 | _leave(" = %d", ret); | |
135 | return ret; | |
136 | } | |
137 | ||
138 | /* | |
139 | * Tell the server to remove a sillyrename file. | |
140 | */ | |
141 | static int afs_do_silly_unlink(struct afs_vnode *dvnode, struct afs_vnode *vnode, | |
142 | struct dentry *dentry, struct key *key) | |
143 | { | |
144 | struct afs_fs_cursor fc; | |
a58823ac | 145 | struct afs_status_cb *scb; |
79ddbfa5 DH |
146 | int ret = -ERESTARTSYS; |
147 | ||
148 | _enter(""); | |
149 | ||
a58823ac DH |
150 | scb = kcalloc(2, sizeof(struct afs_status_cb), GFP_KERNEL); |
151 | if (!scb) | |
152 | return -ENOMEM; | |
153 | ||
79ddbfa5 | 154 | trace_afs_silly_rename(vnode, true); |
20b8391f | 155 | if (afs_begin_vnode_operation(&fc, dvnode, key, false)) { |
a58823ac DH |
156 | afs_dataversion_t dir_data_version = dvnode->status.data_version + 1; |
157 | ||
79ddbfa5 DH |
158 | while (afs_select_fileserver(&fc)) { |
159 | fc.cb_break = afs_calc_vnode_cb_break(dvnode); | |
160 | ||
161 | if (test_bit(AFS_SERVER_FL_IS_YFS, &fc.cbi->server->flags) && | |
162 | !test_bit(AFS_SERVER_FL_NO_RM2, &fc.cbi->server->flags)) { | |
163 | yfs_fs_remove_file2(&fc, vnode, dentry->d_name.name, | |
a58823ac | 164 | &scb[0], &scb[1]); |
79ddbfa5 DH |
165 | if (fc.ac.error != -ECONNABORTED || |
166 | fc.ac.abort_code != RXGEN_OPCODE) | |
167 | continue; | |
168 | set_bit(AFS_SERVER_FL_NO_RM2, &fc.cbi->server->flags); | |
169 | } | |
170 | ||
a58823ac | 171 | afs_fs_remove(&fc, vnode, dentry->d_name.name, false, &scb[0]); |
79ddbfa5 DH |
172 | } |
173 | ||
a58823ac DH |
174 | afs_vnode_commit_status(&fc, dvnode, fc.cb_break, |
175 | &dir_data_version, &scb[0]); | |
79ddbfa5 DH |
176 | ret = afs_end_vnode_operation(&fc); |
177 | if (ret == 0) { | |
178 | drop_nlink(&vnode->vfs_inode); | |
179 | if (vnode->vfs_inode.i_nlink == 0) { | |
180 | set_bit(AFS_VNODE_DELETED, &vnode->flags); | |
181 | clear_bit(AFS_VNODE_CB_PROMISED, &vnode->flags); | |
182 | } | |
183 | } | |
184 | if (ret == 0 && | |
185 | test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags)) | |
186 | afs_edit_dir_remove(dvnode, &dentry->d_name, | |
187 | afs_edit_dir_for_unlink); | |
188 | } | |
189 | ||
a58823ac | 190 | kfree(scb); |
79ddbfa5 DH |
191 | _leave(" = %d", ret); |
192 | return ret; | |
193 | } | |
194 | ||
195 | /* | |
196 | * Remove sillyrename file on iput. | |
197 | */ | |
198 | int afs_silly_iput(struct dentry *dentry, struct inode *inode) | |
199 | { | |
200 | struct afs_vnode *dvnode = AFS_FS_I(d_inode(dentry->d_parent)); | |
201 | struct afs_vnode *vnode = AFS_FS_I(inode); | |
202 | struct dentry *alias; | |
203 | int ret; | |
204 | ||
205 | DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); | |
206 | ||
207 | _enter("%p{%pd},%llx", dentry, dentry, vnode->fid.vnode); | |
208 | ||
209 | down_read(&dvnode->rmdir_lock); | |
210 | ||
211 | alias = d_alloc_parallel(dentry->d_parent, &dentry->d_name, &wq); | |
212 | if (IS_ERR(alias)) { | |
213 | up_read(&dvnode->rmdir_lock); | |
214 | return 0; | |
215 | } | |
216 | ||
217 | if (!d_in_lookup(alias)) { | |
218 | /* We raced with lookup... See if we need to transfer the | |
219 | * sillyrename information to the aliased dentry. | |
220 | */ | |
221 | ret = 0; | |
222 | spin_lock(&alias->d_lock); | |
223 | if (d_really_is_positive(alias) && | |
224 | !(alias->d_flags & DCACHE_NFSFS_RENAMED)) { | |
225 | alias->d_flags |= DCACHE_NFSFS_RENAMED; | |
226 | ret = 1; | |
227 | } | |
228 | spin_unlock(&alias->d_lock); | |
229 | up_read(&dvnode->rmdir_lock); | |
230 | dput(alias); | |
231 | return ret; | |
232 | } | |
233 | ||
234 | /* Stop lock-release from complaining. */ | |
235 | spin_lock(&vnode->lock); | |
236 | vnode->lock_state = AFS_VNODE_LOCK_DELETED; | |
237 | trace_afs_flock_ev(vnode, NULL, afs_flock_silly_delete, 0); | |
238 | spin_unlock(&vnode->lock); | |
239 | ||
240 | afs_do_silly_unlink(dvnode, vnode, dentry, dvnode->silly_key); | |
241 | up_read(&dvnode->rmdir_lock); | |
242 | d_lookup_done(alias); | |
243 | dput(alias); | |
244 | return 1; | |
245 | } |