xref: /linux/fs/afs/symlink.c (revision 5dfa01ef37a8b944773aef8dee747cd76dec4234)
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 (dhowells@redhat.com)
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 
afs_put_symlink(struct afs_symlink * symlink)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 
afs_replace_symlink(struct afs_vnode * vnode,struct afs_symlink * symlink)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  */
afs_invalidate_symlink(struct afs_vnode * vnode)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  */
afs_evict_symlink(struct afs_vnode * vnode)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  */
afs_init_new_symlink(struct afs_vnode * vnode,struct afs_operation * op)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  */
afs_do_read_symlink(struct afs_vnode * vnode)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 
afs_read_symlink(struct afs_vnode * vnode)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 
afs_put_link(void * arg)157 static void afs_put_link(void *arg)
158 {
159 	afs_put_symlink(arg);
160 }
161 
afs_get_link(struct dentry * dentry,struct inode * inode,struct delayed_call * callback)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 
afs_readlink(struct dentry * dentry,char __user * buffer,int buflen)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  */
afs_symlink_writepages(struct address_space * mapping,struct writeback_control * wbc)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