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