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 | ||
e49c7b2f DH |
15 | static void afs_silly_rename_success(struct afs_operation *op) |
16 | { | |
17 | _enter("op=%08x", op->debug_id); | |
18 | ||
19 | afs_vnode_commit_status(op, &op->file[0]); | |
20 | } | |
21 | ||
22 | static void afs_silly_rename_edit_dir(struct afs_operation *op) | |
23 | { | |
24 | struct afs_vnode_param *dvp = &op->file[0]; | |
25 | struct afs_vnode *dvnode = dvp->vnode; | |
26 | struct afs_vnode *vnode = AFS_FS_I(d_inode(op->dentry)); | |
27 | struct dentry *old = op->dentry; | |
28 | struct dentry *new = op->dentry_2; | |
29 | ||
30 | spin_lock(&old->d_lock); | |
31 | old->d_flags |= DCACHE_NFSFS_RENAMED; | |
32 | spin_unlock(&old->d_lock); | |
33 | if (dvnode->silly_key != op->key) { | |
34 | key_put(dvnode->silly_key); | |
35 | dvnode->silly_key = key_get(op->key); | |
36 | } | |
37 | ||
38 | down_write(&dvnode->validate_lock); | |
39 | if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) && | |
40 | dvnode->status.data_version == dvp->dv_before + dvp->dv_delta) { | |
41 | afs_edit_dir_remove(dvnode, &old->d_name, | |
42 | afs_edit_dir_for_silly_0); | |
43 | afs_edit_dir_add(dvnode, &new->d_name, | |
44 | &vnode->fid, afs_edit_dir_for_silly_1); | |
45 | } | |
46 | up_write(&dvnode->validate_lock); | |
47 | } | |
48 | ||
49 | static const struct afs_operation_ops afs_silly_rename_operation = { | |
50 | .issue_afs_rpc = afs_fs_rename, | |
51 | .issue_yfs_rpc = yfs_fs_rename, | |
52 | .success = afs_silly_rename_success, | |
53 | .edit_dir = afs_silly_rename_edit_dir, | |
54 | }; | |
55 | ||
79ddbfa5 DH |
56 | /* |
57 | * Actually perform the silly rename step. | |
58 | */ | |
59 | static int afs_do_silly_rename(struct afs_vnode *dvnode, struct afs_vnode *vnode, | |
60 | struct dentry *old, struct dentry *new, | |
61 | struct key *key) | |
62 | { | |
e49c7b2f | 63 | struct afs_operation *op; |
79ddbfa5 DH |
64 | |
65 | _enter("%pd,%pd", old, new); | |
66 | ||
e49c7b2f DH |
67 | op = afs_alloc_operation(key, dvnode->volume); |
68 | if (IS_ERR(op)) | |
69 | return PTR_ERR(op); | |
a58823ac | 70 | |
e49c7b2f | 71 | afs_op_set_vnode(op, 0, dvnode); |
79ddbfa5 | 72 | |
e49c7b2f DH |
73 | op->dentry = old; |
74 | op->dentry_2 = new; | |
75 | op->ops = &afs_silly_rename_operation; | |
79ddbfa5 | 76 | |
e49c7b2f DH |
77 | trace_afs_silly_rename(vnode, false); |
78 | return afs_do_sync_operation(op); | |
79ddbfa5 DH |
79 | } |
80 | ||
81 | /** | |
82 | * afs_sillyrename - Perform a silly-rename of a dentry | |
83 | * | |
84 | * AFS is stateless and the server doesn't know when the client is holding a | |
85 | * file open. To prevent application problems when a file is unlinked while | |
86 | * it's still open, the client performs a "silly-rename". That is, it renames | |
87 | * the file to a hidden file in the same directory, and only performs the | |
88 | * unlink once the last reference to it is put. | |
89 | * | |
90 | * The final cleanup is done during dentry_iput. | |
91 | */ | |
92 | int afs_sillyrename(struct afs_vnode *dvnode, struct afs_vnode *vnode, | |
93 | struct dentry *dentry, struct key *key) | |
94 | { | |
95 | static unsigned int sillycounter; | |
96 | struct dentry *sdentry = NULL; | |
97 | unsigned char silly[16]; | |
98 | int ret = -EBUSY; | |
99 | ||
100 | _enter(""); | |
101 | ||
102 | /* We don't allow a dentry to be silly-renamed twice. */ | |
103 | if (dentry->d_flags & DCACHE_NFSFS_RENAMED) | |
104 | return -EBUSY; | |
105 | ||
106 | sdentry = NULL; | |
107 | do { | |
108 | int slen; | |
109 | ||
110 | dput(sdentry); | |
111 | sillycounter++; | |
112 | ||
113 | /* Create a silly name. Note that the ".__afs" prefix is | |
114 | * understood by the salvager and must not be changed. | |
115 | */ | |
116 | slen = scnprintf(silly, sizeof(silly), ".__afs%04X", sillycounter); | |
117 | sdentry = lookup_one_len(silly, dentry->d_parent, slen); | |
118 | ||
119 | /* N.B. Better to return EBUSY here ... it could be dangerous | |
120 | * to delete the file while it's in use. | |
121 | */ | |
122 | if (IS_ERR(sdentry)) | |
123 | goto out; | |
124 | } while (!d_is_negative(sdentry)); | |
125 | ||
126 | ihold(&vnode->vfs_inode); | |
127 | ||
128 | ret = afs_do_silly_rename(dvnode, vnode, dentry, sdentry, key); | |
129 | switch (ret) { | |
130 | case 0: | |
131 | /* The rename succeeded. */ | |
132 | d_move(dentry, sdentry); | |
133 | break; | |
134 | case -ERESTARTSYS: | |
135 | /* The result of the rename is unknown. Play it safe by forcing | |
136 | * a new lookup. | |
137 | */ | |
138 | d_drop(dentry); | |
139 | d_drop(sdentry); | |
140 | } | |
141 | ||
142 | iput(&vnode->vfs_inode); | |
143 | dput(sdentry); | |
144 | out: | |
145 | _leave(" = %d", ret); | |
146 | return ret; | |
147 | } | |
148 | ||
e49c7b2f DH |
149 | static void afs_silly_unlink_success(struct afs_operation *op) |
150 | { | |
151 | struct afs_vnode *vnode = op->file[1].vnode; | |
152 | ||
153 | _enter("op=%08x", op->debug_id); | |
154 | afs_check_for_remote_deletion(op, op->file[0].vnode); | |
155 | afs_vnode_commit_status(op, &op->file[0]); | |
156 | afs_vnode_commit_status(op, &op->file[1]); | |
157 | afs_update_dentry_version(op, &op->file[0], op->dentry); | |
158 | ||
159 | drop_nlink(&vnode->vfs_inode); | |
160 | if (vnode->vfs_inode.i_nlink == 0) { | |
161 | set_bit(AFS_VNODE_DELETED, &vnode->flags); | |
162 | clear_bit(AFS_VNODE_CB_PROMISED, &vnode->flags); | |
163 | } | |
164 | } | |
165 | ||
166 | static void afs_silly_unlink_edit_dir(struct afs_operation *op) | |
167 | { | |
168 | struct afs_vnode_param *dvp = &op->file[0]; | |
169 | struct afs_vnode *dvnode = dvp->vnode; | |
170 | ||
171 | _enter("op=%08x", op->debug_id); | |
172 | down_write(&dvnode->validate_lock); | |
173 | if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) && | |
174 | dvnode->status.data_version == dvp->dv_before + dvp->dv_delta) | |
175 | afs_edit_dir_remove(dvnode, &op->dentry->d_name, | |
176 | afs_edit_dir_for_unlink); | |
177 | up_write(&dvnode->validate_lock); | |
178 | } | |
179 | ||
180 | static const struct afs_operation_ops afs_silly_unlink_operation = { | |
181 | .issue_afs_rpc = afs_fs_remove_file, | |
182 | .issue_yfs_rpc = yfs_fs_remove_file, | |
183 | .success = afs_silly_unlink_success, | |
184 | .edit_dir = afs_silly_unlink_edit_dir, | |
185 | }; | |
186 | ||
79ddbfa5 DH |
187 | /* |
188 | * Tell the server to remove a sillyrename file. | |
189 | */ | |
190 | static int afs_do_silly_unlink(struct afs_vnode *dvnode, struct afs_vnode *vnode, | |
191 | struct dentry *dentry, struct key *key) | |
192 | { | |
e49c7b2f | 193 | struct afs_operation *op; |
79ddbfa5 DH |
194 | |
195 | _enter(""); | |
196 | ||
e49c7b2f DH |
197 | op = afs_alloc_operation(NULL, dvnode->volume); |
198 | if (IS_ERR(op)) | |
199 | return PTR_ERR(op); | |
a58823ac | 200 | |
e49c7b2f DH |
201 | afs_op_set_vnode(op, 0, dvnode); |
202 | afs_op_set_vnode(op, 1, vnode); | |
79ddbfa5 | 203 | |
e49c7b2f DH |
204 | op->dentry = dentry; |
205 | op->ops = &afs_silly_unlink_operation; | |
206 | ||
207 | trace_afs_silly_rename(vnode, true); | |
208 | return afs_do_sync_operation(op); | |
79ddbfa5 DH |
209 | } |
210 | ||
211 | /* | |
212 | * Remove sillyrename file on iput. | |
213 | */ | |
214 | int afs_silly_iput(struct dentry *dentry, struct inode *inode) | |
215 | { | |
216 | struct afs_vnode *dvnode = AFS_FS_I(d_inode(dentry->d_parent)); | |
217 | struct afs_vnode *vnode = AFS_FS_I(inode); | |
218 | struct dentry *alias; | |
219 | int ret; | |
220 | ||
221 | DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); | |
222 | ||
223 | _enter("%p{%pd},%llx", dentry, dentry, vnode->fid.vnode); | |
224 | ||
225 | down_read(&dvnode->rmdir_lock); | |
226 | ||
227 | alias = d_alloc_parallel(dentry->d_parent, &dentry->d_name, &wq); | |
228 | if (IS_ERR(alias)) { | |
229 | up_read(&dvnode->rmdir_lock); | |
230 | return 0; | |
231 | } | |
232 | ||
233 | if (!d_in_lookup(alias)) { | |
234 | /* We raced with lookup... See if we need to transfer the | |
235 | * sillyrename information to the aliased dentry. | |
236 | */ | |
237 | ret = 0; | |
238 | spin_lock(&alias->d_lock); | |
239 | if (d_really_is_positive(alias) && | |
240 | !(alias->d_flags & DCACHE_NFSFS_RENAMED)) { | |
241 | alias->d_flags |= DCACHE_NFSFS_RENAMED; | |
242 | ret = 1; | |
243 | } | |
244 | spin_unlock(&alias->d_lock); | |
245 | up_read(&dvnode->rmdir_lock); | |
246 | dput(alias); | |
247 | return ret; | |
248 | } | |
249 | ||
250 | /* Stop lock-release from complaining. */ | |
251 | spin_lock(&vnode->lock); | |
252 | vnode->lock_state = AFS_VNODE_LOCK_DELETED; | |
253 | trace_afs_flock_ev(vnode, NULL, afs_flock_silly_delete, 0); | |
254 | spin_unlock(&vnode->lock); | |
255 | ||
256 | afs_do_silly_unlink(dvnode, vnode, dentry, dvnode->silly_key); | |
257 | up_read(&dvnode->rmdir_lock); | |
258 | d_lookup_done(alias); | |
259 | dput(alias); | |
260 | return 1; | |
261 | } |