Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
# SPDX-License-Identifier: GPL-2.0-only
config CEPH_FS
tristate "Ceph distributed file system"
depends on INET
select CEPH_LIB
select NETFS_SUPPORT
select FS_ENCRYPTION_ALGS if FS_ENCRYPTION
default n
help
Choose Y or M here to include support for mounting the
experimental Ceph distributed file system. Ceph is an extremely
scalable file system designed to provide high performance,
reliable access to petabytes of storage.
More information at https://ceph.io/.
If unsure, say N.
if CEPH_FS
config CEPH_FSCACHE
bool "Enable Ceph client caching support"
depends on CEPH_FS=m && FSCACHE || CEPH_FS=y && FSCACHE=y
help
Choose Y here to enable persistent, read-only local
caching support for Ceph clients using FS-Cache
endif
config CEPH_FS_POSIX_ACL
bool "Ceph POSIX Access Control Lists"
depends on CEPH_FS
select FS_POSIX_ACL
help
POSIX Access Control Lists (ACLs) support permissions for users and
groups beyond the owner/group/world scheme.
If you don't know what Access Control Lists are, say N
config CEPH_FS_SECURITY_LABEL
bool "CephFS Security Labels"
depends on CEPH_FS && SECURITY
help
Security labels support alternative access control models
implemented by security modules like SELinux. This option
enables an extended attribute handler for file security
labels in the Ceph filesystem.
If you are not using a security module that requires using
extended attributes for file security labels, say N.
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for CEPH filesystem.
#
obj-$(CONFIG_CEPH_FS) += ceph.o
ceph-y := super.o inode.o dir.o file.o locks.o addr.o ioctl.o \
export.o caps.o snap.o xattr.o quota.o io.o \
mds_client.o mdsmap.o strings.o ceph_frag.o \
debugfs.o util.o metric.o subvolume_metrics.o
ceph-$(CONFIG_CEPH_FSCACHE) += cache.o
ceph-$(CONFIG_CEPH_FS_POSIX_ACL) += acl.o
ceph-$(CONFIG_FS_ENCRYPTION) += crypto.o
+259
View File
@@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/fs/ceph/acl.c
*
* Copyright (C) 2013 Guangliang Zhao, <lucienchao@gmail.com>
*/
#include <linux/ceph/ceph_debug.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/xattr.h>
#include <linux/posix_acl_xattr.h>
#include <linux/posix_acl.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "super.h"
#include "mds_client.h"
static inline void ceph_set_cached_acl(struct inode *inode,
int type, struct posix_acl *acl)
{
struct ceph_inode_info *ci = ceph_inode(inode);
spin_lock(&ci->i_ceph_lock);
if (__ceph_caps_issued_mask_metric(ci, CEPH_CAP_XATTR_SHARED, 0))
set_cached_acl(inode, type, acl);
else
forget_cached_acl(inode, type);
spin_unlock(&ci->i_ceph_lock);
}
struct posix_acl *ceph_get_acl(struct inode *inode, int type, bool rcu)
{
struct ceph_client *cl = ceph_inode_to_client(inode);
int size;
unsigned int retry_cnt = 0;
const char *name;
char *value = NULL;
struct posix_acl *acl;
if (rcu)
return ERR_PTR(-ECHILD);
switch (type) {
case ACL_TYPE_ACCESS:
name = XATTR_NAME_POSIX_ACL_ACCESS;
break;
case ACL_TYPE_DEFAULT:
name = XATTR_NAME_POSIX_ACL_DEFAULT;
break;
default:
BUG();
}
retry:
size = __ceph_getxattr(inode, name, "", 0);
if (size > 0) {
value = kzalloc(size, GFP_NOFS);
if (!value)
return ERR_PTR(-ENOMEM);
size = __ceph_getxattr(inode, name, value, size);
}
if (size == -ERANGE && retry_cnt < 10) {
retry_cnt++;
kfree(value);
value = NULL;
goto retry;
}
if (size > 0) {
acl = posix_acl_from_xattr(&init_user_ns, value, size);
} else if (size == -ENODATA || size == 0) {
acl = NULL;
} else {
pr_err_ratelimited_client(cl, "%llx.%llx failed, err=%d\n",
ceph_vinop(inode), size);
acl = ERR_PTR(-EIO);
}
kfree(value);
if (!IS_ERR(acl))
ceph_set_cached_acl(inode, type, acl);
return acl;
}
int ceph_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
struct posix_acl *acl, int type)
{
int ret = 0;
size_t size = 0;
const char *name = NULL;
char *value = NULL;
struct iattr newattrs;
struct inode *inode = d_inode(dentry);
struct timespec64 old_ctime = inode_get_ctime(inode);
umode_t new_mode = inode->i_mode, old_mode = inode->i_mode;
if (ceph_snap(inode) != CEPH_NOSNAP) {
ret = -EROFS;
goto out;
}
switch (type) {
case ACL_TYPE_ACCESS:
name = XATTR_NAME_POSIX_ACL_ACCESS;
if (acl) {
ret = posix_acl_update_mode(idmap, inode,
&new_mode, &acl);
if (ret)
goto out;
}
break;
case ACL_TYPE_DEFAULT:
if (!S_ISDIR(inode->i_mode)) {
ret = acl ? -EINVAL : 0;
goto out;
}
name = XATTR_NAME_POSIX_ACL_DEFAULT;
break;
default:
ret = -EINVAL;
goto out;
}
if (acl) {
value = posix_acl_to_xattr(&init_user_ns, acl, &size, GFP_NOFS);
if (!value) {
ret = -ENOMEM;
goto out;
}
}
if (new_mode != old_mode) {
newattrs.ia_ctime = current_time(inode);
newattrs.ia_mode = new_mode;
newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
ret = __ceph_setattr(idmap, inode, &newattrs, NULL);
if (ret)
goto out_free;
}
ret = __ceph_setxattr(inode, name, value, size, 0);
if (ret) {
if (new_mode != old_mode) {
newattrs.ia_ctime = old_ctime;
newattrs.ia_mode = old_mode;
newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
__ceph_setattr(idmap, inode, &newattrs, NULL);
}
goto out_free;
}
ceph_set_cached_acl(inode, type, acl);
out_free:
kfree(value);
out:
return ret;
}
int ceph_pre_init_acls(struct inode *dir, umode_t *mode,
struct ceph_acl_sec_ctx *as_ctx)
{
struct posix_acl *acl, *default_acl;
size_t val_size1 = 0, val_size2 = 0;
struct ceph_pagelist *pagelist = NULL;
void *tmp_buf1 = NULL, *tmp_buf2 = NULL;
int err;
err = posix_acl_create(dir, mode, &default_acl, &acl);
if (err)
return err;
if (acl) {
err = posix_acl_equiv_mode(acl, mode);
if (err < 0)
goto out_err;
if (err == 0) {
posix_acl_release(acl);
acl = NULL;
}
}
if (!default_acl && !acl)
return 0;
err = -ENOMEM;
pagelist = ceph_pagelist_alloc(GFP_KERNEL);
if (!pagelist)
goto out_err;
err = ceph_pagelist_reserve(pagelist, PAGE_SIZE);
if (err)
goto out_err;
ceph_pagelist_encode_32(pagelist, acl && default_acl ? 2 : 1);
if (acl) {
size_t len = strlen(XATTR_NAME_POSIX_ACL_ACCESS);
err = -ENOMEM;
tmp_buf1 = posix_acl_to_xattr(&init_user_ns, acl,
&val_size1, GFP_KERNEL);
if (!tmp_buf1)
goto out_err;
err = ceph_pagelist_reserve(pagelist, len + val_size1 + 8);
if (err)
goto out_err;
ceph_pagelist_encode_string(pagelist, XATTR_NAME_POSIX_ACL_ACCESS,
len);
ceph_pagelist_encode_32(pagelist, val_size1);
ceph_pagelist_append(pagelist, tmp_buf1, val_size1);
}
if (default_acl) {
size_t len = strlen(XATTR_NAME_POSIX_ACL_DEFAULT);
err = -ENOMEM;
tmp_buf2 = posix_acl_to_xattr(&init_user_ns, default_acl,
&val_size2, GFP_KERNEL);
if (!tmp_buf2)
goto out_err;
err = ceph_pagelist_reserve(pagelist, len + val_size2 + 8);
if (err)
goto out_err;
ceph_pagelist_encode_string(pagelist,
XATTR_NAME_POSIX_ACL_DEFAULT, len);
ceph_pagelist_encode_32(pagelist, val_size2);
ceph_pagelist_append(pagelist, tmp_buf2, val_size2);
}
kfree(tmp_buf1);
kfree(tmp_buf2);
as_ctx->acl = acl;
as_ctx->default_acl = default_acl;
as_ctx->pagelist = pagelist;
return 0;
out_err:
posix_acl_release(acl);
posix_acl_release(default_acl);
kfree(tmp_buf1);
kfree(tmp_buf2);
if (pagelist)
ceph_pagelist_release(pagelist);
return err;
}
void ceph_init_inode_acls(struct inode *inode, struct ceph_acl_sec_ctx *as_ctx)
{
if (!inode)
return;
ceph_set_cached_acl(inode, ACL_TYPE_ACCESS, as_ctx->acl);
ceph_set_cached_acl(inode, ACL_TYPE_DEFAULT, as_ctx->default_acl);
}
File diff suppressed because it is too large Load Diff
+112
View File
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Ceph cache definitions.
*
* Copyright (C) 2013 by Adfin Solutions, Inc. All Rights Reserved.
* Written by Milosz Tanski (milosz@adfin.com)
*/
#include <linux/ceph/ceph_debug.h>
#include <linux/fs_context.h>
#include "super.h"
#include "cache.h"
void ceph_fscache_register_inode_cookie(struct inode *inode)
{
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
/* No caching for filesystem? */
if (!fsc->fscache)
return;
/* Regular files only */
if (!S_ISREG(inode->i_mode))
return;
/* Only new inodes! */
if (!(inode_state_read_once(inode) & I_NEW))
return;
WARN_ON_ONCE(ci->netfs.cache);
ci->netfs.cache =
fscache_acquire_cookie(fsc->fscache, 0,
&ci->i_vino, sizeof(ci->i_vino),
&ci->i_version, sizeof(ci->i_version),
i_size_read(inode));
if (ci->netfs.cache)
mapping_set_release_always(inode->i_mapping);
}
void ceph_fscache_unregister_inode_cookie(struct ceph_inode_info *ci)
{
fscache_relinquish_cookie(ceph_fscache_cookie(ci), false);
}
void ceph_fscache_use_cookie(struct inode *inode, bool will_modify)
{
struct ceph_inode_info *ci = ceph_inode(inode);
fscache_use_cookie(ceph_fscache_cookie(ci), will_modify);
}
void ceph_fscache_unuse_cookie(struct inode *inode, bool update)
{
struct ceph_inode_info *ci = ceph_inode(inode);
if (update) {
loff_t i_size = i_size_read(inode);
fscache_unuse_cookie(ceph_fscache_cookie(ci),
&ci->i_version, &i_size);
} else {
fscache_unuse_cookie(ceph_fscache_cookie(ci), NULL, NULL);
}
}
void ceph_fscache_update(struct inode *inode)
{
struct ceph_inode_info *ci = ceph_inode(inode);
loff_t i_size = i_size_read(inode);
fscache_update_cookie(ceph_fscache_cookie(ci), &ci->i_version, &i_size);
}
void ceph_fscache_invalidate(struct inode *inode, bool dio_write)
{
struct ceph_inode_info *ci = ceph_inode(inode);
fscache_invalidate(ceph_fscache_cookie(ci),
&ci->i_version, i_size_read(inode),
dio_write ? FSCACHE_INVAL_DIO_WRITE : 0);
}
int ceph_fscache_register_fs(struct ceph_fs_client* fsc, struct fs_context *fc)
{
const struct ceph_fsid *fsid = &fsc->client->fsid;
const char *fscache_uniq = fsc->mount_options->fscache_uniq;
size_t uniq_len = fscache_uniq ? strlen(fscache_uniq) : 0;
char *name;
int err = 0;
name = kasprintf(GFP_KERNEL, "ceph,%pU%s%s", fsid, uniq_len ? "," : "",
uniq_len ? fscache_uniq : "");
if (!name)
return -ENOMEM;
fsc->fscache = fscache_acquire_volume(name, NULL, NULL, 0);
if (IS_ERR_OR_NULL(fsc->fscache)) {
errorfc(fc, "Unable to register fscache cookie for %s", name);
err = fsc->fscache ? PTR_ERR(fsc->fscache) : -EOPNOTSUPP;
fsc->fscache = NULL;
}
kfree(name);
return err;
}
void ceph_fscache_unregister_fs(struct ceph_fs_client* fsc)
{
fscache_relinquish_volume(fsc->fscache, NULL, false);
}
+117
View File
@@ -0,0 +1,117 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Ceph cache definitions.
*
* Copyright (C) 2013 by Adfin Solutions, Inc. All Rights Reserved.
* Written by Milosz Tanski (milosz@adfin.com)
*/
#ifndef _CEPH_CACHE_H
#define _CEPH_CACHE_H
#include <linux/netfs.h>
#ifdef CONFIG_CEPH_FSCACHE
#include <linux/fscache.h>
int ceph_fscache_register_fs(struct ceph_fs_client* fsc, struct fs_context *fc);
void ceph_fscache_unregister_fs(struct ceph_fs_client* fsc);
void ceph_fscache_register_inode_cookie(struct inode *inode);
void ceph_fscache_unregister_inode_cookie(struct ceph_inode_info* ci);
void ceph_fscache_use_cookie(struct inode *inode, bool will_modify);
void ceph_fscache_unuse_cookie(struct inode *inode, bool update);
void ceph_fscache_update(struct inode *inode);
void ceph_fscache_invalidate(struct inode *inode, bool dio_write);
static inline struct fscache_cookie *ceph_fscache_cookie(struct ceph_inode_info *ci)
{
return netfs_i_cookie(&ci->netfs);
}
static inline void ceph_fscache_resize(struct inode *inode, loff_t to)
{
struct ceph_inode_info *ci = ceph_inode(inode);
struct fscache_cookie *cookie = ceph_fscache_cookie(ci);
if (cookie) {
ceph_fscache_use_cookie(inode, true);
fscache_resize_cookie(cookie, to);
ceph_fscache_unuse_cookie(inode, true);
}
}
static inline int ceph_fscache_unpin_writeback(struct inode *inode,
struct writeback_control *wbc)
{
return netfs_unpin_writeback(inode, wbc);
}
#define ceph_fscache_dirty_folio netfs_dirty_folio
static inline bool ceph_is_cache_enabled(struct inode *inode)
{
return fscache_cookie_enabled(ceph_fscache_cookie(ceph_inode(inode)));
}
#else /* CONFIG_CEPH_FSCACHE */
static inline int ceph_fscache_register_fs(struct ceph_fs_client* fsc,
struct fs_context *fc)
{
return 0;
}
static inline void ceph_fscache_unregister_fs(struct ceph_fs_client* fsc)
{
}
static inline void ceph_fscache_register_inode_cookie(struct inode *inode)
{
}
static inline void ceph_fscache_unregister_inode_cookie(struct ceph_inode_info* ci)
{
}
static inline void ceph_fscache_use_cookie(struct inode *inode, bool will_modify)
{
}
static inline void ceph_fscache_unuse_cookie(struct inode *inode, bool update)
{
}
static inline void ceph_fscache_update(struct inode *inode)
{
}
static inline void ceph_fscache_invalidate(struct inode *inode, bool dio_write)
{
}
static inline struct fscache_cookie *ceph_fscache_cookie(struct ceph_inode_info *ci)
{
return NULL;
}
static inline void ceph_fscache_resize(struct inode *inode, loff_t to)
{
}
static inline int ceph_fscache_unpin_writeback(struct inode *inode,
struct writeback_control *wbc)
{
return 0;
}
#define ceph_fscache_dirty_folio filemap_dirty_folio
static inline bool ceph_is_cache_enabled(struct inode *inode)
{
return false;
}
#endif /* CONFIG_CEPH_FSCACHE */
#endif
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Ceph 'frag' type
*/
#include <linux/module.h>
#include <linux/ceph/types.h>
int ceph_frag_compare(__u32 a, __u32 b)
{
unsigned va = ceph_frag_value(a);
unsigned vb = ceph_frag_value(b);
if (va < vb)
return -1;
if (va > vb)
return 1;
va = ceph_frag_bits(a);
vb = ceph_frag_bits(b);
if (va < vb)
return -1;
if (va > vb)
return 1;
return 0;
}
+605
View File
@@ -0,0 +1,605 @@
// SPDX-License-Identifier: GPL-2.0
/*
* The base64 encode/decode code was copied from fscrypt:
* Copyright (C) 2015, Google, Inc.
* Copyright (C) 2015, Motorola Mobility
* Written by Uday Savagaonkar, 2014.
* Modified by Jaegeuk Kim, 2015.
*/
#include <linux/ceph/ceph_debug.h>
#include <linux/xattr.h>
#include <linux/fscrypt.h>
#include <linux/ceph/striper.h>
#include "super.h"
#include "mds_client.h"
#include "crypto.h"
static int ceph_crypt_get_context(struct inode *inode, void *ctx, size_t len)
{
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_fscrypt_auth *cfa = (struct ceph_fscrypt_auth *)ci->fscrypt_auth;
u32 ctxlen;
/* Non existent or too short? */
if (!cfa || (ci->fscrypt_auth_len < (offsetof(struct ceph_fscrypt_auth, cfa_blob) + 1)))
return -ENOBUFS;
/* Some format we don't recognize? */
if (le32_to_cpu(cfa->cfa_version) != CEPH_FSCRYPT_AUTH_VERSION)
return -ENOBUFS;
ctxlen = le32_to_cpu(cfa->cfa_blob_len);
if (len < ctxlen)
return -ERANGE;
memcpy(ctx, cfa->cfa_blob, ctxlen);
return ctxlen;
}
static int ceph_crypt_set_context(struct inode *inode, const void *ctx,
size_t len, void *fs_data)
{
int ret;
struct iattr attr = { };
struct ceph_iattr cia = { };
struct ceph_fscrypt_auth *cfa;
WARN_ON_ONCE(fs_data);
if (len > FSCRYPT_SET_CONTEXT_MAX_SIZE)
return -EINVAL;
cfa = kzalloc_obj(*cfa);
if (!cfa)
return -ENOMEM;
cfa->cfa_version = cpu_to_le32(CEPH_FSCRYPT_AUTH_VERSION);
cfa->cfa_blob_len = cpu_to_le32(len);
memcpy(cfa->cfa_blob, ctx, len);
cia.fscrypt_auth = cfa;
ret = __ceph_setattr(&nop_mnt_idmap, inode, &attr, &cia);
if (ret == 0)
inode_set_flags(inode, S_ENCRYPTED, S_ENCRYPTED);
kfree(cia.fscrypt_auth);
return ret;
}
static bool ceph_crypt_empty_dir(struct inode *inode)
{
struct ceph_inode_info *ci = ceph_inode(inode);
return ci->i_rsubdirs + ci->i_rfiles == 1;
}
static const union fscrypt_policy *ceph_get_dummy_policy(struct super_block *sb)
{
return ceph_sb_to_fs_client(sb)->fsc_dummy_enc_policy.policy;
}
static struct fscrypt_operations ceph_fscrypt_ops = {
.inode_info_offs = (int)offsetof(struct ceph_inode_info, i_crypt_info) -
(int)offsetof(struct ceph_inode_info, netfs.inode),
.needs_bounce_pages = 1,
.get_context = ceph_crypt_get_context,
.set_context = ceph_crypt_set_context,
.get_dummy_policy = ceph_get_dummy_policy,
.empty_dir = ceph_crypt_empty_dir,
};
void ceph_fscrypt_set_ops(struct super_block *sb)
{
fscrypt_set_ops(sb, &ceph_fscrypt_ops);
}
void ceph_fscrypt_free_dummy_policy(struct ceph_fs_client *fsc)
{
fscrypt_free_dummy_policy(&fsc->fsc_dummy_enc_policy);
}
int ceph_fscrypt_prepare_context(struct inode *dir, struct inode *inode,
struct ceph_acl_sec_ctx *as)
{
int ret, ctxsize;
bool encrypted = false;
struct ceph_inode_info *ci = ceph_inode(inode);
ret = fscrypt_prepare_new_inode(dir, inode, &encrypted);
if (ret)
return ret;
if (!encrypted)
return 0;
as->fscrypt_auth = kzalloc_obj(*as->fscrypt_auth);
if (!as->fscrypt_auth)
return -ENOMEM;
ctxsize = fscrypt_context_for_new_inode(as->fscrypt_auth->cfa_blob,
inode);
if (ctxsize < 0)
return ctxsize;
as->fscrypt_auth->cfa_version = cpu_to_le32(CEPH_FSCRYPT_AUTH_VERSION);
as->fscrypt_auth->cfa_blob_len = cpu_to_le32(ctxsize);
WARN_ON_ONCE(ci->fscrypt_auth);
kfree(ci->fscrypt_auth);
ci->fscrypt_auth_len = ceph_fscrypt_auth_len(as->fscrypt_auth);
ci->fscrypt_auth = kmemdup(as->fscrypt_auth, ci->fscrypt_auth_len,
GFP_KERNEL);
if (!ci->fscrypt_auth)
return -ENOMEM;
inode->i_flags |= S_ENCRYPTED;
return 0;
}
void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req,
struct ceph_acl_sec_ctx *as)
{
swap(req->r_fscrypt_auth, as->fscrypt_auth);
}
/*
* User-created snapshots can't start with '_'. Snapshots that start with this
* character are special (hint: there aren't real snapshots) and use the
* following format:
*
* _<SNAPSHOT-NAME>_<INODE-NUMBER>
*
* where:
* - <SNAPSHOT-NAME> - the real snapshot name that may need to be decrypted,
* - <INODE-NUMBER> - the inode number (in decimal) for the actual snapshot
*
* This function parses these snapshot names and returns the inode
* <INODE-NUMBER>. 'name_len' will also bet set with the <SNAPSHOT-NAME>
* length.
*/
static struct inode *parse_longname(const struct inode *parent,
const char *name, int *name_len)
{
struct ceph_client *cl = ceph_inode_to_client(parent);
struct inode *dir = NULL;
struct ceph_vino vino = { .snap = CEPH_NOSNAP };
char *name_end, *inode_number;
int ret = -EIO;
/* Snapshot name must start with an underscore */
if (*name_len <= 0 || name[0] != '_')
return ERR_PTR(-EIO);
/* Skip initial '_' and NUL-terminate */
char *str __free(kfree) = kmemdup_nul(name + 1, *name_len - 1, GFP_KERNEL);
if (!str)
return ERR_PTR(-ENOMEM);
name_end = strrchr(str, '_');
if (!name_end) {
doutc(cl, "failed to parse long snapshot name: %s\n", str);
return ERR_PTR(-EIO);
}
*name_len = (name_end - str);
if (*name_len <= 0) {
pr_err_client(cl, "failed to parse long snapshot name\n");
return ERR_PTR(-EIO);
}
/* Get the inode number */
inode_number = name_end + 1;
ret = kstrtou64(inode_number, 10, &vino.ino);
if (ret) {
doutc(cl, "failed to parse inode number: %s\n", str);
return ERR_PTR(ret);
}
/* And finally the inode */
dir = ceph_find_inode(parent->i_sb, vino);
if (!dir) {
/* This can happen if we're not mounting cephfs on the root */
dir = ceph_get_inode(parent->i_sb, vino, NULL);
if (IS_ERR(dir))
doutc(cl, "can't find inode %s (%s)\n", inode_number, name);
}
return dir;
}
int ceph_encode_encrypted_dname(struct inode *parent, char *buf, int elen)
{
struct ceph_client *cl = ceph_inode_to_client(parent);
struct inode *dir = parent;
char *p = buf;
u32 len;
int name_len = elen;
int ret;
u8 *cryptbuf = NULL;
/* Handle the special case of snapshot names that start with '_' */
if (ceph_snap(dir) == CEPH_SNAPDIR && *p == '_') {
dir = parse_longname(parent, p, &name_len);
if (IS_ERR(dir))
return PTR_ERR(dir);
p++; /* skip initial '_' */
}
if (!fscrypt_has_encryption_key(dir))
goto out;
/*
* Convert cleartext d_name to ciphertext. If result is longer than
* CEPH_NOHASH_NAME_MAX, sha256 the remaining bytes
*
* See: fscrypt_setup_filename
*/
if (!fscrypt_fname_encrypted_size(dir, name_len, NAME_MAX, &len)) {
elen = -ENAMETOOLONG;
goto out;
}
/* Allocate a buffer appropriate to hold the result */
cryptbuf = kmalloc(len > CEPH_NOHASH_NAME_MAX ? NAME_MAX : len,
GFP_KERNEL);
if (!cryptbuf) {
elen = -ENOMEM;
goto out;
}
ret = fscrypt_fname_encrypt(dir,
&(struct qstr)QSTR_INIT(p, name_len),
cryptbuf, len);
if (ret) {
elen = ret;
goto out;
}
/* hash the end if the name is long enough */
if (len > CEPH_NOHASH_NAME_MAX) {
u8 hash[SHA256_DIGEST_SIZE];
u8 *extra = cryptbuf + CEPH_NOHASH_NAME_MAX;
/*
* hash the extra bytes and overwrite crypttext beyond that
* point with it
*/
sha256(extra, len - CEPH_NOHASH_NAME_MAX, hash);
memcpy(extra, hash, SHA256_DIGEST_SIZE);
len = CEPH_NOHASH_NAME_MAX + SHA256_DIGEST_SIZE;
}
/* base64 encode the encrypted name */
elen = base64_encode(cryptbuf, len, p, false, BASE64_IMAP);
doutc(cl, "base64-encoded ciphertext name = %.*s\n", elen, p);
/* To understand the 240 limit, see CEPH_NOHASH_NAME_MAX comments */
WARN_ON(elen > 240);
if (dir != parent) // leading _ is already there; append _<inum>
elen += 1 + sprintf(p + elen, "_%llu", dir->i_ino);
out:
kfree(cryptbuf);
if (dir != parent) {
if ((inode_state_read_once(dir) & I_NEW))
discard_new_inode(dir);
else
iput(dir);
}
return elen;
}
/**
* ceph_fname_to_usr - convert a filename for userland presentation
* @fname: ceph_fname to be converted
* @tname: temporary name buffer to use for conversion (may be NULL)
* @oname: where converted name should be placed
* @is_nokey: set to true if key wasn't available during conversion (may be NULL)
*
* Given a filename (usually from the MDS), format it for presentation to
* userland. If @parent is not encrypted, just pass it back as-is.
*
* Otherwise, base64 decode the string, and then ask fscrypt to format it
* for userland presentation.
*
* Returns 0 on success or negative error code on error.
*/
int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
struct fscrypt_str *oname, bool *is_nokey)
{
struct inode *dir = fname->dir;
struct fscrypt_str _tname = FSTR_INIT(NULL, 0);
struct fscrypt_str iname;
char *name = fname->name;
int name_len = fname->name_len;
int ret;
/* Sanity check that the resulting name will fit in the buffer */
if (fname->name_len > NAME_MAX || fname->ctext_len > NAME_MAX)
return -EIO;
/* Handle the special case of snapshot names that start with '_' */
if ((ceph_snap(dir) == CEPH_SNAPDIR) && (name_len > 0) &&
(name[0] == '_')) {
dir = parse_longname(dir, name, &name_len);
if (IS_ERR(dir))
return PTR_ERR(dir);
name++; /* skip initial '_' */
}
if (!IS_ENCRYPTED(dir)) {
oname->name = fname->name;
oname->len = fname->name_len;
ret = 0;
goto out_inode;
}
ret = ceph_fscrypt_prepare_readdir(dir);
if (ret)
goto out_inode;
/*
* Use the raw dentry name as sent by the MDS instead of
* generating a nokey name via fscrypt.
*/
if (!fscrypt_has_encryption_key(dir)) {
if (fname->no_copy)
oname->name = fname->name;
else
memcpy(oname->name, fname->name, fname->name_len);
oname->len = fname->name_len;
if (is_nokey)
*is_nokey = true;
ret = 0;
goto out_inode;
}
if (fname->ctext_len == 0) {
int declen;
if (!tname) {
ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname);
if (ret)
goto out_inode;
tname = &_tname;
}
declen = base64_decode(name, name_len,
tname->name, false, BASE64_IMAP);
if (declen <= 0) {
ret = -EIO;
goto out;
}
iname.name = tname->name;
iname.len = declen;
} else {
iname.name = fname->ctext;
iname.len = fname->ctext_len;
}
ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, oname);
if (!ret && (dir != fname->dir)) {
char tmp_buf[BASE64_CHARS(NAME_MAX)];
name_len = snprintf(tmp_buf, sizeof(tmp_buf), "_%.*s_%llu",
oname->len, oname->name, dir->i_ino);
memcpy(oname->name, tmp_buf, name_len);
oname->len = name_len;
}
out:
fscrypt_fname_free_buffer(&_tname);
out_inode:
if (dir != fname->dir) {
if ((inode_state_read_once(dir) & I_NEW))
discard_new_inode(dir);
else
iput(dir);
}
return ret;
}
/**
* ceph_fscrypt_prepare_readdir - simple __fscrypt_prepare_readdir() wrapper
* @dir: directory inode for readdir prep
*
* Simple wrapper around __fscrypt_prepare_readdir() that will mark directory as
* non-complete if this call results in having the directory unlocked.
*
* Returns:
* 1 - if directory was locked and key is now loaded (i.e. dir is unlocked)
* 0 - if directory is still locked
* < 0 - if __fscrypt_prepare_readdir() fails
*/
int ceph_fscrypt_prepare_readdir(struct inode *dir)
{
bool had_key = fscrypt_has_encryption_key(dir);
int err;
if (!IS_ENCRYPTED(dir))
return 0;
err = __fscrypt_prepare_readdir(dir);
if (err)
return err;
if (!had_key && fscrypt_has_encryption_key(dir)) {
/* directory just got unlocked, mark it as not complete */
ceph_dir_clear_complete(dir);
return 1;
}
return 0;
}
int ceph_fscrypt_decrypt_block_inplace(const struct inode *inode,
struct page *page, unsigned int len,
unsigned int offs, u64 lblk_num)
{
struct ceph_client *cl = ceph_inode_to_client(inode);
doutc(cl, "%p %llx.%llx len %u offs %u blk %llu\n", inode,
ceph_vinop(inode), len, offs, lblk_num);
return fscrypt_decrypt_block_inplace(inode, page, len, offs, lblk_num);
}
int ceph_fscrypt_encrypt_block_inplace(const struct inode *inode,
struct page *page, unsigned int len,
unsigned int offs, u64 lblk_num)
{
struct ceph_client *cl = ceph_inode_to_client(inode);
doutc(cl, "%p %llx.%llx len %u offs %u blk %llu\n", inode,
ceph_vinop(inode), len, offs, lblk_num);
return fscrypt_encrypt_block_inplace(inode, page, len, offs, lblk_num);
}
/**
* ceph_fscrypt_decrypt_pages - decrypt an array of pages
* @inode: pointer to inode associated with these pages
* @page: pointer to page array
* @off: offset into the file that the read data starts
* @len: max length to decrypt
*
* Decrypt an array of fscrypt'ed pages and return the amount of
* data decrypted. Any data in the page prior to the start of the
* first complete block in the read is ignored. Any incomplete
* crypto blocks at the end of the array are ignored (and should
* probably be zeroed by the caller).
*
* Returns the length of the decrypted data or a negative errno.
*/
int ceph_fscrypt_decrypt_pages(struct inode *inode, struct page **page,
u64 off, int len)
{
int i, num_blocks;
u64 baseblk = off >> CEPH_FSCRYPT_BLOCK_SHIFT;
int ret = 0;
/*
* We can't deal with partial blocks on an encrypted file, so mask off
* the last bit.
*/
num_blocks = ceph_fscrypt_blocks(off, len & CEPH_FSCRYPT_BLOCK_MASK);
/* Decrypt each block */
for (i = 0; i < num_blocks; ++i) {
int blkoff = i << CEPH_FSCRYPT_BLOCK_SHIFT;
int pgidx = blkoff >> PAGE_SHIFT;
unsigned int pgoffs = offset_in_page(blkoff);
int fret;
fret = ceph_fscrypt_decrypt_block_inplace(inode, page[pgidx],
CEPH_FSCRYPT_BLOCK_SIZE, pgoffs,
baseblk + i);
if (fret < 0) {
if (ret == 0)
ret = fret;
break;
}
ret += CEPH_FSCRYPT_BLOCK_SIZE;
}
return ret;
}
/**
* ceph_fscrypt_decrypt_extents: decrypt received extents in given buffer
* @inode: inode associated with pages being decrypted
* @page: pointer to page array
* @off: offset into the file that the data in page[0] starts
* @map: pointer to extent array
* @ext_cnt: length of extent array
*
* Given an extent map and a page array, decrypt the received data in-place,
* skipping holes. Returns the offset into buffer of end of last decrypted
* block.
*/
int ceph_fscrypt_decrypt_extents(struct inode *inode, struct page **page,
u64 off, struct ceph_sparse_extent *map,
u32 ext_cnt)
{
struct ceph_client *cl = ceph_inode_to_client(inode);
int i, ret = 0;
struct ceph_inode_info *ci = ceph_inode(inode);
u64 objno, objoff;
u32 xlen;
/* Nothing to do for empty array */
if (ext_cnt == 0) {
doutc(cl, "%p %llx.%llx empty array, ret 0\n", inode,
ceph_vinop(inode));
return 0;
}
ceph_calc_file_object_mapping(&ci->i_layout, off, map[0].len,
&objno, &objoff, &xlen);
for (i = 0; i < ext_cnt; ++i) {
struct ceph_sparse_extent *ext = &map[i];
int pgsoff = ext->off - objoff;
int pgidx = pgsoff >> PAGE_SHIFT;
int fret;
if ((ext->off | ext->len) & ~CEPH_FSCRYPT_BLOCK_MASK) {
pr_warn_client(cl,
"%p %llx.%llx bad encrypted sparse extent "
"idx %d off %llx len %llx\n",
inode, ceph_vinop(inode), i, ext->off,
ext->len);
return -EIO;
}
fret = ceph_fscrypt_decrypt_pages(inode, &page[pgidx],
off + pgsoff, ext->len);
doutc(cl, "%p %llx.%llx [%d] 0x%llx~0x%llx fret %d\n", inode,
ceph_vinop(inode), i, ext->off, ext->len, fret);
if (fret < 0) {
if (ret == 0)
ret = fret;
break;
}
ret = pgsoff + fret;
}
doutc(cl, "ret %d\n", ret);
return ret;
}
/**
* ceph_fscrypt_encrypt_pages - encrypt an array of pages
* @inode: pointer to inode associated with these pages
* @page: pointer to page array
* @off: offset into the file that the data starts
* @len: max length to encrypt
*
* Encrypt an array of cleartext pages and return the amount of
* data encrypted. Any data in the page prior to the start of the
* first complete block in the read is ignored. Any incomplete
* crypto blocks at the end of the array are ignored.
*
* Returns the length of the encrypted data or a negative errno.
*/
int ceph_fscrypt_encrypt_pages(struct inode *inode, struct page **page, u64 off,
int len)
{
int i, num_blocks;
u64 baseblk = off >> CEPH_FSCRYPT_BLOCK_SHIFT;
int ret = 0;
/*
* We can't deal with partial blocks on an encrypted file, so mask off
* the last bit.
*/
num_blocks = ceph_fscrypt_blocks(off, len & CEPH_FSCRYPT_BLOCK_MASK);
/* Encrypt each block */
for (i = 0; i < num_blocks; ++i) {
int blkoff = i << CEPH_FSCRYPT_BLOCK_SHIFT;
int pgidx = blkoff >> PAGE_SHIFT;
unsigned int pgoffs = offset_in_page(blkoff);
int fret;
fret = ceph_fscrypt_encrypt_block_inplace(inode, page[pgidx],
CEPH_FSCRYPT_BLOCK_SIZE, pgoffs,
baseblk + i);
if (fret < 0) {
if (ret == 0)
ret = fret;
break;
}
ret += CEPH_FSCRYPT_BLOCK_SIZE;
}
return ret;
}
+272
View File
@@ -0,0 +1,272 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Ceph fscrypt functionality
*/
#ifndef _CEPH_CRYPTO_H
#define _CEPH_CRYPTO_H
#include <crypto/sha2.h>
#include <linux/fscrypt.h>
#include <linux/base64.h>
#define CEPH_FSCRYPT_BLOCK_SHIFT 12
#define CEPH_FSCRYPT_BLOCK_SIZE (_AC(1, UL) << CEPH_FSCRYPT_BLOCK_SHIFT)
#define CEPH_FSCRYPT_BLOCK_MASK (~(CEPH_FSCRYPT_BLOCK_SIZE-1))
struct ceph_fs_client;
struct ceph_acl_sec_ctx;
struct ceph_mds_request;
struct ceph_fname {
struct inode *dir;
char *name; // b64 encoded, possibly hashed
unsigned char *ctext; // binary crypttext (if any)
u32 name_len; // length of name buffer
u32 ctext_len; // length of crypttext
bool no_copy;
};
/*
* Header for the encrypted file when truncating the size, this
* will be sent to MDS, and the MDS will update the encrypted
* last block and then truncate the size.
*/
struct ceph_fscrypt_truncate_size_header {
__u8 ver;
__u8 compat;
/*
* It will be sizeof(assert_ver + file_offset + block_size)
* if the last block is empty when it's located in a file
* hole. Or the data_len will plus CEPH_FSCRYPT_BLOCK_SIZE.
*/
__le32 data_len;
__le64 change_attr;
__le64 file_offset;
__le32 block_size;
} __packed;
struct ceph_fscrypt_auth {
__le32 cfa_version;
__le32 cfa_blob_len;
u8 cfa_blob[FSCRYPT_SET_CONTEXT_MAX_SIZE];
} __packed;
#define CEPH_FSCRYPT_AUTH_VERSION 1
static inline u32 ceph_fscrypt_auth_len(struct ceph_fscrypt_auth *fa)
{
u32 ctxsize = le32_to_cpu(fa->cfa_blob_len);
return offsetof(struct ceph_fscrypt_auth, cfa_blob) + ctxsize;
}
#ifdef CONFIG_FS_ENCRYPTION
/*
* We want to encrypt filenames when creating them, but the encrypted
* versions of those names may have illegal characters in them. To mitigate
* that, we base64 encode them, but that gives us a result that can exceed
* NAME_MAX.
*
* Follow a similar scheme to fscrypt itself, and cap the filename to a
* smaller size. If the ciphertext name is longer than the value below, then
* sha256 hash the remaining bytes.
*
* For the fscrypt_nokey_name struct the dirhash[2] member is useless in ceph
* so the corresponding struct will be:
*
* struct fscrypt_ceph_nokey_name {
* u8 bytes[157];
* u8 sha256[SHA256_DIGEST_SIZE];
* }; // 180 bytes => 240 bytes base64-encoded, which is <= NAME_MAX (255)
*
* (240 bytes is the maximum size allowed for snapshot names to take into
* account the format: '_<SNAPSHOT-NAME>_<INODE-NUMBER>'.)
*
* Note that for long names that end up having their tail portion hashed, we
* must also store the full encrypted name (in the dentry's alternate_name
* field).
*/
#define CEPH_NOHASH_NAME_MAX (180 - SHA256_DIGEST_SIZE)
void ceph_fscrypt_set_ops(struct super_block *sb);
void ceph_fscrypt_free_dummy_policy(struct ceph_fs_client *fsc);
int ceph_fscrypt_prepare_context(struct inode *dir, struct inode *inode,
struct ceph_acl_sec_ctx *as);
void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req,
struct ceph_acl_sec_ctx *as);
int ceph_encode_encrypted_dname(struct inode *parent, char *buf, int len);
static inline int ceph_fname_alloc_buffer(struct inode *parent,
struct fscrypt_str *fname)
{
if (!IS_ENCRYPTED(parent))
return 0;
return fscrypt_fname_alloc_buffer(NAME_MAX, fname);
}
static inline void ceph_fname_free_buffer(struct inode *parent,
struct fscrypt_str *fname)
{
if (IS_ENCRYPTED(parent))
fscrypt_fname_free_buffer(fname);
}
int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
struct fscrypt_str *oname, bool *is_nokey);
int ceph_fscrypt_prepare_readdir(struct inode *dir);
static inline unsigned int ceph_fscrypt_blocks(u64 off, u64 len)
{
/* crypto blocks cannot span more than one page */
BUILD_BUG_ON(CEPH_FSCRYPT_BLOCK_SHIFT > PAGE_SHIFT);
return ((off+len+CEPH_FSCRYPT_BLOCK_SIZE-1) >> CEPH_FSCRYPT_BLOCK_SHIFT) -
(off >> CEPH_FSCRYPT_BLOCK_SHIFT);
}
/*
* If we have an encrypted inode then we must adjust the offset and
* range of the on-the-wire read to cover an entire encryption block.
* The copy will be done using the original offset and length, after
* we've decrypted the result.
*/
static inline void ceph_fscrypt_adjust_off_and_len(struct inode *inode,
u64 *off, u64 *len)
{
if (IS_ENCRYPTED(inode)) {
*len = ceph_fscrypt_blocks(*off, *len) * CEPH_FSCRYPT_BLOCK_SIZE;
*off &= CEPH_FSCRYPT_BLOCK_MASK;
}
}
int ceph_fscrypt_decrypt_block_inplace(const struct inode *inode,
struct page *page, unsigned int len,
unsigned int offs, u64 lblk_num);
int ceph_fscrypt_encrypt_block_inplace(const struct inode *inode,
struct page *page, unsigned int len,
unsigned int offs, u64 lblk_num);
int ceph_fscrypt_decrypt_pages(struct inode *inode, struct page **page,
u64 off, int len);
int ceph_fscrypt_decrypt_extents(struct inode *inode, struct page **page,
u64 off, struct ceph_sparse_extent *map,
u32 ext_cnt);
int ceph_fscrypt_encrypt_pages(struct inode *inode, struct page **page, u64 off,
int len);
static inline struct page *ceph_fscrypt_pagecache_page(struct page *page)
{
return fscrypt_is_bounce_page(page) ? fscrypt_pagecache_page(page) : page;
}
#else /* CONFIG_FS_ENCRYPTION */
static inline void ceph_fscrypt_set_ops(struct super_block *sb)
{
}
static inline void ceph_fscrypt_free_dummy_policy(struct ceph_fs_client *fsc)
{
}
static inline int ceph_fscrypt_prepare_context(struct inode *dir,
struct inode *inode,
struct ceph_acl_sec_ctx *as)
{
if (IS_ENCRYPTED(dir))
return -EOPNOTSUPP;
return 0;
}
static inline void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req,
struct ceph_acl_sec_ctx *as_ctx)
{
}
static inline int ceph_encode_encrypted_dname(struct inode *parent, char *buf,
int len)
{
return len;
}
static inline int ceph_fname_alloc_buffer(struct inode *parent,
struct fscrypt_str *fname)
{
return 0;
}
static inline void ceph_fname_free_buffer(struct inode *parent,
struct fscrypt_str *fname)
{
}
static inline int ceph_fname_to_usr(const struct ceph_fname *fname,
struct fscrypt_str *tname,
struct fscrypt_str *oname, bool *is_nokey)
{
oname->name = fname->name;
oname->len = fname->name_len;
return 0;
}
static inline int ceph_fscrypt_prepare_readdir(struct inode *dir)
{
return 0;
}
static inline void ceph_fscrypt_adjust_off_and_len(struct inode *inode,
u64 *off, u64 *len)
{
}
static inline int ceph_fscrypt_decrypt_block_inplace(const struct inode *inode,
struct page *page, unsigned int len,
unsigned int offs, u64 lblk_num)
{
return 0;
}
static inline int ceph_fscrypt_encrypt_block_inplace(const struct inode *inode,
struct page *page, unsigned int len,
unsigned int offs, u64 lblk_num)
{
return 0;
}
static inline int ceph_fscrypt_decrypt_pages(struct inode *inode,
struct page **page, u64 off,
int len)
{
return 0;
}
static inline int ceph_fscrypt_decrypt_extents(struct inode *inode,
struct page **page, u64 off,
struct ceph_sparse_extent *map,
u32 ext_cnt)
{
return 0;
}
static inline int ceph_fscrypt_encrypt_pages(struct inode *inode,
struct page **page, u64 off,
int len)
{
return 0;
}
static inline struct page *ceph_fscrypt_pagecache_page(struct page *page)
{
return page;
}
#endif /* CONFIG_FS_ENCRYPTION */
static inline loff_t ceph_fscrypt_page_offset(struct page *page)
{
return page_offset(ceph_fscrypt_pagecache_page(page));
}
#endif /* _CEPH_CRYPTO_H */
+745
View File
@@ -0,0 +1,745 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ceph/ceph_debug.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/math64.h>
#include <linux/ktime.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#include <linux/ceph/libceph.h>
#include <linux/ceph/mon_client.h>
#include <linux/ceph/auth.h>
#include <linux/ceph/debugfs.h>
#include <linux/ceph/decode.h>
#include "super.h"
#ifdef CONFIG_DEBUG_FS
#include "mds_client.h"
#include "metric.h"
#include "subvolume_metrics.h"
/**
* struct ceph_session_feature_desc - Maps feature bits to names for debugfs
* @bit: Feature bit number from enum ceph_feature_type (see mds_client.h)
* @name: Human-readable feature name for debugfs output
*
* Used by metric_features_show() to display negotiated session features.
*/
struct ceph_session_feature_desc {
unsigned int bit;
const char *name;
};
static const struct ceph_session_feature_desc ceph_session_feature_table[] = {
{ CEPHFS_FEATURE_METRIC_COLLECT, "METRIC_COLLECT" },
{ CEPHFS_FEATURE_REPLY_ENCODING, "REPLY_ENCODING" },
{ CEPHFS_FEATURE_RECLAIM_CLIENT, "RECLAIM_CLIENT" },
{ CEPHFS_FEATURE_LAZY_CAP_WANTED, "LAZY_CAP_WANTED" },
{ CEPHFS_FEATURE_MULTI_RECONNECT, "MULTI_RECONNECT" },
{ CEPHFS_FEATURE_DELEG_INO, "DELEG_INO" },
{ CEPHFS_FEATURE_ALTERNATE_NAME, "ALTERNATE_NAME" },
{ CEPHFS_FEATURE_NOTIFY_SESSION_STATE, "NOTIFY_SESSION_STATE" },
{ CEPHFS_FEATURE_OP_GETVXATTR, "OP_GETVXATTR" },
{ CEPHFS_FEATURE_32BITS_RETRY_FWD, "32BITS_RETRY_FWD" },
{ CEPHFS_FEATURE_NEW_SNAPREALM_INFO, "NEW_SNAPREALM_INFO" },
{ CEPHFS_FEATURE_HAS_OWNER_UIDGID, "HAS_OWNER_UIDGID" },
{ CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK, "MDS_AUTH_CAPS_CHECK" },
{ CEPHFS_FEATURE_SUBVOLUME_METRICS, "SUBVOLUME_METRICS" },
};
static int mdsmap_show(struct seq_file *s, void *p)
{
int i;
struct ceph_fs_client *fsc = s->private;
struct ceph_mdsmap *mdsmap;
if (!fsc->mdsc || !fsc->mdsc->mdsmap)
return 0;
mdsmap = fsc->mdsc->mdsmap;
seq_printf(s, "epoch %d\n", mdsmap->m_epoch);
seq_printf(s, "root %d\n", mdsmap->m_root);
seq_printf(s, "max_mds %d\n", mdsmap->m_max_mds);
seq_printf(s, "session_timeout %d\n", mdsmap->m_session_timeout);
seq_printf(s, "session_autoclose %d\n", mdsmap->m_session_autoclose);
for (i = 0; i < mdsmap->possible_max_rank; i++) {
struct ceph_entity_addr *addr = &mdsmap->m_info[i].addr;
int state = mdsmap->m_info[i].state;
seq_printf(s, "\tmds%d\t%s\t(%s)\n", i,
ceph_pr_addr(addr),
ceph_mds_state_name(state));
}
return 0;
}
/*
* mdsc debugfs
*/
static int mdsc_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_mds_client *mdsc = fsc->mdsc;
struct ceph_mds_request *req;
struct rb_node *rp;
char *path;
mutex_lock(&mdsc->mutex);
for (rp = rb_first(&mdsc->request_tree); rp; rp = rb_next(rp)) {
req = rb_entry(rp, struct ceph_mds_request, r_node);
if (req->r_request && req->r_session)
seq_printf(s, "%lld\tmds%d\t", req->r_tid,
req->r_session->s_mds);
else if (!req->r_request)
seq_printf(s, "%lld\t(no request)\t", req->r_tid);
else
seq_printf(s, "%lld\t(no session)\t", req->r_tid);
seq_printf(s, "%s", ceph_mds_op_name(req->r_op));
if (test_bit(CEPH_MDS_R_GOT_UNSAFE, &req->r_req_flags))
seq_puts(s, "\t(unsafe)");
else
seq_puts(s, "\t");
if (req->r_inode) {
seq_printf(s, " #%llx", ceph_ino(req->r_inode));
} else if (req->r_dentry) {
struct ceph_path_info path_info = {0};
path = ceph_mdsc_build_path(mdsc, req->r_dentry, &path_info, 0);
if (IS_ERR(path))
path = NULL;
spin_lock(&req->r_dentry->d_lock);
seq_printf(s, " #%llx/%pd (%s)",
ceph_ino(d_inode(req->r_dentry->d_parent)),
req->r_dentry,
path ? path : "");
spin_unlock(&req->r_dentry->d_lock);
ceph_mdsc_free_path_info(&path_info);
} else if (req->r_path1) {
seq_printf(s, " #%llx/%s", req->r_ino1.ino,
req->r_path1);
} else {
seq_printf(s, " #%llx", req->r_ino1.ino);
}
if (req->r_old_dentry) {
struct ceph_path_info path_info = {0};
path = ceph_mdsc_build_path(mdsc, req->r_old_dentry, &path_info, 0);
if (IS_ERR(path))
path = NULL;
spin_lock(&req->r_old_dentry->d_lock);
seq_printf(s, " #%llx/%pd (%s)",
req->r_old_dentry_dir ?
ceph_ino(req->r_old_dentry_dir) : 0,
req->r_old_dentry,
path ? path : "");
spin_unlock(&req->r_old_dentry->d_lock);
ceph_mdsc_free_path_info(&path_info);
} else if (req->r_path2 && req->r_op != CEPH_MDS_OP_SYMLINK) {
if (req->r_ino2.ino)
seq_printf(s, " #%llx/%s", req->r_ino2.ino,
req->r_path2);
else
seq_printf(s, " %s", req->r_path2);
}
seq_puts(s, "\n");
}
mutex_unlock(&mdsc->mutex);
return 0;
}
#define CEPH_LAT_METRIC_SHOW(name, total, avg, min, max, sq) { \
s64 _total, _avg, _min, _max, _sq, _st; \
_avg = ktime_to_us(avg); \
_min = ktime_to_us(min == KTIME_MAX ? 0 : min); \
_max = ktime_to_us(max); \
_total = total - 1; \
_sq = _total > 0 ? DIV64_U64_ROUND_CLOSEST(sq, _total) : 0; \
_st = int_sqrt64(_sq); \
_st = ktime_to_us(_st); \
seq_printf(s, "%-14s%-12lld%-16lld%-16lld%-16lld%lld\n", \
name, total, _avg, _min, _max, _st); \
}
#define CEPH_SZ_METRIC_SHOW(name, total, avg, min, max, sum) { \
u64 _min = min == U64_MAX ? 0 : min; \
seq_printf(s, "%-14s%-12lld%-16llu%-16llu%-16llu%llu\n", \
name, total, avg, _min, max, sum); \
}
static int metrics_file_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_client_metric *m = &fsc->mdsc->metric;
seq_printf(s, "item total\n");
seq_printf(s, "------------------------------------------\n");
seq_printf(s, "%-35s%lld\n", "total inodes",
percpu_counter_sum(&m->total_inodes));
seq_printf(s, "%-35s%lld\n", "opened files",
atomic64_read(&m->opened_files));
seq_printf(s, "%-35s%lld\n", "pinned i_caps",
atomic64_read(&m->total_caps));
seq_printf(s, "%-35s%lld\n", "opened inodes",
percpu_counter_sum(&m->opened_inodes));
return 0;
}
static const char * const metric_str[] = {
"read",
"write",
"metadata",
"copyfrom"
};
static int metrics_latency_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_client_metric *cm = &fsc->mdsc->metric;
struct ceph_metric *m;
s64 total, avg, min, max, sq;
int i;
seq_printf(s, "item total avg_lat(us) min_lat(us) max_lat(us) stdev(us)\n");
seq_printf(s, "-----------------------------------------------------------------------------------\n");
for (i = 0; i < METRIC_MAX; i++) {
m = &cm->metric[i];
spin_lock(&m->lock);
total = m->total;
avg = m->latency_avg;
min = m->latency_min;
max = m->latency_max;
sq = m->latency_sq_sum;
spin_unlock(&m->lock);
CEPH_LAT_METRIC_SHOW(metric_str[i], total, avg, min, max, sq);
}
return 0;
}
static int metrics_size_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_client_metric *cm = &fsc->mdsc->metric;
struct ceph_metric *m;
s64 total;
u64 sum, avg, min, max;
int i;
seq_printf(s, "item total avg_sz(bytes) min_sz(bytes) max_sz(bytes) total_sz(bytes)\n");
seq_printf(s, "----------------------------------------------------------------------------------------\n");
for (i = 0; i < METRIC_MAX; i++) {
/* skip 'metadata' as it doesn't use the size metric */
if (i == METRIC_METADATA)
continue;
m = &cm->metric[i];
spin_lock(&m->lock);
total = m->total;
sum = m->size_sum;
avg = total > 0 ? DIV64_U64_ROUND_CLOSEST(sum, total) : 0;
min = m->size_min;
max = m->size_max;
spin_unlock(&m->lock);
CEPH_SZ_METRIC_SHOW(metric_str[i], total, avg, min, max, sum);
}
return 0;
}
static int metrics_caps_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_client_metric *m = &fsc->mdsc->metric;
int nr_caps = 0;
seq_printf(s, "item total miss hit\n");
seq_printf(s, "-------------------------------------------------\n");
seq_printf(s, "%-14s%-16lld%-16lld%lld\n", "d_lease",
atomic64_read(&m->total_dentries),
percpu_counter_sum(&m->d_lease_mis),
percpu_counter_sum(&m->d_lease_hit));
nr_caps = atomic64_read(&m->total_caps);
seq_printf(s, "%-14s%-16d%-16lld%lld\n", "caps", nr_caps,
percpu_counter_sum(&m->i_caps_mis),
percpu_counter_sum(&m->i_caps_hit));
return 0;
}
static int caps_show_cb(struct inode *inode, int mds, void *p)
{
struct ceph_inode_info *ci = ceph_inode(inode);
struct seq_file *s = p;
struct ceph_cap *cap;
spin_lock(&ci->i_ceph_lock);
cap = __get_cap_for_mds(ci, mds);
if (cap)
seq_printf(s, "0x%-17llx%-3d%-17s%-17s\n", ceph_ino(inode),
cap->session->s_mds,
ceph_cap_string(cap->issued),
ceph_cap_string(cap->implemented));
spin_unlock(&ci->i_ceph_lock);
return 0;
}
static int caps_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_mds_client *mdsc = fsc->mdsc;
int total, avail, used, reserved, min, i;
struct cap_wait *cw;
ceph_reservation_status(fsc, &total, &avail, &used, &reserved, &min);
seq_printf(s, "total\t\t%d\n"
"avail\t\t%d\n"
"used\t\t%d\n"
"reserved\t%d\n"
"min\t\t%d\n\n",
total, avail, used, reserved, min);
seq_printf(s, "ino mds issued implemented\n");
seq_printf(s, "--------------------------------------------------\n");
mutex_lock(&mdsc->mutex);
for (i = 0; i < mdsc->max_sessions; i++) {
struct ceph_mds_session *session;
session = __ceph_lookup_mds_session(mdsc, i);
if (!session)
continue;
mutex_unlock(&mdsc->mutex);
mutex_lock(&session->s_mutex);
ceph_iterate_session_caps(session, caps_show_cb, s);
mutex_unlock(&session->s_mutex);
ceph_put_mds_session(session);
mutex_lock(&mdsc->mutex);
}
mutex_unlock(&mdsc->mutex);
seq_printf(s, "\n\nWaiters:\n--------\n");
seq_printf(s, "tgid ino need want\n");
seq_printf(s, "-----------------------------------------------------\n");
spin_lock(&mdsc->caps_list_lock);
list_for_each_entry(cw, &mdsc->cap_wait_list, list) {
seq_printf(s, "%-13d0x%-17llx%-17s%-17s\n", cw->tgid, cw->ino,
ceph_cap_string(cw->need),
ceph_cap_string(cw->want));
}
spin_unlock(&mdsc->caps_list_lock);
return 0;
}
static int mds_sessions_show(struct seq_file *s, void *ptr)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_mds_client *mdsc = fsc->mdsc;
struct ceph_auth_client *ac = fsc->client->monc.auth;
struct ceph_options *opt = fsc->client->options;
int mds;
mutex_lock(&mdsc->mutex);
/* The 'num' portion of an 'entity name' */
seq_printf(s, "global_id %llu\n", ac->global_id);
/* The -o name mount argument */
seq_printf(s, "name \"%s\"\n", opt->name ? opt->name : "");
/* The list of MDS session rank+state */
for (mds = 0; mds < mdsc->max_sessions; mds++) {
struct ceph_mds_session *session =
__ceph_lookup_mds_session(mdsc, mds);
if (!session) {
continue;
}
mutex_unlock(&mdsc->mutex);
seq_printf(s, "mds.%d %s\n",
session->s_mds,
ceph_session_state_name(session->s_state));
ceph_put_mds_session(session);
mutex_lock(&mdsc->mutex);
}
mutex_unlock(&mdsc->mutex);
return 0;
}
static int status_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_entity_inst *inst = &fsc->client->msgr.inst;
struct ceph_entity_addr *client_addr = ceph_client_addr(fsc->client);
seq_printf(s, "instance: %s.%lld %s/%u\n", ENTITY_NAME(inst->name),
ceph_pr_addr(client_addr), le32_to_cpu(client_addr->nonce));
seq_printf(s, "blocklisted: %s\n", str_true_false(fsc->blocklisted));
return 0;
}
static int reset_status_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_mds_client *mdsc = fsc->mdsc;
struct ceph_client_reset_state *st;
u64 trigger = 0, success = 0, failure = 0;
unsigned long last_start = 0, last_finish = 0;
int last_errno = 0;
enum ceph_client_reset_phase phase = CEPH_CLIENT_RESET_IDLE;
bool drain_timed_out = false;
int sessions_reset = 0;
int blocked_requests = 0;
char reason[CEPH_CLIENT_RESET_REASON_LEN];
if (!mdsc)
return 0;
st = &mdsc->reset_state;
spin_lock(&st->lock);
trigger = st->trigger_count;
success = st->success_count;
failure = st->failure_count;
last_start = st->last_start;
last_finish = st->last_finish;
last_errno = st->last_errno;
phase = st->phase;
drain_timed_out = st->drain_timed_out;
sessions_reset = st->sessions_reset;
strscpy(reason, st->last_reason, sizeof(reason));
spin_unlock(&st->lock);
blocked_requests = atomic_read(&st->blocked_requests);
seq_printf(s, "phase: %s\n", ceph_reset_phase_name(phase));
seq_printf(s, "trigger_count: %llu\n", trigger);
seq_printf(s, "success_count: %llu\n", success);
seq_printf(s, "failure_count: %llu\n", failure);
if (last_start)
seq_printf(s, "last_start_ms_ago: %u\n",
jiffies_to_msecs(jiffies - last_start));
else
seq_puts(s, "last_start_ms_ago: (never)\n");
if (last_finish)
seq_printf(s, "last_finish_ms_ago: %u\n",
jiffies_to_msecs(jiffies - last_finish));
else
seq_puts(s, "last_finish_ms_ago: (never)\n");
seq_printf(s, "last_errno: %d\n", last_errno);
seq_printf(s, "last_reason: %s\n",
reason[0] ? reason : "(none)");
seq_printf(s, "drain_timed_out: %s\n",
drain_timed_out ? "yes" : "no");
seq_printf(s, "sessions_reset: %d\n", sessions_reset);
seq_printf(s, "blocked_requests: %d\n", blocked_requests);
return 0;
}
static ssize_t reset_trigger_write(struct file *file, const char __user *buf,
size_t len, loff_t *ppos)
{
struct ceph_fs_client *fsc = file->private_data;
struct ceph_mds_client *mdsc = fsc->mdsc;
char reason[CEPH_CLIENT_RESET_REASON_LEN];
size_t copy;
int ret;
if (!mdsc)
return -ENODEV;
copy = min_t(size_t, len, sizeof(reason) - 1);
if (copy && copy_from_user(reason, buf, copy))
return -EFAULT;
reason[copy] = '\0';
strim(reason);
ret = ceph_mdsc_schedule_reset(mdsc, reason);
if (ret)
return ret;
return len;
}
static int subvolume_metrics_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_mds_client *mdsc = fsc->mdsc;
struct ceph_subvol_metric_snapshot *snapshot = NULL;
u32 nr = 0;
u64 total_sent = 0;
u64 nonzero_sends = 0;
u32 i;
if (!mdsc) {
seq_puts(s, "mds client unavailable\n");
return 0;
}
mutex_lock(&mdsc->subvol_metrics_last_mutex);
if (mdsc->subvol_metrics_last && mdsc->subvol_metrics_last_nr) {
nr = mdsc->subvol_metrics_last_nr;
snapshot = kmemdup_array(mdsc->subvol_metrics_last, nr,
sizeof(*snapshot), GFP_KERNEL);
if (!snapshot)
nr = 0;
}
total_sent = mdsc->subvol_metrics_sent;
nonzero_sends = mdsc->subvol_metrics_nonzero_sends;
mutex_unlock(&mdsc->subvol_metrics_last_mutex);
seq_puts(s, "Last sent subvolume metrics:\n");
if (!nr) {
seq_puts(s, " (none)\n");
} else {
seq_puts(s, " subvol_id rd_ops wr_ops rd_bytes wr_bytes rd_lat_us wr_lat_us\n");
for (i = 0; i < nr; i++) {
const struct ceph_subvol_metric_snapshot *e = &snapshot[i];
seq_printf(s, " %-18llu %-9llu %-9llu %-14llu %-14llu %-14llu %-14llu\n",
e->subvolume_id,
e->read_ops, e->write_ops,
e->read_bytes, e->write_bytes,
e->read_latency_us, e->write_latency_us);
}
}
kfree(snapshot);
seq_puts(s, "\nStatistics:\n");
seq_printf(s, " entries_sent: %llu\n", total_sent);
seq_printf(s, " non_zero_sends: %llu\n", nonzero_sends);
seq_puts(s, "\nPending (unsent) subvolume metrics:\n");
ceph_subvolume_metrics_dump(&mdsc->subvol_metrics, s);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(mdsmap);
DEFINE_SHOW_ATTRIBUTE(mdsc);
DEFINE_SHOW_ATTRIBUTE(caps);
DEFINE_SHOW_ATTRIBUTE(mds_sessions);
DEFINE_SHOW_ATTRIBUTE(status);
DEFINE_SHOW_ATTRIBUTE(reset_status);
DEFINE_SHOW_ATTRIBUTE(metrics_file);
DEFINE_SHOW_ATTRIBUTE(metrics_latency);
DEFINE_SHOW_ATTRIBUTE(metrics_size);
DEFINE_SHOW_ATTRIBUTE(metrics_caps);
DEFINE_SHOW_ATTRIBUTE(subvolume_metrics);
static int metric_features_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
struct ceph_mds_client *mdsc = fsc->mdsc;
unsigned long session_features = 0;
bool have_session = false;
bool metric_collect = false;
bool subvol_support = false;
bool metrics_enabled = false;
bool subvol_enabled = false;
int i;
if (!mdsc) {
seq_puts(s, "mds client unavailable\n");
return 0;
}
mutex_lock(&mdsc->mutex);
if (mdsc->metric.session) {
have_session = true;
session_features = mdsc->metric.session->s_features;
}
mutex_unlock(&mdsc->mutex);
if (have_session) {
metric_collect =
test_bit(CEPHFS_FEATURE_METRIC_COLLECT,
&session_features);
subvol_support =
test_bit(CEPHFS_FEATURE_SUBVOLUME_METRICS,
&session_features);
}
metrics_enabled = !disable_send_metrics && have_session && metric_collect;
subvol_enabled = metrics_enabled && subvol_support;
seq_printf(s,
"metrics_enabled: %s (disable_send_metrics=%d, session=%s, metric_collect=%s)\n",
metrics_enabled ? "yes" : "no",
disable_send_metrics ? 1 : 0,
have_session ? "yes" : "no",
metric_collect ? "yes" : "no");
seq_printf(s, "subvolume_metrics_enabled: %s\n",
subvol_enabled ? "yes" : "no");
seq_printf(s, "session_feature_bits: 0x%lx\n", session_features);
if (!have_session) {
seq_puts(s, "(no active MDS session for metrics)\n");
return 0;
}
for (i = 0; i < ARRAY_SIZE(ceph_session_feature_table); i++) {
const struct ceph_session_feature_desc *desc =
&ceph_session_feature_table[i];
bool set = test_bit(desc->bit, &session_features);
seq_printf(s, " %-24s : %s\n", desc->name,
set ? "yes" : "no");
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(metric_features);
static const struct file_operations ceph_reset_trigger_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.write = reset_trigger_write,
.llseek = noop_llseek,
};
/*
* debugfs
*/
static int congestion_kb_set(void *data, u64 val)
{
struct ceph_fs_client *fsc = (struct ceph_fs_client *)data;
fsc->mount_options->congestion_kb = (int)val;
return 0;
}
static int congestion_kb_get(void *data, u64 *val)
{
struct ceph_fs_client *fsc = (struct ceph_fs_client *)data;
*val = (u64)fsc->mount_options->congestion_kb;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(congestion_kb_fops, congestion_kb_get,
congestion_kb_set, "%llu\n");
void ceph_fs_debugfs_cleanup(struct ceph_fs_client *fsc)
{
doutc(fsc->client, "begin\n");
debugfs_remove(fsc->debugfs_bdi);
debugfs_remove(fsc->debugfs_congestion_kb);
debugfs_remove(fsc->debugfs_mdsmap);
debugfs_remove(fsc->debugfs_mds_sessions);
debugfs_remove(fsc->debugfs_caps);
debugfs_remove(fsc->debugfs_status);
debugfs_remove(fsc->debugfs_mdsc);
debugfs_remove_recursive(fsc->debugfs_reset_dir);
debugfs_remove(fsc->debugfs_subvolume_metrics);
debugfs_remove_recursive(fsc->debugfs_metrics_dir);
doutc(fsc->client, "done\n");
}
void ceph_fs_debugfs_init(struct ceph_fs_client *fsc)
{
char name[NAME_MAX];
doutc(fsc->client, "begin\n");
fsc->debugfs_congestion_kb =
debugfs_create_file("writeback_congestion_kb",
0600,
fsc->client->debugfs_dir,
fsc,
&congestion_kb_fops);
snprintf(name, sizeof(name), "../../bdi/%s",
bdi_dev_name(fsc->sb->s_bdi));
fsc->debugfs_bdi =
debugfs_create_symlink("bdi",
fsc->client->debugfs_dir,
name);
fsc->debugfs_mdsmap = debugfs_create_file("mdsmap",
0400,
fsc->client->debugfs_dir,
fsc,
&mdsmap_fops);
fsc->debugfs_mds_sessions = debugfs_create_file("mds_sessions",
0400,
fsc->client->debugfs_dir,
fsc,
&mds_sessions_fops);
fsc->debugfs_mdsc = debugfs_create_file("mdsc",
0400,
fsc->client->debugfs_dir,
fsc,
&mdsc_fops);
fsc->debugfs_caps = debugfs_create_file("caps",
0400,
fsc->client->debugfs_dir,
fsc,
&caps_fops);
fsc->debugfs_reset_dir = debugfs_create_dir("reset",
fsc->client->debugfs_dir);
debugfs_create_file("trigger", 0200,
fsc->debugfs_reset_dir, fsc,
&ceph_reset_trigger_fops);
debugfs_create_file("status", 0400,
fsc->debugfs_reset_dir, fsc,
&reset_status_fops);
fsc->debugfs_status = debugfs_create_file("status",
0400,
fsc->client->debugfs_dir,
fsc,
&status_fops);
fsc->debugfs_metrics_dir = debugfs_create_dir("metrics",
fsc->client->debugfs_dir);
debugfs_create_file("file", 0400, fsc->debugfs_metrics_dir, fsc,
&metrics_file_fops);
debugfs_create_file("latency", 0400, fsc->debugfs_metrics_dir, fsc,
&metrics_latency_fops);
debugfs_create_file("size", 0400, fsc->debugfs_metrics_dir, fsc,
&metrics_size_fops);
debugfs_create_file("caps", 0400, fsc->debugfs_metrics_dir, fsc,
&metrics_caps_fops);
debugfs_create_file("metric_features", 0400, fsc->debugfs_metrics_dir,
fsc, &metric_features_fops);
fsc->debugfs_subvolume_metrics =
debugfs_create_file("subvolumes", 0400,
fsc->debugfs_metrics_dir, fsc,
&subvolume_metrics_fops);
doutc(fsc->client, "done\n");
}
#else /* CONFIG_DEBUG_FS */
void ceph_fs_debugfs_init(struct ceph_fs_client *fsc)
{
}
void ceph_fs_debugfs_cleanup(struct ceph_fs_client *fsc)
{
}
#endif /* CONFIG_DEBUG_FS */
File diff suppressed because it is too large Load Diff
+618
View File
@@ -0,0 +1,618 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ceph/ceph_debug.h>
#include <linux/exportfs.h>
#include <linux/slab.h>
#include <linux/unaligned.h>
#include "super.h"
#include "mds_client.h"
#include "crypto.h"
/*
* Basic fh
*/
struct ceph_nfs_fh {
u64 ino;
} __attribute__ ((packed));
/*
* Larger fh that includes parent ino.
*/
struct ceph_nfs_confh {
u64 ino, parent_ino;
} __attribute__ ((packed));
/*
* fh for snapped inode
*/
struct ceph_nfs_snapfh {
u64 ino;
u64 snapid;
u64 parent_ino;
u32 hash;
} __attribute__ ((packed));
#define BYTES_PER_U32 (sizeof(u32))
#define CEPH_FH_BASIC_SIZE \
(sizeof(struct ceph_nfs_fh) / BYTES_PER_U32)
#define CEPH_FH_WITH_PARENT_SIZE \
(sizeof(struct ceph_nfs_confh) / BYTES_PER_U32)
#define CEPH_FH_SNAPPED_INODE_SIZE \
(sizeof(struct ceph_nfs_snapfh) / BYTES_PER_U32)
static int ceph_encode_snapfh(struct inode *inode, u32 *rawfh, int *max_len,
struct inode *parent_inode)
{
struct ceph_client *cl = ceph_inode_to_client(inode);
static const int snap_handle_length = CEPH_FH_SNAPPED_INODE_SIZE;
struct ceph_nfs_snapfh *sfh = (void *)rawfh;
u64 snapid = ceph_snap(inode);
int ret;
bool no_parent = true;
if (*max_len < snap_handle_length) {
*max_len = snap_handle_length;
ret = FILEID_INVALID;
goto out;
}
ret = -EINVAL;
if (snapid != CEPH_SNAPDIR) {
struct inode *dir;
struct dentry *dentry = d_find_alias(inode);
if (!dentry)
goto out;
rcu_read_lock();
dir = d_inode_rcu(dentry->d_parent);
if (ceph_snap(dir) != CEPH_SNAPDIR) {
sfh->parent_ino = ceph_ino(dir);
sfh->hash = ceph_dentry_hash(dir, dentry);
no_parent = false;
}
rcu_read_unlock();
dput(dentry);
}
if (no_parent) {
if (!S_ISDIR(inode->i_mode))
goto out;
sfh->parent_ino = sfh->ino;
sfh->hash = 0;
}
sfh->ino = ceph_ino(inode);
sfh->snapid = snapid;
*max_len = snap_handle_length;
ret = FILEID_BTRFS_WITH_PARENT;
out:
doutc(cl, "%p %llx.%llx ret=%d\n", inode, ceph_vinop(inode), ret);
return ret;
}
static int ceph_encode_fh(struct inode *inode, u32 *rawfh, int *max_len,
struct inode *parent_inode)
{
struct ceph_client *cl = ceph_inode_to_client(inode);
static const int handle_length = CEPH_FH_BASIC_SIZE;
static const int connected_handle_length = CEPH_FH_WITH_PARENT_SIZE;
int type;
if (ceph_snap(inode) != CEPH_NOSNAP)
return ceph_encode_snapfh(inode, rawfh, max_len, parent_inode);
if (parent_inode && (*max_len < connected_handle_length)) {
*max_len = connected_handle_length;
return FILEID_INVALID;
} else if (*max_len < handle_length) {
*max_len = handle_length;
return FILEID_INVALID;
}
if (parent_inode) {
struct ceph_nfs_confh *cfh = (void *)rawfh;
doutc(cl, "%p %llx.%llx with parent %p %llx.%llx\n", inode,
ceph_vinop(inode), parent_inode, ceph_vinop(parent_inode));
cfh->ino = ceph_ino(inode);
cfh->parent_ino = ceph_ino(parent_inode);
*max_len = connected_handle_length;
type = FILEID_INO32_GEN_PARENT;
} else {
struct ceph_nfs_fh *fh = (void *)rawfh;
doutc(cl, "%p %llx.%llx\n", inode, ceph_vinop(inode));
fh->ino = ceph_ino(inode);
*max_len = handle_length;
type = FILEID_INO32_GEN;
}
return type;
}
static struct inode *__lookup_inode(struct super_block *sb, u64 ino)
{
struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(sb)->mdsc;
struct inode *inode;
struct ceph_vino vino;
int err;
vino.ino = ino;
vino.snap = CEPH_NOSNAP;
if (ceph_vino_is_reserved(vino))
return ERR_PTR(-ESTALE);
inode = ceph_find_inode(sb, vino);
if (!inode) {
struct ceph_mds_request *req;
int mask;
req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPINO,
USE_ANY_MDS);
if (IS_ERR(req))
return ERR_CAST(req);
mask = CEPH_STAT_CAP_INODE;
if (ceph_security_xattr_wanted(d_inode(sb->s_root)))
mask |= CEPH_CAP_XATTR_SHARED;
req->r_args.lookupino.mask = cpu_to_le32(mask);
req->r_ino1 = vino;
req->r_num_caps = 1;
err = ceph_mdsc_do_request(mdsc, NULL, req);
inode = req->r_target_inode;
if (inode)
ihold(inode);
ceph_mdsc_put_request(req);
if (!inode)
return err < 0 ? ERR_PTR(err) : ERR_PTR(-ESTALE);
} else {
if (ceph_inode_is_shutdown(inode)) {
iput(inode);
return ERR_PTR(-ESTALE);
}
}
return inode;
}
struct inode *ceph_lookup_inode(struct super_block *sb, u64 ino)
{
struct inode *inode = __lookup_inode(sb, ino);
if (IS_ERR(inode))
return inode;
if (inode->i_nlink == 0) {
iput(inode);
return ERR_PTR(-ESTALE);
}
return inode;
}
static struct dentry *__fh_to_dentry(struct super_block *sb, u64 ino)
{
struct inode *inode = __lookup_inode(sb, ino);
struct ceph_inode_info *ci = ceph_inode(inode);
int err;
if (IS_ERR(inode))
return ERR_CAST(inode);
/* We need LINK caps to reliably check i_nlink */
err = ceph_do_getattr(inode, CEPH_CAP_LINK_SHARED, false);
if (err) {
iput(inode);
return ERR_PTR(err);
}
/* -ESTALE if inode as been unlinked and no file is open */
if ((inode->i_nlink == 0) && !__ceph_is_file_opened(ci)) {
iput(inode);
return ERR_PTR(-ESTALE);
}
return d_obtain_alias(inode);
}
static struct dentry *__snapfh_to_dentry(struct super_block *sb,
struct ceph_nfs_snapfh *sfh,
bool want_parent)
{
struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(sb)->mdsc;
struct ceph_client *cl = mdsc->fsc->client;
struct ceph_mds_request *req;
struct inode *inode;
struct ceph_vino vino;
int mask;
int err;
bool unlinked = false;
if (want_parent) {
vino.ino = sfh->parent_ino;
if (sfh->snapid == CEPH_SNAPDIR)
vino.snap = CEPH_NOSNAP;
else if (sfh->ino == sfh->parent_ino)
vino.snap = CEPH_SNAPDIR;
else
vino.snap = sfh->snapid;
} else {
vino.ino = sfh->ino;
vino.snap = sfh->snapid;
}
if (ceph_vino_is_reserved(vino))
return ERR_PTR(-ESTALE);
inode = ceph_find_inode(sb, vino);
if (inode) {
if (ceph_inode_is_shutdown(inode)) {
iput(inode);
return ERR_PTR(-ESTALE);
}
return d_obtain_alias(inode);
}
req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPINO,
USE_ANY_MDS);
if (IS_ERR(req))
return ERR_CAST(req);
mask = CEPH_STAT_CAP_INODE;
if (ceph_security_xattr_wanted(d_inode(sb->s_root)))
mask |= CEPH_CAP_XATTR_SHARED;
req->r_args.lookupino.mask = cpu_to_le32(mask);
if (vino.snap < CEPH_NOSNAP) {
req->r_args.lookupino.snapid = cpu_to_le64(vino.snap);
if (!want_parent && sfh->ino != sfh->parent_ino) {
req->r_args.lookupino.parent =
cpu_to_le64(sfh->parent_ino);
req->r_args.lookupino.hash =
cpu_to_le32(sfh->hash);
}
}
req->r_ino1 = vino;
req->r_num_caps = 1;
err = ceph_mdsc_do_request(mdsc, NULL, req);
inode = req->r_target_inode;
if (inode) {
if (vino.snap == CEPH_SNAPDIR) {
if (inode->i_nlink == 0)
unlinked = true;
inode = ceph_get_snapdir(inode);
} else if (ceph_snap(inode) == vino.snap) {
ihold(inode);
} else {
/* mds does not support lookup snapped inode */
inode = ERR_PTR(-EOPNOTSUPP);
}
} else {
inode = ERR_PTR(-ESTALE);
}
ceph_mdsc_put_request(req);
if (want_parent) {
doutc(cl, "%llx.%llx\n err=%d\n", vino.ino, vino.snap, err);
} else {
doutc(cl, "%llx.%llx parent %llx hash %x err=%d", vino.ino,
vino.snap, sfh->parent_ino, sfh->hash, err);
}
/* see comments in ceph_get_parent() */
return unlinked ? d_obtain_root(inode) : d_obtain_alias(inode);
}
/*
* convert regular fh to dentry
*/
static struct dentry *ceph_fh_to_dentry(struct super_block *sb,
struct fid *fid,
int fh_len, int fh_type)
{
struct ceph_fs_client *fsc = ceph_sb_to_fs_client(sb);
struct ceph_nfs_fh *fh = (void *)fid->raw;
if (fh_type == FILEID_BTRFS_WITH_PARENT) {
struct ceph_nfs_snapfh *sfh = (void *)fid->raw;
return __snapfh_to_dentry(sb, sfh, false);
}
if (fh_type != FILEID_INO32_GEN &&
fh_type != FILEID_INO32_GEN_PARENT)
return NULL;
if (fh_len < sizeof(*fh) / BYTES_PER_U32)
return NULL;
doutc(fsc->client, "%llx\n", fh->ino);
return __fh_to_dentry(sb, fh->ino);
}
static struct dentry *__get_parent(struct super_block *sb,
struct dentry *child, u64 ino)
{
struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(sb)->mdsc;
struct ceph_mds_request *req;
struct inode *inode;
int mask;
int err;
req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPPARENT,
USE_ANY_MDS);
if (IS_ERR(req))
return ERR_CAST(req);
if (child) {
req->r_inode = d_inode(child);
ihold(d_inode(child));
} else {
req->r_ino1 = (struct ceph_vino) {
.ino = ino,
.snap = CEPH_NOSNAP,
};
}
mask = CEPH_STAT_CAP_INODE;
if (ceph_security_xattr_wanted(d_inode(sb->s_root)))
mask |= CEPH_CAP_XATTR_SHARED;
req->r_args.getattr.mask = cpu_to_le32(mask);
req->r_num_caps = 1;
err = ceph_mdsc_do_request(mdsc, NULL, req);
if (err) {
ceph_mdsc_put_request(req);
return ERR_PTR(err);
}
inode = req->r_target_inode;
if (inode)
ihold(inode);
ceph_mdsc_put_request(req);
if (!inode)
return ERR_PTR(-ENOENT);
return d_obtain_alias(inode);
}
static struct dentry *ceph_get_parent(struct dentry *child)
{
struct inode *inode = d_inode(child);
struct ceph_client *cl = ceph_inode_to_client(inode);
struct dentry *dn;
if (ceph_snap(inode) != CEPH_NOSNAP) {
struct inode* dir;
bool unlinked = false;
/* do not support non-directory */
if (!d_is_dir(child)) {
dn = ERR_PTR(-EINVAL);
goto out;
}
dir = __lookup_inode(inode->i_sb, ceph_ino(inode));
if (IS_ERR(dir)) {
dn = ERR_CAST(dir);
goto out;
}
/* There can be multiple paths to access snapped inode.
* For simplicity, treat snapdir of head inode as parent */
if (ceph_snap(inode) != CEPH_SNAPDIR) {
struct inode *snapdir = ceph_get_snapdir(dir);
if (dir->i_nlink == 0)
unlinked = true;
iput(dir);
if (IS_ERR(snapdir)) {
dn = ERR_CAST(snapdir);
goto out;
}
dir = snapdir;
}
/* If directory has already been deleted, further get_parent
* will fail. Do not mark snapdir dentry as disconnected,
* this prevents exportfs from doing further get_parent. */
if (unlinked)
dn = d_obtain_root(dir);
else
dn = d_obtain_alias(dir);
} else {
dn = __get_parent(child->d_sb, child, 0);
}
out:
doutc(cl, "child %p %p %llx.%llx err=%ld\n", child, inode,
ceph_vinop(inode), (long)PTR_ERR_OR_ZERO(dn));
return dn;
}
/*
* convert regular fh to parent
*/
static struct dentry *ceph_fh_to_parent(struct super_block *sb,
struct fid *fid,
int fh_len, int fh_type)
{
struct ceph_fs_client *fsc = ceph_sb_to_fs_client(sb);
struct ceph_nfs_confh *cfh = (void *)fid->raw;
struct dentry *dentry;
if (fh_type == FILEID_BTRFS_WITH_PARENT) {
struct ceph_nfs_snapfh *sfh = (void *)fid->raw;
return __snapfh_to_dentry(sb, sfh, true);
}
if (fh_type != FILEID_INO32_GEN_PARENT)
return NULL;
if (fh_len < sizeof(*cfh) / BYTES_PER_U32)
return NULL;
doutc(fsc->client, "%llx\n", cfh->parent_ino);
dentry = __get_parent(sb, NULL, cfh->ino);
if (unlikely(dentry == ERR_PTR(-ENOENT)))
dentry = __fh_to_dentry(sb, cfh->parent_ino);
return dentry;
}
static int __get_snap_name(struct dentry *parent, char *name,
struct dentry *child)
{
struct inode *inode = d_inode(child);
struct inode *dir = d_inode(parent);
struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
struct ceph_mds_request *req = NULL;
char *last_name = NULL;
unsigned next_offset = 2;
int err = -EINVAL;
if (ceph_ino(inode) != ceph_ino(dir))
goto out;
if (ceph_snap(inode) == CEPH_SNAPDIR) {
if (ceph_snap(dir) == CEPH_NOSNAP) {
/*
* .get_name() from struct export_operations
* assumes that its 'name' parameter is pointing
* to a NAME_MAX+1 sized buffer
*/
strscpy(name, fsc->mount_options->snapdir_name,
NAME_MAX + 1);
err = 0;
}
goto out;
}
if (ceph_snap(dir) != CEPH_SNAPDIR)
goto out;
while (1) {
struct ceph_mds_reply_info_parsed *rinfo;
struct ceph_mds_reply_dir_entry *rde;
int i;
req = ceph_mdsc_create_request(fsc->mdsc, CEPH_MDS_OP_LSSNAP,
USE_AUTH_MDS);
if (IS_ERR(req)) {
err = PTR_ERR(req);
req = NULL;
goto out;
}
err = ceph_alloc_readdir_reply_buffer(req, inode);
if (err)
goto out;
req->r_direct_mode = USE_AUTH_MDS;
req->r_readdir_offset = next_offset;
req->r_args.readdir.flags =
cpu_to_le16(CEPH_READDIR_REPLY_BITFLAGS);
if (last_name) {
req->r_path2 = last_name;
last_name = NULL;
}
req->r_inode = dir;
ihold(dir);
req->r_dentry = dget(parent);
inode_lock(dir);
err = ceph_mdsc_do_request(fsc->mdsc, NULL, req);
inode_unlock(dir);
if (err < 0)
goto out;
rinfo = &req->r_reply_info;
for (i = 0; i < rinfo->dir_nr; i++) {
rde = rinfo->dir_entries + i;
BUG_ON(!rde->inode.in);
if (ceph_snap(inode) ==
le64_to_cpu(rde->inode.in->snapid)) {
memcpy(name, rde->name, rde->name_len);
name[rde->name_len] = '\0';
err = 0;
goto out;
}
}
if (rinfo->dir_end)
break;
BUG_ON(rinfo->dir_nr <= 0);
rde = rinfo->dir_entries + (rinfo->dir_nr - 1);
next_offset += rinfo->dir_nr;
last_name = kstrndup(rde->name, rde->name_len, GFP_KERNEL);
if (!last_name) {
err = -ENOMEM;
goto out;
}
ceph_mdsc_put_request(req);
req = NULL;
}
err = -ENOENT;
out:
if (req)
ceph_mdsc_put_request(req);
kfree(last_name);
doutc(fsc->client, "child dentry %p %p %llx.%llx err=%d\n", child,
inode, ceph_vinop(inode), err);
return err;
}
static int ceph_get_name(struct dentry *parent, char *name,
struct dentry *child)
{
struct ceph_mds_client *mdsc;
struct ceph_mds_request *req;
struct inode *dir = d_inode(parent);
struct inode *inode = d_inode(child);
struct ceph_mds_reply_info_parsed *rinfo;
int err;
if (ceph_snap(inode) != CEPH_NOSNAP)
return __get_snap_name(parent, name, child);
mdsc = ceph_inode_to_fs_client(inode)->mdsc;
req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPNAME,
USE_ANY_MDS);
if (IS_ERR(req))
return PTR_ERR(req);
inode_lock(dir);
req->r_inode = inode;
ihold(inode);
req->r_ino2 = ceph_vino(d_inode(parent));
req->r_parent = dir;
ihold(dir);
set_bit(CEPH_MDS_R_PARENT_LOCKED, &req->r_req_flags);
req->r_num_caps = 2;
err = ceph_mdsc_do_request(mdsc, NULL, req);
inode_unlock(dir);
if (err)
goto out;
rinfo = &req->r_reply_info;
if (!IS_ENCRYPTED(dir)) {
memcpy(name, rinfo->dname, rinfo->dname_len);
name[rinfo->dname_len] = 0;
} else {
struct fscrypt_str oname = FSTR_INIT(NULL, 0);
struct ceph_fname fname = { .dir = dir,
.name = rinfo->dname,
.ctext = rinfo->altname,
.name_len = rinfo->dname_len,
.ctext_len = rinfo->altname_len };
err = ceph_fname_alloc_buffer(dir, &oname);
if (err < 0)
goto out;
err = ceph_fname_to_usr(&fname, NULL, &oname, NULL);
if (!err) {
memcpy(name, oname.name, oname.len);
name[oname.len] = 0;
}
ceph_fname_free_buffer(dir, &oname);
}
out:
doutc(mdsc->fsc->client, "child dentry %p %p %llx.%llx err %d %s%s\n",
child, inode, ceph_vinop(inode), err, err ? "" : "name ",
err ? "" : name);
ceph_mdsc_put_request(req);
return err;
}
const struct export_operations ceph_export_ops = {
.encode_fh = ceph_encode_fh,
.fh_to_dentry = ceph_fh_to_dentry,
.fh_to_parent = ceph_fh_to_parent,
.get_parent = ceph_get_parent,
.get_name = ceph_get_name,
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+213
View File
@@ -0,0 +1,213 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2016 Trond Myklebust
* Copyright (c) 2019 Jeff Layton
*
* I/O and data path helper functionality.
*
* Heavily borrowed from equivalent code in fs/nfs/io.c
*/
#include <linux/ceph/ceph_debug.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/rwsem.h>
#include <linux/fs.h>
#include "super.h"
#include "io.h"
/* Call with exclusively locked inode->i_rwsem */
static void ceph_block_o_direct(struct ceph_inode_info *ci, struct inode *inode)
{
bool is_odirect;
lockdep_assert_held_write(&inode->i_rwsem);
spin_lock(&ci->i_ceph_lock);
/* ensure that bit state is consistent */
smp_mb__before_atomic();
is_odirect = READ_ONCE(ci->i_ceph_flags) & CEPH_I_ODIRECT;
if (is_odirect) {
clear_bit(CEPH_I_ODIRECT_BIT, &ci->i_ceph_flags);
/* ensure modified bit is visible */
smp_mb__after_atomic();
}
spin_unlock(&ci->i_ceph_lock);
if (is_odirect)
inode_dio_wait(inode);
}
/**
* ceph_start_io_read - declare the file is being used for buffered reads
* @inode: file inode
*
* Declare that a buffered read operation is about to start, and ensure
* that we block all direct I/O.
* On exit, the function ensures that the CEPH_I_ODIRECT flag is unset,
* and holds a shared lock on inode->i_rwsem to ensure that the flag
* cannot be changed.
* In practice, this means that buffered read operations are allowed to
* execute in parallel, thanks to the shared lock, whereas direct I/O
* operations need to wait to grab an exclusive lock in order to set
* CEPH_I_ODIRECT.
* Note that buffered writes and truncates both take a write lock on
* inode->i_rwsem, meaning that those are serialised w.r.t. the reads.
*/
int ceph_start_io_read(struct inode *inode)
{
struct ceph_inode_info *ci = ceph_inode(inode);
bool is_odirect;
int err;
/* Be an optimist! */
err = down_read_killable(&inode->i_rwsem);
if (err)
return err;
spin_lock(&ci->i_ceph_lock);
/* ensure that bit state is consistent */
smp_mb__before_atomic();
is_odirect = READ_ONCE(ci->i_ceph_flags) & CEPH_I_ODIRECT;
spin_unlock(&ci->i_ceph_lock);
if (!is_odirect)
return 0;
up_read(&inode->i_rwsem);
/* Slow path.... */
err = down_write_killable(&inode->i_rwsem);
if (err)
return err;
ceph_block_o_direct(ci, inode);
downgrade_write(&inode->i_rwsem);
return 0;
}
/**
* ceph_end_io_read - declare that the buffered read operation is done
* @inode: file inode
*
* Declare that a buffered read operation is done, and release the shared
* lock on inode->i_rwsem.
*/
void
ceph_end_io_read(struct inode *inode)
{
up_read(&inode->i_rwsem);
}
/**
* ceph_start_io_write - declare the file is being used for buffered writes
* @inode: file inode
*
* Declare that a buffered write operation is about to start, and ensure
* that we block all direct I/O.
*/
int ceph_start_io_write(struct inode *inode)
{
int err = down_write_killable(&inode->i_rwsem);
if (!err)
ceph_block_o_direct(ceph_inode(inode), inode);
return err;
}
/**
* ceph_end_io_write - declare that the buffered write operation is done
* @inode: file inode
*
* Declare that a buffered write operation is done, and release the
* lock on inode->i_rwsem.
*/
void
ceph_end_io_write(struct inode *inode)
{
up_write(&inode->i_rwsem);
}
/* Call with exclusively locked inode->i_rwsem */
static void ceph_block_buffered(struct ceph_inode_info *ci, struct inode *inode)
{
bool is_odirect;
lockdep_assert_held_write(&inode->i_rwsem);
spin_lock(&ci->i_ceph_lock);
/* ensure that bit state is consistent */
smp_mb__before_atomic();
is_odirect = READ_ONCE(ci->i_ceph_flags) & CEPH_I_ODIRECT;
if (!is_odirect) {
set_bit(CEPH_I_ODIRECT_BIT, &ci->i_ceph_flags);
/* ensure modified bit is visible */
smp_mb__after_atomic();
}
spin_unlock(&ci->i_ceph_lock);
if (!is_odirect) {
/* FIXME: unmap_mapping_range? */
filemap_write_and_wait(inode->i_mapping);
}
}
/**
* ceph_start_io_direct - declare the file is being used for direct i/o
* @inode: file inode
*
* Declare that a direct I/O operation is about to start, and ensure
* that we block all buffered I/O.
* On exit, the function ensures that the CEPH_I_ODIRECT flag is set,
* and holds a shared lock on inode->i_rwsem to ensure that the flag
* cannot be changed.
* In practice, this means that direct I/O operations are allowed to
* execute in parallel, thanks to the shared lock, whereas buffered I/O
* operations need to wait to grab an exclusive lock in order to clear
* CEPH_I_ODIRECT.
* Note that buffered writes and truncates both take a write lock on
* inode->i_rwsem, meaning that those are serialised w.r.t. O_DIRECT.
*/
int ceph_start_io_direct(struct inode *inode)
{
struct ceph_inode_info *ci = ceph_inode(inode);
bool is_odirect;
int err;
/* Be an optimist! */
err = down_read_killable(&inode->i_rwsem);
if (err)
return err;
spin_lock(&ci->i_ceph_lock);
/* ensure that bit state is consistent */
smp_mb__before_atomic();
is_odirect = READ_ONCE(ci->i_ceph_flags) & CEPH_I_ODIRECT;
spin_unlock(&ci->i_ceph_lock);
if (is_odirect)
return 0;
up_read(&inode->i_rwsem);
/* Slow path.... */
err = down_write_killable(&inode->i_rwsem);
if (err)
return err;
ceph_block_buffered(ci, inode);
downgrade_write(&inode->i_rwsem);
return 0;
}
/**
* ceph_end_io_direct - declare that the direct i/o operation is done
* @inode: file inode
*
* Declare that a direct I/O operation is done, and release the shared
* lock on inode->i_rwsem.
*/
void
ceph_end_io_direct(struct inode *inode)
{
up_read(&inode->i_rwsem);
}
+14
View File
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FS_CEPH_IO_H
#define _FS_CEPH_IO_H
#include <linux/compiler_attributes.h>
int __must_check ceph_start_io_read(struct inode *inode);
void ceph_end_io_read(struct inode *inode);
int __must_check ceph_start_io_write(struct inode *inode);
void ceph_end_io_write(struct inode *inode);
int __must_check ceph_start_io_direct(struct inode *inode);
void ceph_end_io_direct(struct inode *inode);
#endif /* FS_CEPH_IO_H */
+438
View File
@@ -0,0 +1,438 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ceph/ceph_debug.h>
#include <linux/in.h>
#include "super.h"
#include "mds_client.h"
#include "ioctl.h"
#include <linux/ceph/striper.h>
#include <linux/fscrypt.h>
/*
* ioctls
*/
/*
* get and set the file layout
*/
static long ceph_ioctl_get_layout(struct file *file, void __user *arg)
{
struct ceph_inode_info *ci = ceph_inode(file_inode(file));
struct ceph_ioctl_layout l;
int err;
err = ceph_do_getattr(file_inode(file), CEPH_STAT_CAP_LAYOUT, false);
if (!err) {
l.stripe_unit = ci->i_layout.stripe_unit;
l.stripe_count = ci->i_layout.stripe_count;
l.object_size = ci->i_layout.object_size;
l.data_pool = ci->i_layout.pool_id;
l.preferred_osd = -1;
if (copy_to_user(arg, &l, sizeof(l)))
return -EFAULT;
}
return err;
}
static long __validate_layout(struct ceph_mds_client *mdsc,
struct ceph_ioctl_layout *l)
{
int i, err;
/* validate striping parameters */
if ((l->object_size & ~PAGE_MASK) ||
(l->stripe_unit & ~PAGE_MASK) ||
((unsigned)l->stripe_unit != 0 &&
((unsigned)l->object_size % (unsigned)l->stripe_unit)))
return -EINVAL;
/* make sure it's a valid data pool */
mutex_lock(&mdsc->mutex);
err = -EINVAL;
for (i = 0; i < mdsc->mdsmap->m_num_data_pg_pools; i++)
if (mdsc->mdsmap->m_data_pg_pools[i] == l->data_pool) {
err = 0;
break;
}
mutex_unlock(&mdsc->mutex);
if (err)
return err;
return 0;
}
static long ceph_ioctl_set_layout(struct file *file, void __user *arg)
{
struct inode *inode = file_inode(file);
struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
struct ceph_mds_request *req;
struct ceph_ioctl_layout l;
struct ceph_inode_info *ci = ceph_inode(file_inode(file));
struct ceph_ioctl_layout nl;
int err;
if (!inode_owner_or_capable(file_mnt_idmap(file), inode))
return -EACCES;
if (copy_from_user(&l, arg, sizeof(l)))
return -EFAULT;
/* validate changed params against current layout */
err = ceph_do_getattr(file_inode(file), CEPH_STAT_CAP_LAYOUT, false);
if (err)
return err;
memset(&nl, 0, sizeof(nl));
if (l.stripe_count)
nl.stripe_count = l.stripe_count;
else
nl.stripe_count = ci->i_layout.stripe_count;
if (l.stripe_unit)
nl.stripe_unit = l.stripe_unit;
else
nl.stripe_unit = ci->i_layout.stripe_unit;
if (l.object_size)
nl.object_size = l.object_size;
else
nl.object_size = ci->i_layout.object_size;
if (l.data_pool)
nl.data_pool = l.data_pool;
else
nl.data_pool = ci->i_layout.pool_id;
/* this is obsolete, and always -1 */
nl.preferred_osd = -1;
err = __validate_layout(mdsc, &nl);
if (err)
return err;
req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_SETLAYOUT,
USE_AUTH_MDS);
if (IS_ERR(req))
return PTR_ERR(req);
req->r_inode = inode;
ihold(inode);
req->r_num_caps = 1;
req->r_inode_drop = CEPH_CAP_FILE_SHARED | CEPH_CAP_FILE_EXCL;
req->r_args.setlayout.layout.fl_stripe_unit =
cpu_to_le32(l.stripe_unit);
req->r_args.setlayout.layout.fl_stripe_count =
cpu_to_le32(l.stripe_count);
req->r_args.setlayout.layout.fl_object_size =
cpu_to_le32(l.object_size);
req->r_args.setlayout.layout.fl_pg_pool = cpu_to_le32(l.data_pool);
err = ceph_mdsc_do_request(mdsc, NULL, req);
ceph_mdsc_put_request(req);
return err;
}
/*
* Set a layout policy on a directory inode. All items in the tree
* rooted at this inode will inherit this layout on creation,
* (It doesn't apply retroactively )
* unless a subdirectory has its own layout policy.
*/
static long ceph_ioctl_set_layout_policy (struct file *file, void __user *arg)
{
struct inode *inode = file_inode(file);
struct ceph_mds_request *req;
struct ceph_ioctl_layout l;
int err;
struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
if (!inode_owner_or_capable(file_mnt_idmap(file), inode))
return -EACCES;
/* copy and validate */
if (copy_from_user(&l, arg, sizeof(l)))
return -EFAULT;
err = __validate_layout(mdsc, &l);
if (err)
return err;
req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_SETDIRLAYOUT,
USE_AUTH_MDS);
if (IS_ERR(req))
return PTR_ERR(req);
req->r_inode = inode;
ihold(inode);
req->r_num_caps = 1;
req->r_args.setlayout.layout.fl_stripe_unit =
cpu_to_le32(l.stripe_unit);
req->r_args.setlayout.layout.fl_stripe_count =
cpu_to_le32(l.stripe_count);
req->r_args.setlayout.layout.fl_object_size =
cpu_to_le32(l.object_size);
req->r_args.setlayout.layout.fl_pg_pool =
cpu_to_le32(l.data_pool);
err = ceph_mdsc_do_request(mdsc, inode, req);
ceph_mdsc_put_request(req);
return err;
}
/*
* Return object name, size/offset information, and location (OSD
* number, network address) for a given file offset.
*/
static long ceph_ioctl_get_dataloc(struct file *file, void __user *arg)
{
struct ceph_ioctl_dataloc dl;
struct inode *inode = file_inode(file);
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_osd_client *osdc =
&ceph_sb_to_fs_client(inode->i_sb)->client->osdc;
struct ceph_object_locator oloc;
CEPH_DEFINE_OID_ONSTACK(oid);
u32 xlen;
u64 tmp;
struct ceph_pg pgid;
int r;
/* copy and validate */
if (copy_from_user(&dl, arg, sizeof(dl)))
return -EFAULT;
down_read(&osdc->lock);
ceph_calc_file_object_mapping(&ci->i_layout, dl.file_offset, 1,
&dl.object_no, &dl.object_offset, &xlen);
dl.file_offset -= dl.object_offset;
dl.object_size = ci->i_layout.object_size;
dl.block_size = ci->i_layout.stripe_unit;
/* block_offset = object_offset % block_size */
tmp = dl.object_offset;
dl.block_offset = do_div(tmp, dl.block_size);
snprintf(dl.object_name, sizeof(dl.object_name), "%llx.%08llx",
ceph_ino(inode), dl.object_no);
oloc.pool = ci->i_layout.pool_id;
oloc.pool_ns = ceph_try_get_string(ci->i_layout.pool_ns);
ceph_oid_printf(&oid, "%s", dl.object_name);
r = ceph_object_locator_to_pg(osdc->osdmap, &oid, &oloc, &pgid);
ceph_oloc_destroy(&oloc);
if (r < 0) {
up_read(&osdc->lock);
return r;
}
dl.osd = ceph_pg_to_acting_primary(osdc->osdmap, &pgid);
if (dl.osd >= 0) {
struct ceph_entity_addr *a =
ceph_osd_addr(osdc->osdmap, dl.osd);
if (a)
memcpy(&dl.osd_addr, &a->in_addr, sizeof(dl.osd_addr));
} else {
memset(&dl.osd_addr, 0, sizeof(dl.osd_addr));
}
up_read(&osdc->lock);
/* send result back to user */
if (copy_to_user(arg, &dl, sizeof(dl)))
return -EFAULT;
return 0;
}
static long ceph_ioctl_lazyio(struct file *file)
{
struct ceph_file_info *fi = file->private_data;
struct inode *inode = file_inode(file);
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_mds_client *mdsc = ceph_inode_to_fs_client(inode)->mdsc;
struct ceph_client *cl = mdsc->fsc->client;
bool is_file_already_lazy = false;
spin_lock(&ci->i_ceph_lock);
if ((fi->fmode & CEPH_FILE_MODE_LAZY) == 0) {
fi->fmode |= CEPH_FILE_MODE_LAZY;
ci->i_nr_by_mode[ffs(CEPH_FILE_MODE_LAZY)]++;
__ceph_touch_fmode(ci, mdsc, fi->fmode);
} else {
is_file_already_lazy = true;
}
spin_unlock(&ci->i_ceph_lock);
if (is_file_already_lazy) {
doutc(cl, "file %p %p %llx.%llx already lazy\n", file, inode,
ceph_vinop(inode));
} else {
doutc(cl, "file %p %p %llx.%llx marked lazy\n", file, inode,
ceph_vinop(inode));
ceph_check_caps(ci, 0);
}
return 0;
}
static long ceph_ioctl_syncio(struct file *file)
{
struct ceph_file_info *fi = file->private_data;
fi->flags |= CEPH_F_SYNC;
return 0;
}
static int vet_mds_for_fscrypt(struct file *file)
{
int i, ret = -EOPNOTSUPP;
struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(file_inode(file)->i_sb);
mutex_lock(&mdsc->mutex);
for (i = 0; i < mdsc->max_sessions; i++) {
struct ceph_mds_session *s = mdsc->sessions[i];
if (!s)
continue;
if (test_bit(CEPHFS_FEATURE_ALTERNATE_NAME, &s->s_features))
ret = 0;
break;
}
mutex_unlock(&mdsc->mutex);
return ret;
}
static long ceph_set_encryption_policy(struct file *file, unsigned long arg)
{
int ret, got = 0;
struct inode *inode = file_inode(file);
struct ceph_inode_info *ci = ceph_inode(inode);
/* encrypted directories can't have striped layout */
if (ci->i_layout.stripe_count > 1)
return -EINVAL;
ret = vet_mds_for_fscrypt(file);
if (ret)
return ret;
/*
* Ensure we hold these caps so that we _know_ that the rstats check
* in the empty_dir check is reliable.
*/
ret = ceph_get_caps(file, CEPH_CAP_FILE_SHARED, 0, -1, &got);
if (ret)
return ret;
ret = fscrypt_ioctl_set_policy(file, (const void __user *)arg);
if (got)
ceph_put_cap_refs(ci, got);
return ret;
}
static const char *ceph_ioctl_cmd_name(const unsigned int cmd)
{
switch (cmd) {
case CEPH_IOC_GET_LAYOUT:
return "get_layout";
case CEPH_IOC_SET_LAYOUT:
return "set_layout";
case CEPH_IOC_SET_LAYOUT_POLICY:
return "set_layout_policy";
case CEPH_IOC_GET_DATALOC:
return "get_dataloc";
case CEPH_IOC_LAZYIO:
return "lazyio";
case CEPH_IOC_SYNCIO:
return "syncio";
case FS_IOC_SET_ENCRYPTION_POLICY:
return "set_encryption_policy";
case FS_IOC_GET_ENCRYPTION_POLICY:
return "get_encryption_policy";
case FS_IOC_GET_ENCRYPTION_POLICY_EX:
return "get_encryption_policy_ex";
case FS_IOC_ADD_ENCRYPTION_KEY:
return "add_encryption_key";
case FS_IOC_REMOVE_ENCRYPTION_KEY:
return "remove_encryption_key";
case FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS:
return "remove_encryption_key_all_users";
case FS_IOC_GET_ENCRYPTION_KEY_STATUS:
return "get_encryption_key_status";
case FS_IOC_GET_ENCRYPTION_NONCE:
return "get_encryption_nonce";
default:
return "unknown";
}
}
long ceph_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(file);
struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
int ret;
doutc(fsc->client, "file %p %p %llx.%llx cmd %s arg %lu\n", file,
inode, ceph_vinop(inode), ceph_ioctl_cmd_name(cmd), arg);
switch (cmd) {
case CEPH_IOC_GET_LAYOUT:
return ceph_ioctl_get_layout(file, (void __user *)arg);
case CEPH_IOC_SET_LAYOUT:
return ceph_ioctl_set_layout(file, (void __user *)arg);
case CEPH_IOC_SET_LAYOUT_POLICY:
return ceph_ioctl_set_layout_policy(file, (void __user *)arg);
case CEPH_IOC_GET_DATALOC:
return ceph_ioctl_get_dataloc(file, (void __user *)arg);
case CEPH_IOC_LAZYIO:
return ceph_ioctl_lazyio(file);
case CEPH_IOC_SYNCIO:
return ceph_ioctl_syncio(file);
case FS_IOC_SET_ENCRYPTION_POLICY:
return ceph_set_encryption_policy(file, arg);
case FS_IOC_GET_ENCRYPTION_POLICY:
ret = vet_mds_for_fscrypt(file);
if (ret)
return ret;
return fscrypt_ioctl_get_policy(file, (void __user *)arg);
case FS_IOC_GET_ENCRYPTION_POLICY_EX:
ret = vet_mds_for_fscrypt(file);
if (ret)
return ret;
return fscrypt_ioctl_get_policy_ex(file, (void __user *)arg);
case FS_IOC_ADD_ENCRYPTION_KEY:
ret = vet_mds_for_fscrypt(file);
if (ret)
return ret;
return fscrypt_ioctl_add_key(file, (void __user *)arg);
case FS_IOC_REMOVE_ENCRYPTION_KEY:
return fscrypt_ioctl_remove_key(file, (void __user *)arg);
case FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS:
return fscrypt_ioctl_remove_key_all_users(file,
(void __user *)arg);
case FS_IOC_GET_ENCRYPTION_KEY_STATUS:
return fscrypt_ioctl_get_key_status(file, (void __user *)arg);
case FS_IOC_GET_ENCRYPTION_NONCE:
ret = vet_mds_for_fscrypt(file);
if (ret)
return ret;
return fscrypt_ioctl_get_nonce(file, (void __user *)arg);
}
return -ENOTTY;
}
+101
View File
@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef FS_CEPH_IOCTL_H
#define FS_CEPH_IOCTL_H
#include <linux/ioctl.h>
#include <linux/types.h>
#define CEPH_IOCTL_MAGIC 0x97
/*
* CEPH_IOC_GET_LAYOUT - get file layout or dir layout policy
* CEPH_IOC_SET_LAYOUT - set file layout
* CEPH_IOC_SET_LAYOUT_POLICY - set dir layout policy
*
* The file layout specifies how file data is striped over objects in
* the distributed object store, which object pool they belong to (if
* it differs from the default), and an optional 'preferred osd' to
* store them on.
*
* Files get a new layout based on the policy set on the containing
* directory or one of its ancestors. The GET_LAYOUT ioctl will let
* you examine the layout for a file or the policy on a directory.
*
* SET_LAYOUT will let you set a layout on a newly created file. This
* only works immediately after the file is created and before any
* data is written to it.
*
* SET_LAYOUT_POLICY will let you set a layout policy (default layout)
* on a directory that will apply to any new files created in that
* directory (or any child directory that doesn't specify a layout of
* its own).
*/
/* use u64 to align sanely on all archs */
struct ceph_ioctl_layout {
__u64 stripe_unit, stripe_count, object_size;
__u64 data_pool;
/* obsolete. new values ignored, always return -1 */
__s64 preferred_osd;
};
#define CEPH_IOC_GET_LAYOUT _IOR(CEPH_IOCTL_MAGIC, 1, \
struct ceph_ioctl_layout)
#define CEPH_IOC_SET_LAYOUT _IOW(CEPH_IOCTL_MAGIC, 2, \
struct ceph_ioctl_layout)
#define CEPH_IOC_SET_LAYOUT_POLICY _IOW(CEPH_IOCTL_MAGIC, 5, \
struct ceph_ioctl_layout)
/*
* CEPH_IOC_GET_DATALOC - get location of file data in the cluster
*
* Extract identity, address of the OSD and object storing a given
* file offset.
*/
struct ceph_ioctl_dataloc {
__u64 file_offset; /* in+out: file offset */
__u64 object_offset; /* out: offset in object */
__u64 object_no; /* out: object # */
__u64 object_size; /* out: object size */
char object_name[64]; /* out: object name */
__u64 block_offset; /* out: offset in block */
__u64 block_size; /* out: block length */
__s64 osd; /* out: osd # */
struct sockaddr_storage osd_addr; /* out: osd address */
};
#define CEPH_IOC_GET_DATALOC _IOWR(CEPH_IOCTL_MAGIC, 3, \
struct ceph_ioctl_dataloc)
/*
* CEPH_IOC_LAZYIO - relax consistency
*
* Normally Ceph switches to synchronous IO when multiple clients have
* the file open (and or more for write). Reads and writes bypass the
* page cache and go directly to the OSD. Setting this flag on a file
* descriptor will allow buffered IO for this file in cases where the
* application knows it won't interfere with other nodes (or doesn't
* care).
*/
#define CEPH_IOC_LAZYIO _IO(CEPH_IOCTL_MAGIC, 4)
/*
* CEPH_IOC_SYNCIO - force synchronous IO
*
* This ioctl sets a file flag that forces the synchronous IO that
* bypasses the page cache, even if it is not necessary. This is
* essentially the opposite behavior of IOC_LAZYIO. This forces the
* same read/write path as a file opened by multiple clients when one
* or more of those clients is opened for write.
*
* Note that this type of sync IO takes a different path than a file
* opened with O_SYNC/D_SYNC (writes hit the page cache and are
* immediately flushed on page boundaries). It is very similar to
* O_DIRECT (writes bypass the page cache) excep that O_DIRECT writes
* are not copied (user page must remain stable) and O_DIRECT writes
* have alignment restrictions (on the buffer and file offset).
*/
#define CEPH_IOC_SYNCIO _IO(CEPH_IOCTL_MAGIC, 5)
#endif
+528
View File
@@ -0,0 +1,528 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ceph/ceph_debug.h>
#include <linux/file.h>
#include <linux/namei.h>
#include <linux/random.h>
#include "super.h"
#include "mds_client.h"
#include <linux/filelock.h>
#include <linux/ceph/pagelist.h>
static u64 lock_secret;
static int ceph_lock_wait_for_completion(struct ceph_mds_client *mdsc,
struct ceph_mds_request *req);
static inline u64 secure_addr(void *addr)
{
u64 v = lock_secret ^ (u64)(unsigned long)addr;
/*
* Set the most significant bit, so that MDS knows the 'owner'
* is sufficient to identify the owner of lock. (old code uses
* both 'owner' and 'pid')
*/
v |= (1ULL << 63);
return v;
}
void __init ceph_flock_init(void)
{
get_random_bytes(&lock_secret, sizeof(lock_secret));
}
static void ceph_fl_copy_lock(struct file_lock *dst, struct file_lock *src)
{
struct inode *inode = file_inode(dst->c.flc_file);
atomic_inc(&ceph_inode(inode)->i_filelock_ref);
dst->fl_u.ceph.inode = igrab(inode);
}
/*
* Do not use the 'fl->fl_file' in release function, which
* is possibly already released by another thread.
*/
static void ceph_fl_release_lock(struct file_lock *fl)
{
struct inode *inode = fl->fl_u.ceph.inode;
struct ceph_inode_info *ci;
/*
* If inode is NULL it should be a request file_lock,
* nothing we can do.
*/
if (!inode)
return;
ci = ceph_inode(inode);
if (atomic_dec_and_test(&ci->i_filelock_ref)) {
/* clear error when all locks are released */
clear_bit(CEPH_I_ERROR_FILELOCK_BIT, &ci->i_ceph_flags);
}
fl->fl_u.ceph.inode = NULL;
iput(inode);
}
static const struct file_lock_operations ceph_fl_lock_ops = {
.fl_copy_lock = ceph_fl_copy_lock,
.fl_release_private = ceph_fl_release_lock,
};
/*
* Implement fcntl and flock locking functions.
*/
static int ceph_lock_message(u8 lock_type, u16 operation, struct inode *inode,
int cmd, u8 wait, struct file_lock *fl)
{
struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(inode->i_sb);
struct ceph_client *cl = mdsc->fsc->client;
struct ceph_mds_request *req;
int err;
u64 length = 0;
u64 owner;
if (operation == CEPH_MDS_OP_SETFILELOCK) {
/*
* increasing i_filelock_ref closes race window between
* handling request reply and adding file_lock struct to
* inode. Otherwise, auth caps may get trimmed in the
* window. Caller function will decrease the counter.
*/
fl->fl_ops = &ceph_fl_lock_ops;
fl->fl_ops->fl_copy_lock(fl, NULL);
}
if (operation != CEPH_MDS_OP_SETFILELOCK || cmd == CEPH_LOCK_UNLOCK)
wait = 0;
req = ceph_mdsc_create_request(mdsc, operation, USE_AUTH_MDS);
if (IS_ERR(req))
return PTR_ERR(req);
req->r_inode = inode;
ihold(inode);
req->r_num_caps = 1;
/* mds requires start and length rather than start and end */
if (LLONG_MAX == fl->fl_end)
length = 0;
else
length = fl->fl_end - fl->fl_start + 1;
owner = secure_addr(fl->c.flc_owner);
doutc(cl, "rule: %d, op: %d, owner: %llx, pid: %llu, "
"start: %llu, length: %llu, wait: %d, type: %d\n",
(int)lock_type, (int)operation, owner,
(u64) fl->c.flc_pid,
fl->fl_start, length, wait, fl->c.flc_type);
req->r_args.filelock_change.rule = lock_type;
req->r_args.filelock_change.type = cmd;
req->r_args.filelock_change.owner = cpu_to_le64(owner);
req->r_args.filelock_change.pid = cpu_to_le64((u64) fl->c.flc_pid);
req->r_args.filelock_change.start = cpu_to_le64(fl->fl_start);
req->r_args.filelock_change.length = cpu_to_le64(length);
req->r_args.filelock_change.wait = wait;
err = ceph_mdsc_submit_request(mdsc, inode, req);
if (!err)
err = ceph_mdsc_wait_request(mdsc, req, wait ?
ceph_lock_wait_for_completion : NULL);
if (!err && operation == CEPH_MDS_OP_GETFILELOCK) {
fl->c.flc_pid = -le64_to_cpu(req->r_reply_info.filelock_reply->pid);
if (CEPH_LOCK_SHARED == req->r_reply_info.filelock_reply->type)
fl->c.flc_type = F_RDLCK;
else if (CEPH_LOCK_EXCL == req->r_reply_info.filelock_reply->type)
fl->c.flc_type = F_WRLCK;
else
fl->c.flc_type = F_UNLCK;
fl->fl_start = le64_to_cpu(req->r_reply_info.filelock_reply->start);
length = le64_to_cpu(req->r_reply_info.filelock_reply->start) +
le64_to_cpu(req->r_reply_info.filelock_reply->length);
if (length >= 1)
fl->fl_end = length -1;
else
fl->fl_end = 0;
}
ceph_mdsc_put_request(req);
doutc(cl, "rule: %d, op: %d, pid: %llu, start: %llu, "
"length: %llu, wait: %d, type: %d, err code %d\n",
(int)lock_type, (int)operation, (u64) fl->c.flc_pid,
fl->fl_start, length, wait, fl->c.flc_type, err);
return err;
}
static int ceph_lock_wait_for_completion(struct ceph_mds_client *mdsc,
struct ceph_mds_request *req)
{
struct ceph_client *cl = mdsc->fsc->client;
struct ceph_mds_request *intr_req;
struct inode *inode = req->r_inode;
int err, lock_type;
BUG_ON(req->r_op != CEPH_MDS_OP_SETFILELOCK);
if (req->r_args.filelock_change.rule == CEPH_LOCK_FCNTL)
lock_type = CEPH_LOCK_FCNTL_INTR;
else if (req->r_args.filelock_change.rule == CEPH_LOCK_FLOCK)
lock_type = CEPH_LOCK_FLOCK_INTR;
else
BUG_ON(1);
BUG_ON(req->r_args.filelock_change.type == CEPH_LOCK_UNLOCK);
err = wait_for_completion_interruptible(&req->r_completion);
if (!err)
return 0;
doutc(cl, "request %llu was interrupted\n", req->r_tid);
mutex_lock(&mdsc->mutex);
if (test_bit(CEPH_MDS_R_GOT_RESULT, &req->r_req_flags)) {
err = 0;
} else {
/*
* ensure we aren't running concurrently with
* ceph_fill_trace or ceph_readdir_prepopulate, which
* rely on locks (dir mutex) held by our caller.
*/
mutex_lock(&req->r_fill_mutex);
req->r_err = err;
set_bit(CEPH_MDS_R_ABORTED, &req->r_req_flags);
mutex_unlock(&req->r_fill_mutex);
if (!req->r_session) {
// haven't sent the request
err = 0;
}
}
mutex_unlock(&mdsc->mutex);
if (!err)
return 0;
intr_req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_SETFILELOCK,
USE_AUTH_MDS);
if (IS_ERR(intr_req))
return PTR_ERR(intr_req);
intr_req->r_inode = inode;
ihold(inode);
intr_req->r_num_caps = 1;
intr_req->r_args.filelock_change = req->r_args.filelock_change;
intr_req->r_args.filelock_change.rule = lock_type;
intr_req->r_args.filelock_change.type = CEPH_LOCK_UNLOCK;
err = ceph_mdsc_do_request(mdsc, inode, intr_req);
ceph_mdsc_put_request(intr_req);
if (err && err != -ERESTARTSYS)
return err;
err = wait_for_completion_killable(&req->r_safe_completion);
if (err)
return err;
return 0;
}
static int try_unlock_file(struct file *file, struct file_lock *fl)
{
int err;
unsigned int orig_flags = fl->c.flc_flags;
fl->c.flc_flags |= FL_EXISTS;
err = locks_lock_file_wait(file, fl);
fl->c.flc_flags = orig_flags;
if (err == -ENOENT) {
if (!(orig_flags & FL_EXISTS))
err = 0;
return err;
}
return 1;
}
/*
* Attempt to set an fcntl lock.
* For now, this just goes away to the server. Later it may be more awesome.
*/
int ceph_lock(struct file *file, int cmd, struct file_lock *fl)
{
struct inode *inode = file_inode(file);
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(inode->i_sb);
struct ceph_client *cl = ceph_inode_to_client(inode);
int err = 0;
u16 op = CEPH_MDS_OP_SETFILELOCK;
u8 wait = 0;
u8 lock_cmd;
if (!(fl->c.flc_flags & FL_POSIX))
return -ENOLCK;
if (ceph_inode_is_shutdown(inode))
return -ESTALE;
doutc(cl, "fl_owner: %p\n", fl->c.flc_owner);
/* set wait bit as appropriate, then make command as Ceph expects it*/
if (IS_GETLK(cmd))
op = CEPH_MDS_OP_GETFILELOCK;
else if (IS_SETLKW(cmd))
wait = 1;
if (test_bit(CEPH_I_ERROR_FILELOCK_BIT, &ci->i_ceph_flags)) {
if (op == CEPH_MDS_OP_SETFILELOCK && lock_is_unlock(fl))
posix_lock_file(file, fl, NULL);
return -EIO;
}
/* Wait for reset to complete before acquiring new locks */
if (op == CEPH_MDS_OP_SETFILELOCK && !lock_is_unlock(fl)) {
err = ceph_mdsc_wait_for_reset(mdsc);
if (err)
return err;
}
if (lock_is_read(fl))
lock_cmd = CEPH_LOCK_SHARED;
else if (lock_is_write(fl))
lock_cmd = CEPH_LOCK_EXCL;
else
lock_cmd = CEPH_LOCK_UNLOCK;
if (op == CEPH_MDS_OP_SETFILELOCK && lock_is_unlock(fl)) {
err = try_unlock_file(file, fl);
if (err <= 0)
return err;
}
err = ceph_lock_message(CEPH_LOCK_FCNTL, op, inode, lock_cmd, wait, fl);
if (!err) {
if (op == CEPH_MDS_OP_SETFILELOCK && F_UNLCK != fl->c.flc_type) {
doutc(cl, "locking locally\n");
err = posix_lock_file(file, fl, NULL);
if (err) {
/* undo! This should only happen if
* the kernel detects local
* deadlock. */
ceph_lock_message(CEPH_LOCK_FCNTL, op, inode,
CEPH_LOCK_UNLOCK, 0, fl);
doutc(cl, "got %d on posix_lock_file, undid lock\n",
err);
}
}
}
return err;
}
int ceph_flock(struct file *file, int cmd, struct file_lock *fl)
{
struct inode *inode = file_inode(file);
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(inode->i_sb);
struct ceph_client *cl = ceph_inode_to_client(inode);
int err = 0;
u8 wait = 0;
u8 lock_cmd;
if (!(fl->c.flc_flags & FL_FLOCK))
return -ENOLCK;
if (ceph_inode_is_shutdown(inode))
return -ESTALE;
doutc(cl, "fl_file: %p\n", fl->c.flc_file);
if (test_bit(CEPH_I_ERROR_FILELOCK_BIT, &ci->i_ceph_flags)) {
if (lock_is_unlock(fl))
locks_lock_file_wait(file, fl);
return -EIO;
}
/* Wait for reset to complete before acquiring new locks */
if (!lock_is_unlock(fl)) {
err = ceph_mdsc_wait_for_reset(mdsc);
if (err)
return err;
}
if (IS_SETLKW(cmd))
wait = 1;
if (lock_is_read(fl))
lock_cmd = CEPH_LOCK_SHARED;
else if (lock_is_write(fl))
lock_cmd = CEPH_LOCK_EXCL;
else
lock_cmd = CEPH_LOCK_UNLOCK;
if (lock_is_unlock(fl)) {
err = try_unlock_file(file, fl);
if (err <= 0)
return err;
}
err = ceph_lock_message(CEPH_LOCK_FLOCK, CEPH_MDS_OP_SETFILELOCK,
inode, lock_cmd, wait, fl);
if (!err && F_UNLCK != fl->c.flc_type) {
err = locks_lock_file_wait(file, fl);
if (err) {
ceph_lock_message(CEPH_LOCK_FLOCK,
CEPH_MDS_OP_SETFILELOCK,
inode, CEPH_LOCK_UNLOCK, 0, fl);
doutc(cl, "got %d on locks_lock_file_wait, undid lock\n",
err);
}
}
return err;
}
/*
* Fills in the passed counter variables, so you can prepare pagelist metadata
* before calling ceph_encode_locks.
*/
void ceph_count_locks(struct inode *inode, int *fcntl_count, int *flock_count)
{
struct ceph_client *cl = ceph_inode_to_client(inode);
struct file_lock *lock;
struct file_lock_context *ctx;
*fcntl_count = 0;
*flock_count = 0;
ctx = locks_inode_context(inode);
if (ctx) {
spin_lock(&ctx->flc_lock);
for_each_file_lock(lock, &ctx->flc_posix)
++(*fcntl_count);
for_each_file_lock(lock, &ctx->flc_flock)
++(*flock_count);
spin_unlock(&ctx->flc_lock);
}
doutc(cl, "counted %d flock locks and %d fcntl locks\n",
*flock_count, *fcntl_count);
}
/*
* Given a pointer to a lock, convert it to a ceph filelock
*/
static int lock_to_ceph_filelock(struct inode *inode,
struct file_lock *lock,
struct ceph_filelock *cephlock)
{
struct ceph_client *cl = ceph_inode_to_client(inode);
int err = 0;
cephlock->start = cpu_to_le64(lock->fl_start);
cephlock->length = cpu_to_le64(lock->fl_end - lock->fl_start + 1);
cephlock->client = cpu_to_le64(0);
cephlock->pid = cpu_to_le64((u64) lock->c.flc_pid);
cephlock->owner = cpu_to_le64(secure_addr(lock->c.flc_owner));
switch (lock->c.flc_type) {
case F_RDLCK:
cephlock->type = CEPH_LOCK_SHARED;
break;
case F_WRLCK:
cephlock->type = CEPH_LOCK_EXCL;
break;
case F_UNLCK:
cephlock->type = CEPH_LOCK_UNLOCK;
break;
default:
doutc(cl, "Have unknown lock type %d\n",
lock->c.flc_type);
err = -EINVAL;
}
return err;
}
/*
* Encode the flock and fcntl locks for the given inode into the ceph_filelock
* array. Must be called with inode->i_lock already held.
* If we encounter more of a specific lock type than expected, return -ENOSPC.
*/
int ceph_encode_locks_to_buffer(struct inode *inode,
struct ceph_filelock *flocks,
int num_fcntl_locks, int num_flock_locks)
{
struct file_lock *lock;
struct file_lock_context *ctx = locks_inode_context(inode);
struct ceph_client *cl = ceph_inode_to_client(inode);
int err = 0;
int seen_fcntl = 0;
int seen_flock = 0;
int l = 0;
doutc(cl, "encoding %d flock and %d fcntl locks\n", num_flock_locks,
num_fcntl_locks);
if (!ctx)
return 0;
spin_lock(&ctx->flc_lock);
for_each_file_lock(lock, &ctx->flc_posix) {
++seen_fcntl;
if (seen_fcntl > num_fcntl_locks) {
err = -ENOSPC;
goto fail;
}
err = lock_to_ceph_filelock(inode, lock, &flocks[l]);
if (err)
goto fail;
++l;
}
for_each_file_lock(lock, &ctx->flc_flock) {
++seen_flock;
if (seen_flock > num_flock_locks) {
err = -ENOSPC;
goto fail;
}
err = lock_to_ceph_filelock(inode, lock, &flocks[l]);
if (err)
goto fail;
++l;
}
fail:
spin_unlock(&ctx->flc_lock);
return err;
}
/*
* Copy the encoded flock and fcntl locks into the pagelist.
* Format is: #fcntl locks, sequential fcntl locks, #flock locks,
* sequential flock locks.
* Returns zero on success.
*/
int ceph_locks_to_pagelist(struct ceph_filelock *flocks,
struct ceph_pagelist *pagelist,
int num_fcntl_locks, int num_flock_locks)
{
int err = 0;
__le32 nlocks;
nlocks = cpu_to_le32(num_fcntl_locks);
err = ceph_pagelist_append(pagelist, &nlocks, sizeof(nlocks));
if (err)
goto out_fail;
if (num_fcntl_locks > 0) {
err = ceph_pagelist_append(pagelist, flocks,
num_fcntl_locks * sizeof(*flocks));
if (err)
goto out_fail;
}
nlocks = cpu_to_le32(num_flock_locks);
err = ceph_pagelist_append(pagelist, &nlocks, sizeof(nlocks));
if (err)
goto out_fail;
if (num_flock_locks > 0) {
err = ceph_pagelist_append(pagelist, &flocks[num_fcntl_locks],
num_flock_locks * sizeof(*flocks));
}
out_fail:
return err;
}
File diff suppressed because it is too large Load Diff
+733
View File
@@ -0,0 +1,733 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FS_CEPH_MDS_CLIENT_H
#define _FS_CEPH_MDS_CLIENT_H
#include <linux/completion.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/rbtree.h>
#include <linux/spinlock.h>
#include <linux/refcount.h>
#include <linux/utsname.h>
#include <linux/ktime.h>
#include <linux/ceph/types.h>
#include <linux/ceph/messenger.h>
#include <linux/ceph/auth.h>
#include "mdsmap.h"
#include "metric.h"
#include "subvolume_metrics.h"
#include "super.h"
/* The first 8 bits are reserved for old ceph releases */
enum ceph_feature_type {
CEPHFS_FEATURE_MIMIC = 8,
CEPHFS_FEATURE_REPLY_ENCODING,
CEPHFS_FEATURE_RECLAIM_CLIENT,
CEPHFS_FEATURE_LAZY_CAP_WANTED,
CEPHFS_FEATURE_MULTI_RECONNECT,
CEPHFS_FEATURE_DELEG_INO,
CEPHFS_FEATURE_METRIC_COLLECT,
CEPHFS_FEATURE_ALTERNATE_NAME,
CEPHFS_FEATURE_NOTIFY_SESSION_STATE,
CEPHFS_FEATURE_OP_GETVXATTR,
CEPHFS_FEATURE_32BITS_RETRY_FWD,
CEPHFS_FEATURE_NEW_SNAPREALM_INFO,
CEPHFS_FEATURE_HAS_OWNER_UIDGID,
CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK,
CEPHFS_FEATURE_SUBVOLUME_METRICS,
CEPHFS_FEATURE_MAX = CEPHFS_FEATURE_SUBVOLUME_METRICS,
};
#define CEPHFS_FEATURES_CLIENT_SUPPORTED { \
0, 1, 2, 3, 4, 5, 6, 7, \
CEPHFS_FEATURE_MIMIC, \
CEPHFS_FEATURE_REPLY_ENCODING, \
CEPHFS_FEATURE_LAZY_CAP_WANTED, \
CEPHFS_FEATURE_MULTI_RECONNECT, \
CEPHFS_FEATURE_DELEG_INO, \
CEPHFS_FEATURE_METRIC_COLLECT, \
CEPHFS_FEATURE_ALTERNATE_NAME, \
CEPHFS_FEATURE_NOTIFY_SESSION_STATE, \
CEPHFS_FEATURE_OP_GETVXATTR, \
CEPHFS_FEATURE_32BITS_RETRY_FWD, \
CEPHFS_FEATURE_HAS_OWNER_UIDGID, \
CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK, \
CEPHFS_FEATURE_SUBVOLUME_METRICS, \
}
/*
* Some lock dependencies:
*
* session->s_mutex
* mdsc->mutex
*
* mdsc->snap_rwsem
*
* ci->i_ceph_lock
* mdsc->snap_flush_lock
* mdsc->cap_delay_lock
*
*/
struct ceph_fs_client;
struct ceph_cap;
#define MDS_AUTH_UID_ANY -1
#define CEPH_GET_CAPS_WAIT_TIMEOUT (5 * HZ)
#define CEPH_CAP_FLUSH_WAIT_TIMEOUT_SEC 60
#define CEPH_CAP_FLUSH_MAX_DUMP_ENTRIES 5
#define CEPH_CAP_FLUSH_MAX_DUMP_ITERS 5
#define CEPH_CLIENT_RESET_REASON_LEN 64
#define CEPH_CLIENT_RESET_DRAIN_SEC 30
#define CEPH_CLIENT_RESET_CLOSE_GRACE_MS 100
#define CEPH_CLIENT_RESET_WAIT_TIMEOUT_SEC 120
enum ceph_client_reset_phase {
CEPH_CLIENT_RESET_IDLE = 0,
/*
* QUIESCING is set synchronously by schedule_reset() before the
* workqueue item is dispatched. It gates new requests (any
* phase != IDLE blocks callers) during the window between
* scheduling and the work function's transition to DRAINING.
*/
CEPH_CLIENT_RESET_QUIESCING,
CEPH_CLIENT_RESET_DRAINING,
CEPH_CLIENT_RESET_TEARDOWN,
};
struct ceph_client_reset_state {
spinlock_t lock; /* protects all fields below */
u64 trigger_count; /* number of resets triggered */
u64 success_count; /* number of successful resets */
u64 failure_count; /* number of failed resets */
unsigned long last_start; /* jiffies when last reset started */
unsigned long last_finish; /* jiffies when last reset finished */
int last_errno; /* result of most recent reset */
enum ceph_client_reset_phase phase; /* current reset phase */
bool drain_timed_out; /* drain exceeded timeout */
bool shutdown; /* destroy in progress */
int sessions_reset; /* sessions torn down in last reset */
char last_reason[CEPH_CLIENT_RESET_REASON_LEN]; /* operator-supplied reason */
/* Request blocking during reset */
wait_queue_head_t blocked_wq; /* waitqueue for blocked callers */
atomic_t blocked_requests; /* count of blocked callers */
};
static inline bool ceph_reset_is_idle(struct ceph_client_reset_state *st)
{
return READ_ONCE(st->phase) == CEPH_CLIENT_RESET_IDLE;
}
struct ceph_mds_cap_match {
s64 uid; /* default to MDS_AUTH_UID_ANY */
u32 num_gids;
u32 *gids; /* use these GIDs */
char *path; /* require path to be child of this
(may be "" or "/" for any) */
char *fs_name;
bool root_squash; /* default to false */
};
struct ceph_mds_cap_auth {
struct ceph_mds_cap_match match;
bool readable;
bool writeable;
};
/*
* parsed info about a single inode. pointers are into the encoded
* on-wire structures within the mds reply message payload.
*/
struct ceph_mds_reply_info_in {
struct ceph_mds_reply_inode *in;
struct ceph_dir_layout dir_layout;
u32 symlink_len;
char *symlink;
u32 xattr_len;
char *xattr_data;
u64 inline_version;
u32 inline_len;
char *inline_data;
u32 pool_ns_len;
char *pool_ns_data;
u64 max_bytes;
u64 max_files;
s32 dir_pin;
struct ceph_timespec btime;
struct ceph_timespec snap_btime;
u8 *fscrypt_auth;
u8 *fscrypt_file;
u32 fscrypt_auth_len;
u32 fscrypt_file_len;
u64 rsnaps;
u64 change_attr;
u64 subvolume_id;
};
struct ceph_mds_reply_dir_entry {
bool is_nokey;
char *name;
u32 name_len;
u32 raw_hash;
struct ceph_mds_reply_lease *lease;
struct ceph_mds_reply_info_in inode;
loff_t offset;
};
struct ceph_mds_reply_xattr {
char *xattr_value;
size_t xattr_value_len;
};
/*
* parsed info about an mds reply, including information about
* either: 1) the target inode and/or its parent directory and dentry,
* and directory contents (for readdir results), or
* 2) the file range lock info (for fcntl F_GETLK results).
*/
struct ceph_mds_reply_info_parsed {
struct ceph_mds_reply_head *head;
/* trace */
struct ceph_mds_reply_info_in diri, targeti;
struct ceph_mds_reply_dirfrag *dirfrag;
char *dname;
u8 *altname;
u32 dname_len;
u32 altname_len;
struct ceph_mds_reply_lease *dlease;
struct ceph_mds_reply_xattr xattr_info;
/* extra */
union {
/* for fcntl F_GETLK results */
struct ceph_filelock *filelock_reply;
/* for readdir results */
struct {
struct ceph_mds_reply_dirfrag *dir_dir;
size_t dir_buf_size;
int dir_nr;
bool dir_end;
bool dir_complete;
bool hash_order;
bool offset_hash;
struct ceph_mds_reply_dir_entry *dir_entries;
};
/* for create results */
struct {
bool has_create_ino;
u64 ino;
};
};
/* encoded blob describing snapshot contexts for certain
operations (e.g., open) */
void *snapblob;
int snapblob_len;
};
/*
* cap releases are batched and sent to the MDS en masse.
*
* Account for per-message overhead of mds_cap_release header
* and __le32 for osd epoch barrier trailing field.
*/
#define CEPH_CAPS_PER_RELEASE ((PAGE_SIZE - sizeof(u32) - \
sizeof(struct ceph_mds_cap_release)) / \
sizeof(struct ceph_mds_cap_item))
/*
* state associated with each MDS<->client session
*/
enum {
CEPH_MDS_SESSION_NEW = 1,
CEPH_MDS_SESSION_OPENING = 2,
CEPH_MDS_SESSION_OPEN = 3,
CEPH_MDS_SESSION_HUNG = 4,
CEPH_MDS_SESSION_RESTARTING = 5,
CEPH_MDS_SESSION_RECONNECTING = 6,
CEPH_MDS_SESSION_CLOSING = 7,
CEPH_MDS_SESSION_CLOSED = 8,
CEPH_MDS_SESSION_REJECTED = 9,
};
struct ceph_mds_session {
struct ceph_mds_client *s_mdsc;
int s_mds;
int s_state;
unsigned long s_ttl; /* time until mds kills us */
unsigned long s_features;
u64 s_seq; /* incoming msg seq # */
struct mutex s_mutex; /* serialize session messages */
struct ceph_connection s_con;
struct ceph_auth_handshake s_auth;
atomic_t s_cap_gen; /* inc each time we get mds stale msg */
unsigned long s_cap_ttl; /* when session caps expire. protected by s_mutex */
/* protected by s_cap_lock */
spinlock_t s_cap_lock;
refcount_t s_ref;
struct list_head s_caps; /* all caps issued by this session */
struct ceph_cap *s_cap_iterator;
int s_nr_caps;
int s_num_cap_releases;
int s_cap_reconnect;
int s_readonly;
struct list_head s_cap_releases; /* waiting cap_release messages */
struct work_struct s_cap_release_work;
/* See ceph_inode_info->i_dirty_item. */
struct list_head s_cap_dirty; /* inodes w/ dirty caps */
/* See ceph_inode_info->i_flushing_item. */
struct list_head s_cap_flushing; /* inodes w/ flushing caps */
unsigned long s_renew_requested; /* last time we sent a renew req */
u64 s_renew_seq;
struct list_head s_waiting; /* waiting requests */
struct list_head s_unsafe; /* unsafe requests */
struct xarray s_delegated_inos;
};
/*
* modes of choosing which MDS to send a request to
*/
enum {
USE_ANY_MDS,
USE_RANDOM_MDS,
USE_AUTH_MDS, /* prefer authoritative mds for this metadata item */
};
struct ceph_mds_request;
struct ceph_mds_client;
/*
* request completion callback
*/
typedef void (*ceph_mds_request_callback_t) (struct ceph_mds_client *mdsc,
struct ceph_mds_request *req);
/*
* wait for request completion callback
*/
typedef int (*ceph_mds_request_wait_callback_t) (struct ceph_mds_client *mdsc,
struct ceph_mds_request *req);
/*
* an in-flight mds request
*/
struct ceph_mds_request {
u64 r_tid; /* transaction id */
struct rb_node r_node;
struct ceph_mds_client *r_mdsc;
struct kref r_kref;
int r_op; /* mds op code */
/* operation on what? */
struct inode *r_inode; /* arg1 */
struct dentry *r_dentry; /* arg1 */
struct dentry *r_old_dentry; /* arg2: rename from or link from */
struct inode *r_old_dentry_dir; /* arg2: old dentry's parent dir */
char *r_path1, *r_path2;
struct ceph_vino r_ino1, r_ino2;
struct inode *r_parent; /* parent dir inode */
struct inode *r_target_inode; /* resulting inode */
struct inode *r_new_inode; /* new inode (for creates) */
const struct qstr *r_dname; /* stable name (for ->d_revalidate) */
#define CEPH_MDS_R_DIRECT_IS_HASH (1) /* r_direct_hash is valid */
#define CEPH_MDS_R_ABORTED (2) /* call was aborted */
#define CEPH_MDS_R_GOT_UNSAFE (3) /* got an unsafe reply */
#define CEPH_MDS_R_GOT_SAFE (4) /* got a safe reply */
#define CEPH_MDS_R_GOT_RESULT (5) /* got a result */
#define CEPH_MDS_R_DID_PREPOPULATE (6) /* prepopulated readdir */
#define CEPH_MDS_R_PARENT_LOCKED (7) /* is r_parent->i_rwsem wlocked? */
#define CEPH_MDS_R_ASYNC (8) /* async request */
#define CEPH_MDS_R_FSCRYPT_FILE (9) /* must marshal fscrypt_file field */
unsigned long r_req_flags;
struct mutex r_fill_mutex;
union ceph_mds_request_args r_args;
struct ceph_fscrypt_auth *r_fscrypt_auth;
u64 r_fscrypt_file;
u8 *r_altname; /* fscrypt binary crypttext for long filenames */
u32 r_altname_len; /* length of r_altname */
int r_fmode; /* file mode, if expecting cap */
int r_request_release_offset;
const struct cred *r_cred;
struct mnt_idmap *r_mnt_idmap;
struct timespec64 r_stamp;
/* for choosing which mds to send this request to */
int r_direct_mode;
u32 r_direct_hash; /* choose dir frag based on this dentry hash */
/* data payload is used for xattr ops */
struct ceph_pagelist *r_pagelist;
/* what caps shall we drop? */
int r_inode_drop, r_inode_unless;
int r_dentry_drop, r_dentry_unless;
int r_old_dentry_drop, r_old_dentry_unless;
struct inode *r_old_inode;
int r_old_inode_drop, r_old_inode_unless;
struct ceph_msg *r_request; /* original request */
struct ceph_msg *r_reply;
struct ceph_mds_reply_info_parsed r_reply_info;
int r_err;
u32 r_readdir_offset;
struct page *r_locked_page;
int r_dir_caps;
int r_num_caps;
unsigned long r_timeout; /* optional. jiffies, 0 is "wait forever" */
unsigned long r_started; /* start time to measure timeout against */
unsigned long r_start_latency; /* start time to measure latency */
unsigned long r_end_latency; /* finish time to measure latency */
unsigned long r_request_started; /* start time for mds request only,
used to measure lease durations */
/* link unsafe requests to parent directory, for fsync */
struct inode *r_unsafe_dir;
struct list_head r_unsafe_dir_item;
/* unsafe requests that modify the target inode */
struct list_head r_unsafe_target_item;
struct ceph_mds_session *r_session;
int r_attempts; /* resend attempts */
int r_num_fwd; /* number of forward attempts */
int r_resend_mds; /* mds to resend to next, if any*/
u32 r_sent_on_mseq; /* cap mseq request was sent at*/
u64 r_deleg_ino;
struct list_head r_wait;
struct completion r_completion;
struct completion r_safe_completion;
ceph_mds_request_callback_t r_callback;
struct list_head r_unsafe_item; /* per-session unsafe list item */
long long r_dir_release_cnt;
long long r_dir_ordered_cnt;
int r_readdir_cache_idx;
int r_feature_needed;
struct ceph_cap_reservation r_caps_reservation;
};
struct ceph_pool_perm {
struct rb_node node;
int perm;
s64 pool;
size_t pool_ns_len;
char pool_ns[];
};
struct ceph_snapid_map {
struct rb_node node;
struct list_head lru;
atomic_t ref;
dev_t dev;
u64 snap;
unsigned long last_used;
};
/*
* node for list of quotarealm inodes that are not visible from the filesystem
* mountpoint, but required to handle, e.g. quotas.
*/
struct ceph_quotarealm_inode {
struct rb_node node;
u64 ino;
unsigned long timeout; /* last time a lookup failed for this inode */
struct mutex mutex;
struct inode *inode;
};
#ifdef CONFIG_DEBUG_FS
struct cap_wait {
struct list_head list;
u64 ino;
pid_t tgid;
int need;
int want;
};
#endif
enum {
CEPH_MDSC_STOPPING_BEGIN = 1,
CEPH_MDSC_STOPPING_FLUSHING = 2,
CEPH_MDSC_STOPPING_FLUSHED = 3,
};
/*
* mds client state
*/
struct ceph_mds_client {
struct ceph_fs_client *fsc;
struct mutex mutex; /* all nested structures */
struct ceph_mdsmap *mdsmap;
struct completion safe_umount_waiters;
wait_queue_head_t session_close_wq;
struct list_head waiting_for_map;
int mdsmap_err;
struct ceph_mds_session **sessions; /* NULL for mds if no session */
atomic_t num_sessions;
int max_sessions; /* len of sessions array */
spinlock_t stopping_lock; /* protect snap_empty */
int stopping; /* the stage of shutting down */
atomic_t stopping_blockers;
struct completion stopping_waiter;
atomic64_t dirty_folios;
wait_queue_head_t flush_end_wq;
atomic64_t quotarealms_count; /* # realms with quota */
/*
* We keep a list of inodes we don't see in the mountpoint but that we
* need to track quota realms.
*/
struct rb_root quotarealms_inodes;
struct mutex quotarealms_inodes_mutex;
/*
* snap_rwsem will cover cap linkage into snaprealms, and
* realm snap contexts. (later, we can do per-realm snap
* contexts locks..) the empty list contains realms with no
* references (implying they contain no inodes with caps) that
* should be destroyed.
*/
u64 last_snap_seq;
struct rw_semaphore snap_rwsem;
struct rb_root snap_realms;
struct list_head snap_empty;
int num_snap_realms;
spinlock_t snap_empty_lock; /* protect snap_empty */
u64 last_tid; /* most recent mds request */
u64 oldest_tid; /* oldest incomplete mds request,
excluding setfilelock requests */
struct rb_root request_tree; /* pending mds requests */
struct delayed_work delayed_work; /* delayed work */
unsigned long last_renew_caps; /* last time we renewed our caps */
struct list_head cap_delay_list; /* caps with delayed release */
struct list_head cap_unlink_delay_list; /* caps with delayed release for unlink */
spinlock_t cap_delay_lock; /* protects cap_delay_list and cap_unlink_delay_list */
struct list_head snap_flush_list; /* cap_snaps ready to flush */
spinlock_t snap_flush_lock;
u64 last_cap_flush_tid;
struct list_head cap_flush_list;
struct list_head cap_dirty_migrating; /* ...that are migration... */
int num_cap_flushing; /* # caps we are flushing */
spinlock_t cap_dirty_lock; /* protects above items */
wait_queue_head_t cap_flushing_wq;
struct work_struct cap_reclaim_work;
atomic_t cap_reclaim_pending;
struct work_struct cap_unlink_work;
/*
* Cap reservations
*
* Maintain a global pool of preallocated struct ceph_caps, referenced
* by struct ceph_caps_reservations. This ensures that we preallocate
* memory needed to successfully process an MDS response. (If an MDS
* sends us cap information and we fail to process it, we will have
* problems due to the client and MDS being out of sync.)
*
* Reservations are 'owned' by a ceph_cap_reservation context.
*/
spinlock_t caps_list_lock;
struct list_head caps_list; /* unused (reserved or
unreserved) */
#ifdef CONFIG_DEBUG_FS
struct list_head cap_wait_list;
#endif
int caps_total_count; /* total caps allocated */
int caps_use_count; /* in use */
int caps_use_max; /* max used caps */
int caps_reserve_count; /* unused, reserved */
int caps_avail_count; /* unused, unreserved */
int caps_min_count; /* keep at least this many
(unreserved) */
spinlock_t dentry_list_lock;
struct list_head dentry_leases; /* fifo list */
struct list_head dentry_dir_leases; /* lru list */
struct ceph_client_metric metric;
struct work_struct reset_work;
struct ceph_client_reset_state reset_state;
struct ceph_subvolume_metrics_tracker subvol_metrics;
/* Subvolume metrics send tracking */
struct mutex subvol_metrics_last_mutex;
struct ceph_subvol_metric_snapshot *subvol_metrics_last;
u32 subvol_metrics_last_nr;
u64 subvol_metrics_sent;
u64 subvol_metrics_nonzero_sends;
spinlock_t snapid_map_lock;
struct rb_root snapid_map_tree;
struct list_head snapid_map_lru;
struct rw_semaphore pool_perm_rwsem;
struct rb_root pool_perm_tree;
u32 s_cap_auths_num;
struct ceph_mds_cap_auth *s_cap_auths;
char nodename[__NEW_UTS_LEN + 1];
};
extern const char *ceph_mds_op_name(int op);
extern bool check_session_state(struct ceph_mds_session *s);
void inc_session_sequence(struct ceph_mds_session *s);
extern struct ceph_mds_session *
__ceph_lookup_mds_session(struct ceph_mds_client *, int mds);
extern const char *ceph_session_state_name(int s);
extern const char *ceph_reset_phase_name(enum ceph_client_reset_phase phase);
extern struct ceph_mds_session *
ceph_get_mds_session(struct ceph_mds_session *s);
extern void ceph_put_mds_session(struct ceph_mds_session *s);
int ceph_mdsc_schedule_reset(struct ceph_mds_client *mdsc,
const char *reason);
int ceph_mdsc_wait_for_reset(struct ceph_mds_client *mdsc);
extern int ceph_mdsc_init(struct ceph_fs_client *fsc);
extern void ceph_mdsc_close_sessions(struct ceph_mds_client *mdsc);
extern void ceph_mdsc_force_umount(struct ceph_mds_client *mdsc);
extern void ceph_mdsc_destroy(struct ceph_fs_client *fsc);
extern void ceph_mdsc_sync(struct ceph_mds_client *mdsc);
extern void ceph_invalidate_dir_request(struct ceph_mds_request *req);
extern int ceph_alloc_readdir_reply_buffer(struct ceph_mds_request *req,
struct inode *dir);
extern struct ceph_mds_request *
ceph_mdsc_create_request(struct ceph_mds_client *mdsc, int op, int mode);
extern int ceph_mdsc_submit_request(struct ceph_mds_client *mdsc,
struct inode *dir,
struct ceph_mds_request *req);
int ceph_mdsc_wait_request(struct ceph_mds_client *mdsc,
struct ceph_mds_request *req,
ceph_mds_request_wait_callback_t wait_func);
extern int ceph_mdsc_do_request(struct ceph_mds_client *mdsc,
struct inode *dir,
struct ceph_mds_request *req);
extern void ceph_mdsc_release_dir_caps(struct ceph_mds_request *req);
extern void ceph_mdsc_release_dir_caps_async(struct ceph_mds_request *req);
static inline void ceph_mdsc_get_request(struct ceph_mds_request *req)
{
kref_get(&req->r_kref);
}
extern void ceph_mdsc_release_request(struct kref *kref);
static inline void ceph_mdsc_put_request(struct ceph_mds_request *req)
{
kref_put(&req->r_kref, ceph_mdsc_release_request);
}
extern void send_flush_mdlog(struct ceph_mds_session *s);
extern void ceph_mdsc_iterate_sessions(struct ceph_mds_client *mdsc,
void (*cb)(struct ceph_mds_session *),
bool check_state);
extern struct ceph_msg *ceph_create_session_msg(u32 op, u64 seq);
extern void __ceph_queue_cap_release(struct ceph_mds_session *session,
struct ceph_cap *cap);
extern void ceph_flush_session_cap_releases(struct ceph_mds_client *mdsc,
struct ceph_mds_session *session);
extern void ceph_queue_cap_reclaim_work(struct ceph_mds_client *mdsc);
extern void ceph_reclaim_caps_nr(struct ceph_mds_client *mdsc, int nr);
extern void ceph_queue_cap_unlink_work(struct ceph_mds_client *mdsc);
extern int ceph_iterate_session_caps(struct ceph_mds_session *session,
int (*cb)(struct inode *, int mds, void *),
void *arg);
extern int ceph_mds_check_access(struct ceph_mds_client *mdsc, char *tpath,
int mask);
extern void ceph_mdsc_pre_umount(struct ceph_mds_client *mdsc);
/*
* Structure to group path-related output parameters for build_*_path functions
*/
struct ceph_path_info {
const char *path;
int pathlen;
struct ceph_vino vino;
bool freepath;
};
static inline void ceph_mdsc_free_path_info(const struct ceph_path_info *path_info)
{
if (path_info && path_info->freepath && !IS_ERR_OR_NULL(path_info->path))
__putname((char *)path_info->path - (PATH_MAX - 1 - path_info->pathlen));
}
extern char *ceph_mdsc_build_path(struct ceph_mds_client *mdsc,
struct dentry *dentry, struct ceph_path_info *path_info,
int for_wire);
extern void __ceph_mdsc_drop_dentry_lease(struct dentry *dentry);
extern void ceph_mdsc_lease_send_msg(struct ceph_mds_session *session,
struct dentry *dentry, char action,
u32 seq);
extern void ceph_mdsc_handle_mdsmap(struct ceph_mds_client *mdsc,
struct ceph_msg *msg);
extern void ceph_mdsc_handle_fsmap(struct ceph_mds_client *mdsc,
struct ceph_msg *msg);
extern struct ceph_mds_session *
ceph_mdsc_open_export_target_session(struct ceph_mds_client *mdsc, int target);
extern int ceph_trim_caps(struct ceph_mds_client *mdsc,
struct ceph_mds_session *session,
int max_caps);
static inline int ceph_wait_on_async_create(struct inode *inode)
{
struct ceph_inode_info *ci = ceph_inode(inode);
return wait_on_bit(&ci->i_ceph_flags, CEPH_I_ASYNC_CREATE_BIT,
TASK_KILLABLE);
}
extern int ceph_wait_on_conflict_unlink(struct dentry *dentry);
extern u64 ceph_get_deleg_ino(struct ceph_mds_session *session);
extern int ceph_restore_deleg_ino(struct ceph_mds_session *session, u64 ino);
extern bool enable_unsafe_idmap;
#endif
+462
View File
@@ -0,0 +1,462 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ceph/ceph_debug.h>
#include <linux/bug.h>
#include <linux/err.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/ceph/messenger.h>
#include <linux/ceph/decode.h>
#include "mdsmap.h"
#include "mds_client.h"
#include "super.h"
#define CEPH_MDS_IS_READY(i, ignore_laggy) \
(m->m_info[i].state > 0 && (ignore_laggy ? true : !m->m_info[i].laggy))
static int __mdsmap_get_random_mds(struct ceph_mdsmap *m, bool ignore_laggy)
{
int n = 0;
int i, j;
/* count */
for (i = 0; i < m->possible_max_rank; i++)
if (CEPH_MDS_IS_READY(i, ignore_laggy))
n++;
if (n == 0)
return -1;
/* pick */
n = get_random_u32_below(n);
for (j = 0, i = 0; i < m->possible_max_rank; i++) {
if (CEPH_MDS_IS_READY(i, ignore_laggy))
j++;
if (j > n)
break;
}
return i;
}
/*
* choose a random mds that is "up" (i.e. has a state > 0), or -1.
*/
int ceph_mdsmap_get_random_mds(struct ceph_mdsmap *m)
{
int mds;
mds = __mdsmap_get_random_mds(m, false);
if (mds == m->possible_max_rank || mds == -1)
mds = __mdsmap_get_random_mds(m, true);
return mds == m->possible_max_rank ? -1 : mds;
}
#define __decode_and_drop_type(p, end, type, bad) \
do { \
if (*p + sizeof(type) > end) \
goto bad; \
*p += sizeof(type); \
} while (0)
#define __decode_and_drop_set(p, end, type, bad) \
do { \
u32 n; \
size_t need; \
ceph_decode_32_safe(p, end, n, bad); \
need = sizeof(type) * n; \
ceph_decode_need(p, end, need, bad); \
*p += need; \
} while (0)
#define __decode_and_drop_map(p, end, ktype, vtype, bad) \
do { \
u32 n; \
size_t need; \
ceph_decode_32_safe(p, end, n, bad); \
need = (sizeof(ktype) + sizeof(vtype)) * n; \
ceph_decode_need(p, end, need, bad); \
*p += need; \
} while (0)
static int __decode_and_drop_compat_set(void **p, void* end)
{
int i;
/* compat, ro_compat, incompat*/
for (i = 0; i < 3; i++) {
u32 n;
ceph_decode_need(p, end, sizeof(u64) + sizeof(u32), bad);
/* mask */
*p += sizeof(u64);
/* names (map<u64, string>) */
n = ceph_decode_32(p);
while (n-- > 0) {
u32 len;
ceph_decode_need(p, end, sizeof(u64) + sizeof(u32),
bad);
*p += sizeof(u64);
len = ceph_decode_32(p);
ceph_decode_need(p, end, len, bad);
*p += len;
}
}
return 0;
bad:
return -1;
}
/*
* Decode an MDS map
*
* Ignore any fields we don't care about (there are quite a few of
* them).
*/
struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
void *end, bool msgr2)
{
struct ceph_client *cl = mdsc->fsc->client;
struct ceph_mdsmap *m;
const void *start = *p;
int i, j, n;
int err;
u8 mdsmap_v;
u16 mdsmap_ev;
u32 target;
m = kzalloc_obj(*m, GFP_NOFS);
if (!m)
return ERR_PTR(-ENOMEM);
ceph_decode_need(p, end, 1 + 1, bad);
mdsmap_v = ceph_decode_8(p);
*p += sizeof(u8); /* mdsmap_cv */
if (mdsmap_v >= 4) {
u32 mdsmap_len;
ceph_decode_32_safe(p, end, mdsmap_len, bad);
if (end < *p + mdsmap_len)
goto bad;
end = *p + mdsmap_len;
}
ceph_decode_need(p, end, 8*sizeof(u32) + sizeof(u64), bad);
m->m_epoch = ceph_decode_32(p);
m->m_client_epoch = ceph_decode_32(p);
m->m_last_failure = ceph_decode_32(p);
m->m_root = ceph_decode_32(p);
m->m_session_timeout = ceph_decode_32(p);
m->m_session_autoclose = ceph_decode_32(p);
m->m_max_file_size = ceph_decode_64(p);
m->m_max_mds = ceph_decode_32(p);
/*
* pick out the active nodes as the m_num_active_mds, the
* m_num_active_mds maybe larger than m_max_mds when decreasing
* the max_mds in cluster side, in other case it should less
* than or equal to m_max_mds.
*/
m->m_num_active_mds = n = ceph_decode_32(p);
/*
* the possible max rank, it maybe larger than the m_num_active_mds,
* for example if the mds_max == 2 in the cluster, when the MDS(0)
* was laggy and being replaced by a new MDS, we will temporarily
* receive a new mds map with n_num_mds == 1 and the active MDS(1),
* and the mds rank >= m_num_active_mds.
*/
m->possible_max_rank = max(m->m_num_active_mds, m->m_max_mds);
m->m_info = kzalloc_objs(*m->m_info, m->possible_max_rank, GFP_NOFS);
if (!m->m_info)
goto nomem;
/* pick out active nodes from mds_info (state > 0) */
for (i = 0; i < n; i++) {
u64 global_id;
u32 namelen;
s32 mds, inc, state;
u8 info_v;
void *info_end = NULL;
struct ceph_entity_addr addr;
u32 num_export_targets;
void *pexport_targets = NULL;
struct ceph_timespec laggy_since;
struct ceph_mds_info *info;
bool laggy;
ceph_decode_need(p, end, sizeof(u64) + 1, bad);
global_id = ceph_decode_64(p);
info_v= ceph_decode_8(p);
if (info_v >= 4) {
u32 info_len;
ceph_decode_need(p, end, 1 + sizeof(u32), bad);
*p += sizeof(u8); /* info_cv */
info_len = ceph_decode_32(p);
info_end = *p + info_len;
if (info_end > end)
goto bad;
}
ceph_decode_need(p, end, sizeof(u64) + sizeof(u32), bad);
*p += sizeof(u64);
namelen = ceph_decode_32(p); /* skip mds name */
*p += namelen;
ceph_decode_32_safe(p, end, mds, bad);
ceph_decode_32_safe(p, end, inc, bad);
ceph_decode_32_safe(p, end, state, bad);
*p += sizeof(u64); /* state_seq */
if (info_v >= 8)
err = ceph_decode_entity_addrvec(p, end, msgr2, &addr);
else
err = ceph_decode_entity_addr(p, end, &addr);
if (err)
goto corrupt;
ceph_decode_copy_safe(p, end, &laggy_since, sizeof(laggy_since),
bad);
laggy = laggy_since.tv_sec != 0 || laggy_since.tv_nsec != 0;
*p += sizeof(u32);
ceph_decode_32_safe(p, end, namelen, bad);
*p += namelen;
if (info_v >= 2) {
ceph_decode_32_safe(p, end, num_export_targets, bad);
pexport_targets = *p;
*p += num_export_targets * sizeof(u32);
} else {
num_export_targets = 0;
}
if (info_end && *p != info_end) {
if (*p > info_end)
goto bad;
*p = info_end;
}
doutc(cl, "%d/%d %lld mds%d.%d %s %s%s\n", i+1, n, global_id,
mds, inc, ceph_pr_addr(&addr),
ceph_mds_state_name(state), laggy ? "(laggy)" : "");
if (mds < 0 || mds >= m->possible_max_rank) {
pr_warn_client(cl, "got incorrect mds(%d)\n", mds);
continue;
}
if (state <= 0) {
doutc(cl, "got incorrect state(%s)\n",
ceph_mds_state_name(state));
continue;
}
info = &m->m_info[mds];
info->global_id = global_id;
info->state = state;
info->addr = addr;
info->laggy = laggy;
info->num_export_targets = num_export_targets;
if (num_export_targets) {
info->export_targets = kcalloc(num_export_targets,
sizeof(u32), GFP_NOFS);
if (!info->export_targets)
goto nomem;
for (j = 0; j < num_export_targets; j++) {
target = ceph_decode_32(&pexport_targets);
info->export_targets[j] = target;
}
} else {
info->export_targets = NULL;
}
}
/* pg_pools */
ceph_decode_32_safe(p, end, n, bad);
m->m_num_data_pg_pools = n;
m->m_data_pg_pools = kcalloc(n, sizeof(u64), GFP_NOFS);
if (!m->m_data_pg_pools)
goto nomem;
ceph_decode_need(p, end, sizeof(u64)*(n+1), bad);
for (i = 0; i < n; i++)
m->m_data_pg_pools[i] = ceph_decode_64(p);
m->m_cas_pg_pool = ceph_decode_64(p);
m->m_enabled = m->m_epoch > 1;
mdsmap_ev = 1;
if (mdsmap_v >= 2) {
ceph_decode_16_safe(p, end, mdsmap_ev, bad_ext);
}
if (mdsmap_ev >= 3) {
if (__decode_and_drop_compat_set(p, end) < 0)
goto bad_ext;
}
/* metadata_pool */
if (mdsmap_ev < 5) {
__decode_and_drop_type(p, end, u32, bad_ext);
} else {
__decode_and_drop_type(p, end, u64, bad_ext);
}
/* created + modified + tableserver */
__decode_and_drop_type(p, end, struct ceph_timespec, bad_ext);
__decode_and_drop_type(p, end, struct ceph_timespec, bad_ext);
__decode_and_drop_type(p, end, u32, bad_ext);
/* in */
{
int num_laggy = 0;
ceph_decode_32_safe(p, end, n, bad_ext);
ceph_decode_need(p, end, sizeof(u32) * n, bad_ext);
for (i = 0; i < n; i++) {
s32 mds = ceph_decode_32(p);
if (mds >= 0 && mds < m->possible_max_rank) {
if (m->m_info[mds].laggy)
num_laggy++;
}
}
m->m_num_laggy = num_laggy;
if (n > m->possible_max_rank) {
void *new_m_info = krealloc(m->m_info,
n * sizeof(*m->m_info),
GFP_NOFS | __GFP_ZERO);
if (!new_m_info)
goto nomem;
m->m_info = new_m_info;
}
m->possible_max_rank = n;
}
/* inc */
__decode_and_drop_map(p, end, u32, u32, bad_ext);
/* up */
__decode_and_drop_map(p, end, u32, u64, bad_ext);
/* failed */
__decode_and_drop_set(p, end, u32, bad_ext);
/* stopped */
__decode_and_drop_set(p, end, u32, bad_ext);
if (mdsmap_ev >= 4) {
/* last_failure_osd_epoch */
__decode_and_drop_type(p, end, u32, bad_ext);
}
if (mdsmap_ev >= 6) {
/* ever_allowed_snaps */
__decode_and_drop_type(p, end, u8, bad_ext);
/* explicitly_allowed_snaps */
__decode_and_drop_type(p, end, u8, bad_ext);
}
if (mdsmap_ev >= 7) {
/* inline_data_enabled */
__decode_and_drop_type(p, end, u8, bad_ext);
}
if (mdsmap_ev >= 8) {
size_t fsname_len;
/* enabled */
ceph_decode_8_safe(p, end, m->m_enabled, bad_ext);
/* fs_name */
m->m_fs_name = ceph_extract_encoded_string(p, end,
&fsname_len,
GFP_NOFS);
if (IS_ERR(m->m_fs_name)) {
m->m_fs_name = NULL;
goto nomem;
}
/* validate fsname against mds_namespace */
if (!namespace_equals(mdsc->fsc->mount_options, m->m_fs_name,
fsname_len)) {
pr_warn_client(cl, "fsname %s doesn't match mds_namespace %s\n",
m->m_fs_name,
mdsc->fsc->mount_options->mds_namespace);
goto bad;
}
} else {
m->m_enabled = false;
m->m_fs_name = kstrdup(CEPH_OLD_FS_NAME, GFP_NOFS);
if (!m->m_fs_name)
goto nomem;
}
/* damaged */
if (mdsmap_ev >= 9) {
size_t need;
ceph_decode_32_safe(p, end, n, bad_ext);
need = sizeof(u32) * n;
ceph_decode_need(p, end, need, bad_ext);
*p += need;
m->m_damaged = n > 0;
} else {
m->m_damaged = false;
}
if (mdsmap_ev >= 17) {
/* balancer */
ceph_decode_skip_string(p, end, bad_ext);
/* standby_count_wanted */
ceph_decode_skip_32(p, end, bad_ext);
/* old_max_mds */
ceph_decode_skip_32(p, end, bad_ext);
/* min_compat_client */
ceph_decode_skip_8(p, end, bad_ext);
/* required_client_features */
ceph_decode_skip_set(p, end, 64, bad_ext);
/* bal_rank_mask */
ceph_decode_skip_string(p, end, bad_ext);
}
if (mdsmap_ev >= 18) {
ceph_decode_64_safe(p, end, m->m_max_xattr_size, bad_ext);
}
bad_ext:
doutc(cl, "m_enabled: %d, m_damaged: %d, m_num_laggy: %d\n",
!!m->m_enabled, !!m->m_damaged, m->m_num_laggy);
*p = end;
doutc(cl, "success epoch %u\n", m->m_epoch);
return m;
nomem:
err = -ENOMEM;
goto out_err;
corrupt:
pr_err_client(cl, "corrupt mdsmap\n");
print_hex_dump(KERN_DEBUG, "mdsmap: ",
DUMP_PREFIX_OFFSET, 16, 1,
start, end - start, true);
out_err:
ceph_mdsmap_destroy(m);
return ERR_PTR(err);
bad:
err = -EINVAL;
goto corrupt;
}
void ceph_mdsmap_destroy(struct ceph_mdsmap *m)
{
int i;
if (m->m_info) {
for (i = 0; i < m->possible_max_rank; i++)
kfree(m->m_info[i].export_targets);
kfree(m->m_info);
}
kfree(m->m_data_pg_pools);
kfree(m->m_fs_name);
kfree(m);
}
bool ceph_mdsmap_is_cluster_available(struct ceph_mdsmap *m)
{
int i, nr_active = 0;
if (!m->m_enabled)
return false;
if (m->m_damaged)
return false;
if (m->m_num_laggy == m->m_num_active_mds)
return false;
for (i = 0; i < m->possible_max_rank; i++) {
if (m->m_info[i].state == CEPH_MDS_STATE_ACTIVE)
nr_active++;
}
return nr_active > 0;
}
+80
View File
@@ -0,0 +1,80 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FS_CEPH_MDSMAP_H
#define _FS_CEPH_MDSMAP_H
#include <linux/bug.h>
#include <linux/ceph/types.h>
struct ceph_mds_client;
/*
* mds map - describe servers in the mds cluster.
*
* we limit fields to those the client actually xcares about
*/
struct ceph_mds_info {
u64 global_id;
struct ceph_entity_addr addr;
s32 state;
int num_export_targets;
bool laggy;
u32 *export_targets;
};
struct ceph_mdsmap {
u32 m_epoch, m_client_epoch, m_last_failure;
u32 m_root;
u32 m_session_timeout; /* seconds */
u32 m_session_autoclose; /* seconds */
u64 m_max_file_size;
/*
* maximum size for xattrs blob.
* Zeroed by default to force the usage of the (sync) SETXATTR Op.
*/
u64 m_max_xattr_size;
u32 m_max_mds; /* expected up:active mds number */
u32 m_num_active_mds; /* actual up:active mds number */
u32 possible_max_rank; /* possible max rank index */
struct ceph_mds_info *m_info;
/* which object pools file data can be stored in */
int m_num_data_pg_pools;
u64 *m_data_pg_pools;
u64 m_cas_pg_pool;
bool m_enabled;
bool m_damaged;
int m_num_laggy;
char *m_fs_name;
};
static inline struct ceph_entity_addr *
ceph_mdsmap_get_addr(struct ceph_mdsmap *m, int w)
{
if (w >= m->possible_max_rank)
return NULL;
return &m->m_info[w].addr;
}
static inline int ceph_mdsmap_get_state(struct ceph_mdsmap *m, int w)
{
BUG_ON(w < 0);
if (w >= m->possible_max_rank)
return CEPH_MDS_STATE_DNE;
return m->m_info[w].state;
}
static inline bool ceph_mdsmap_is_laggy(struct ceph_mdsmap *m, int w)
{
if (w >= 0 && w < m->possible_max_rank)
return m->m_info[w].laggy;
return false;
}
extern int ceph_mdsmap_get_random_mds(struct ceph_mdsmap *m);
struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
void *end, bool msgr2);
extern void ceph_mdsmap_destroy(struct ceph_mdsmap *m);
extern bool ceph_mdsmap_is_cluster_available(struct ceph_mdsmap *m);
#endif
+533
View File
@@ -0,0 +1,533 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/ceph/ceph_debug.h>
#include <linux/types.h>
#include <linux/percpu_counter.h>
#include <linux/math64.h>
#include <linux/ratelimit.h>
#include <linux/ceph/decode.h>
#include "metric.h"
#include "mds_client.h"
static bool metrics_disable_warned;
static inline u32 ceph_subvolume_entry_payload_len(void)
{
return sizeof(struct ceph_subvolume_metric_entry_wire);
}
static inline u32 ceph_subvolume_entry_encoded_len(void)
{
return CEPH_ENCODING_START_BLK_LEN +
ceph_subvolume_entry_payload_len();
}
static inline u32 ceph_subvolume_outer_payload_len(u32 nr_subvols)
{
/* count is encoded as le64 (size_t on wire) to match FUSE client */
return sizeof(__le64) +
nr_subvols * ceph_subvolume_entry_encoded_len();
}
static inline u32 ceph_subvolume_metric_data_len(u32 nr_subvols)
{
return CEPH_ENCODING_START_BLK_LEN +
ceph_subvolume_outer_payload_len(nr_subvols);
}
static inline u32 ceph_subvolume_clamp_u32(u64 val)
{
return val > U32_MAX ? U32_MAX : (u32)val;
}
static void ceph_init_subvolume_wire_entry(
struct ceph_subvolume_metric_entry_wire *dst,
const struct ceph_subvol_metric_snapshot *src)
{
dst->subvolume_id = cpu_to_le64(src->subvolume_id);
dst->read_ops = cpu_to_le32(ceph_subvolume_clamp_u32(src->read_ops));
dst->write_ops = cpu_to_le32(ceph_subvolume_clamp_u32(src->write_ops));
dst->read_bytes = cpu_to_le64(src->read_bytes);
dst->write_bytes = cpu_to_le64(src->write_bytes);
dst->read_latency_us = cpu_to_le64(src->read_latency_us);
dst->write_latency_us = cpu_to_le64(src->write_latency_us);
dst->time_stamp = 0;
}
static int ceph_encode_subvolume_metrics(void **p, void *end,
struct ceph_subvol_metric_snapshot *subvols,
u32 nr_subvols)
{
u32 i;
ceph_start_encoding(p, 1, 1,
ceph_subvolume_outer_payload_len(nr_subvols));
/* count is encoded as le64 (size_t on wire) to match FUSE client */
ceph_encode_64_safe(p, end, (u64)nr_subvols, enc_err);
for (i = 0; i < nr_subvols; i++) {
struct ceph_subvolume_metric_entry_wire wire_entry;
ceph_init_subvolume_wire_entry(&wire_entry, &subvols[i]);
ceph_start_encoding(p, 1, 1,
ceph_subvolume_entry_payload_len());
ceph_encode_copy_safe(p, end, &wire_entry,
sizeof(wire_entry), enc_err);
}
return 0;
enc_err:
return -ERANGE;
}
static void ktime_to_ceph_timespec(struct ceph_timespec *ts, ktime_t val)
{
struct timespec64 t = ktime_to_timespec64(val);
ceph_encode_timespec64(ts, &t);
}
static bool ceph_mdsc_send_metrics(struct ceph_mds_client *mdsc,
struct ceph_mds_session *s)
{
struct ceph_metric_head *head;
struct ceph_metric_cap *cap;
struct ceph_metric_read_latency *read;
struct ceph_metric_write_latency *write;
struct ceph_metric_metadata_latency *meta;
struct ceph_metric_dlease *dlease;
struct ceph_opened_files *files;
struct ceph_pinned_icaps *icaps;
struct ceph_opened_inodes *inodes;
struct ceph_read_io_size *rsize;
struct ceph_write_io_size *wsize;
struct ceph_client_metric *m = &mdsc->metric;
struct ceph_subvol_metric_snapshot *subvols = NULL;
u64 nr_caps = atomic64_read(&m->total_caps);
u32 header_len = sizeof(struct ceph_metric_header);
struct ceph_client *cl = mdsc->fsc->client;
struct ceph_msg *msg;
u32 nr_subvols = 0;
size_t subvol_len = 0;
void *cursor;
s64 sum;
s32 items = 0;
s32 len;
/* Do not send the metrics until the MDS rank is ready */
mutex_lock(&mdsc->mutex);
if (ceph_mdsmap_get_state(mdsc->mdsmap, s->s_mds) != CEPH_MDS_STATE_ACTIVE) {
mutex_unlock(&mdsc->mutex);
return false;
}
mutex_unlock(&mdsc->mutex);
if (ceph_subvolume_metrics_enabled(&mdsc->subvol_metrics) &&
test_bit(CEPHFS_FEATURE_SUBVOLUME_METRICS, &s->s_features)) {
int ret;
ret = ceph_subvolume_metrics_snapshot(&mdsc->subvol_metrics,
&subvols, &nr_subvols,
true);
if (ret) {
pr_warn_client(cl, "failed to snapshot subvolume metrics: %d\n",
ret);
/*
* On error, ceph_subvolume_metrics_snapshot() guarantees
* *out = NULL and *nr = 0 at function entry, so subvols
* is already NULL here - no cleanup needed.
*/
nr_subvols = 0;
subvols = NULL;
}
}
if (nr_subvols) {
/* type (le32) + ENCODE_START payload - no metric header */
subvol_len = sizeof(__le32) +
ceph_subvolume_metric_data_len(nr_subvols);
}
len = sizeof(*head) + sizeof(*cap) + sizeof(*read) + sizeof(*write)
+ sizeof(*meta) + sizeof(*dlease) + sizeof(*files)
+ sizeof(*icaps) + sizeof(*inodes) + sizeof(*rsize)
+ sizeof(*wsize) + subvol_len;
msg = ceph_msg_new(CEPH_MSG_CLIENT_METRICS, len, GFP_NOFS, true);
if (!msg) {
pr_err_client(cl, "to mds%d, failed to allocate message\n",
s->s_mds);
kfree(subvols);
return false;
}
head = msg->front.iov_base;
/* encode the cap metric */
cap = (struct ceph_metric_cap *)(head + 1);
cap->header.type = cpu_to_le32(CLIENT_METRIC_TYPE_CAP_INFO);
cap->header.ver = 1;
cap->header.compat = 1;
cap->header.data_len = cpu_to_le32(sizeof(*cap) - header_len);
cap->hit = cpu_to_le64(percpu_counter_sum(&m->i_caps_hit));
cap->mis = cpu_to_le64(percpu_counter_sum(&m->i_caps_mis));
cap->total = cpu_to_le64(nr_caps);
items++;
/* encode the read latency metric */
read = (struct ceph_metric_read_latency *)(cap + 1);
read->header.type = cpu_to_le32(CLIENT_METRIC_TYPE_READ_LATENCY);
read->header.ver = 2;
read->header.compat = 1;
read->header.data_len = cpu_to_le32(sizeof(*read) - header_len);
sum = m->metric[METRIC_READ].latency_sum;
ktime_to_ceph_timespec(&read->lat, sum);
ktime_to_ceph_timespec(&read->avg, m->metric[METRIC_READ].latency_avg);
read->sq_sum = cpu_to_le64(m->metric[METRIC_READ].latency_sq_sum);
read->count = cpu_to_le64(m->metric[METRIC_READ].total);
items++;
/* encode the write latency metric */
write = (struct ceph_metric_write_latency *)(read + 1);
write->header.type = cpu_to_le32(CLIENT_METRIC_TYPE_WRITE_LATENCY);
write->header.ver = 2;
write->header.compat = 1;
write->header.data_len = cpu_to_le32(sizeof(*write) - header_len);
sum = m->metric[METRIC_WRITE].latency_sum;
ktime_to_ceph_timespec(&write->lat, sum);
ktime_to_ceph_timespec(&write->avg, m->metric[METRIC_WRITE].latency_avg);
write->sq_sum = cpu_to_le64(m->metric[METRIC_WRITE].latency_sq_sum);
write->count = cpu_to_le64(m->metric[METRIC_WRITE].total);
items++;
/* encode the metadata latency metric */
meta = (struct ceph_metric_metadata_latency *)(write + 1);
meta->header.type = cpu_to_le32(CLIENT_METRIC_TYPE_METADATA_LATENCY);
meta->header.ver = 2;
meta->header.compat = 1;
meta->header.data_len = cpu_to_le32(sizeof(*meta) - header_len);
sum = m->metric[METRIC_METADATA].latency_sum;
ktime_to_ceph_timespec(&meta->lat, sum);
ktime_to_ceph_timespec(&meta->avg, m->metric[METRIC_METADATA].latency_avg);
meta->sq_sum = cpu_to_le64(m->metric[METRIC_METADATA].latency_sq_sum);
meta->count = cpu_to_le64(m->metric[METRIC_METADATA].total);
items++;
/* encode the dentry lease metric */
dlease = (struct ceph_metric_dlease *)(meta + 1);
dlease->header.type = cpu_to_le32(CLIENT_METRIC_TYPE_DENTRY_LEASE);
dlease->header.ver = 1;
dlease->header.compat = 1;
dlease->header.data_len = cpu_to_le32(sizeof(*dlease) - header_len);
dlease->hit = cpu_to_le64(percpu_counter_sum(&m->d_lease_hit));
dlease->mis = cpu_to_le64(percpu_counter_sum(&m->d_lease_mis));
dlease->total = cpu_to_le64(atomic64_read(&m->total_dentries));
items++;
sum = percpu_counter_sum(&m->total_inodes);
/* encode the opened files metric */
files = (struct ceph_opened_files *)(dlease + 1);
files->header.type = cpu_to_le32(CLIENT_METRIC_TYPE_OPENED_FILES);
files->header.ver = 1;
files->header.compat = 1;
files->header.data_len = cpu_to_le32(sizeof(*files) - header_len);
files->opened_files = cpu_to_le64(atomic64_read(&m->opened_files));
files->total = cpu_to_le64(sum);
items++;
/* encode the pinned icaps metric */
icaps = (struct ceph_pinned_icaps *)(files + 1);
icaps->header.type = cpu_to_le32(CLIENT_METRIC_TYPE_PINNED_ICAPS);
icaps->header.ver = 1;
icaps->header.compat = 1;
icaps->header.data_len = cpu_to_le32(sizeof(*icaps) - header_len);
icaps->pinned_icaps = cpu_to_le64(nr_caps);
icaps->total = cpu_to_le64(sum);
items++;
/* encode the opened inodes metric */
inodes = (struct ceph_opened_inodes *)(icaps + 1);
inodes->header.type = cpu_to_le32(CLIENT_METRIC_TYPE_OPENED_INODES);
inodes->header.ver = 1;
inodes->header.compat = 1;
inodes->header.data_len = cpu_to_le32(sizeof(*inodes) - header_len);
inodes->opened_inodes = cpu_to_le64(percpu_counter_sum(&m->opened_inodes));
inodes->total = cpu_to_le64(sum);
items++;
/* encode the read io size metric */
rsize = (struct ceph_read_io_size *)(inodes + 1);
rsize->header.type = cpu_to_le32(CLIENT_METRIC_TYPE_READ_IO_SIZES);
rsize->header.ver = 1;
rsize->header.compat = 1;
rsize->header.data_len = cpu_to_le32(sizeof(*rsize) - header_len);
rsize->total_ops = cpu_to_le64(m->metric[METRIC_READ].total);
rsize->total_size = cpu_to_le64(m->metric[METRIC_READ].size_sum);
items++;
/* encode the write io size metric */
wsize = (struct ceph_write_io_size *)(rsize + 1);
wsize->header.type = cpu_to_le32(CLIENT_METRIC_TYPE_WRITE_IO_SIZES);
wsize->header.ver = 1;
wsize->header.compat = 1;
wsize->header.data_len = cpu_to_le32(sizeof(*wsize) - header_len);
wsize->total_ops = cpu_to_le64(m->metric[METRIC_WRITE].total);
wsize->total_size = cpu_to_le64(m->metric[METRIC_WRITE].size_sum);
items++;
cursor = wsize + 1;
if (nr_subvols) {
void *payload;
void *payload_end;
int ret;
/* Emit only the type (le32), no ver/compat/data_len */
ceph_encode_32(&cursor, CLIENT_METRIC_TYPE_SUBVOLUME_METRICS);
items++;
payload = cursor;
payload_end = (char *)payload +
ceph_subvolume_metric_data_len(nr_subvols);
ret = ceph_encode_subvolume_metrics(&payload, payload_end,
subvols, nr_subvols);
if (ret) {
pr_warn_client(cl,
"failed to encode subvolume metrics\n");
kfree(subvols);
ceph_msg_put(msg);
return false;
}
WARN_ON(payload != payload_end);
cursor = payload;
}
put_unaligned_le32(items, &head->num);
msg->front.iov_len = (char *)cursor - (char *)head;
msg->hdr.version = cpu_to_le16(1);
msg->hdr.compat_version = cpu_to_le16(1);
msg->hdr.front_len = cpu_to_le32(msg->front.iov_len);
ceph_con_send(&s->s_con, msg);
if (nr_subvols) {
mutex_lock(&mdsc->subvol_metrics_last_mutex);
kfree(mdsc->subvol_metrics_last);
mdsc->subvol_metrics_last = subvols;
mdsc->subvol_metrics_last_nr = nr_subvols;
mdsc->subvol_metrics_sent += nr_subvols;
mdsc->subvol_metrics_nonzero_sends++;
mutex_unlock(&mdsc->subvol_metrics_last_mutex);
subvols = NULL;
}
kfree(subvols);
return true;
}
static void metric_get_session(struct ceph_mds_client *mdsc)
{
struct ceph_mds_session *s;
int i;
mutex_lock(&mdsc->mutex);
for (i = 0; i < mdsc->max_sessions; i++) {
s = __ceph_lookup_mds_session(mdsc, i);
if (!s)
continue;
/*
* Skip it if MDS doesn't support the metric collection,
* or the MDS will close the session's socket connection
* directly when it get this message.
*
* Also skip sessions that don't support SUBVOLUME_METRICS
* when subvolume metrics collection is enabled. This ensures
* we only send subvolume metrics to MDSs that understand them.
* If no session supports the feature, metrics won't be sent.
*/
if (check_session_state(s) &&
test_bit(CEPHFS_FEATURE_METRIC_COLLECT, &s->s_features)) {
if (ceph_subvolume_metrics_enabled(&mdsc->subvol_metrics) &&
!test_bit(CEPHFS_FEATURE_SUBVOLUME_METRICS,
&s->s_features)) {
ceph_put_mds_session(s);
continue;
}
mdsc->metric.session = s;
break;
}
ceph_put_mds_session(s);
}
mutex_unlock(&mdsc->mutex);
}
static void metric_delayed_work(struct work_struct *work)
{
struct ceph_client_metric *m =
container_of(work, struct ceph_client_metric, delayed_work.work);
struct ceph_mds_client *mdsc =
container_of(m, struct ceph_mds_client, metric);
if (mdsc->stopping)
return;
if (disable_send_metrics) {
if (!metrics_disable_warned) {
pr_info("ceph: metrics sending disabled via module parameter\n");
metrics_disable_warned = true;
}
return;
}
metrics_disable_warned = false;
if (!m->session || !check_session_state(m->session)) {
if (m->session) {
ceph_put_mds_session(m->session);
m->session = NULL;
}
metric_get_session(mdsc);
}
if (m->session)
ceph_mdsc_send_metrics(mdsc, m->session);
else
pr_warn_ratelimited("ceph: metrics worker has no MDS session\n");
metric_schedule_delayed(m);
}
int ceph_metric_init(struct ceph_client_metric *m)
{
struct ceph_metric *metric;
int ret, i;
if (!m)
return -EINVAL;
atomic64_set(&m->total_dentries, 0);
ret = percpu_counter_init(&m->d_lease_hit, 0, GFP_KERNEL);
if (ret)
return ret;
ret = percpu_counter_init(&m->d_lease_mis, 0, GFP_KERNEL);
if (ret)
goto err_d_lease_mis;
atomic64_set(&m->total_caps, 0);
ret = percpu_counter_init(&m->i_caps_hit, 0, GFP_KERNEL);
if (ret)
goto err_i_caps_hit;
ret = percpu_counter_init(&m->i_caps_mis, 0, GFP_KERNEL);
if (ret)
goto err_i_caps_mis;
for (i = 0; i < METRIC_MAX; i++) {
metric = &m->metric[i];
spin_lock_init(&metric->lock);
metric->size_sum = 0;
metric->size_min = U64_MAX;
metric->size_max = 0;
metric->total = 0;
metric->latency_sum = 0;
metric->latency_avg = 0;
metric->latency_sq_sum = 0;
metric->latency_min = KTIME_MAX;
metric->latency_max = 0;
}
atomic64_set(&m->opened_files, 0);
ret = percpu_counter_init(&m->opened_inodes, 0, GFP_KERNEL);
if (ret)
goto err_opened_inodes;
ret = percpu_counter_init(&m->total_inodes, 0, GFP_KERNEL);
if (ret)
goto err_total_inodes;
m->session = NULL;
INIT_DELAYED_WORK(&m->delayed_work, metric_delayed_work);
return 0;
err_total_inodes:
percpu_counter_destroy(&m->opened_inodes);
err_opened_inodes:
percpu_counter_destroy(&m->i_caps_mis);
err_i_caps_mis:
percpu_counter_destroy(&m->i_caps_hit);
err_i_caps_hit:
percpu_counter_destroy(&m->d_lease_mis);
err_d_lease_mis:
percpu_counter_destroy(&m->d_lease_hit);
return ret;
}
void ceph_metric_destroy(struct ceph_client_metric *m)
{
if (!m)
return;
cancel_delayed_work_sync(&m->delayed_work);
percpu_counter_destroy(&m->total_inodes);
percpu_counter_destroy(&m->opened_inodes);
percpu_counter_destroy(&m->i_caps_mis);
percpu_counter_destroy(&m->i_caps_hit);
percpu_counter_destroy(&m->d_lease_mis);
percpu_counter_destroy(&m->d_lease_hit);
ceph_put_mds_session(m->session);
}
#define METRIC_UPDATE_MIN_MAX(min, max, new) \
{ \
if (unlikely(new < min)) \
min = new; \
if (unlikely(new > max)) \
max = new; \
}
static inline void __update_mean_and_stdev(ktime_t total, ktime_t *lavg,
ktime_t *sq_sump, ktime_t lat)
{
ktime_t avg;
if (unlikely(total == 1)) {
*lavg = lat;
} else {
/* the sq is (lat - old_avg) * (lat - new_avg) */
avg = *lavg + div64_s64(lat - *lavg, total);
*sq_sump += (lat - *lavg)*(lat - avg);
*lavg = avg;
}
}
void ceph_update_metrics(struct ceph_metric *m,
ktime_t r_start, ktime_t r_end,
unsigned int size, int rc)
{
ktime_t lat = ktime_sub(r_end, r_start);
ktime_t total;
if (unlikely(rc < 0 && rc != -ENOENT && rc != -ETIMEDOUT))
return;
spin_lock(&m->lock);
total = ++m->total;
m->size_sum += size;
METRIC_UPDATE_MIN_MAX(m->size_min, m->size_max, size);
m->latency_sum += lat;
METRIC_UPDATE_MIN_MAX(m->latency_min, m->latency_max, lat);
__update_mean_and_stdev(total, &m->latency_avg, &m->latency_sq_sum,
lat);
spin_unlock(&m->lock);
}
+281
View File
@@ -0,0 +1,281 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FS_CEPH_MDS_METRIC_H
#define _FS_CEPH_MDS_METRIC_H
#include <linux/ceph/types.h>
#include <linux/percpu_counter.h>
#include <linux/ktime.h>
extern bool disable_send_metrics;
enum ceph_metric_type {
CLIENT_METRIC_TYPE_CAP_INFO,
CLIENT_METRIC_TYPE_READ_LATENCY,
CLIENT_METRIC_TYPE_WRITE_LATENCY,
CLIENT_METRIC_TYPE_METADATA_LATENCY,
CLIENT_METRIC_TYPE_DENTRY_LEASE,
CLIENT_METRIC_TYPE_OPENED_FILES,
CLIENT_METRIC_TYPE_PINNED_ICAPS,
CLIENT_METRIC_TYPE_OPENED_INODES,
CLIENT_METRIC_TYPE_READ_IO_SIZES,
CLIENT_METRIC_TYPE_WRITE_IO_SIZES,
CLIENT_METRIC_TYPE_AVG_READ_LATENCY,
CLIENT_METRIC_TYPE_STDEV_READ_LATENCY,
CLIENT_METRIC_TYPE_AVG_WRITE_LATENCY,
CLIENT_METRIC_TYPE_STDEV_WRITE_LATENCY,
CLIENT_METRIC_TYPE_AVG_METADATA_LATENCY,
CLIENT_METRIC_TYPE_STDEV_METADATA_LATENCY,
CLIENT_METRIC_TYPE_SUBVOLUME_METRICS,
CLIENT_METRIC_TYPE_MAX = CLIENT_METRIC_TYPE_SUBVOLUME_METRICS,
};
/*
* This will always have the highest metric bit value
* as the last element of the array.
*/
#define CEPHFS_METRIC_SPEC_CLIENT_SUPPORTED { \
CLIENT_METRIC_TYPE_CAP_INFO, \
CLIENT_METRIC_TYPE_READ_LATENCY, \
CLIENT_METRIC_TYPE_WRITE_LATENCY, \
CLIENT_METRIC_TYPE_METADATA_LATENCY, \
CLIENT_METRIC_TYPE_DENTRY_LEASE, \
CLIENT_METRIC_TYPE_OPENED_FILES, \
CLIENT_METRIC_TYPE_PINNED_ICAPS, \
CLIENT_METRIC_TYPE_OPENED_INODES, \
CLIENT_METRIC_TYPE_READ_IO_SIZES, \
CLIENT_METRIC_TYPE_WRITE_IO_SIZES, \
CLIENT_METRIC_TYPE_AVG_READ_LATENCY, \
CLIENT_METRIC_TYPE_STDEV_READ_LATENCY, \
CLIENT_METRIC_TYPE_AVG_WRITE_LATENCY, \
CLIENT_METRIC_TYPE_STDEV_WRITE_LATENCY, \
CLIENT_METRIC_TYPE_AVG_METADATA_LATENCY, \
CLIENT_METRIC_TYPE_STDEV_METADATA_LATENCY, \
CLIENT_METRIC_TYPE_SUBVOLUME_METRICS, \
\
CLIENT_METRIC_TYPE_MAX, \
}
struct ceph_metric_header {
__le32 type; /* ceph metric type */
__u8 ver;
__u8 compat;
__le32 data_len; /* length of sizeof(hit + mis + total) */
} __packed;
/* metric caps header */
struct ceph_metric_cap {
struct ceph_metric_header header;
__le64 hit;
__le64 mis;
__le64 total;
} __packed;
/* metric read latency header */
struct ceph_metric_read_latency {
struct ceph_metric_header header;
struct ceph_timespec lat;
struct ceph_timespec avg;
__le64 sq_sum;
__le64 count;
} __packed;
/* metric write latency header */
struct ceph_metric_write_latency {
struct ceph_metric_header header;
struct ceph_timespec lat;
struct ceph_timespec avg;
__le64 sq_sum;
__le64 count;
} __packed;
/* metric metadata latency header */
struct ceph_metric_metadata_latency {
struct ceph_metric_header header;
struct ceph_timespec lat;
struct ceph_timespec avg;
__le64 sq_sum;
__le64 count;
} __packed;
/* metric dentry lease header */
struct ceph_metric_dlease {
struct ceph_metric_header header;
__le64 hit;
__le64 mis;
__le64 total;
} __packed;
/* metric opened files header */
struct ceph_opened_files {
struct ceph_metric_header header;
__le64 opened_files;
__le64 total;
} __packed;
/* metric pinned i_caps header */
struct ceph_pinned_icaps {
struct ceph_metric_header header;
__le64 pinned_icaps;
__le64 total;
} __packed;
/* metric opened inodes header */
struct ceph_opened_inodes {
struct ceph_metric_header header;
__le64 opened_inodes;
__le64 total;
} __packed;
/* metric read io size header */
struct ceph_read_io_size {
struct ceph_metric_header header;
__le64 total_ops;
__le64 total_size;
} __packed;
/* metric write io size header */
struct ceph_write_io_size {
struct ceph_metric_header header;
__le64 total_ops;
__le64 total_size;
} __packed;
/**
* struct ceph_subvolume_metric_entry_wire - On-wire format sent to MDS
* @subvolume_id: Subvolume identifier
* @read_ops: Read operation count (32-bit, clamped from 64-bit internal)
* @write_ops: Write operation count (32-bit, clamped from 64-bit internal)
* @read_bytes: Total bytes read
* @write_bytes: Total bytes written
* @read_latency_us: Cumulative read latency in microseconds
* @write_latency_us: Cumulative write latency in microseconds
* @time_stamp: Collection timestamp (currently unused, set to 0)
*
* Wire format must match C++ AggregatedIOMetrics struct in MDS.
*/
struct ceph_subvolume_metric_entry_wire {
__le64 subvolume_id;
__le32 read_ops;
__le32 write_ops;
__le64 read_bytes;
__le64 write_bytes;
__le64 read_latency_us;
__le64 write_latency_us;
__le64 time_stamp;
} __packed;
/* Old struct kept for internal tracking, not used on wire */
struct ceph_subvolume_metric_entry {
__le64 subvolume_id;
__le64 read_ops;
__le64 write_ops;
__le64 read_bytes;
__le64 write_bytes;
__le64 read_latency_us;
__le64 write_latency_us;
} __packed;
struct ceph_metric_head {
__le32 num; /* the number of metrics that will be sent */
} __packed;
enum metric_type {
METRIC_READ,
METRIC_WRITE,
METRIC_METADATA,
METRIC_COPYFROM,
METRIC_MAX
};
struct ceph_metric {
spinlock_t lock;
u64 total;
u64 size_sum;
u64 size_min;
u64 size_max;
ktime_t latency_sum;
ktime_t latency_avg;
ktime_t latency_sq_sum;
ktime_t latency_min;
ktime_t latency_max;
};
/* This is the global metrics */
struct ceph_client_metric {
atomic64_t total_dentries;
struct percpu_counter d_lease_hit;
struct percpu_counter d_lease_mis;
atomic64_t total_caps;
struct percpu_counter i_caps_hit;
struct percpu_counter i_caps_mis;
struct ceph_metric metric[METRIC_MAX];
/* The total number of directories and files that are opened */
atomic64_t opened_files;
/* The total number of inodes that have opened files or directories */
struct percpu_counter opened_inodes;
struct percpu_counter total_inodes;
struct ceph_mds_session *session;
struct delayed_work delayed_work; /* delayed work */
};
static inline void metric_schedule_delayed(struct ceph_client_metric *m)
{
if (disable_send_metrics)
return;
/* per second */
schedule_delayed_work(&m->delayed_work, round_jiffies_relative(HZ));
}
extern int ceph_metric_init(struct ceph_client_metric *m);
extern void ceph_metric_destroy(struct ceph_client_metric *m);
static inline void ceph_update_cap_hit(struct ceph_client_metric *m)
{
percpu_counter_inc(&m->i_caps_hit);
}
static inline void ceph_update_cap_mis(struct ceph_client_metric *m)
{
percpu_counter_inc(&m->i_caps_mis);
}
extern void ceph_update_metrics(struct ceph_metric *m,
ktime_t r_start, ktime_t r_end,
unsigned int size, int rc);
static inline void ceph_update_read_metrics(struct ceph_client_metric *m,
ktime_t r_start, ktime_t r_end,
unsigned int size, int rc)
{
ceph_update_metrics(&m->metric[METRIC_READ],
r_start, r_end, size, rc);
}
static inline void ceph_update_write_metrics(struct ceph_client_metric *m,
ktime_t r_start, ktime_t r_end,
unsigned int size, int rc)
{
ceph_update_metrics(&m->metric[METRIC_WRITE],
r_start, r_end, size, rc);
}
static inline void ceph_update_metadata_metrics(struct ceph_client_metric *m,
ktime_t r_start, ktime_t r_end,
int rc)
{
ceph_update_metrics(&m->metric[METRIC_METADATA],
r_start, r_end, 0, rc);
}
static inline void ceph_update_copyfrom_metrics(struct ceph_client_metric *m,
ktime_t r_start, ktime_t r_end,
unsigned int size, int rc)
{
ceph_update_metrics(&m->metric[METRIC_COPYFROM],
r_start, r_end, size, rc);
}
#endif /* _FS_CEPH_MDS_METRIC_H */
+564
View File
@@ -0,0 +1,564 @@
// SPDX-License-Identifier: GPL-2.0
/*
* quota.c - CephFS quota
*
* Copyright (C) 2017-2018 SUSE
*/
#include <linux/statfs.h>
#include "super.h"
#include "mds_client.h"
void ceph_adjust_quota_realms_count(struct inode *inode, bool inc)
{
struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(inode->i_sb);
if (inc)
atomic64_inc(&mdsc->quotarealms_count);
else
atomic64_dec(&mdsc->quotarealms_count);
}
static inline bool ceph_has_realms_with_quotas(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(sb);
struct inode *root = d_inode(sb->s_root);
if (atomic64_read(&mdsc->quotarealms_count) > 0)
return true;
/* if root is the real CephFS root, we don't have quota realms */
if (root && ceph_ino(root) == CEPH_INO_ROOT)
return false;
/* MDS stray dirs have no quota realms */
if (ceph_vino_is_reserved(ceph_inode(inode)->i_vino))
return false;
/* otherwise, we can't know for sure */
return true;
}
void ceph_handle_quota(struct ceph_mds_client *mdsc,
struct ceph_mds_session *session,
struct ceph_msg *msg)
{
struct super_block *sb = mdsc->fsc->sb;
struct ceph_mds_quota *h = msg->front.iov_base;
struct ceph_client *cl = mdsc->fsc->client;
struct ceph_vino vino;
struct inode *inode;
struct ceph_inode_info *ci;
if (!ceph_inc_mds_stopping_blocker(mdsc, session))
return;
if (msg->front.iov_len < sizeof(*h)) {
pr_err_client(cl, "corrupt message mds%d len %d\n",
session->s_mds, (int)msg->front.iov_len);
ceph_msg_dump(msg);
goto out;
}
/* lookup inode */
vino.ino = le64_to_cpu(h->ino);
vino.snap = CEPH_NOSNAP;
inode = ceph_find_inode(sb, vino);
if (!inode) {
pr_warn_client(cl, "failed to find inode %llx\n", vino.ino);
goto out;
}
ci = ceph_inode(inode);
spin_lock(&ci->i_ceph_lock);
ci->i_rbytes = le64_to_cpu(h->rbytes);
ci->i_rfiles = le64_to_cpu(h->rfiles);
ci->i_rsubdirs = le64_to_cpu(h->rsubdirs);
__ceph_update_quota(ci, le64_to_cpu(h->max_bytes),
le64_to_cpu(h->max_files));
spin_unlock(&ci->i_ceph_lock);
iput(inode);
out:
ceph_dec_mds_stopping_blocker(mdsc);
}
static struct ceph_quotarealm_inode *
find_quotarealm_inode(struct ceph_mds_client *mdsc, u64 ino)
{
struct ceph_quotarealm_inode *qri = NULL;
struct rb_node **node, *parent = NULL;
struct ceph_client *cl = mdsc->fsc->client;
mutex_lock(&mdsc->quotarealms_inodes_mutex);
node = &(mdsc->quotarealms_inodes.rb_node);
while (*node) {
parent = *node;
qri = container_of(*node, struct ceph_quotarealm_inode, node);
if (ino < qri->ino)
node = &((*node)->rb_left);
else if (ino > qri->ino)
node = &((*node)->rb_right);
else
break;
}
if (!qri || (qri->ino != ino)) {
/* Not found, create a new one and insert it */
qri = kmalloc_obj(*qri);
if (qri) {
qri->ino = ino;
qri->inode = NULL;
qri->timeout = 0;
mutex_init(&qri->mutex);
rb_link_node(&qri->node, parent, node);
rb_insert_color(&qri->node, &mdsc->quotarealms_inodes);
} else
pr_warn_client(cl, "Failed to alloc quotarealms_inode\n");
}
mutex_unlock(&mdsc->quotarealms_inodes_mutex);
return qri;
}
/*
* This function will try to lookup a realm inode which isn't visible in the
* filesystem mountpoint. A list of these kind of inodes (not visible) is
* maintained in the mdsc and freed only when the filesystem is umounted.
*
* Note that these inodes are kept in this list even if the lookup fails, which
* allows to prevent useless lookup requests.
*/
static struct inode *lookup_quotarealm_inode(struct ceph_mds_client *mdsc,
struct super_block *sb,
struct ceph_snap_realm *realm)
{
struct ceph_client *cl = mdsc->fsc->client;
struct ceph_quotarealm_inode *qri;
struct inode *in;
qri = find_quotarealm_inode(mdsc, realm->ino);
if (!qri)
return NULL;
mutex_lock(&qri->mutex);
if (qri->inode && ceph_is_any_caps(qri->inode)) {
/* A request has already returned the inode */
mutex_unlock(&qri->mutex);
return qri->inode;
}
/* Check if this inode lookup has failed recently */
if (qri->timeout &&
time_before_eq(jiffies, qri->timeout)) {
mutex_unlock(&qri->mutex);
return NULL;
}
if (qri->inode) {
/* get caps */
int ret = __ceph_do_getattr(qri->inode, NULL,
CEPH_STAT_CAP_INODE, true);
if (ret >= 0)
in = qri->inode;
else
in = ERR_PTR(ret);
} else {
in = ceph_lookup_inode(sb, realm->ino);
}
if (IS_ERR(in)) {
doutc(cl, "Can't lookup inode %llx (err: %ld)\n", realm->ino,
PTR_ERR(in));
qri->timeout = jiffies + secs_to_jiffies(60); /* XXX */
} else {
qri->timeout = 0;
qri->inode = in;
}
mutex_unlock(&qri->mutex);
return in;
}
void ceph_cleanup_quotarealms_inodes(struct ceph_mds_client *mdsc)
{
struct ceph_quotarealm_inode *qri;
struct rb_node *node;
/*
* It should now be safe to clean quotarealms_inode tree without holding
* mdsc->quotarealms_inodes_mutex...
*/
mutex_lock(&mdsc->quotarealms_inodes_mutex);
while (!RB_EMPTY_ROOT(&mdsc->quotarealms_inodes)) {
node = rb_first(&mdsc->quotarealms_inodes);
qri = rb_entry(node, struct ceph_quotarealm_inode, node);
rb_erase(node, &mdsc->quotarealms_inodes);
iput(qri->inode);
kfree(qri);
}
mutex_unlock(&mdsc->quotarealms_inodes_mutex);
}
/*
* This function walks through the snaprealm for an inode and set the
* realmp with the first snaprealm that has quotas set (max_files,
* max_bytes, or any, depending on the 'which_quota' argument). If the root is
* reached, set the realmp with the root ceph_snap_realm instead.
*
* Note that the caller is responsible for calling ceph_put_snap_realm() on the
* returned realm.
*
* Callers of this function need to hold mdsc->snap_rwsem. However, if there's
* a need to do an inode lookup, this rwsem will be temporarily dropped. Hence
* the 'retry' argument: if rwsem needs to be dropped and 'retry' is 'false'
* this function will return -EAGAIN; otherwise, the snaprealms walk-through
* will be restarted.
*/
static int get_quota_realm(struct ceph_mds_client *mdsc, struct inode *inode,
enum quota_get_realm which_quota,
struct ceph_snap_realm **realmp, bool retry)
{
struct ceph_client *cl = mdsc->fsc->client;
struct ceph_inode_info *ci = NULL;
struct ceph_snap_realm *realm, *next;
struct inode *in;
bool has_quota;
if (realmp)
*realmp = NULL;
if (ceph_snap(inode) != CEPH_NOSNAP)
return 0;
restart:
realm = ceph_inode(inode)->i_snap_realm;
if (realm) {
ceph_get_snap_realm(mdsc, realm);
} else {
/*
* i_snap_realm is NULL when all caps have been released, e.g.
* after an MDS session rejection. This is a transient state;
* the realm will be restored once caps are re-granted.
* Treat it as "no quota realm found".
*/
doutc(cl, "%p %llx.%llx null i_snap_realm\n",
inode, ceph_vinop(inode));
}
while (realm) {
bool has_inode;
spin_lock(&realm->inodes_with_caps_lock);
has_inode = realm->inode;
in = has_inode ? igrab(realm->inode) : NULL;
spin_unlock(&realm->inodes_with_caps_lock);
if (has_inode && !in)
break;
if (!in) {
up_read(&mdsc->snap_rwsem);
in = lookup_quotarealm_inode(mdsc, inode->i_sb, realm);
down_read(&mdsc->snap_rwsem);
if (IS_ERR_OR_NULL(in))
break;
ceph_put_snap_realm(mdsc, realm);
if (!retry)
return -EAGAIN;
goto restart;
}
ci = ceph_inode(in);
has_quota = __ceph_has_quota(ci, which_quota);
iput(in);
next = realm->parent;
if (has_quota || !next) {
if (realmp)
*realmp = realm;
return 0;
}
ceph_get_snap_realm(mdsc, next);
ceph_put_snap_realm(mdsc, realm);
realm = next;
}
if (realm)
ceph_put_snap_realm(mdsc, realm);
return 0;
}
bool ceph_quota_is_same_realm(struct inode *old, struct inode *new)
{
struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(old->i_sb);
struct ceph_snap_realm *old_realm, *new_realm;
bool is_same;
int ret;
restart:
/*
* We need to lookup 2 quota realms atomically, i.e. with snap_rwsem.
* However, get_quota_realm may drop it temporarily. By setting the
* 'retry' parameter to 'false', we'll get -EAGAIN if the rwsem was
* dropped and we can then restart the whole operation.
*/
down_read(&mdsc->snap_rwsem);
get_quota_realm(mdsc, old, QUOTA_GET_ANY, &old_realm, true);
ret = get_quota_realm(mdsc, new, QUOTA_GET_ANY, &new_realm, false);
if (ret == -EAGAIN) {
up_read(&mdsc->snap_rwsem);
if (old_realm)
ceph_put_snap_realm(mdsc, old_realm);
goto restart;
}
is_same = (old_realm == new_realm);
up_read(&mdsc->snap_rwsem);
if (old_realm)
ceph_put_snap_realm(mdsc, old_realm);
if (new_realm)
ceph_put_snap_realm(mdsc, new_realm);
return is_same;
}
enum quota_check_op {
QUOTA_CHECK_MAX_FILES_OP, /* check quota max_files limit */
QUOTA_CHECK_MAX_BYTES_OP, /* check quota max_files limit */
QUOTA_CHECK_MAX_BYTES_APPROACHING_OP /* check if quota max_files
limit is approaching */
};
/*
* check_quota_exceeded() will walk up the snaprealm hierarchy and, for each
* realm, it will execute quota check operation defined by the 'op' parameter.
* The snaprealm walk is interrupted if the quota check detects that the quota
* is exceeded or if the root inode is reached.
*/
static bool check_quota_exceeded(struct inode *inode, enum quota_check_op op,
loff_t delta)
{
struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(inode->i_sb);
struct ceph_client *cl = mdsc->fsc->client;
struct ceph_inode_info *ci;
struct ceph_snap_realm *realm, *next;
struct inode *in;
u64 max, rvalue;
bool exceeded = false;
if (ceph_snap(inode) != CEPH_NOSNAP)
return false;
down_read(&mdsc->snap_rwsem);
restart:
realm = ceph_inode(inode)->i_snap_realm;
if (realm) {
ceph_get_snap_realm(mdsc, realm);
} else {
/*
* i_snap_realm is NULL when all caps have been released, e.g.
* after an MDS session rejection. This is a transient state;
* the realm will be restored once caps are re-granted.
* Treat it as "quota not exceeded".
*/
doutc(cl, "%p %llx.%llx null i_snap_realm\n",
inode, ceph_vinop(inode));
}
while (realm) {
bool has_inode;
spin_lock(&realm->inodes_with_caps_lock);
has_inode = realm->inode;
in = has_inode ? igrab(realm->inode) : NULL;
spin_unlock(&realm->inodes_with_caps_lock);
if (has_inode && !in)
break;
if (!in) {
up_read(&mdsc->snap_rwsem);
in = lookup_quotarealm_inode(mdsc, inode->i_sb, realm);
down_read(&mdsc->snap_rwsem);
if (IS_ERR_OR_NULL(in))
break;
ceph_put_snap_realm(mdsc, realm);
goto restart;
}
ci = ceph_inode(in);
spin_lock(&ci->i_ceph_lock);
if (op == QUOTA_CHECK_MAX_FILES_OP) {
max = ci->i_max_files;
rvalue = ci->i_rfiles + ci->i_rsubdirs;
} else {
max = ci->i_max_bytes;
rvalue = ci->i_rbytes;
}
spin_unlock(&ci->i_ceph_lock);
switch (op) {
case QUOTA_CHECK_MAX_FILES_OP:
case QUOTA_CHECK_MAX_BYTES_OP:
exceeded = (max && (rvalue + delta > max));
break;
case QUOTA_CHECK_MAX_BYTES_APPROACHING_OP:
if (max) {
if (rvalue >= max)
exceeded = true;
else {
/*
* when we're writing more that 1/16th
* of the available space
*/
exceeded =
(((max - rvalue) >> 4) < delta);
}
}
break;
default:
/* Shouldn't happen */
pr_warn_client(cl, "Invalid quota check op (%d)\n", op);
exceeded = true; /* Just break the loop */
}
iput(in);
next = realm->parent;
if (exceeded || !next)
break;
ceph_get_snap_realm(mdsc, next);
ceph_put_snap_realm(mdsc, realm);
realm = next;
}
if (realm)
ceph_put_snap_realm(mdsc, realm);
up_read(&mdsc->snap_rwsem);
return exceeded;
}
/*
* ceph_quota_is_max_files_exceeded - check if we can create a new file
* @inode: directory where a new file is being created
*
* This functions returns true is max_files quota allows a new file to be
* created. It is necessary to walk through the snaprealm hierarchy (until the
* FS root) to check all realms with quotas set.
*/
bool ceph_quota_is_max_files_exceeded(struct inode *inode)
{
if (!ceph_has_realms_with_quotas(inode))
return false;
WARN_ON(!S_ISDIR(inode->i_mode));
return check_quota_exceeded(inode, QUOTA_CHECK_MAX_FILES_OP, 1);
}
/*
* ceph_quota_is_max_bytes_exceeded - check if we can write to a file
* @inode: inode being written
* @newsize: new size if write succeeds
*
* This functions returns true is max_bytes quota allows a file size to reach
* @newsize; it returns false otherwise.
*/
bool ceph_quota_is_max_bytes_exceeded(struct inode *inode, loff_t newsize)
{
loff_t size = i_size_read(inode);
if (!ceph_has_realms_with_quotas(inode))
return false;
/* return immediately if we're decreasing file size */
if (newsize <= size)
return false;
return check_quota_exceeded(inode, QUOTA_CHECK_MAX_BYTES_OP, (newsize - size));
}
/*
* ceph_quota_is_max_bytes_approaching - check if we're reaching max_bytes
* @inode: inode being written
* @newsize: new size if write succeeds
*
* This function returns true if the new file size @newsize will be consuming
* more than 1/16th of the available quota space; it returns false otherwise.
*/
bool ceph_quota_is_max_bytes_approaching(struct inode *inode, loff_t newsize)
{
loff_t size = ceph_inode(inode)->i_reported_size;
if (!ceph_has_realms_with_quotas(inode))
return false;
/* return immediately if we're decreasing file size */
if (newsize <= size)
return false;
return check_quota_exceeded(inode, QUOTA_CHECK_MAX_BYTES_APPROACHING_OP,
(newsize - size));
}
/*
* ceph_quota_update_statfs - if root has quota update statfs with quota status
* @fsc: filesystem client instance
* @buf: statfs to update
*
* If the mounted filesystem root has max_bytes quota set, update the filesystem
* statistics with the quota status.
*
* This function returns true if the stats have been updated, false otherwise.
*/
bool ceph_quota_update_statfs(struct ceph_fs_client *fsc, struct kstatfs *buf)
{
struct ceph_mds_client *mdsc = fsc->mdsc;
struct ceph_inode_info *ci;
struct ceph_snap_realm *realm;
struct inode *in;
u64 total = 0, used, free;
bool is_updated = false;
if (!ceph_has_realms_with_quotas(d_inode(fsc->sb->s_root)))
return false;
down_read(&mdsc->snap_rwsem);
get_quota_realm(mdsc, d_inode(fsc->sb->s_root), QUOTA_GET_MAX_BYTES,
&realm, true);
up_read(&mdsc->snap_rwsem);
if (!realm)
return false;
spin_lock(&realm->inodes_with_caps_lock);
in = realm->inode ? igrab(realm->inode) : NULL;
spin_unlock(&realm->inodes_with_caps_lock);
if (in) {
ci = ceph_inode(in);
spin_lock(&ci->i_ceph_lock);
if (ci->i_max_bytes) {
total = ci->i_max_bytes >> CEPH_BLOCK_SHIFT;
used = ci->i_rbytes >> CEPH_BLOCK_SHIFT;
/* For quota size less than 4MB, use 4KB block size */
if (!total) {
total = ci->i_max_bytes >> CEPH_4K_BLOCK_SHIFT;
used = ci->i_rbytes >> CEPH_4K_BLOCK_SHIFT;
buf->f_frsize = 1 << CEPH_4K_BLOCK_SHIFT;
}
/* It is possible for a quota to be exceeded.
* Report 'zero' in that case
*/
free = total > used ? total - used : 0;
/* For quota size less than 4KB, report the
* total=used=4KB,free=0 when quota is full
* and total=free=4KB, used=0 otherwise */
if (!total) {
total = 1;
free = ci->i_max_bytes > ci->i_rbytes ? 1 : 0;
buf->f_frsize = 1 << CEPH_4K_BLOCK_SHIFT;
}
}
spin_unlock(&ci->i_ceph_lock);
if (total) {
buf->f_blocks = total;
buf->f_bfree = free;
buf->f_bavail = free;
is_updated = true;
}
iput(in);
}
ceph_put_snap_realm(mdsc, realm);
return is_updated;
}
File diff suppressed because it is too large Load Diff
+130
View File
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Ceph fs string constants
*/
#include <linux/module.h>
#include <linux/ceph/types.h>
const char *ceph_mds_state_name(int s)
{
switch (s) {
/* down and out */
case CEPH_MDS_STATE_DNE: return "down:dne";
case CEPH_MDS_STATE_STOPPED: return "down:stopped";
/* up and out */
case CEPH_MDS_STATE_BOOT: return "up:boot";
case CEPH_MDS_STATE_STANDBY: return "up:standby";
case CEPH_MDS_STATE_STANDBY_REPLAY: return "up:standby-replay";
case CEPH_MDS_STATE_REPLAYONCE: return "up:oneshot-replay";
case CEPH_MDS_STATE_CREATING: return "up:creating";
case CEPH_MDS_STATE_STARTING: return "up:starting";
/* up and in */
case CEPH_MDS_STATE_REPLAY: return "up:replay";
case CEPH_MDS_STATE_RESOLVE: return "up:resolve";
case CEPH_MDS_STATE_RECONNECT: return "up:reconnect";
case CEPH_MDS_STATE_REJOIN: return "up:rejoin";
case CEPH_MDS_STATE_CLIENTREPLAY: return "up:clientreplay";
case CEPH_MDS_STATE_ACTIVE: return "up:active";
case CEPH_MDS_STATE_STOPPING: return "up:stopping";
}
return "???";
}
const char *ceph_session_op_name(int op)
{
switch (op) {
case CEPH_SESSION_REQUEST_OPEN: return "request_open";
case CEPH_SESSION_OPEN: return "open";
case CEPH_SESSION_REQUEST_CLOSE: return "request_close";
case CEPH_SESSION_CLOSE: return "close";
case CEPH_SESSION_REQUEST_RENEWCAPS: return "request_renewcaps";
case CEPH_SESSION_RENEWCAPS: return "renewcaps";
case CEPH_SESSION_STALE: return "stale";
case CEPH_SESSION_RECALL_STATE: return "recall_state";
case CEPH_SESSION_FLUSHMSG: return "flushmsg";
case CEPH_SESSION_FLUSHMSG_ACK: return "flushmsg_ack";
case CEPH_SESSION_FORCE_RO: return "force_ro";
case CEPH_SESSION_REJECT: return "reject";
case CEPH_SESSION_REQUEST_FLUSH_MDLOG: return "flush_mdlog";
}
return "???";
}
const char *ceph_mds_op_name(int op)
{
switch (op) {
case CEPH_MDS_OP_LOOKUP: return "lookup";
case CEPH_MDS_OP_LOOKUPHASH: return "lookuphash";
case CEPH_MDS_OP_LOOKUPPARENT: return "lookupparent";
case CEPH_MDS_OP_LOOKUPINO: return "lookupino";
case CEPH_MDS_OP_LOOKUPNAME: return "lookupname";
case CEPH_MDS_OP_GETATTR: return "getattr";
case CEPH_MDS_OP_GETVXATTR: return "getvxattr";
case CEPH_MDS_OP_SETXATTR: return "setxattr";
case CEPH_MDS_OP_SETATTR: return "setattr";
case CEPH_MDS_OP_RMXATTR: return "rmxattr";
case CEPH_MDS_OP_SETLAYOUT: return "setlayou";
case CEPH_MDS_OP_SETDIRLAYOUT: return "setdirlayout";
case CEPH_MDS_OP_READDIR: return "readdir";
case CEPH_MDS_OP_MKNOD: return "mknod";
case CEPH_MDS_OP_LINK: return "link";
case CEPH_MDS_OP_UNLINK: return "unlink";
case CEPH_MDS_OP_RENAME: return "rename";
case CEPH_MDS_OP_MKDIR: return "mkdir";
case CEPH_MDS_OP_RMDIR: return "rmdir";
case CEPH_MDS_OP_SYMLINK: return "symlink";
case CEPH_MDS_OP_CREATE: return "create";
case CEPH_MDS_OP_OPEN: return "open";
case CEPH_MDS_OP_LOOKUPSNAP: return "lookupsnap";
case CEPH_MDS_OP_LSSNAP: return "lssnap";
case CEPH_MDS_OP_MKSNAP: return "mksnap";
case CEPH_MDS_OP_RMSNAP: return "rmsnap";
case CEPH_MDS_OP_RENAMESNAP: return "renamesnap";
case CEPH_MDS_OP_SETFILELOCK: return "setfilelock";
case CEPH_MDS_OP_GETFILELOCK: return "getfilelock";
}
return "???";
}
const char *ceph_cap_op_name(int op)
{
switch (op) {
case CEPH_CAP_OP_GRANT: return "grant";
case CEPH_CAP_OP_REVOKE: return "revoke";
case CEPH_CAP_OP_TRUNC: return "trunc";
case CEPH_CAP_OP_EXPORT: return "export";
case CEPH_CAP_OP_IMPORT: return "import";
case CEPH_CAP_OP_UPDATE: return "update";
case CEPH_CAP_OP_DROP: return "drop";
case CEPH_CAP_OP_FLUSH: return "flush";
case CEPH_CAP_OP_FLUSH_ACK: return "flush_ack";
case CEPH_CAP_OP_FLUSHSNAP: return "flushsnap";
case CEPH_CAP_OP_FLUSHSNAP_ACK: return "flushsnap_ack";
case CEPH_CAP_OP_RELEASE: return "release";
case CEPH_CAP_OP_RENEW: return "renew";
}
return "???";
}
const char *ceph_lease_op_name(int o)
{
switch (o) {
case CEPH_MDS_LEASE_REVOKE: return "revoke";
case CEPH_MDS_LEASE_RELEASE: return "release";
case CEPH_MDS_LEASE_RENEW: return "renew";
case CEPH_MDS_LEASE_REVOKE_ACK: return "revoke_ack";
}
return "???";
}
const char *ceph_snap_op_name(int o)
{
switch (o) {
case CEPH_SNAP_OP_UPDATE: return "update";
case CEPH_SNAP_OP_CREATE: return "create";
case CEPH_SNAP_OP_DESTROY: return "destroy";
case CEPH_SNAP_OP_SPLIT: return "split";
}
return "???";
}
+416
View File
@@ -0,0 +1,416 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ceph/ceph_debug.h>
#include <linux/math64.h>
#include <linux/slab.h>
#include <linux/seq_file.h>
#include "subvolume_metrics.h"
#include "mds_client.h"
#include "super.h"
/**
* struct ceph_subvol_metric_rb_entry - Per-subvolume I/O metrics node
* @node: Red-black tree linkage for tracker->tree
* @subvolume_id: Subvolume identifier (key for rb-tree lookup)
* @read_ops: Accumulated read operation count since last snapshot
* @write_ops: Accumulated write operation count since last snapshot
* @read_bytes: Accumulated bytes read since last snapshot
* @write_bytes: Accumulated bytes written since last snapshot
* @read_latency_us: Sum of read latencies in microseconds
* @write_latency_us: Sum of write latencies in microseconds
*/
struct ceph_subvol_metric_rb_entry {
struct rb_node node;
u64 subvolume_id;
u64 read_ops;
u64 write_ops;
u64 read_bytes;
u64 write_bytes;
u64 read_latency_us;
u64 write_latency_us;
};
static struct kmem_cache *ceph_subvol_metric_entry_cachep;
void ceph_subvolume_metrics_init(struct ceph_subvolume_metrics_tracker *tracker)
{
spin_lock_init(&tracker->lock);
tracker->tree = RB_ROOT_CACHED;
tracker->nr_entries = 0;
tracker->enabled = false;
atomic64_set(&tracker->snapshot_attempts, 0);
atomic64_set(&tracker->snapshot_empty, 0);
atomic64_set(&tracker->snapshot_failures, 0);
atomic64_set(&tracker->record_calls, 0);
atomic64_set(&tracker->record_disabled, 0);
atomic64_set(&tracker->record_no_subvol, 0);
atomic64_set(&tracker->total_read_ops, 0);
atomic64_set(&tracker->total_read_bytes, 0);
atomic64_set(&tracker->total_write_ops, 0);
atomic64_set(&tracker->total_write_bytes, 0);
}
static struct ceph_subvol_metric_rb_entry *
__lookup_entry(struct ceph_subvolume_metrics_tracker *tracker, u64 subvol_id)
{
struct rb_node *node;
node = tracker->tree.rb_root.rb_node;
while (node) {
struct ceph_subvol_metric_rb_entry *entry =
rb_entry(node, struct ceph_subvol_metric_rb_entry, node);
if (subvol_id < entry->subvolume_id)
node = node->rb_left;
else if (subvol_id > entry->subvolume_id)
node = node->rb_right;
else
return entry;
}
return NULL;
}
static struct ceph_subvol_metric_rb_entry *
__insert_entry(struct ceph_subvolume_metrics_tracker *tracker,
struct ceph_subvol_metric_rb_entry *entry)
{
struct rb_node **link = &tracker->tree.rb_root.rb_node;
struct rb_node *parent = NULL;
bool leftmost = true;
while (*link) {
struct ceph_subvol_metric_rb_entry *cur =
rb_entry(*link, struct ceph_subvol_metric_rb_entry, node);
parent = *link;
if (entry->subvolume_id < cur->subvolume_id)
link = &(*link)->rb_left;
else if (entry->subvolume_id > cur->subvolume_id) {
link = &(*link)->rb_right;
leftmost = false;
} else
return cur;
}
rb_link_node(&entry->node, parent, link);
rb_insert_color_cached(&entry->node, &tracker->tree, leftmost);
tracker->nr_entries++;
return entry;
}
static void ceph_subvolume_metrics_clear_locked(
struct ceph_subvolume_metrics_tracker *tracker)
{
struct rb_node *node = rb_first_cached(&tracker->tree);
while (node) {
struct ceph_subvol_metric_rb_entry *entry =
rb_entry(node, struct ceph_subvol_metric_rb_entry, node);
struct rb_node *next = rb_next(node);
rb_erase_cached(&entry->node, &tracker->tree);
tracker->nr_entries--;
kmem_cache_free(ceph_subvol_metric_entry_cachep, entry);
node = next;
}
tracker->tree = RB_ROOT_CACHED;
}
void ceph_subvolume_metrics_destroy(struct ceph_subvolume_metrics_tracker *tracker)
{
spin_lock(&tracker->lock);
ceph_subvolume_metrics_clear_locked(tracker);
tracker->enabled = false;
spin_unlock(&tracker->lock);
}
void ceph_subvolume_metrics_enable(struct ceph_subvolume_metrics_tracker *tracker,
bool enable)
{
spin_lock(&tracker->lock);
if (enable) {
tracker->enabled = true;
} else {
tracker->enabled = false;
ceph_subvolume_metrics_clear_locked(tracker);
}
spin_unlock(&tracker->lock);
}
void ceph_subvolume_metrics_record(struct ceph_subvolume_metrics_tracker *tracker,
u64 subvol_id, bool is_write,
size_t size, u64 latency_us)
{
struct ceph_subvol_metric_rb_entry *entry, *new_entry = NULL;
bool retry = false;
/* CEPH_SUBVOLUME_ID_NONE (0) means unknown/unset subvolume */
if (!READ_ONCE(tracker->enabled) ||
subvol_id == CEPH_SUBVOLUME_ID_NONE || !size || !latency_us)
return;
/*
* Retry loop for lock-free allocation pattern:
* 1. First iteration: lookup under lock, if miss -> drop lock, alloc, retry
* 2. Second iteration: lookup again (may have been inserted), insert if still missing
* 3. On race (another thread inserted same key): free our alloc, retry
* All successful paths exit via return, so retry flag doesn't need reset.
*/
do {
spin_lock(&tracker->lock);
if (!tracker->enabled) {
spin_unlock(&tracker->lock);
if (new_entry)
kmem_cache_free(ceph_subvol_metric_entry_cachep, new_entry);
return;
}
entry = __lookup_entry(tracker, subvol_id);
if (!entry) {
if (!new_entry) {
spin_unlock(&tracker->lock);
new_entry = kmem_cache_zalloc(ceph_subvol_metric_entry_cachep,
GFP_NOFS);
if (!new_entry)
return;
new_entry->subvolume_id = subvol_id;
retry = true;
continue;
}
entry = __insert_entry(tracker, new_entry);
if (entry != new_entry) {
/* raced with another insert */
spin_unlock(&tracker->lock);
kmem_cache_free(ceph_subvol_metric_entry_cachep, new_entry);
new_entry = NULL;
retry = true;
continue;
}
new_entry = NULL;
}
if (is_write) {
entry->write_ops++;
entry->write_bytes += size;
entry->write_latency_us += latency_us;
atomic64_inc(&tracker->total_write_ops);
atomic64_add(size, &tracker->total_write_bytes);
} else {
entry->read_ops++;
entry->read_bytes += size;
entry->read_latency_us += latency_us;
atomic64_inc(&tracker->total_read_ops);
atomic64_add(size, &tracker->total_read_bytes);
}
spin_unlock(&tracker->lock);
if (new_entry)
kmem_cache_free(ceph_subvol_metric_entry_cachep, new_entry);
return;
} while (retry);
}
int ceph_subvolume_metrics_snapshot(struct ceph_subvolume_metrics_tracker *tracker,
struct ceph_subvol_metric_snapshot **out,
u32 *nr, bool consume)
{
struct ceph_subvol_metric_snapshot *snap = NULL;
struct rb_node *node;
u32 count = 0, idx = 0;
int ret = 0;
*out = NULL;
*nr = 0;
if (!READ_ONCE(tracker->enabled))
return 0;
atomic64_inc(&tracker->snapshot_attempts);
spin_lock(&tracker->lock);
for (node = rb_first_cached(&tracker->tree); node; node = rb_next(node)) {
struct ceph_subvol_metric_rb_entry *entry =
rb_entry(node, struct ceph_subvol_metric_rb_entry, node);
/* Include entries with ANY I/O activity (read OR write) */
if (entry->read_ops || entry->write_ops)
count++;
}
spin_unlock(&tracker->lock);
if (!count) {
atomic64_inc(&tracker->snapshot_empty);
return 0;
}
snap = kcalloc(count, sizeof(*snap), GFP_NOFS);
if (!snap) {
atomic64_inc(&tracker->snapshot_failures);
return -ENOMEM;
}
spin_lock(&tracker->lock);
node = rb_first_cached(&tracker->tree);
while (node) {
struct ceph_subvol_metric_rb_entry *entry =
rb_entry(node, struct ceph_subvol_metric_rb_entry, node);
struct rb_node *next = rb_next(node);
/* Skip entries with NO I/O activity at all */
if (!entry->read_ops && !entry->write_ops) {
rb_erase_cached(&entry->node, &tracker->tree);
tracker->nr_entries--;
kmem_cache_free(ceph_subvol_metric_entry_cachep, entry);
node = next;
continue;
}
if (idx >= count) {
pr_warn("ceph: subvol metrics snapshot race (idx=%u count=%u)\n",
idx, count);
break;
}
snap[idx].subvolume_id = entry->subvolume_id;
snap[idx].read_ops = entry->read_ops;
snap[idx].write_ops = entry->write_ops;
snap[idx].read_bytes = entry->read_bytes;
snap[idx].write_bytes = entry->write_bytes;
snap[idx].read_latency_us = entry->read_latency_us;
snap[idx].write_latency_us = entry->write_latency_us;
idx++;
if (consume) {
entry->read_ops = 0;
entry->write_ops = 0;
entry->read_bytes = 0;
entry->write_bytes = 0;
entry->read_latency_us = 0;
entry->write_latency_us = 0;
rb_erase_cached(&entry->node, &tracker->tree);
tracker->nr_entries--;
kmem_cache_free(ceph_subvol_metric_entry_cachep, entry);
}
node = next;
}
spin_unlock(&tracker->lock);
if (!idx) {
kfree(snap);
snap = NULL;
ret = 0;
} else {
*nr = idx;
*out = snap;
}
return ret;
}
void ceph_subvolume_metrics_free_snapshot(struct ceph_subvol_metric_snapshot *snapshot)
{
kfree(snapshot);
}
/*
* Dump subvolume metrics to a seq_file for debugfs.
*
* Iterates the rb-tree directly under spinlock to avoid allocation.
* The lock hold time is minimal since we're only doing seq_printf calls.
*/
void ceph_subvolume_metrics_dump(struct ceph_subvolume_metrics_tracker *tracker,
struct seq_file *s)
{
struct rb_node *node;
bool found = false;
spin_lock(&tracker->lock);
if (!tracker->enabled) {
spin_unlock(&tracker->lock);
seq_puts(s, "subvolume metrics disabled\n");
return;
}
for (node = rb_first_cached(&tracker->tree); node; node = rb_next(node)) {
struct ceph_subvol_metric_rb_entry *entry =
rb_entry(node, struct ceph_subvol_metric_rb_entry, node);
u64 avg_rd_lat, avg_wr_lat;
if (!entry->read_ops && !entry->write_ops)
continue;
if (!found) {
seq_puts(s, "subvol_id rd_ops rd_bytes rd_avg_lat_us wr_ops wr_bytes wr_avg_lat_us\n");
seq_puts(s, "------------------------------------------------------------------------------------------------\n");
found = true;
}
avg_rd_lat = entry->read_ops ?
div64_u64(entry->read_latency_us, entry->read_ops) : 0;
avg_wr_lat = entry->write_ops ?
div64_u64(entry->write_latency_us, entry->write_ops) : 0;
seq_printf(s, "%-15llu%-10llu%-12llu%-16llu%-10llu%-12llu%-16llu\n",
entry->subvolume_id,
entry->read_ops,
entry->read_bytes,
avg_rd_lat,
entry->write_ops,
entry->write_bytes,
avg_wr_lat);
}
spin_unlock(&tracker->lock);
if (!found)
seq_puts(s, "(no subvolume metrics collected)\n");
}
void ceph_subvolume_metrics_record_io(struct ceph_mds_client *mdsc,
struct ceph_inode_info *ci,
bool is_write, size_t bytes,
ktime_t start, ktime_t end)
{
struct ceph_subvolume_metrics_tracker *tracker;
u64 subvol_id;
s64 delta_us;
if (!mdsc || !ci || !bytes)
return;
tracker = &mdsc->subvol_metrics;
atomic64_inc(&tracker->record_calls);
if (!ceph_subvolume_metrics_enabled(tracker)) {
atomic64_inc(&tracker->record_disabled);
return;
}
subvol_id = READ_ONCE(ci->i_subvolume_id);
if (subvol_id == CEPH_SUBVOLUME_ID_NONE) {
atomic64_inc(&tracker->record_no_subvol);
return;
}
delta_us = ktime_to_us(ktime_sub(end, start));
if (delta_us <= 0)
delta_us = 1;
ceph_subvolume_metrics_record(tracker, subvol_id, is_write,
bytes, (u64)delta_us);
}
int __init ceph_subvolume_metrics_cache_init(void)
{
ceph_subvol_metric_entry_cachep = KMEM_CACHE(ceph_subvol_metric_rb_entry,
SLAB_RECLAIM_ACCOUNT);
if (!ceph_subvol_metric_entry_cachep)
return -ENOMEM;
return 0;
}
void ceph_subvolume_metrics_cache_destroy(void)
{
kmem_cache_destroy(ceph_subvol_metric_entry_cachep);
}
+97
View File
@@ -0,0 +1,97 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FS_CEPH_SUBVOLUME_METRICS_H
#define _FS_CEPH_SUBVOLUME_METRICS_H
#include <linux/types.h>
#include <linux/rbtree.h>
#include <linux/spinlock.h>
#include <linux/ktime.h>
#include <linux/atomic.h>
struct seq_file;
struct ceph_mds_client;
struct ceph_inode_info;
/**
* struct ceph_subvol_metric_snapshot - Point-in-time snapshot of subvolume metrics
* @subvolume_id: Subvolume identifier (inode number of subvolume root)
* @read_ops: Number of read operations since last snapshot
* @write_ops: Number of write operations since last snapshot
* @read_bytes: Total bytes read since last snapshot
* @write_bytes: Total bytes written since last snapshot
* @read_latency_us: Sum of read latencies in microseconds (for avg calculation)
* @write_latency_us: Sum of write latencies in microseconds (for avg calculation)
*/
struct ceph_subvol_metric_snapshot {
u64 subvolume_id;
u64 read_ops;
u64 write_ops;
u64 read_bytes;
u64 write_bytes;
u64 read_latency_us;
u64 write_latency_us;
};
/**
* struct ceph_subvolume_metrics_tracker - Tracks per-subvolume I/O metrics
* @lock: Protects @tree and @nr_entries during concurrent access
* @tree: Red-black tree of per-subvolume entries, keyed by subvolume_id
* @nr_entries: Number of entries currently in @tree
* @enabled: Whether collection is enabled (requires MDS feature support)
* @snapshot_attempts: Debug counter: total ceph_subvolume_metrics_snapshot() calls
* @snapshot_empty: Debug counter: snapshots that found no data to report
* @snapshot_failures: Debug counter: snapshots that failed to allocate memory
* @record_calls: Debug counter: total ceph_subvolume_metrics_record() calls
* @record_disabled: Debug counter: record calls skipped because disabled
* @record_no_subvol: Debug counter: record calls skipped (no subvolume_id)
* @total_read_ops: Cumulative read ops across all snapshots (never reset)
* @total_read_bytes: Cumulative bytes read across all snapshots (never reset)
* @total_write_ops: Cumulative write ops across all snapshots (never reset)
* @total_write_bytes: Cumulative bytes written across all snapshots (never reset)
*/
struct ceph_subvolume_metrics_tracker {
spinlock_t lock;
struct rb_root_cached tree;
u32 nr_entries;
bool enabled;
atomic64_t snapshot_attempts;
atomic64_t snapshot_empty;
atomic64_t snapshot_failures;
atomic64_t record_calls;
atomic64_t record_disabled;
atomic64_t record_no_subvol;
atomic64_t total_read_ops;
atomic64_t total_read_bytes;
atomic64_t total_write_ops;
atomic64_t total_write_bytes;
};
void ceph_subvolume_metrics_init(struct ceph_subvolume_metrics_tracker *tracker);
void ceph_subvolume_metrics_destroy(struct ceph_subvolume_metrics_tracker *tracker);
void ceph_subvolume_metrics_enable(struct ceph_subvolume_metrics_tracker *tracker,
bool enable);
void ceph_subvolume_metrics_record(struct ceph_subvolume_metrics_tracker *tracker,
u64 subvol_id, bool is_write,
size_t size, u64 latency_us);
int ceph_subvolume_metrics_snapshot(struct ceph_subvolume_metrics_tracker *tracker,
struct ceph_subvol_metric_snapshot **out,
u32 *nr, bool consume);
void ceph_subvolume_metrics_free_snapshot(struct ceph_subvol_metric_snapshot *snapshot);
void ceph_subvolume_metrics_dump(struct ceph_subvolume_metrics_tracker *tracker,
struct seq_file *s);
void ceph_subvolume_metrics_record_io(struct ceph_mds_client *mdsc,
struct ceph_inode_info *ci,
bool is_write, size_t bytes,
ktime_t start, ktime_t end);
static inline bool ceph_subvolume_metrics_enabled(
const struct ceph_subvolume_metrics_tracker *tracker)
{
return READ_ONCE(tracker->enabled);
}
int __init ceph_subvolume_metrics_cache_init(void);
void ceph_subvolume_metrics_cache_destroy(void);
#endif /* _FS_CEPH_SUBVOLUME_METRICS_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+100
View File
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Some non-inline ceph helpers
*/
#include <linux/module.h>
#include <linux/ceph/types.h>
/*
* return true if @layout appears to be valid
*/
int ceph_file_layout_is_valid(const struct ceph_file_layout *layout)
{
__u32 su = layout->stripe_unit;
__u32 sc = layout->stripe_count;
__u32 os = layout->object_size;
/* stripe unit, object size must be non-zero, 64k increment */
if (!su || (su & (CEPH_MIN_STRIPE_UNIT-1)))
return 0;
if (!os || (os & (CEPH_MIN_STRIPE_UNIT-1)))
return 0;
/* object size must be a multiple of stripe unit */
if (os < su || os % su)
return 0;
/* stripe count must be non-zero */
if (!sc)
return 0;
return 1;
}
void ceph_file_layout_from_legacy(struct ceph_file_layout *fl,
struct ceph_file_layout_legacy *legacy)
{
fl->stripe_unit = le32_to_cpu(legacy->fl_stripe_unit);
fl->stripe_count = le32_to_cpu(legacy->fl_stripe_count);
fl->object_size = le32_to_cpu(legacy->fl_object_size);
fl->pool_id = le32_to_cpu(legacy->fl_pg_pool);
if (fl->pool_id == 0 && fl->stripe_unit == 0 &&
fl->stripe_count == 0 && fl->object_size == 0)
fl->pool_id = -1;
}
void ceph_file_layout_to_legacy(struct ceph_file_layout *fl,
struct ceph_file_layout_legacy *legacy)
{
legacy->fl_stripe_unit = cpu_to_le32(fl->stripe_unit);
legacy->fl_stripe_count = cpu_to_le32(fl->stripe_count);
legacy->fl_object_size = cpu_to_le32(fl->object_size);
if (fl->pool_id >= 0)
legacy->fl_pg_pool = cpu_to_le32(fl->pool_id);
else
legacy->fl_pg_pool = 0;
}
int ceph_flags_to_mode(int flags)
{
int mode;
#ifdef O_DIRECTORY /* fixme */
if ((flags & O_DIRECTORY) == O_DIRECTORY)
return CEPH_FILE_MODE_PIN;
#endif
switch (flags & O_ACCMODE) {
case O_WRONLY:
mode = CEPH_FILE_MODE_WR;
break;
case O_RDONLY:
mode = CEPH_FILE_MODE_RD;
break;
case O_RDWR:
case O_ACCMODE: /* this is what the VFS does */
mode = CEPH_FILE_MODE_RDWR;
break;
}
#ifdef O_LAZY
if (flags & O_LAZY)
mode |= CEPH_FILE_MODE_LAZY;
#endif
return mode;
}
int ceph_caps_for_mode(int mode)
{
int caps = CEPH_CAP_PIN;
if (mode & CEPH_FILE_MODE_RD)
caps |= CEPH_CAP_FILE_SHARED |
CEPH_CAP_FILE_RD | CEPH_CAP_FILE_CACHE;
if (mode & CEPH_FILE_MODE_WR)
caps |= CEPH_CAP_FILE_EXCL |
CEPH_CAP_FILE_WR | CEPH_CAP_FILE_BUFFER |
CEPH_CAP_AUTH_SHARED | CEPH_CAP_AUTH_EXCL |
CEPH_CAP_XATTR_SHARED | CEPH_CAP_XATTR_EXCL;
if (mode & CEPH_FILE_MODE_LAZY)
caps |= CEPH_CAP_FILE_LAZYIO;
return caps;
}
File diff suppressed because it is too large Load Diff