162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* CacheFiles extended attribute management
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
562306a36Sopenharmony_ci * Written by David Howells (dhowells@redhat.com)
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/sched.h>
1062306a36Sopenharmony_ci#include <linux/file.h>
1162306a36Sopenharmony_ci#include <linux/fs.h>
1262306a36Sopenharmony_ci#include <linux/fsnotify.h>
1362306a36Sopenharmony_ci#include <linux/quotaops.h>
1462306a36Sopenharmony_ci#include <linux/xattr.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include "internal.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define CACHEFILES_COOKIE_TYPE_DATA 1
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistruct cachefiles_xattr {
2162306a36Sopenharmony_ci	__be64	object_size;	/* Actual size of the object */
2262306a36Sopenharmony_ci	__be64	zero_point;	/* Size after which server has no data not written by us */
2362306a36Sopenharmony_ci	__u8	type;		/* Type of object */
2462306a36Sopenharmony_ci	__u8	content;	/* Content presence (enum cachefiles_content) */
2562306a36Sopenharmony_ci	__u8	data[];		/* netfs coherency data */
2662306a36Sopenharmony_ci} __packed;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic const char cachefiles_xattr_cache[] =
2962306a36Sopenharmony_ci	XATTR_USER_PREFIX "CacheFiles.cache";
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistruct cachefiles_vol_xattr {
3262306a36Sopenharmony_ci	__be32	reserved;	/* Reserved, should be 0 */
3362306a36Sopenharmony_ci	__u8	data[];		/* netfs volume coherency data */
3462306a36Sopenharmony_ci} __packed;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/*
3762306a36Sopenharmony_ci * set the state xattr on a cache file
3862306a36Sopenharmony_ci */
3962306a36Sopenharmony_ciint cachefiles_set_object_xattr(struct cachefiles_object *object)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	struct cachefiles_xattr *buf;
4262306a36Sopenharmony_ci	struct dentry *dentry;
4362306a36Sopenharmony_ci	struct file *file = object->file;
4462306a36Sopenharmony_ci	unsigned int len = object->cookie->aux_len;
4562306a36Sopenharmony_ci	int ret;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	if (!file)
4862306a36Sopenharmony_ci		return -ESTALE;
4962306a36Sopenharmony_ci	dentry = file->f_path.dentry;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	_enter("%x,#%d", object->debug_id, len);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	buf = kmalloc(sizeof(struct cachefiles_xattr) + len, GFP_KERNEL);
5462306a36Sopenharmony_ci	if (!buf)
5562306a36Sopenharmony_ci		return -ENOMEM;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	buf->object_size	= cpu_to_be64(object->cookie->object_size);
5862306a36Sopenharmony_ci	buf->zero_point		= 0;
5962306a36Sopenharmony_ci	buf->type		= CACHEFILES_COOKIE_TYPE_DATA;
6062306a36Sopenharmony_ci	buf->content		= object->content_info;
6162306a36Sopenharmony_ci	if (test_bit(FSCACHE_COOKIE_LOCAL_WRITE, &object->cookie->flags))
6262306a36Sopenharmony_ci		buf->content	= CACHEFILES_CONTENT_DIRTY;
6362306a36Sopenharmony_ci	if (len > 0)
6462306a36Sopenharmony_ci		memcpy(buf->data, fscache_get_aux(object->cookie), len);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	ret = cachefiles_inject_write_error();
6762306a36Sopenharmony_ci	if (ret == 0)
6862306a36Sopenharmony_ci		ret = vfs_setxattr(&nop_mnt_idmap, dentry, cachefiles_xattr_cache,
6962306a36Sopenharmony_ci				   buf, sizeof(struct cachefiles_xattr) + len, 0);
7062306a36Sopenharmony_ci	if (ret < 0) {
7162306a36Sopenharmony_ci		trace_cachefiles_vfs_error(object, file_inode(file), ret,
7262306a36Sopenharmony_ci					   cachefiles_trace_setxattr_error);
7362306a36Sopenharmony_ci		trace_cachefiles_coherency(object, file_inode(file)->i_ino,
7462306a36Sopenharmony_ci					   buf->content,
7562306a36Sopenharmony_ci					   cachefiles_coherency_set_fail);
7662306a36Sopenharmony_ci		if (ret != -ENOMEM)
7762306a36Sopenharmony_ci			cachefiles_io_error_obj(
7862306a36Sopenharmony_ci				object,
7962306a36Sopenharmony_ci				"Failed to set xattr with error %d", ret);
8062306a36Sopenharmony_ci	} else {
8162306a36Sopenharmony_ci		trace_cachefiles_coherency(object, file_inode(file)->i_ino,
8262306a36Sopenharmony_ci					   buf->content,
8362306a36Sopenharmony_ci					   cachefiles_coherency_set_ok);
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	kfree(buf);
8762306a36Sopenharmony_ci	_leave(" = %d", ret);
8862306a36Sopenharmony_ci	return ret;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci/*
9262306a36Sopenharmony_ci * check the consistency between the backing cache and the FS-Cache cookie
9362306a36Sopenharmony_ci */
9462306a36Sopenharmony_ciint cachefiles_check_auxdata(struct cachefiles_object *object, struct file *file)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	struct cachefiles_xattr *buf;
9762306a36Sopenharmony_ci	struct dentry *dentry = file->f_path.dentry;
9862306a36Sopenharmony_ci	unsigned int len = object->cookie->aux_len, tlen;
9962306a36Sopenharmony_ci	const void *p = fscache_get_aux(object->cookie);
10062306a36Sopenharmony_ci	enum cachefiles_coherency_trace why;
10162306a36Sopenharmony_ci	ssize_t xlen;
10262306a36Sopenharmony_ci	int ret = -ESTALE;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	tlen = sizeof(struct cachefiles_xattr) + len;
10562306a36Sopenharmony_ci	buf = kmalloc(tlen, GFP_KERNEL);
10662306a36Sopenharmony_ci	if (!buf)
10762306a36Sopenharmony_ci		return -ENOMEM;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	xlen = cachefiles_inject_read_error();
11062306a36Sopenharmony_ci	if (xlen == 0)
11162306a36Sopenharmony_ci		xlen = vfs_getxattr(&nop_mnt_idmap, dentry, cachefiles_xattr_cache, buf, tlen);
11262306a36Sopenharmony_ci	if (xlen != tlen) {
11362306a36Sopenharmony_ci		if (xlen < 0)
11462306a36Sopenharmony_ci			trace_cachefiles_vfs_error(object, file_inode(file), xlen,
11562306a36Sopenharmony_ci						   cachefiles_trace_getxattr_error);
11662306a36Sopenharmony_ci		if (xlen == -EIO)
11762306a36Sopenharmony_ci			cachefiles_io_error_obj(
11862306a36Sopenharmony_ci				object,
11962306a36Sopenharmony_ci				"Failed to read aux with error %zd", xlen);
12062306a36Sopenharmony_ci		why = cachefiles_coherency_check_xattr;
12162306a36Sopenharmony_ci	} else if (buf->type != CACHEFILES_COOKIE_TYPE_DATA) {
12262306a36Sopenharmony_ci		why = cachefiles_coherency_check_type;
12362306a36Sopenharmony_ci	} else if (memcmp(buf->data, p, len) != 0) {
12462306a36Sopenharmony_ci		why = cachefiles_coherency_check_aux;
12562306a36Sopenharmony_ci	} else if (be64_to_cpu(buf->object_size) != object->cookie->object_size) {
12662306a36Sopenharmony_ci		why = cachefiles_coherency_check_objsize;
12762306a36Sopenharmony_ci	} else if (buf->content == CACHEFILES_CONTENT_DIRTY) {
12862306a36Sopenharmony_ci		// TODO: Begin conflict resolution
12962306a36Sopenharmony_ci		pr_warn("Dirty object in cache\n");
13062306a36Sopenharmony_ci		why = cachefiles_coherency_check_dirty;
13162306a36Sopenharmony_ci	} else {
13262306a36Sopenharmony_ci		why = cachefiles_coherency_check_ok;
13362306a36Sopenharmony_ci		ret = 0;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	trace_cachefiles_coherency(object, file_inode(file)->i_ino,
13762306a36Sopenharmony_ci				   buf->content, why);
13862306a36Sopenharmony_ci	kfree(buf);
13962306a36Sopenharmony_ci	return ret;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci/*
14362306a36Sopenharmony_ci * remove the object's xattr to mark it stale
14462306a36Sopenharmony_ci */
14562306a36Sopenharmony_ciint cachefiles_remove_object_xattr(struct cachefiles_cache *cache,
14662306a36Sopenharmony_ci				   struct cachefiles_object *object,
14762306a36Sopenharmony_ci				   struct dentry *dentry)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	int ret;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	ret = cachefiles_inject_remove_error();
15262306a36Sopenharmony_ci	if (ret == 0)
15362306a36Sopenharmony_ci		ret = vfs_removexattr(&nop_mnt_idmap, dentry, cachefiles_xattr_cache);
15462306a36Sopenharmony_ci	if (ret < 0) {
15562306a36Sopenharmony_ci		trace_cachefiles_vfs_error(object, d_inode(dentry), ret,
15662306a36Sopenharmony_ci					   cachefiles_trace_remxattr_error);
15762306a36Sopenharmony_ci		if (ret == -ENOENT || ret == -ENODATA)
15862306a36Sopenharmony_ci			ret = 0;
15962306a36Sopenharmony_ci		else if (ret != -ENOMEM)
16062306a36Sopenharmony_ci			cachefiles_io_error(cache,
16162306a36Sopenharmony_ci					    "Can't remove xattr from %lu"
16262306a36Sopenharmony_ci					    " (error %d)",
16362306a36Sopenharmony_ci					    d_backing_inode(dentry)->i_ino, -ret);
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	_leave(" = %d", ret);
16762306a36Sopenharmony_ci	return ret;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci/*
17162306a36Sopenharmony_ci * Stick a marker on the cache object to indicate that it's dirty.
17262306a36Sopenharmony_ci */
17362306a36Sopenharmony_civoid cachefiles_prepare_to_write(struct fscache_cookie *cookie)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	const struct cred *saved_cred;
17662306a36Sopenharmony_ci	struct cachefiles_object *object = cookie->cache_priv;
17762306a36Sopenharmony_ci	struct cachefiles_cache *cache = object->volume->cache;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	_enter("c=%08x", object->cookie->debug_id);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (!test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags)) {
18262306a36Sopenharmony_ci		cachefiles_begin_secure(cache, &saved_cred);
18362306a36Sopenharmony_ci		cachefiles_set_object_xattr(object);
18462306a36Sopenharmony_ci		cachefiles_end_secure(cache, saved_cred);
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci/*
18962306a36Sopenharmony_ci * Set the state xattr on a volume directory.
19062306a36Sopenharmony_ci */
19162306a36Sopenharmony_cibool cachefiles_set_volume_xattr(struct cachefiles_volume *volume)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	struct cachefiles_vol_xattr *buf;
19462306a36Sopenharmony_ci	unsigned int len = volume->vcookie->coherency_len;
19562306a36Sopenharmony_ci	const void *p = volume->vcookie->coherency;
19662306a36Sopenharmony_ci	struct dentry *dentry = volume->dentry;
19762306a36Sopenharmony_ci	int ret;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	_enter("%x,#%d", volume->vcookie->debug_id, len);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	len += sizeof(*buf);
20262306a36Sopenharmony_ci	buf = kmalloc(len, GFP_KERNEL);
20362306a36Sopenharmony_ci	if (!buf)
20462306a36Sopenharmony_ci		return false;
20562306a36Sopenharmony_ci	buf->reserved = cpu_to_be32(0);
20662306a36Sopenharmony_ci	memcpy(buf->data, p, volume->vcookie->coherency_len);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	ret = cachefiles_inject_write_error();
20962306a36Sopenharmony_ci	if (ret == 0)
21062306a36Sopenharmony_ci		ret = vfs_setxattr(&nop_mnt_idmap, dentry, cachefiles_xattr_cache,
21162306a36Sopenharmony_ci				   buf, len, 0);
21262306a36Sopenharmony_ci	if (ret < 0) {
21362306a36Sopenharmony_ci		trace_cachefiles_vfs_error(NULL, d_inode(dentry), ret,
21462306a36Sopenharmony_ci					   cachefiles_trace_setxattr_error);
21562306a36Sopenharmony_ci		trace_cachefiles_vol_coherency(volume, d_inode(dentry)->i_ino,
21662306a36Sopenharmony_ci					       cachefiles_coherency_vol_set_fail);
21762306a36Sopenharmony_ci		if (ret != -ENOMEM)
21862306a36Sopenharmony_ci			cachefiles_io_error(
21962306a36Sopenharmony_ci				volume->cache, "Failed to set xattr with error %d", ret);
22062306a36Sopenharmony_ci	} else {
22162306a36Sopenharmony_ci		trace_cachefiles_vol_coherency(volume, d_inode(dentry)->i_ino,
22262306a36Sopenharmony_ci					       cachefiles_coherency_vol_set_ok);
22362306a36Sopenharmony_ci	}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	kfree(buf);
22662306a36Sopenharmony_ci	_leave(" = %d", ret);
22762306a36Sopenharmony_ci	return ret == 0;
22862306a36Sopenharmony_ci}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci/*
23162306a36Sopenharmony_ci * Check the consistency between the backing cache and the volume cookie.
23262306a36Sopenharmony_ci */
23362306a36Sopenharmony_ciint cachefiles_check_volume_xattr(struct cachefiles_volume *volume)
23462306a36Sopenharmony_ci{
23562306a36Sopenharmony_ci	struct cachefiles_vol_xattr *buf;
23662306a36Sopenharmony_ci	struct dentry *dentry = volume->dentry;
23762306a36Sopenharmony_ci	unsigned int len = volume->vcookie->coherency_len;
23862306a36Sopenharmony_ci	const void *p = volume->vcookie->coherency;
23962306a36Sopenharmony_ci	enum cachefiles_coherency_trace why;
24062306a36Sopenharmony_ci	ssize_t xlen;
24162306a36Sopenharmony_ci	int ret = -ESTALE;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	_enter("");
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	len += sizeof(*buf);
24662306a36Sopenharmony_ci	buf = kmalloc(len, GFP_KERNEL);
24762306a36Sopenharmony_ci	if (!buf)
24862306a36Sopenharmony_ci		return -ENOMEM;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	xlen = cachefiles_inject_read_error();
25162306a36Sopenharmony_ci	if (xlen == 0)
25262306a36Sopenharmony_ci		xlen = vfs_getxattr(&nop_mnt_idmap, dentry, cachefiles_xattr_cache, buf, len);
25362306a36Sopenharmony_ci	if (xlen != len) {
25462306a36Sopenharmony_ci		if (xlen < 0) {
25562306a36Sopenharmony_ci			trace_cachefiles_vfs_error(NULL, d_inode(dentry), xlen,
25662306a36Sopenharmony_ci						   cachefiles_trace_getxattr_error);
25762306a36Sopenharmony_ci			if (xlen == -EIO)
25862306a36Sopenharmony_ci				cachefiles_io_error(
25962306a36Sopenharmony_ci					volume->cache,
26062306a36Sopenharmony_ci					"Failed to read xattr with error %zd", xlen);
26162306a36Sopenharmony_ci		}
26262306a36Sopenharmony_ci		why = cachefiles_coherency_vol_check_xattr;
26362306a36Sopenharmony_ci	} else if (buf->reserved != cpu_to_be32(0)) {
26462306a36Sopenharmony_ci		why = cachefiles_coherency_vol_check_resv;
26562306a36Sopenharmony_ci	} else if (memcmp(buf->data, p, len - sizeof(*buf)) != 0) {
26662306a36Sopenharmony_ci		why = cachefiles_coherency_vol_check_cmp;
26762306a36Sopenharmony_ci	} else {
26862306a36Sopenharmony_ci		why = cachefiles_coherency_vol_check_ok;
26962306a36Sopenharmony_ci		ret = 0;
27062306a36Sopenharmony_ci	}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	trace_cachefiles_vol_coherency(volume, d_inode(dentry)->i_ino, why);
27362306a36Sopenharmony_ci	kfree(buf);
27462306a36Sopenharmony_ci	_leave(" = %d", ret);
27562306a36Sopenharmony_ci	return ret;
27662306a36Sopenharmony_ci}
277