xref: /openbmc/linux/fs/cachefiles/xattr.c (revision 34d6f206a88c2651d216bd3487ac956a40b2ba8e)
172b95785SDavid Howells // SPDX-License-Identifier: GPL-2.0-or-later
272b95785SDavid Howells /* CacheFiles extended attribute management
372b95785SDavid Howells  *
472b95785SDavid Howells  * Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
572b95785SDavid Howells  * Written by David Howells (dhowells@redhat.com)
672b95785SDavid Howells  */
772b95785SDavid Howells 
872b95785SDavid Howells #include <linux/module.h>
972b95785SDavid Howells #include <linux/sched.h>
1072b95785SDavid Howells #include <linux/file.h>
1172b95785SDavid Howells #include <linux/fs.h>
1272b95785SDavid Howells #include <linux/fsnotify.h>
1372b95785SDavid Howells #include <linux/quotaops.h>
1472b95785SDavid Howells #include <linux/xattr.h>
1572b95785SDavid Howells #include <linux/slab.h>
1672b95785SDavid Howells #include "internal.h"
1772b95785SDavid Howells 
1872b95785SDavid Howells #define CACHEFILES_COOKIE_TYPE_DATA 1
1972b95785SDavid Howells 
2072b95785SDavid Howells struct cachefiles_xattr {
2172b95785SDavid Howells 	__be64	object_size;	/* Actual size of the object */
2272b95785SDavid Howells 	__be64	zero_point;	/* Size after which server has no data not written by us */
2372b95785SDavid Howells 	__u8	type;		/* Type of object */
2472b95785SDavid Howells 	__u8	content;	/* Content presence (enum cachefiles_content) */
2572b95785SDavid Howells 	__u8	data[];		/* netfs coherency data */
2672b95785SDavid Howells } __packed;
2772b95785SDavid Howells 
2872b95785SDavid Howells static const char cachefiles_xattr_cache[] =
2972b95785SDavid Howells 	XATTR_USER_PREFIX "CacheFiles.cache";
3072b95785SDavid Howells 
31413a4a6bSDavid Howells struct cachefiles_vol_xattr {
32413a4a6bSDavid Howells 	__be32	reserved;	/* Reserved, should be 0 */
33413a4a6bSDavid Howells 	__u8	data[];		/* netfs volume coherency data */
34413a4a6bSDavid Howells } __packed;
35413a4a6bSDavid Howells 
3672b95785SDavid Howells /*
3772b95785SDavid Howells  * set the state xattr on a cache file
3872b95785SDavid Howells  */
cachefiles_set_object_xattr(struct cachefiles_object * object)3972b95785SDavid Howells int cachefiles_set_object_xattr(struct cachefiles_object *object)
4072b95785SDavid Howells {
4172b95785SDavid Howells 	struct cachefiles_xattr *buf;
4272b95785SDavid Howells 	struct dentry *dentry;
4372b95785SDavid Howells 	struct file *file = object->file;
4472b95785SDavid Howells 	unsigned int len = object->cookie->aux_len;
4572b95785SDavid Howells 	int ret;
4672b95785SDavid Howells 
4772b95785SDavid Howells 	if (!file)
4872b95785SDavid Howells 		return -ESTALE;
4972b95785SDavid Howells 	dentry = file->f_path.dentry;
5072b95785SDavid Howells 
5172b95785SDavid Howells 	_enter("%x,#%d", object->debug_id, len);
5272b95785SDavid Howells 
5372b95785SDavid Howells 	buf = kmalloc(sizeof(struct cachefiles_xattr) + len, GFP_KERNEL);
5472b95785SDavid Howells 	if (!buf)
5572b95785SDavid Howells 		return -ENOMEM;
5672b95785SDavid Howells 
5772b95785SDavid Howells 	buf->object_size	= cpu_to_be64(object->cookie->object_size);
5872b95785SDavid Howells 	buf->zero_point		= 0;
5972b95785SDavid Howells 	buf->type		= CACHEFILES_COOKIE_TYPE_DATA;
6072b95785SDavid Howells 	buf->content		= object->content_info;
6172b95785SDavid Howells 	if (test_bit(FSCACHE_COOKIE_LOCAL_WRITE, &object->cookie->flags))
6272b95785SDavid Howells 		buf->content	= CACHEFILES_CONTENT_DIRTY;
6372b95785SDavid Howells 	if (len > 0)
6472b95785SDavid Howells 		memcpy(buf->data, fscache_get_aux(object->cookie), len);
6572b95785SDavid Howells 
6672b95785SDavid Howells 	ret = cachefiles_inject_write_error();
67*81b048b9SDavid Howells 	if (ret == 0) {
68*81b048b9SDavid Howells 		ret = mnt_want_write_file(file);
69*81b048b9SDavid Howells 		if (ret == 0) {
70*81b048b9SDavid Howells 			ret = vfs_setxattr(&nop_mnt_idmap, dentry,
71*81b048b9SDavid Howells 					   cachefiles_xattr_cache, buf,
72*81b048b9SDavid Howells 					   sizeof(struct cachefiles_xattr) + len, 0);
73*81b048b9SDavid Howells 			mnt_drop_write_file(file);
74*81b048b9SDavid Howells 		}
75*81b048b9SDavid Howells 	}
7672b95785SDavid Howells 	if (ret < 0) {
7772b95785SDavid Howells 		trace_cachefiles_vfs_error(object, file_inode(file), ret,
7872b95785SDavid Howells 					   cachefiles_trace_setxattr_error);
7972b95785SDavid Howells 		trace_cachefiles_coherency(object, file_inode(file)->i_ino,
8072b95785SDavid Howells 					   buf->content,
8172b95785SDavid Howells 					   cachefiles_coherency_set_fail);
8272b95785SDavid Howells 		if (ret != -ENOMEM)
8372b95785SDavid Howells 			cachefiles_io_error_obj(
8472b95785SDavid Howells 				object,
8572b95785SDavid Howells 				"Failed to set xattr with error %d", ret);
8672b95785SDavid Howells 	} else {
8772b95785SDavid Howells 		trace_cachefiles_coherency(object, file_inode(file)->i_ino,
8872b95785SDavid Howells 					   buf->content,
8972b95785SDavid Howells 					   cachefiles_coherency_set_ok);
9072b95785SDavid Howells 	}
9172b95785SDavid Howells 
9272b95785SDavid Howells 	kfree(buf);
9372b95785SDavid Howells 	_leave(" = %d", ret);
9472b95785SDavid Howells 	return ret;
9572b95785SDavid Howells }
9672b95785SDavid Howells 
9772b95785SDavid Howells /*
9872b95785SDavid Howells  * check the consistency between the backing cache and the FS-Cache cookie
9972b95785SDavid Howells  */
cachefiles_check_auxdata(struct cachefiles_object * object,struct file * file)10072b95785SDavid Howells int cachefiles_check_auxdata(struct cachefiles_object *object, struct file *file)
10172b95785SDavid Howells {
10272b95785SDavid Howells 	struct cachefiles_xattr *buf;
10372b95785SDavid Howells 	struct dentry *dentry = file->f_path.dentry;
10472b95785SDavid Howells 	unsigned int len = object->cookie->aux_len, tlen;
10572b95785SDavid Howells 	const void *p = fscache_get_aux(object->cookie);
10672b95785SDavid Howells 	enum cachefiles_coherency_trace why;
10772b95785SDavid Howells 	ssize_t xlen;
10872b95785SDavid Howells 	int ret = -ESTALE;
10972b95785SDavid Howells 
11072b95785SDavid Howells 	tlen = sizeof(struct cachefiles_xattr) + len;
11172b95785SDavid Howells 	buf = kmalloc(tlen, GFP_KERNEL);
11272b95785SDavid Howells 	if (!buf)
11372b95785SDavid Howells 		return -ENOMEM;
11472b95785SDavid Howells 
11572b95785SDavid Howells 	xlen = cachefiles_inject_read_error();
11672b95785SDavid Howells 	if (xlen == 0)
1174609e1f1SChristian Brauner 		xlen = vfs_getxattr(&nop_mnt_idmap, dentry, cachefiles_xattr_cache, buf, tlen);
11872b95785SDavid Howells 	if (xlen != tlen) {
119a04cc7f0SBaokun Li 		if (xlen < 0) {
120a04cc7f0SBaokun Li 			ret = xlen;
12172b95785SDavid Howells 			trace_cachefiles_vfs_error(object, file_inode(file), xlen,
12272b95785SDavid Howells 						   cachefiles_trace_getxattr_error);
123a04cc7f0SBaokun Li 		}
12472b95785SDavid Howells 		if (xlen == -EIO)
12572b95785SDavid Howells 			cachefiles_io_error_obj(
12672b95785SDavid Howells 				object,
12772b95785SDavid Howells 				"Failed to read aux with error %zd", xlen);
12872b95785SDavid Howells 		why = cachefiles_coherency_check_xattr;
12972b95785SDavid Howells 	} else if (buf->type != CACHEFILES_COOKIE_TYPE_DATA) {
13072b95785SDavid Howells 		why = cachefiles_coherency_check_type;
13172b95785SDavid Howells 	} else if (memcmp(buf->data, p, len) != 0) {
13272b95785SDavid Howells 		why = cachefiles_coherency_check_aux;
13372b95785SDavid Howells 	} else if (be64_to_cpu(buf->object_size) != object->cookie->object_size) {
13472b95785SDavid Howells 		why = cachefiles_coherency_check_objsize;
13572b95785SDavid Howells 	} else if (buf->content == CACHEFILES_CONTENT_DIRTY) {
13672b95785SDavid Howells 		// TODO: Begin conflict resolution
13772b95785SDavid Howells 		pr_warn("Dirty object in cache\n");
13872b95785SDavid Howells 		why = cachefiles_coherency_check_dirty;
13972b95785SDavid Howells 	} else {
14072b95785SDavid Howells 		why = cachefiles_coherency_check_ok;
14172b95785SDavid Howells 		ret = 0;
14272b95785SDavid Howells 	}
14372b95785SDavid Howells 
14472b95785SDavid Howells 	trace_cachefiles_coherency(object, file_inode(file)->i_ino,
14572b95785SDavid Howells 				   buf->content, why);
14672b95785SDavid Howells 	kfree(buf);
14772b95785SDavid Howells 	return ret;
14872b95785SDavid Howells }
14972b95785SDavid Howells 
15072b95785SDavid Howells /*
15172b95785SDavid Howells  * remove the object's xattr to mark it stale
15272b95785SDavid Howells  */
cachefiles_remove_object_xattr(struct cachefiles_cache * cache,struct cachefiles_object * object,struct dentry * dentry)15372b95785SDavid Howells int cachefiles_remove_object_xattr(struct cachefiles_cache *cache,
15472b95785SDavid Howells 				   struct cachefiles_object *object,
15572b95785SDavid Howells 				   struct dentry *dentry)
15672b95785SDavid Howells {
15772b95785SDavid Howells 	int ret;
15872b95785SDavid Howells 
15972b95785SDavid Howells 	ret = cachefiles_inject_remove_error();
160*81b048b9SDavid Howells 	if (ret == 0) {
161*81b048b9SDavid Howells 		ret = mnt_want_write(cache->mnt);
162*81b048b9SDavid Howells 		if (ret == 0) {
163*81b048b9SDavid Howells 			ret = vfs_removexattr(&nop_mnt_idmap, dentry,
164*81b048b9SDavid Howells 					      cachefiles_xattr_cache);
165*81b048b9SDavid Howells 			mnt_drop_write(cache->mnt);
166*81b048b9SDavid Howells 		}
167*81b048b9SDavid Howells 	}
16872b95785SDavid Howells 	if (ret < 0) {
16972b95785SDavid Howells 		trace_cachefiles_vfs_error(object, d_inode(dentry), ret,
17072b95785SDavid Howells 					   cachefiles_trace_remxattr_error);
17172b95785SDavid Howells 		if (ret == -ENOENT || ret == -ENODATA)
17272b95785SDavid Howells 			ret = 0;
17372b95785SDavid Howells 		else if (ret != -ENOMEM)
17472b95785SDavid Howells 			cachefiles_io_error(cache,
17572b95785SDavid Howells 					    "Can't remove xattr from %lu"
17672b95785SDavid Howells 					    " (error %d)",
17772b95785SDavid Howells 					    d_backing_inode(dentry)->i_ino, -ret);
17872b95785SDavid Howells 	}
17972b95785SDavid Howells 
18072b95785SDavid Howells 	_leave(" = %d", ret);
18172b95785SDavid Howells 	return ret;
18272b95785SDavid Howells }
18372b95785SDavid Howells 
18472b95785SDavid Howells /*
18572b95785SDavid Howells  * Stick a marker on the cache object to indicate that it's dirty.
18672b95785SDavid Howells  */
cachefiles_prepare_to_write(struct fscache_cookie * cookie)18772b95785SDavid Howells void cachefiles_prepare_to_write(struct fscache_cookie *cookie)
18872b95785SDavid Howells {
18972b95785SDavid Howells 	const struct cred *saved_cred;
19072b95785SDavid Howells 	struct cachefiles_object *object = cookie->cache_priv;
19172b95785SDavid Howells 	struct cachefiles_cache *cache = object->volume->cache;
19272b95785SDavid Howells 
19372b95785SDavid Howells 	_enter("c=%08x", object->cookie->debug_id);
19472b95785SDavid Howells 
19572b95785SDavid Howells 	if (!test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags)) {
19672b95785SDavid Howells 		cachefiles_begin_secure(cache, &saved_cred);
19772b95785SDavid Howells 		cachefiles_set_object_xattr(object);
19872b95785SDavid Howells 		cachefiles_end_secure(cache, saved_cred);
19972b95785SDavid Howells 	}
20072b95785SDavid Howells }
20132e15003SDavid Howells 
20232e15003SDavid Howells /*
20332e15003SDavid Howells  * Set the state xattr on a volume directory.
20432e15003SDavid Howells  */
cachefiles_set_volume_xattr(struct cachefiles_volume * volume)20532e15003SDavid Howells bool cachefiles_set_volume_xattr(struct cachefiles_volume *volume)
20632e15003SDavid Howells {
207413a4a6bSDavid Howells 	struct cachefiles_vol_xattr *buf;
20832e15003SDavid Howells 	unsigned int len = volume->vcookie->coherency_len;
20932e15003SDavid Howells 	const void *p = volume->vcookie->coherency;
21032e15003SDavid Howells 	struct dentry *dentry = volume->dentry;
21132e15003SDavid Howells 	int ret;
21232e15003SDavid Howells 
21332e15003SDavid Howells 	_enter("%x,#%d", volume->vcookie->debug_id, len);
21432e15003SDavid Howells 
215413a4a6bSDavid Howells 	len += sizeof(*buf);
216413a4a6bSDavid Howells 	buf = kmalloc(len, GFP_KERNEL);
217413a4a6bSDavid Howells 	if (!buf)
218413a4a6bSDavid Howells 		return false;
219413a4a6bSDavid Howells 	buf->reserved = cpu_to_be32(0);
2207b2f6c30SDave Wysochanski 	memcpy(buf->data, p, volume->vcookie->coherency_len);
221413a4a6bSDavid Howells 
22232e15003SDavid Howells 	ret = cachefiles_inject_write_error();
223*81b048b9SDavid Howells 	if (ret == 0) {
224*81b048b9SDavid Howells 		ret = mnt_want_write(volume->cache->mnt);
225*81b048b9SDavid Howells 		if (ret == 0) {
226*81b048b9SDavid Howells 			ret = vfs_setxattr(&nop_mnt_idmap, dentry,
227*81b048b9SDavid Howells 					   cachefiles_xattr_cache,
228413a4a6bSDavid Howells 					   buf, len, 0);
229*81b048b9SDavid Howells 			mnt_drop_write(volume->cache->mnt);
230*81b048b9SDavid Howells 		}
231*81b048b9SDavid Howells 	}
23232e15003SDavid Howells 	if (ret < 0) {
23332e15003SDavid Howells 		trace_cachefiles_vfs_error(NULL, d_inode(dentry), ret,
23432e15003SDavid Howells 					   cachefiles_trace_setxattr_error);
23532e15003SDavid Howells 		trace_cachefiles_vol_coherency(volume, d_inode(dentry)->i_ino,
23632e15003SDavid Howells 					       cachefiles_coherency_vol_set_fail);
23732e15003SDavid Howells 		if (ret != -ENOMEM)
23832e15003SDavid Howells 			cachefiles_io_error(
23932e15003SDavid Howells 				volume->cache, "Failed to set xattr with error %d", ret);
24032e15003SDavid Howells 	} else {
24132e15003SDavid Howells 		trace_cachefiles_vol_coherency(volume, d_inode(dentry)->i_ino,
24232e15003SDavid Howells 					       cachefiles_coherency_vol_set_ok);
24332e15003SDavid Howells 	}
24432e15003SDavid Howells 
245413a4a6bSDavid Howells 	kfree(buf);
24632e15003SDavid Howells 	_leave(" = %d", ret);
24732e15003SDavid Howells 	return ret == 0;
24832e15003SDavid Howells }
24932e15003SDavid Howells 
25032e15003SDavid Howells /*
25132e15003SDavid Howells  * Check the consistency between the backing cache and the volume cookie.
25232e15003SDavid Howells  */
cachefiles_check_volume_xattr(struct cachefiles_volume * volume)25332e15003SDavid Howells int cachefiles_check_volume_xattr(struct cachefiles_volume *volume)
25432e15003SDavid Howells {
255413a4a6bSDavid Howells 	struct cachefiles_vol_xattr *buf;
25632e15003SDavid Howells 	struct dentry *dentry = volume->dentry;
25732e15003SDavid Howells 	unsigned int len = volume->vcookie->coherency_len;
25832e15003SDavid Howells 	const void *p = volume->vcookie->coherency;
25932e15003SDavid Howells 	enum cachefiles_coherency_trace why;
26032e15003SDavid Howells 	ssize_t xlen;
26132e15003SDavid Howells 	int ret = -ESTALE;
26232e15003SDavid Howells 
26332e15003SDavid Howells 	_enter("");
26432e15003SDavid Howells 
265413a4a6bSDavid Howells 	len += sizeof(*buf);
26632e15003SDavid Howells 	buf = kmalloc(len, GFP_KERNEL);
26732e15003SDavid Howells 	if (!buf)
26832e15003SDavid Howells 		return -ENOMEM;
26932e15003SDavid Howells 
27032e15003SDavid Howells 	xlen = cachefiles_inject_read_error();
27132e15003SDavid Howells 	if (xlen == 0)
2724609e1f1SChristian Brauner 		xlen = vfs_getxattr(&nop_mnt_idmap, dentry, cachefiles_xattr_cache, buf, len);
27332e15003SDavid Howells 	if (xlen != len) {
27432e15003SDavid Howells 		if (xlen < 0) {
275a04cc7f0SBaokun Li 			ret = xlen;
27632e15003SDavid Howells 			trace_cachefiles_vfs_error(NULL, d_inode(dentry), xlen,
27732e15003SDavid Howells 						   cachefiles_trace_getxattr_error);
27832e15003SDavid Howells 			if (xlen == -EIO)
27932e15003SDavid Howells 				cachefiles_io_error(
28032e15003SDavid Howells 					volume->cache,
28132e15003SDavid Howells 					"Failed to read xattr with error %zd", xlen);
28232e15003SDavid Howells 		}
28332e15003SDavid Howells 		why = cachefiles_coherency_vol_check_xattr;
284413a4a6bSDavid Howells 	} else if (buf->reserved != cpu_to_be32(0)) {
285413a4a6bSDavid Howells 		why = cachefiles_coherency_vol_check_resv;
286413a4a6bSDavid Howells 	} else if (memcmp(buf->data, p, len - sizeof(*buf)) != 0) {
28732e15003SDavid Howells 		why = cachefiles_coherency_vol_check_cmp;
28832e15003SDavid Howells 	} else {
28932e15003SDavid Howells 		why = cachefiles_coherency_vol_check_ok;
29032e15003SDavid Howells 		ret = 0;
29132e15003SDavid Howells 	}
29232e15003SDavid Howells 
29332e15003SDavid Howells 	trace_cachefiles_vol_coherency(volume, d_inode(dentry)->i_ino, why);
29432e15003SDavid Howells 	kfree(buf);
29532e15003SDavid Howells 	_leave(" = %d", ret);
29632e15003SDavid Howells 	return ret;
29732e15003SDavid Howells }
298