162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * linux/fs/ceph/acl.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2013 Guangliang Zhao, <lucienchao@gmail.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/ceph/ceph_debug.h>
962306a36Sopenharmony_ci#include <linux/fs.h>
1062306a36Sopenharmony_ci#include <linux/string.h>
1162306a36Sopenharmony_ci#include <linux/xattr.h>
1262306a36Sopenharmony_ci#include <linux/posix_acl_xattr.h>
1362306a36Sopenharmony_ci#include <linux/posix_acl.h>
1462306a36Sopenharmony_ci#include <linux/sched.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include "super.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistatic inline void ceph_set_cached_acl(struct inode *inode,
2062306a36Sopenharmony_ci					int type, struct posix_acl *acl)
2162306a36Sopenharmony_ci{
2262306a36Sopenharmony_ci	struct ceph_inode_info *ci = ceph_inode(inode);
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	spin_lock(&ci->i_ceph_lock);
2562306a36Sopenharmony_ci	if (__ceph_caps_issued_mask_metric(ci, CEPH_CAP_XATTR_SHARED, 0))
2662306a36Sopenharmony_ci		set_cached_acl(inode, type, acl);
2762306a36Sopenharmony_ci	else
2862306a36Sopenharmony_ci		forget_cached_acl(inode, type);
2962306a36Sopenharmony_ci	spin_unlock(&ci->i_ceph_lock);
3062306a36Sopenharmony_ci}
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistruct posix_acl *ceph_get_acl(struct inode *inode, int type, bool rcu)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	int size;
3562306a36Sopenharmony_ci	unsigned int retry_cnt = 0;
3662306a36Sopenharmony_ci	const char *name;
3762306a36Sopenharmony_ci	char *value = NULL;
3862306a36Sopenharmony_ci	struct posix_acl *acl;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if (rcu)
4162306a36Sopenharmony_ci		return ERR_PTR(-ECHILD);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	switch (type) {
4462306a36Sopenharmony_ci	case ACL_TYPE_ACCESS:
4562306a36Sopenharmony_ci		name = XATTR_NAME_POSIX_ACL_ACCESS;
4662306a36Sopenharmony_ci		break;
4762306a36Sopenharmony_ci	case ACL_TYPE_DEFAULT:
4862306a36Sopenharmony_ci		name = XATTR_NAME_POSIX_ACL_DEFAULT;
4962306a36Sopenharmony_ci		break;
5062306a36Sopenharmony_ci	default:
5162306a36Sopenharmony_ci		BUG();
5262306a36Sopenharmony_ci	}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ciretry:
5562306a36Sopenharmony_ci	size = __ceph_getxattr(inode, name, "", 0);
5662306a36Sopenharmony_ci	if (size > 0) {
5762306a36Sopenharmony_ci		value = kzalloc(size, GFP_NOFS);
5862306a36Sopenharmony_ci		if (!value)
5962306a36Sopenharmony_ci			return ERR_PTR(-ENOMEM);
6062306a36Sopenharmony_ci		size = __ceph_getxattr(inode, name, value, size);
6162306a36Sopenharmony_ci	}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (size == -ERANGE && retry_cnt < 10) {
6462306a36Sopenharmony_ci		retry_cnt++;
6562306a36Sopenharmony_ci		kfree(value);
6662306a36Sopenharmony_ci		value = NULL;
6762306a36Sopenharmony_ci		goto retry;
6862306a36Sopenharmony_ci	}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	if (size > 0) {
7162306a36Sopenharmony_ci		acl = posix_acl_from_xattr(&init_user_ns, value, size);
7262306a36Sopenharmony_ci	} else if (size == -ENODATA || size == 0) {
7362306a36Sopenharmony_ci		acl = NULL;
7462306a36Sopenharmony_ci	} else {
7562306a36Sopenharmony_ci		pr_err_ratelimited("get acl %llx.%llx failed, err=%d\n",
7662306a36Sopenharmony_ci				   ceph_vinop(inode), size);
7762306a36Sopenharmony_ci		acl = ERR_PTR(-EIO);
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	kfree(value);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	if (!IS_ERR(acl))
8362306a36Sopenharmony_ci		ceph_set_cached_acl(inode, type, acl);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	return acl;
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ciint ceph_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
8962306a36Sopenharmony_ci		 struct posix_acl *acl, int type)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	int ret = 0, size = 0;
9262306a36Sopenharmony_ci	const char *name = NULL;
9362306a36Sopenharmony_ci	char *value = NULL;
9462306a36Sopenharmony_ci	struct iattr newattrs;
9562306a36Sopenharmony_ci	struct inode *inode = d_inode(dentry);
9662306a36Sopenharmony_ci	struct timespec64 old_ctime = inode_get_ctime(inode);
9762306a36Sopenharmony_ci	umode_t new_mode = inode->i_mode, old_mode = inode->i_mode;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	if (ceph_snap(inode) != CEPH_NOSNAP) {
10062306a36Sopenharmony_ci		ret = -EROFS;
10162306a36Sopenharmony_ci		goto out;
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	switch (type) {
10562306a36Sopenharmony_ci	case ACL_TYPE_ACCESS:
10662306a36Sopenharmony_ci		name = XATTR_NAME_POSIX_ACL_ACCESS;
10762306a36Sopenharmony_ci		if (acl) {
10862306a36Sopenharmony_ci			ret = posix_acl_update_mode(&nop_mnt_idmap, inode,
10962306a36Sopenharmony_ci						    &new_mode, &acl);
11062306a36Sopenharmony_ci			if (ret)
11162306a36Sopenharmony_ci				goto out;
11262306a36Sopenharmony_ci		}
11362306a36Sopenharmony_ci		break;
11462306a36Sopenharmony_ci	case ACL_TYPE_DEFAULT:
11562306a36Sopenharmony_ci		if (!S_ISDIR(inode->i_mode)) {
11662306a36Sopenharmony_ci			ret = acl ? -EINVAL : 0;
11762306a36Sopenharmony_ci			goto out;
11862306a36Sopenharmony_ci		}
11962306a36Sopenharmony_ci		name = XATTR_NAME_POSIX_ACL_DEFAULT;
12062306a36Sopenharmony_ci		break;
12162306a36Sopenharmony_ci	default:
12262306a36Sopenharmony_ci		ret = -EINVAL;
12362306a36Sopenharmony_ci		goto out;
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if (acl) {
12762306a36Sopenharmony_ci		size = posix_acl_xattr_size(acl->a_count);
12862306a36Sopenharmony_ci		value = kmalloc(size, GFP_NOFS);
12962306a36Sopenharmony_ci		if (!value) {
13062306a36Sopenharmony_ci			ret = -ENOMEM;
13162306a36Sopenharmony_ci			goto out;
13262306a36Sopenharmony_ci		}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci		ret = posix_acl_to_xattr(&init_user_ns, acl, value, size);
13562306a36Sopenharmony_ci		if (ret < 0)
13662306a36Sopenharmony_ci			goto out_free;
13762306a36Sopenharmony_ci	}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	if (new_mode != old_mode) {
14062306a36Sopenharmony_ci		newattrs.ia_ctime = current_time(inode);
14162306a36Sopenharmony_ci		newattrs.ia_mode = new_mode;
14262306a36Sopenharmony_ci		newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
14362306a36Sopenharmony_ci		ret = __ceph_setattr(inode, &newattrs, NULL);
14462306a36Sopenharmony_ci		if (ret)
14562306a36Sopenharmony_ci			goto out_free;
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	ret = __ceph_setxattr(inode, name, value, size, 0);
14962306a36Sopenharmony_ci	if (ret) {
15062306a36Sopenharmony_ci		if (new_mode != old_mode) {
15162306a36Sopenharmony_ci			newattrs.ia_ctime = old_ctime;
15262306a36Sopenharmony_ci			newattrs.ia_mode = old_mode;
15362306a36Sopenharmony_ci			newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
15462306a36Sopenharmony_ci			__ceph_setattr(inode, &newattrs, NULL);
15562306a36Sopenharmony_ci		}
15662306a36Sopenharmony_ci		goto out_free;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	ceph_set_cached_acl(inode, type, acl);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ciout_free:
16262306a36Sopenharmony_ci	kfree(value);
16362306a36Sopenharmony_ciout:
16462306a36Sopenharmony_ci	return ret;
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ciint ceph_pre_init_acls(struct inode *dir, umode_t *mode,
16862306a36Sopenharmony_ci		       struct ceph_acl_sec_ctx *as_ctx)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct posix_acl *acl, *default_acl;
17162306a36Sopenharmony_ci	size_t val_size1 = 0, val_size2 = 0;
17262306a36Sopenharmony_ci	struct ceph_pagelist *pagelist = NULL;
17362306a36Sopenharmony_ci	void *tmp_buf = NULL;
17462306a36Sopenharmony_ci	int err;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	err = posix_acl_create(dir, mode, &default_acl, &acl);
17762306a36Sopenharmony_ci	if (err)
17862306a36Sopenharmony_ci		return err;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	if (acl) {
18162306a36Sopenharmony_ci		err = posix_acl_equiv_mode(acl, mode);
18262306a36Sopenharmony_ci		if (err < 0)
18362306a36Sopenharmony_ci			goto out_err;
18462306a36Sopenharmony_ci		if (err == 0) {
18562306a36Sopenharmony_ci			posix_acl_release(acl);
18662306a36Sopenharmony_ci			acl = NULL;
18762306a36Sopenharmony_ci		}
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	if (!default_acl && !acl)
19162306a36Sopenharmony_ci		return 0;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	if (acl)
19462306a36Sopenharmony_ci		val_size1 = posix_acl_xattr_size(acl->a_count);
19562306a36Sopenharmony_ci	if (default_acl)
19662306a36Sopenharmony_ci		val_size2 = posix_acl_xattr_size(default_acl->a_count);
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	err = -ENOMEM;
19962306a36Sopenharmony_ci	tmp_buf = kmalloc(max(val_size1, val_size2), GFP_KERNEL);
20062306a36Sopenharmony_ci	if (!tmp_buf)
20162306a36Sopenharmony_ci		goto out_err;
20262306a36Sopenharmony_ci	pagelist = ceph_pagelist_alloc(GFP_KERNEL);
20362306a36Sopenharmony_ci	if (!pagelist)
20462306a36Sopenharmony_ci		goto out_err;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	err = ceph_pagelist_reserve(pagelist, PAGE_SIZE);
20762306a36Sopenharmony_ci	if (err)
20862306a36Sopenharmony_ci		goto out_err;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	ceph_pagelist_encode_32(pagelist, acl && default_acl ? 2 : 1);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	if (acl) {
21362306a36Sopenharmony_ci		size_t len = strlen(XATTR_NAME_POSIX_ACL_ACCESS);
21462306a36Sopenharmony_ci		err = ceph_pagelist_reserve(pagelist, len + val_size1 + 8);
21562306a36Sopenharmony_ci		if (err)
21662306a36Sopenharmony_ci			goto out_err;
21762306a36Sopenharmony_ci		ceph_pagelist_encode_string(pagelist, XATTR_NAME_POSIX_ACL_ACCESS,
21862306a36Sopenharmony_ci					    len);
21962306a36Sopenharmony_ci		err = posix_acl_to_xattr(&init_user_ns, acl,
22062306a36Sopenharmony_ci					 tmp_buf, val_size1);
22162306a36Sopenharmony_ci		if (err < 0)
22262306a36Sopenharmony_ci			goto out_err;
22362306a36Sopenharmony_ci		ceph_pagelist_encode_32(pagelist, val_size1);
22462306a36Sopenharmony_ci		ceph_pagelist_append(pagelist, tmp_buf, val_size1);
22562306a36Sopenharmony_ci	}
22662306a36Sopenharmony_ci	if (default_acl) {
22762306a36Sopenharmony_ci		size_t len = strlen(XATTR_NAME_POSIX_ACL_DEFAULT);
22862306a36Sopenharmony_ci		err = ceph_pagelist_reserve(pagelist, len + val_size2 + 8);
22962306a36Sopenharmony_ci		if (err)
23062306a36Sopenharmony_ci			goto out_err;
23162306a36Sopenharmony_ci		ceph_pagelist_encode_string(pagelist,
23262306a36Sopenharmony_ci					  XATTR_NAME_POSIX_ACL_DEFAULT, len);
23362306a36Sopenharmony_ci		err = posix_acl_to_xattr(&init_user_ns, default_acl,
23462306a36Sopenharmony_ci					 tmp_buf, val_size2);
23562306a36Sopenharmony_ci		if (err < 0)
23662306a36Sopenharmony_ci			goto out_err;
23762306a36Sopenharmony_ci		ceph_pagelist_encode_32(pagelist, val_size2);
23862306a36Sopenharmony_ci		ceph_pagelist_append(pagelist, tmp_buf, val_size2);
23962306a36Sopenharmony_ci	}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	kfree(tmp_buf);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	as_ctx->acl = acl;
24462306a36Sopenharmony_ci	as_ctx->default_acl = default_acl;
24562306a36Sopenharmony_ci	as_ctx->pagelist = pagelist;
24662306a36Sopenharmony_ci	return 0;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ciout_err:
24962306a36Sopenharmony_ci	posix_acl_release(acl);
25062306a36Sopenharmony_ci	posix_acl_release(default_acl);
25162306a36Sopenharmony_ci	kfree(tmp_buf);
25262306a36Sopenharmony_ci	if (pagelist)
25362306a36Sopenharmony_ci		ceph_pagelist_release(pagelist);
25462306a36Sopenharmony_ci	return err;
25562306a36Sopenharmony_ci}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_civoid ceph_init_inode_acls(struct inode *inode, struct ceph_acl_sec_ctx *as_ctx)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	if (!inode)
26062306a36Sopenharmony_ci		return;
26162306a36Sopenharmony_ci	ceph_set_cached_acl(inode, ACL_TYPE_ACCESS, as_ctx->acl);
26262306a36Sopenharmony_ci	ceph_set_cached_acl(inode, ACL_TYPE_DEFAULT, as_ctx->default_acl);
26362306a36Sopenharmony_ci}
264