Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/fs/afs/symlink.c
216529 views
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/* AFS filesystem symbolic link handling
3
*
4
* Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
5
* Written by David Howells ([email protected])
6
*/
7
8
#include <linux/kernel.h>
9
#include <linux/fs.h>
10
#include <linux/namei.h>
11
#include <linux/pagemap.h>
12
#include <linux/iov_iter.h>
13
#include "internal.h"
14
15
static void afs_put_symlink(struct afs_symlink *symlink)
16
{
17
if (refcount_dec_and_test(&symlink->ref))
18
kfree_rcu(symlink, rcu);
19
}
20
21
static void afs_replace_symlink(struct afs_vnode *vnode, struct afs_symlink *symlink)
22
{
23
struct afs_symlink *old;
24
25
old = rcu_replace_pointer(vnode->symlink, symlink,
26
lockdep_is_held(&vnode->validate_lock));
27
if (old)
28
afs_put_symlink(old);
29
}
30
31
/*
32
* In the event that a third-party update of a symlink occurs, dispose of the
33
* copy of the old contents. Called under ->validate_lock.
34
*/
35
void afs_invalidate_symlink(struct afs_vnode *vnode)
36
{
37
afs_replace_symlink(vnode, NULL);
38
}
39
40
/*
41
* Dispose of a symlink copy during inode deletion.
42
*/
43
void afs_evict_symlink(struct afs_vnode *vnode)
44
{
45
struct afs_symlink *old;
46
47
old = rcu_replace_pointer(vnode->symlink, NULL, true);
48
if (old)
49
afs_put_symlink(old);
50
51
}
52
53
/*
54
* Set up a locally created symlink inode for immediate write to the cache.
55
*/
56
void afs_init_new_symlink(struct afs_vnode *vnode, struct afs_operation *op)
57
{
58
struct afs_symlink *symlink = op->create.symlink;
59
size_t dsize = 0;
60
size_t size = strlen(symlink->content) + 1;
61
char *p;
62
63
rcu_assign_pointer(vnode->symlink, symlink);
64
op->create.symlink = NULL;
65
66
if (!fscache_cookie_enabled(netfs_i_cookie(&vnode->netfs)))
67
return;
68
69
if (netfs_alloc_folioq_buffer(NULL, &vnode->directory, &dsize, size,
70
mapping_gfp_mask(vnode->netfs.inode.i_mapping)) < 0)
71
return;
72
73
vnode->directory_size = dsize;
74
p = kmap_local_folio(folioq_folio(vnode->directory, 0), 0);
75
memcpy(p, symlink->content, size);
76
kunmap_local(p);
77
netfs_single_mark_inode_dirty(&vnode->netfs.inode);
78
}
79
80
/*
81
* Read a symlink in a single download.
82
*/
83
static ssize_t afs_do_read_symlink(struct afs_vnode *vnode)
84
{
85
struct afs_symlink *symlink;
86
struct iov_iter iter;
87
ssize_t ret;
88
loff_t i_size;
89
90
i_size = i_size_read(&vnode->netfs.inode);
91
if (i_size > PAGE_SIZE - 1) {
92
trace_afs_file_error(vnode, -EFBIG, afs_file_error_dir_big);
93
return -EFBIG;
94
}
95
96
if (!vnode->directory) {
97
size_t cur_size = 0;
98
99
ret = netfs_alloc_folioq_buffer(NULL,
100
&vnode->directory, &cur_size, PAGE_SIZE,
101
mapping_gfp_mask(vnode->netfs.inode.i_mapping));
102
vnode->directory_size = PAGE_SIZE - 1;
103
if (ret < 0)
104
return ret;
105
}
106
107
iov_iter_folio_queue(&iter, ITER_DEST, vnode->directory, 0, 0, PAGE_SIZE);
108
109
/* AFS requires us to perform the read of a symlink as a single unit to
110
* avoid issues with the content being changed between reads.
111
*/
112
ret = netfs_read_single(&vnode->netfs.inode, NULL, &iter);
113
if (ret >= 0) {
114
i_size = ret;
115
if (i_size > PAGE_SIZE - 1) {
116
trace_afs_file_error(vnode, -EFBIG, afs_file_error_dir_big);
117
return -EFBIG;
118
}
119
vnode->directory_size = i_size;
120
121
/* Copy the symlink. */
122
symlink = kmalloc_flex(struct afs_symlink, content, i_size + 1,
123
GFP_KERNEL);
124
if (!symlink)
125
return -ENOMEM;
126
127
refcount_set(&symlink->ref, 1);
128
symlink->content[i_size] = 0;
129
130
const char *s = kmap_local_folio(folioq_folio(vnode->directory, 0), 0);
131
132
memcpy(symlink->content, s, i_size);
133
kunmap_local(s);
134
135
afs_replace_symlink(vnode, symlink);
136
}
137
138
if (!fscache_cookie_enabled(netfs_i_cookie(&vnode->netfs))) {
139
netfs_free_folioq_buffer(vnode->directory);
140
vnode->directory = NULL;
141
vnode->directory_size = 0;
142
}
143
144
return ret;
145
}
146
147
static ssize_t afs_read_symlink(struct afs_vnode *vnode)
148
{
149
ssize_t ret;
150
151
fscache_use_cookie(afs_vnode_cache(vnode), false);
152
ret = afs_do_read_symlink(vnode);
153
fscache_unuse_cookie(afs_vnode_cache(vnode), NULL, NULL);
154
return ret;
155
}
156
157
static void afs_put_link(void *arg)
158
{
159
afs_put_symlink(arg);
160
}
161
162
const char *afs_get_link(struct dentry *dentry, struct inode *inode,
163
struct delayed_call *callback)
164
{
165
struct afs_symlink *symlink;
166
struct afs_vnode *vnode = AFS_FS_I(inode);
167
ssize_t ret;
168
169
if (!dentry) {
170
/* RCU pathwalk. */
171
symlink = rcu_dereference(vnode->symlink);
172
if (!symlink || !afs_check_validity(vnode))
173
return ERR_PTR(-ECHILD);
174
set_delayed_call(callback, NULL, NULL);
175
return symlink->content;
176
}
177
178
if (vnode->symlink) {
179
ret = afs_validate(vnode, NULL);
180
if (ret < 0)
181
return ERR_PTR(ret);
182
183
down_read(&vnode->validate_lock);
184
if (vnode->symlink)
185
goto good;
186
up_read(&vnode->validate_lock);
187
}
188
189
if (down_write_killable(&vnode->validate_lock) < 0)
190
return ERR_PTR(-ERESTARTSYS);
191
if (!vnode->symlink) {
192
ret = afs_read_symlink(vnode);
193
if (ret < 0) {
194
up_write(&vnode->validate_lock);
195
return ERR_PTR(ret);
196
}
197
}
198
199
downgrade_write(&vnode->validate_lock);
200
201
good:
202
symlink = rcu_dereference_protected(vnode->symlink,
203
lockdep_is_held(&vnode->validate_lock));
204
refcount_inc(&symlink->ref);
205
up_read(&vnode->validate_lock);
206
207
set_delayed_call(callback, afs_put_link, symlink);
208
return symlink->content;
209
}
210
211
int afs_readlink(struct dentry *dentry, char __user *buffer, int buflen)
212
{
213
DEFINE_DELAYED_CALL(done);
214
const char *content;
215
int len;
216
217
content = afs_get_link(dentry, d_inode(dentry), &done);
218
if (IS_ERR(content)) {
219
do_delayed_call(&done);
220
return PTR_ERR(content);
221
}
222
223
len = umin(strlen(content), buflen);
224
if (copy_to_user(buffer, content, len))
225
len = -EFAULT;
226
do_delayed_call(&done);
227
return len;
228
}
229
230
/*
231
* Write the symlink contents to the cache as a single blob. We then throw
232
* away the page we used to receive it.
233
*/
234
int afs_symlink_writepages(struct address_space *mapping,
235
struct writeback_control *wbc)
236
{
237
struct afs_vnode *vnode = AFS_FS_I(mapping->host);
238
struct iov_iter iter;
239
int ret = 0;
240
241
if (!down_read_trylock(&vnode->validate_lock)) {
242
if (wbc->sync_mode == WB_SYNC_NONE) {
243
/* The VFS will have undirtied the inode. */
244
netfs_single_mark_inode_dirty(&vnode->netfs.inode);
245
return 0;
246
}
247
down_read(&vnode->validate_lock);
248
}
249
250
if (vnode->directory &&
251
atomic64_read(&vnode->cb_expires_at) != AFS_NO_CB_PROMISE) {
252
iov_iter_folio_queue(&iter, ITER_SOURCE, vnode->directory, 0, 0,
253
i_size_read(&vnode->netfs.inode));
254
ret = netfs_writeback_single(mapping, wbc, &iter);
255
}
256
257
if (ret == 0) {
258
mutex_lock(&vnode->netfs.wb_lock);
259
netfs_free_folioq_buffer(vnode->directory);
260
vnode->directory = NULL;
261
vnode->directory_size = 0;
262
mutex_unlock(&vnode->netfs.wb_lock);
263
} else if (ret == 1) {
264
ret = 0; /* Skipped write due to lock conflict. */
265
}
266
267
up_read(&vnode->validate_lock);
268
return ret;
269
}
270
271
const struct inode_operations afs_symlink_inode_operations = {
272
.get_link = afs_get_link,
273
.readlink = afs_readlink,
274
};
275
276
const struct address_space_operations afs_symlink_aops = {
277
.writepages = afs_symlink_writepages,
278
};
279
280