Commit 82995cc6 authored by David Howells's avatar David Howells Committed by Ilya Dryomov

libceph, rbd, ceph: convert to use the new mount API

Convert the ceph filesystem to the new internal mount API as the old
one will be obsoleted and removed.  This allows greater flexibility in
communication of mount parameters between userspace, the VFS and the
filesystem.

See Documentation/filesystems/mount_api.txt for more information.

[ Numerous string handling, leak and regression fixes; rbd conversion
  was particularly broken and had to be redone almost from scratch. ]
Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
Signed-off-by: default avatarJeff Layton <jlayton@kernel.org>
Signed-off-by: default avatarIlya Dryomov <idryomov@gmail.com>
parent 196e2d6d
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
#include <linux/ceph/cls_lock_client.h> #include <linux/ceph/cls_lock_client.h>
#include <linux/ceph/striper.h> #include <linux/ceph/striper.h>
#include <linux/ceph/decode.h> #include <linux/ceph/decode.h>
#include <linux/parser.h> #include <linux/fs_parser.h>
#include <linux/bsearch.h> #include <linux/bsearch.h>
#include <linux/kernel.h> #include <linux/kernel.h>
...@@ -838,34 +838,34 @@ enum { ...@@ -838,34 +838,34 @@ enum {
Opt_queue_depth, Opt_queue_depth,
Opt_alloc_size, Opt_alloc_size,
Opt_lock_timeout, Opt_lock_timeout,
Opt_last_int,
/* int args above */ /* int args above */
Opt_pool_ns, Opt_pool_ns,
Opt_last_string,
/* string args above */ /* string args above */
Opt_read_only, Opt_read_only,
Opt_read_write, Opt_read_write,
Opt_lock_on_read, Opt_lock_on_read,
Opt_exclusive, Opt_exclusive,
Opt_notrim, Opt_notrim,
Opt_err
}; };
static match_table_t rbd_opts_tokens = { static const struct fs_parameter_spec rbd_param_specs[] = {
{Opt_queue_depth, "queue_depth=%d"}, fsparam_u32 ("alloc_size", Opt_alloc_size),
{Opt_alloc_size, "alloc_size=%d"}, fsparam_flag ("exclusive", Opt_exclusive),
{Opt_lock_timeout, "lock_timeout=%d"}, fsparam_flag ("lock_on_read", Opt_lock_on_read),
/* int args above */ fsparam_u32 ("lock_timeout", Opt_lock_timeout),
{Opt_pool_ns, "_pool_ns=%s"}, fsparam_flag ("notrim", Opt_notrim),
/* string args above */ fsparam_string ("_pool_ns", Opt_pool_ns),
{Opt_read_only, "read_only"}, fsparam_u32 ("queue_depth", Opt_queue_depth),
{Opt_read_only, "ro"}, /* Alternate spelling */ fsparam_flag ("read_only", Opt_read_only),
{Opt_read_write, "read_write"}, fsparam_flag ("read_write", Opt_read_write),
{Opt_read_write, "rw"}, /* Alternate spelling */ fsparam_flag ("ro", Opt_read_only),
{Opt_lock_on_read, "lock_on_read"}, fsparam_flag ("rw", Opt_read_write),
{Opt_exclusive, "exclusive"}, {}
{Opt_notrim, "notrim"}, };
{Opt_err, NULL}
static const struct fs_parameter_description rbd_parameters = {
.name = "rbd",
.specs = rbd_param_specs,
}; };
struct rbd_options { struct rbd_options {
...@@ -886,87 +886,12 @@ struct rbd_options { ...@@ -886,87 +886,12 @@ struct rbd_options {
#define RBD_EXCLUSIVE_DEFAULT false #define RBD_EXCLUSIVE_DEFAULT false
#define RBD_TRIM_DEFAULT true #define RBD_TRIM_DEFAULT true
struct parse_rbd_opts_ctx { struct rbd_parse_opts_ctx {
struct rbd_spec *spec; struct rbd_spec *spec;
struct ceph_options *copts;
struct rbd_options *opts; struct rbd_options *opts;
}; };
static int parse_rbd_opts_token(char *c, void *private)
{
struct parse_rbd_opts_ctx *pctx = private;
substring_t argstr[MAX_OPT_ARGS];
int token, intval, ret;
token = match_token(c, rbd_opts_tokens, argstr);
if (token < Opt_last_int) {
ret = match_int(&argstr[0], &intval);
if (ret < 0) {
pr_err("bad option arg (not int) at '%s'\n", c);
return ret;
}
dout("got int token %d val %d\n", token, intval);
} else if (token > Opt_last_int && token < Opt_last_string) {
dout("got string token %d val %s\n", token, argstr[0].from);
} else {
dout("got token %d\n", token);
}
switch (token) {
case Opt_queue_depth:
if (intval < 1) {
pr_err("queue_depth out of range\n");
return -EINVAL;
}
pctx->opts->queue_depth = intval;
break;
case Opt_alloc_size:
if (intval < SECTOR_SIZE) {
pr_err("alloc_size out of range\n");
return -EINVAL;
}
if (!is_power_of_2(intval)) {
pr_err("alloc_size must be a power of 2\n");
return -EINVAL;
}
pctx->opts->alloc_size = intval;
break;
case Opt_lock_timeout:
/* 0 is "wait forever" (i.e. infinite timeout) */
if (intval < 0 || intval > INT_MAX / 1000) {
pr_err("lock_timeout out of range\n");
return -EINVAL;
}
pctx->opts->lock_timeout = msecs_to_jiffies(intval * 1000);
break;
case Opt_pool_ns:
kfree(pctx->spec->pool_ns);
pctx->spec->pool_ns = match_strdup(argstr);
if (!pctx->spec->pool_ns)
return -ENOMEM;
break;
case Opt_read_only:
pctx->opts->read_only = true;
break;
case Opt_read_write:
pctx->opts->read_only = false;
break;
case Opt_lock_on_read:
pctx->opts->lock_on_read = true;
break;
case Opt_exclusive:
pctx->opts->exclusive = true;
break;
case Opt_notrim:
pctx->opts->trim = false;
break;
default:
/* libceph prints "bad option" msg */
return -EINVAL;
}
return 0;
}
static char* obj_op_name(enum obj_operation_type op_type) static char* obj_op_name(enum obj_operation_type op_type)
{ {
switch (op_type) { switch (op_type) {
...@@ -6423,6 +6348,122 @@ static inline char *dup_token(const char **buf, size_t *lenp) ...@@ -6423,6 +6348,122 @@ static inline char *dup_token(const char **buf, size_t *lenp)
return dup; return dup;
} }
static int rbd_parse_param(struct fs_parameter *param,
struct rbd_parse_opts_ctx *pctx)
{
struct rbd_options *opt = pctx->opts;
struct fs_parse_result result;
int token, ret;
ret = ceph_parse_param(param, pctx->copts, NULL);
if (ret != -ENOPARAM)
return ret;
token = fs_parse(NULL, &rbd_parameters, param, &result);
dout("%s fs_parse '%s' token %d\n", __func__, param->key, token);
if (token < 0) {
if (token == -ENOPARAM) {
return invalf(NULL, "rbd: Unknown parameter '%s'",
param->key);
}
return token;
}
switch (token) {
case Opt_queue_depth:
if (result.uint_32 < 1)
goto out_of_range;
opt->queue_depth = result.uint_32;
break;
case Opt_alloc_size:
if (result.uint_32 < SECTOR_SIZE)
goto out_of_range;
if (!is_power_of_2(result.uint_32)) {
return invalf(NULL, "rbd: alloc_size must be a power of 2");
}
opt->alloc_size = result.uint_32;
break;
case Opt_lock_timeout:
/* 0 is "wait forever" (i.e. infinite timeout) */
if (result.uint_32 > INT_MAX / 1000)
goto out_of_range;
opt->lock_timeout = msecs_to_jiffies(result.uint_32 * 1000);
break;
case Opt_pool_ns:
kfree(pctx->spec->pool_ns);
pctx->spec->pool_ns = param->string;
param->string = NULL;
break;
case Opt_read_only:
opt->read_only = true;
break;
case Opt_read_write:
opt->read_only = false;
break;
case Opt_lock_on_read:
opt->lock_on_read = true;
break;
case Opt_exclusive:
opt->exclusive = true;
break;
case Opt_notrim:
opt->trim = false;
break;
default:
BUG();
}
return 0;
out_of_range:
return invalf(NULL, "rbd: %s out of range", param->key);
}
/*
* This duplicates most of generic_parse_monolithic(), untying it from
* fs_context and skipping standard superblock and security options.
*/
static int rbd_parse_options(char *options, struct rbd_parse_opts_ctx *pctx)
{
char *key;
int ret = 0;
dout("%s '%s'\n", __func__, options);
while ((key = strsep(&options, ",")) != NULL) {
if (*key) {
struct fs_parameter param = {
.key = key,
.type = fs_value_is_string,
};
char *value = strchr(key, '=');
size_t v_len = 0;
if (value) {
if (value == key)
continue;
*value++ = 0;
v_len = strlen(value);
}
if (v_len > 0) {
param.string = kmemdup_nul(value, v_len,
GFP_KERNEL);
if (!param.string)
return -ENOMEM;
}
param.size = v_len;
ret = rbd_parse_param(&param, pctx);
kfree(param.string);
if (ret)
break;
}
}
return ret;
}
/* /*
* Parse the options provided for an "rbd add" (i.e., rbd image * Parse the options provided for an "rbd add" (i.e., rbd image
* mapping) request. These arrive via a write to /sys/bus/rbd/add, * mapping) request. These arrive via a write to /sys/bus/rbd/add,
...@@ -6474,8 +6515,7 @@ static int rbd_add_parse_args(const char *buf, ...@@ -6474,8 +6515,7 @@ static int rbd_add_parse_args(const char *buf,
const char *mon_addrs; const char *mon_addrs;
char *snap_name; char *snap_name;
size_t mon_addrs_size; size_t mon_addrs_size;
struct parse_rbd_opts_ctx pctx = { 0 }; struct rbd_parse_opts_ctx pctx = { 0 };
struct ceph_options *copts;
int ret; int ret;
/* The first four tokens are required */ /* The first four tokens are required */
...@@ -6486,7 +6526,7 @@ static int rbd_add_parse_args(const char *buf, ...@@ -6486,7 +6526,7 @@ static int rbd_add_parse_args(const char *buf,
return -EINVAL; return -EINVAL;
} }
mon_addrs = buf; mon_addrs = buf;
mon_addrs_size = len + 1; mon_addrs_size = len;
buf += len; buf += len;
ret = -EINVAL; ret = -EINVAL;
...@@ -6536,6 +6576,10 @@ static int rbd_add_parse_args(const char *buf, ...@@ -6536,6 +6576,10 @@ static int rbd_add_parse_args(const char *buf,
*(snap_name + len) = '\0'; *(snap_name + len) = '\0';
pctx.spec->snap_name = snap_name; pctx.spec->snap_name = snap_name;
pctx.copts = ceph_alloc_options();
if (!pctx.copts)
goto out_mem;
/* Initialize all rbd options to the defaults */ /* Initialize all rbd options to the defaults */
pctx.opts = kzalloc(sizeof(*pctx.opts), GFP_KERNEL); pctx.opts = kzalloc(sizeof(*pctx.opts), GFP_KERNEL);
...@@ -6550,27 +6594,27 @@ static int rbd_add_parse_args(const char *buf, ...@@ -6550,27 +6594,27 @@ static int rbd_add_parse_args(const char *buf,
pctx.opts->exclusive = RBD_EXCLUSIVE_DEFAULT; pctx.opts->exclusive = RBD_EXCLUSIVE_DEFAULT;
pctx.opts->trim = RBD_TRIM_DEFAULT; pctx.opts->trim = RBD_TRIM_DEFAULT;
copts = ceph_parse_options(options, mon_addrs, ret = ceph_parse_mon_ips(mon_addrs, mon_addrs_size, pctx.copts, NULL);
mon_addrs + mon_addrs_size - 1, if (ret)
parse_rbd_opts_token, &pctx);
if (IS_ERR(copts)) {
ret = PTR_ERR(copts);
goto out_err; goto out_err;
}
kfree(options);
*ceph_opts = copts; ret = rbd_parse_options(options, &pctx);
if (ret)
goto out_err;
*ceph_opts = pctx.copts;
*opts = pctx.opts; *opts = pctx.opts;
*rbd_spec = pctx.spec; *rbd_spec = pctx.spec;
kfree(options);
return 0; return 0;
out_mem: out_mem:
ret = -ENOMEM; ret = -ENOMEM;
out_err: out_err:
kfree(pctx.opts); kfree(pctx.opts);
ceph_destroy_options(pctx.copts);
rbd_spec_put(pctx.spec); rbd_spec_put(pctx.spec);
kfree(options); kfree(options);
return ret; return ret;
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <linux/ceph/ceph_debug.h> #include <linux/ceph/ceph_debug.h>
#include <linux/fs_context.h>
#include "super.h" #include "super.h"
#include "cache.h" #include "cache.h"
...@@ -49,7 +50,7 @@ void ceph_fscache_unregister(void) ...@@ -49,7 +50,7 @@ void ceph_fscache_unregister(void)
fscache_unregister_netfs(&ceph_cache_netfs); fscache_unregister_netfs(&ceph_cache_netfs);
} }
int ceph_fscache_register_fs(struct ceph_fs_client* fsc) int ceph_fscache_register_fs(struct ceph_fs_client* fsc, struct fs_context *fc)
{ {
const struct ceph_fsid *fsid = &fsc->client->fsid; const struct ceph_fsid *fsid = &fsc->client->fsid;
const char *fscache_uniq = fsc->mount_options->fscache_uniq; const char *fscache_uniq = fsc->mount_options->fscache_uniq;
...@@ -66,8 +67,8 @@ int ceph_fscache_register_fs(struct ceph_fs_client* fsc) ...@@ -66,8 +67,8 @@ int ceph_fscache_register_fs(struct ceph_fs_client* fsc)
if (uniq_len && memcmp(ent->uniquifier, fscache_uniq, uniq_len)) if (uniq_len && memcmp(ent->uniquifier, fscache_uniq, uniq_len))
continue; continue;
pr_err("fscache cookie already registered for fsid %pU\n", fsid); errorf(fc, "ceph: fscache cookie already registered for fsid %pU, use fsc=<uniquifier> option",
pr_err(" use fsc=%%s mount option to specify a uniquifier\n"); fsid);
err = -EBUSY; err = -EBUSY;
goto out_unlock; goto out_unlock;
} }
...@@ -95,7 +96,7 @@ int ceph_fscache_register_fs(struct ceph_fs_client* fsc) ...@@ -95,7 +96,7 @@ int ceph_fscache_register_fs(struct ceph_fs_client* fsc)
list_add_tail(&ent->list, &ceph_fscache_list); list_add_tail(&ent->list, &ceph_fscache_list);
} else { } else {
kfree(ent); kfree(ent);
pr_err("unable to register fscache cookie for fsid %pU\n", errorf(fc, "ceph: unable to register fscache cookie for fsid %pU",
fsid); fsid);
/* all other fs ignore this error */ /* all other fs ignore this error */
} }
......
...@@ -16,7 +16,7 @@ extern struct fscache_netfs ceph_cache_netfs; ...@@ -16,7 +16,7 @@ extern struct fscache_netfs ceph_cache_netfs;
int ceph_fscache_register(void); int ceph_fscache_register(void);
void ceph_fscache_unregister(void); void ceph_fscache_unregister(void);
int ceph_fscache_register_fs(struct ceph_fs_client* fsc); 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_unregister_fs(struct ceph_fs_client* fsc);
void ceph_fscache_register_inode_cookie(struct inode *inode); void ceph_fscache_register_inode_cookie(struct inode *inode);
...@@ -88,7 +88,8 @@ static inline void ceph_fscache_unregister(void) ...@@ -88,7 +88,8 @@ static inline void ceph_fscache_unregister(void)
{ {
} }
static inline int ceph_fscache_register_fs(struct ceph_fs_client* fsc) static inline int ceph_fscache_register_fs(struct ceph_fs_client* fsc,
struct fs_context *fc)
{ {
return 0; return 0;
} }
......
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
#include <linux/in6.h> #include <linux/in6.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/mount.h> #include <linux/mount.h>
#include <linux/parser.h> #include <linux/fs_context.h>
#include <linux/fs_parser.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/seq_file.h> #include <linux/seq_file.h>
#include <linux/slab.h> #include <linux/slab.h>
...@@ -138,280 +139,308 @@ enum { ...@@ -138,280 +139,308 @@ enum {
Opt_readdir_max_entries, Opt_readdir_max_entries,
Opt_readdir_max_bytes, Opt_readdir_max_bytes,
Opt_congestion_kb, Opt_congestion_kb,
Opt_last_int,
/* int args above */ /* int args above */
Opt_snapdirname, Opt_snapdirname,
Opt_mds_namespace, Opt_mds_namespace,
Opt_fscache_uniq,
Opt_recover_session, Opt_recover_session,
Opt_last_string, Opt_source,
/* string args above */ /* string args above */
Opt_dirstat, Opt_dirstat,
Opt_nodirstat,
Opt_rbytes, Opt_rbytes,
Opt_norbytes,
Opt_asyncreaddir, Opt_asyncreaddir,
Opt_noasyncreaddir,
Opt_dcache, Opt_dcache,
Opt_nodcache,
Opt_ino32, Opt_ino32,
Opt_noino32,
Opt_fscache, Opt_fscache,
Opt_nofscache,
Opt_poolperm, Opt_poolperm,
Opt_nopoolperm,
Opt_require_active_mds, Opt_require_active_mds,
Opt_norequire_active_mds,
#ifdef CONFIG_CEPH_FS_POSIX_ACL
Opt_acl, Opt_acl,
#endif
Opt_noacl,
Opt_quotadf, Opt_quotadf,
Opt_noquotadf,
Opt_copyfrom, Opt_copyfrom,
Opt_nocopyfrom,
}; };
static match_table_t fsopt_tokens = { enum ceph_recover_session_mode {
{Opt_wsize, "wsize=%d"}, ceph_recover_session_no,
{Opt_rsize, "rsize=%d"}, ceph_recover_session_clean
{Opt_rasize, "rasize=%d"}, };
{Opt_caps_wanted_delay_min, "caps_wanted_delay_min=%d"},
{Opt_caps_wanted_delay_max, "caps_wanted_delay_max=%d"}, static const struct fs_parameter_enum ceph_mount_param_enums[] = {
{Opt_caps_max, "caps_max=%d"}, { Opt_recover_session, "no", ceph_recover_session_no },
{Opt_readdir_max_entries, "readdir_max_entries=%d"}, { Opt_recover_session, "clean", ceph_recover_session_clean },
{Opt_readdir_max_bytes, "readdir_max_bytes=%d"}, {}
{Opt_congestion_kb, "write_congestion_kb=%d"}, };
/* int args above */
{Opt_snapdirname, "snapdirname=%s"}, static const struct fs_parameter_spec ceph_mount_param_specs[] = {
{Opt_mds_namespace, "mds_namespace=%s"}, fsparam_flag_no ("acl", Opt_acl),
{Opt_recover_session, "recover_session=%s"}, fsparam_flag_no ("asyncreaddir", Opt_asyncreaddir),
{Opt_fscache_uniq, "fsc=%s"}, fsparam_u32 ("caps_max", Opt_caps_max),
/* string args above */ fsparam_u32 ("caps_wanted_delay_max", Opt_caps_wanted_delay_max),
{Opt_dirstat, "dirstat"}, fsparam_u32 ("caps_wanted_delay_min", Opt_caps_wanted_delay_min),
{Opt_nodirstat, "nodirstat"}, fsparam_s32 ("write_congestion_kb", Opt_congestion_kb),
{Opt_rbytes, "rbytes"}, fsparam_flag_no ("copyfrom", Opt_copyfrom),
{Opt_norbytes, "norbytes"}, fsparam_flag_no ("dcache", Opt_dcache),
{Opt_asyncreaddir, "asyncreaddir"}, fsparam_flag_no ("dirstat", Opt_dirstat),
{Opt_noasyncreaddir, "noasyncreaddir"}, __fsparam (fs_param_is_string, "fsc", Opt_fscache,
{Opt_dcache, "dcache"}, fs_param_neg_with_no | fs_param_v_optional),
{Opt_nodcache, "nodcache"}, fsparam_flag_no ("ino32", Opt_ino32),
{Opt_ino32, "ino32"}, fsparam_string ("mds_namespace", Opt_mds_namespace),
{Opt_noino32, "noino32"}, fsparam_flag_no ("poolperm", Opt_poolperm),
{Opt_fscache, "fsc"}, fsparam_flag_no ("quotadf", Opt_quotadf),
{Opt_nofscache, "nofsc"}, fsparam_u32 ("rasize", Opt_rasize),
{Opt_poolperm, "poolperm"}, fsparam_flag_no ("rbytes", Opt_rbytes),
{Opt_nopoolperm, "nopoolperm"}, fsparam_s32 ("readdir_max_bytes", Opt_readdir_max_bytes),
{Opt_require_active_mds, "require_active_mds"}, fsparam_s32 ("readdir_max_entries", Opt_readdir_max_entries),
{Opt_norequire_active_mds, "norequire_active_mds"}, fsparam_enum ("recover_session", Opt_recover_session),
#ifdef CONFIG_CEPH_FS_POSIX_ACL fsparam_flag_no ("require_active_mds", Opt_require_active_mds),
{Opt_acl, "acl"}, fsparam_u32 ("rsize", Opt_rsize),
#endif fsparam_string ("snapdirname", Opt_snapdirname),
{Opt_noacl, "noacl"}, fsparam_string ("source", Opt_source),
{Opt_quotadf, "quotadf"}, fsparam_u32 ("wsize", Opt_wsize),
{Opt_noquotadf, "noquotadf"}, {}
{Opt_copyfrom, "copyfrom"}, };
{Opt_nocopyfrom, "nocopyfrom"},
{-1, NULL} static const struct fs_parameter_description ceph_mount_parameters = {
.name = "ceph",
.specs = ceph_mount_param_specs,
.enums = ceph_mount_param_enums,
}; };
static int parse_fsopt_token(char *c, void *private) struct ceph_parse_opts_ctx {
struct ceph_options *copts;
struct ceph_mount_options *opts;
};
/*
* Parse the source parameter. Distinguish the server list from the path.
* Internally we do not include the leading '/' in the path.
*
* The source will look like:
* <server_spec>[,<server_spec>...]:[<path>]
* where
* <server_spec> is <ip>[:<port>]
* <path> is optional, but if present must begin with '/'
*/
static int ceph_parse_source(struct fs_parameter *param, struct fs_context *fc)
{ {
struct ceph_mount_options *fsopt = private; struct ceph_parse_opts_ctx *pctx = fc->fs_private;
substring_t argstr[MAX_OPT_ARGS]; struct ceph_mount_options *fsopt = pctx->opts;
int token, intval, ret; char *dev_name = param->string, *dev_name_end;
int ret;
token = match_token((char *)c, fsopt_tokens, argstr); dout("%s '%s'\n", __func__, dev_name);
if (token < 0) if (!dev_name || !*dev_name)
return -EINVAL; return invalf(fc, "ceph: Empty source");
if (token < Opt_last_int) { dev_name_end = strchr(dev_name, '/');
ret = match_int(&argstr[0], &intval); if (dev_name_end) {
if (ret < 0) { if (strlen(dev_name_end) > 1) {
pr_err("bad option arg (not int) at '%s'\n", c); kfree(fsopt->server_path);
return ret; fsopt->server_path = kstrdup(dev_name_end, GFP_KERNEL);
if (!fsopt->server_path)
return -ENOMEM;
} }
dout("got int token %d val %d\n", token, intval);
} else if (token > Opt_last_int && token < Opt_last_string) {
dout("got string token %d val %s\n", token,
argstr[0].from);
} else { } else {
dout("got token %d\n", token); dev_name_end = dev_name + strlen(dev_name);
} }
dev_name_end--; /* back up to ':' separator */
if (dev_name_end < dev_name || *dev_name_end != ':')
return invalf(fc, "ceph: No path or : separator in source");
dout("device name '%.*s'\n", (int)(dev_name_end - dev_name), dev_name);
if (fsopt->server_path)
dout("server path '%s'\n", fsopt->server_path);
ret = ceph_parse_mon_ips(param->string, dev_name_end - dev_name,
pctx->copts, fc);
if (ret)
return ret;
fc->source = param->string;
param->string = NULL;
return 0;
}
static int ceph_parse_mount_param(struct fs_context *fc,
struct fs_parameter *param)
{
struct ceph_parse_opts_ctx *pctx = fc->fs_private;
struct ceph_mount_options *fsopt = pctx->opts;
struct fs_parse_result result;
unsigned int mode;
int token, ret;
ret = ceph_parse_param(param, pctx->copts, fc);
if (ret != -ENOPARAM)
return ret;
token = fs_parse(fc, &ceph_mount_parameters, param, &result);
dout("%s fs_parse '%s' token %d\n", __func__, param->key, token);
if (token < 0)
return token;
switch (token) { switch (token) {
case Opt_snapdirname: case Opt_snapdirname:
kfree(fsopt->snapdir_name); kfree(fsopt->snapdir_name);
fsopt->snapdir_name = kstrndup(argstr[0].from, fsopt->snapdir_name = param->string;
argstr[0].to-argstr[0].from, param->string = NULL;
GFP_KERNEL);
if (!fsopt->snapdir_name)
return -ENOMEM;
break; break;
case Opt_mds_namespace: case Opt_mds_namespace:
kfree(fsopt->mds_namespace); kfree(fsopt->mds_namespace);
fsopt->mds_namespace = kstrndup(argstr[0].from, fsopt->mds_namespace = param->string;
argstr[0].to-argstr[0].from, param->string = NULL;
GFP_KERNEL);
if (!fsopt->mds_namespace)
return -ENOMEM;
break; break;
case Opt_recover_session: case Opt_recover_session:
if (!strncmp(argstr[0].from, "no", mode = result.uint_32;
argstr[0].to - argstr[0].from)) { if (mode == ceph_recover_session_no)
fsopt->flags &= ~CEPH_MOUNT_OPT_CLEANRECOVER; fsopt->flags &= ~CEPH_MOUNT_OPT_CLEANRECOVER;
} else if (!strncmp(argstr[0].from, "clean", else if (mode == ceph_recover_session_clean)
argstr[0].to - argstr[0].from)) {
fsopt->flags |= CEPH_MOUNT_OPT_CLEANRECOVER; fsopt->flags |= CEPH_MOUNT_OPT_CLEANRECOVER;
} else { else
return -EINVAL; BUG();
}
break;
case Opt_fscache_uniq:
#ifdef CONFIG_CEPH_FSCACHE
kfree(fsopt->fscache_uniq);
fsopt->fscache_uniq = kstrndup(argstr[0].from,
argstr[0].to-argstr[0].from,
GFP_KERNEL);
if (!fsopt->fscache_uniq)
return -ENOMEM;
fsopt->flags |= CEPH_MOUNT_OPT_FSCACHE;
break; break;
#else case Opt_source:
pr_err("fscache support is disabled\n"); if (fc->source)
return -EINVAL; return invalf(fc, "ceph: Multiple sources specified");
#endif return ceph_parse_source(param, fc);
case Opt_wsize: case Opt_wsize:
if (intval < (int)PAGE_SIZE || intval > CEPH_MAX_WRITE_SIZE) if (result.uint_32 < PAGE_SIZE ||
return -EINVAL; result.uint_32 > CEPH_MAX_WRITE_SIZE)
fsopt->wsize = ALIGN(intval, PAGE_SIZE); goto out_of_range;
fsopt->wsize = ALIGN(result.uint_32, PAGE_SIZE);
break; break;
case Opt_rsize: case Opt_rsize:
if (intval < (int)PAGE_SIZE || intval > CEPH_MAX_READ_SIZE) if (result.uint_32 < PAGE_SIZE ||
return -EINVAL; result.uint_32 > CEPH_MAX_READ_SIZE)
fsopt->rsize = ALIGN(intval, PAGE_SIZE); goto out_of_range;
fsopt->rsize = ALIGN(result.uint_32, PAGE_SIZE);
break; break;
case Opt_rasize: case Opt_rasize:
if (intval < 0) fsopt->rasize = ALIGN(result.uint_32, PAGE_SIZE);
return -EINVAL;
fsopt->rasize = ALIGN(intval, PAGE_SIZE);
break; break;
case Opt_caps_wanted_delay_min: case Opt_caps_wanted_delay_min:
if (intval < 1) if (result.uint_32 < 1)
return -EINVAL; goto out_of_range;
fsopt->caps_wanted_delay_min = intval; fsopt->caps_wanted_delay_min = result.uint_32;
break; break;
case Opt_caps_wanted_delay_max: case Opt_caps_wanted_delay_max:
if (intval < 1) if (result.uint_32 < 1)
return -EINVAL; goto out_of_range;
fsopt->caps_wanted_delay_max = intval; fsopt->caps_wanted_delay_max = result.uint_32;
break; break;
case Opt_caps_max: case Opt_caps_max:
if (intval < 0) fsopt->caps_max = result.uint_32;
return -EINVAL;
fsopt->caps_max = intval;
break; break;
case Opt_readdir_max_entries: case Opt_readdir_max_entries:
if (intval < 1) if (result.uint_32 < 1)
return -EINVAL; goto out_of_range;
fsopt->max_readdir = intval; fsopt->max_readdir = result.uint_32;
break; break;
case Opt_readdir_max_bytes: case Opt_readdir_max_bytes:
if (intval < (int)PAGE_SIZE && intval != 0) if (result.uint_32 < PAGE_SIZE && result.uint_32 != 0)
return -EINVAL; goto out_of_range;
fsopt->max_readdir_bytes = intval; fsopt->max_readdir_bytes = result.uint_32;
break; break;
case Opt_congestion_kb: case Opt_congestion_kb:
if (intval < 1024) /* at least 1M */ if (result.uint_32 < 1024) /* at least 1M */
return -EINVAL; goto out_of_range;
fsopt->congestion_kb = intval; fsopt->congestion_kb = result.uint_32;
break; break;
case Opt_dirstat: case Opt_dirstat:
fsopt->flags |= CEPH_MOUNT_OPT_DIRSTAT; if (!result.negated)
break; fsopt->flags |= CEPH_MOUNT_OPT_DIRSTAT;
case Opt_nodirstat: else
fsopt->flags &= ~CEPH_MOUNT_OPT_DIRSTAT; fsopt->flags &= ~CEPH_MOUNT_OPT_DIRSTAT;
break; break;
case Opt_rbytes: case Opt_rbytes:
fsopt->flags |= CEPH_MOUNT_OPT_RBYTES; if (!result.negated)
break; fsopt->flags |= CEPH_MOUNT_OPT_RBYTES;
case Opt_norbytes: else
fsopt->flags &= ~CEPH_MOUNT_OPT_RBYTES; fsopt->flags &= ~CEPH_MOUNT_OPT_RBYTES;
break; break;
case Opt_asyncreaddir: case Opt_asyncreaddir:
fsopt->flags &= ~CEPH_MOUNT_OPT_NOASYNCREADDIR; if (!result.negated)
break; fsopt->flags &= ~CEPH_MOUNT_OPT_NOASYNCREADDIR;
case Opt_noasyncreaddir: else
fsopt->flags |= CEPH_MOUNT_OPT_NOASYNCREADDIR; fsopt->flags |= CEPH_MOUNT_OPT_NOASYNCREADDIR;
break; break;
case Opt_dcache: case Opt_dcache:
fsopt->flags |= CEPH_MOUNT_OPT_DCACHE; if (!result.negated)
break; fsopt->flags |= CEPH_MOUNT_OPT_DCACHE;
case Opt_nodcache: else
fsopt->flags &= ~CEPH_MOUNT_OPT_DCACHE; fsopt->flags &= ~CEPH_MOUNT_OPT_DCACHE;
break; break;
case Opt_ino32: case Opt_ino32:
fsopt->flags |= CEPH_MOUNT_OPT_INO32; if (!result.negated)
break; fsopt->flags |= CEPH_MOUNT_OPT_INO32;
case Opt_noino32: else
fsopt->flags &= ~CEPH_MOUNT_OPT_INO32; fsopt->flags &= ~CEPH_MOUNT_OPT_INO32;
break; break;
case Opt_fscache: case Opt_fscache:
#ifdef CONFIG_CEPH_FSCACHE #ifdef CONFIG_CEPH_FSCACHE
fsopt->flags |= CEPH_MOUNT_OPT_FSCACHE;
kfree(fsopt->fscache_uniq); kfree(fsopt->fscache_uniq);
fsopt->fscache_uniq = NULL; fsopt->fscache_uniq = NULL;
if (result.negated) {
fsopt->flags &= ~CEPH_MOUNT_OPT_FSCACHE;
} else {
fsopt->flags |= CEPH_MOUNT_OPT_FSCACHE;
fsopt->fscache_uniq = param->string;
param->string = NULL;
}
break; break;
#else #else
pr_err("fscache support is disabled\n"); return invalf(fc, "ceph: fscache support is disabled");
return -EINVAL;
#endif #endif
case Opt_nofscache:
fsopt->flags &= ~CEPH_MOUNT_OPT_FSCACHE;
kfree(fsopt->fscache_uniq);
fsopt->fscache_uniq = NULL;
break;
case Opt_poolperm: case Opt_poolperm:
fsopt->flags &= ~CEPH_MOUNT_OPT_NOPOOLPERM; if (!result.negated)
break; fsopt->flags &= ~CEPH_MOUNT_OPT_NOPOOLPERM;
case Opt_nopoolperm: else
fsopt->flags |= CEPH_MOUNT_OPT_NOPOOLPERM; fsopt->flags |= CEPH_MOUNT_OPT_NOPOOLPERM;
break; break;
case Opt_require_active_mds: case Opt_require_active_mds:
fsopt->flags &= ~CEPH_MOUNT_OPT_MOUNTWAIT; if (!result.negated)
break; fsopt->flags &= ~CEPH_MOUNT_OPT_MOUNTWAIT;
case Opt_norequire_active_mds: else
fsopt->flags |= CEPH_MOUNT_OPT_MOUNTWAIT; fsopt->flags |= CEPH_MOUNT_OPT_MOUNTWAIT;
break; break;
case Opt_quotadf: case Opt_quotadf:
fsopt->flags &= ~CEPH_MOUNT_OPT_NOQUOTADF; if (!result.negated)
break; fsopt->flags &= ~CEPH_MOUNT_OPT_NOQUOTADF;
case Opt_noquotadf: else
fsopt->flags |= CEPH_MOUNT_OPT_NOQUOTADF; fsopt->flags |= CEPH_MOUNT_OPT_NOQUOTADF;
break; break;
case Opt_copyfrom: case Opt_copyfrom:
fsopt->flags &= ~CEPH_MOUNT_OPT_NOCOPYFROM; if (!result.negated)
break; fsopt->flags &= ~CEPH_MOUNT_OPT_NOCOPYFROM;
case Opt_nocopyfrom: else
fsopt->flags |= CEPH_MOUNT_OPT_NOCOPYFROM; fsopt->flags |= CEPH_MOUNT_OPT_NOCOPYFROM;
break; break;
#ifdef CONFIG_CEPH_FS_POSIX_ACL
case Opt_acl: case Opt_acl:
fsopt->sb_flags |= SB_POSIXACL; if (!result.negated) {
break; #ifdef CONFIG_CEPH_FS_POSIX_ACL
fc->sb_flags |= SB_POSIXACL;
#else
return invalf(fc, "ceph: POSIX ACL support is disabled");
#endif #endif
case Opt_noacl: } else {
fsopt->sb_flags &= ~SB_POSIXACL; fc->sb_flags &= ~SB_POSIXACL;
}
break; break;
default: default:
BUG_ON(token); BUG();
} }
return 0; return 0;
out_of_range:
return invalf(fc, "ceph: %s out of range", param->key);
} }
static void destroy_mount_options(struct ceph_mount_options *args) static void destroy_mount_options(struct ceph_mount_options *args)
{ {
dout("destroy_mount_options %p\n", args); dout("destroy_mount_options %p\n", args);
if (!args)
return;
kfree(args->snapdir_name); kfree(args->snapdir_name);
kfree(args->mds_namespace); kfree(args->mds_namespace);
kfree(args->server_path); kfree(args->server_path);
...@@ -459,91 +488,6 @@ static int compare_mount_options(struct ceph_mount_options *new_fsopt, ...@@ -459,91 +488,6 @@ static int compare_mount_options(struct ceph_mount_options *new_fsopt,
return ceph_compare_options(new_opt, fsc->client); return ceph_compare_options(new_opt, fsc->client);
} }
static int parse_mount_options(struct ceph_mount_options **pfsopt,
struct ceph_options **popt,
int flags, char *options,
const char *dev_name)
{
struct ceph_mount_options *fsopt;
const char *dev_name_end;
int err;
if (!dev_name || !*dev_name)
return -EINVAL;
fsopt = kzalloc(sizeof(*fsopt), GFP_KERNEL);
if (!fsopt)
return -ENOMEM;
dout("parse_mount_options %p, dev_name '%s'\n", fsopt, dev_name);
fsopt->sb_flags = flags;
fsopt->flags = CEPH_MOUNT_OPT_DEFAULT;
fsopt->wsize = CEPH_MAX_WRITE_SIZE;
fsopt->rsize = CEPH_MAX_READ_SIZE;
fsopt->rasize = CEPH_RASIZE_DEFAULT;
fsopt->snapdir_name = kstrdup(CEPH_SNAPDIRNAME_DEFAULT, GFP_KERNEL);
if (!fsopt->snapdir_name) {
err = -ENOMEM;
goto out;
}
fsopt->caps_wanted_delay_min = CEPH_CAPS_WANTED_DELAY_MIN_DEFAULT;
fsopt->caps_wanted_delay_max = CEPH_CAPS_WANTED_DELAY_MAX_DEFAULT;
fsopt->max_readdir = CEPH_MAX_READDIR_DEFAULT;
fsopt->max_readdir_bytes = CEPH_MAX_READDIR_BYTES_DEFAULT;
fsopt->congestion_kb = default_congestion_kb();
/*
* Distinguish the server list from the path in "dev_name".
* Internally we do not include the leading '/' in the path.
*
* "dev_name" will look like:
* <server_spec>[,<server_spec>...]:[<path>]
* where
* <server_spec> is <ip>[:<port>]
* <path> is optional, but if present must begin with '/'
*/
dev_name_end = strchr(dev_name, '/');
if (dev_name_end) {
if (strlen(dev_name_end) > 1) {
fsopt->server_path = kstrdup(dev_name_end, GFP_KERNEL);
if (!fsopt->server_path) {
err = -ENOMEM;
goto out;
}
}
} else {
dev_name_end = dev_name + strlen(dev_name);
}
err = -EINVAL;
dev_name_end--; /* back up to ':' separator */
if (dev_name_end < dev_name || *dev_name_end != ':') {
pr_err("device name is missing path (no : separator in %s)\n",
dev_name);
goto out;
}
dout("device name '%.*s'\n", (int)(dev_name_end - dev_name), dev_name);
if (fsopt->server_path)
dout("server path '%s'\n", fsopt->server_path);
*popt = ceph_parse_options(options, dev_name, dev_name_end,
parse_fsopt_token, (void *)fsopt);
if (IS_ERR(*popt)) {
err = PTR_ERR(*popt);
goto out;
}
/* success */
*pfsopt = fsopt;
return 0;
out:
destroy_mount_options(fsopt);
return err;
}
/** /**
* ceph_show_options - Show mount options in /proc/mounts * ceph_show_options - Show mount options in /proc/mounts
* @m: seq_file to write to * @m: seq_file to write to
...@@ -587,7 +531,7 @@ static int ceph_show_options(struct seq_file *m, struct dentry *root) ...@@ -587,7 +531,7 @@ static int ceph_show_options(struct seq_file *m, struct dentry *root)
seq_puts(m, ",noquotadf"); seq_puts(m, ",noquotadf");
#ifdef CONFIG_CEPH_FS_POSIX_ACL #ifdef CONFIG_CEPH_FS_POSIX_ACL
if (fsopt->sb_flags & SB_POSIXACL) if (root->d_sb->s_flags & SB_POSIXACL)
seq_puts(m, ",acl"); seq_puts(m, ",acl");
else else
seq_puts(m, ",noacl"); seq_puts(m, ",noacl");
...@@ -860,12 +804,6 @@ static void ceph_umount_begin(struct super_block *sb) ...@@ -860,12 +804,6 @@ static void ceph_umount_begin(struct super_block *sb)
fsc->filp_gen++; // invalidate open files fsc->filp_gen++; // invalidate open files
} }
static int ceph_remount(struct super_block *sb, int *flags, char *data)
{
sync_filesystem(sb);
return 0;
}
static const struct super_operations ceph_super_ops = { static const struct super_operations ceph_super_ops = {
.alloc_inode = ceph_alloc_inode, .alloc_inode = ceph_alloc_inode,
.free_inode = ceph_free_inode, .free_inode = ceph_free_inode,
...@@ -874,7 +812,6 @@ static const struct super_operations ceph_super_ops = { ...@@ -874,7 +812,6 @@ static const struct super_operations ceph_super_ops = {
.evict_inode = ceph_evict_inode, .evict_inode = ceph_evict_inode,
.sync_fs = ceph_sync_fs, .sync_fs = ceph_sync_fs,
.put_super = ceph_put_super, .put_super = ceph_put_super,
.remount_fs = ceph_remount,
.show_options = ceph_show_options, .show_options = ceph_show_options,
.statfs = ceph_statfs, .statfs = ceph_statfs,
.umount_begin = ceph_umount_begin, .umount_begin = ceph_umount_begin,
...@@ -935,7 +872,8 @@ static struct dentry *open_root_dentry(struct ceph_fs_client *fsc, ...@@ -935,7 +872,8 @@ static struct dentry *open_root_dentry(struct ceph_fs_client *fsc,
/* /*
* mount: join the ceph cluster, and open root directory. * mount: join the ceph cluster, and open root directory.
*/ */
static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc) static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc,
struct fs_context *fc)
{ {
int err; int err;
unsigned long started = jiffies; /* note the start time */ unsigned long started = jiffies; /* note the start time */
...@@ -952,7 +890,7 @@ static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc) ...@@ -952,7 +890,7 @@ static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc)
/* setup fscache */ /* setup fscache */
if (fsc->mount_options->flags & CEPH_MOUNT_OPT_FSCACHE) { if (fsc->mount_options->flags & CEPH_MOUNT_OPT_FSCACHE) {
err = ceph_fscache_register_fs(fsc); err = ceph_fscache_register_fs(fsc, fc);
if (err < 0) if (err < 0)
goto out; goto out;
} }
...@@ -987,18 +925,16 @@ static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc) ...@@ -987,18 +925,16 @@ static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc)
return ERR_PTR(err); return ERR_PTR(err);
} }
static int ceph_set_super(struct super_block *s, void *data) static int ceph_set_super(struct super_block *s, struct fs_context *fc)
{ {
struct ceph_fs_client *fsc = data; struct ceph_fs_client *fsc = s->s_fs_info;
int ret; int ret;
dout("set_super %p data %p\n", s, data); dout("set_super %p\n", s);
s->s_flags = fsc->mount_options->sb_flags;
s->s_maxbytes = MAX_LFS_FILESIZE; s->s_maxbytes = MAX_LFS_FILESIZE;
s->s_xattr = ceph_xattr_handlers; s->s_xattr = ceph_xattr_handlers;
s->s_fs_info = fsc;
fsc->sb = s; fsc->sb = s;
fsc->max_file_size = 1ULL << 40; /* temp value until we get mdsmap */ fsc->max_file_size = 1ULL << 40; /* temp value until we get mdsmap */
...@@ -1010,24 +946,18 @@ static int ceph_set_super(struct super_block *s, void *data) ...@@ -1010,24 +946,18 @@ static int ceph_set_super(struct super_block *s, void *data)
s->s_time_min = 0; s->s_time_min = 0;
s->s_time_max = U32_MAX; s->s_time_max = U32_MAX;
ret = set_anon_super(s, NULL); /* what is that second arg for? */ ret = set_anon_super_fc(s, fc);
if (ret != 0) if (ret != 0)
goto fail; fsc->sb = NULL;
return ret;
fail:
s->s_fs_info = NULL;
fsc->sb = NULL;
return ret; return ret;
} }
/* /*
* share superblock if same fs AND options * share superblock if same fs AND options
*/ */
static int ceph_compare_super(struct super_block *sb, void *data) static int ceph_compare_super(struct super_block *sb, struct fs_context *fc)
{ {
struct ceph_fs_client *new = data; struct ceph_fs_client *new = fc->s_fs_info;
struct ceph_mount_options *fsopt = new->mount_options; struct ceph_mount_options *fsopt = new->mount_options;
struct ceph_options *opt = new->client->options; struct ceph_options *opt = new->client->options;
struct ceph_fs_client *other = ceph_sb_to_client(sb); struct ceph_fs_client *other = ceph_sb_to_client(sb);
...@@ -1043,7 +973,7 @@ static int ceph_compare_super(struct super_block *sb, void *data) ...@@ -1043,7 +973,7 @@ static int ceph_compare_super(struct super_block *sb, void *data)
dout("fsid doesn't match\n"); dout("fsid doesn't match\n");
return 0; return 0;
} }
if (fsopt->sb_flags != other->mount_options->sb_flags) { if (fc->sb_flags != (sb->s_flags & ~SB_BORN)) {
dout("flags differ\n"); dout("flags differ\n");
return 0; return 0;
} }
...@@ -1073,46 +1003,46 @@ static int ceph_setup_bdi(struct super_block *sb, struct ceph_fs_client *fsc) ...@@ -1073,46 +1003,46 @@ static int ceph_setup_bdi(struct super_block *sb, struct ceph_fs_client *fsc)
return 0; return 0;
} }
static struct dentry *ceph_mount(struct file_system_type *fs_type, static int ceph_get_tree(struct fs_context *fc)
int flags, const char *dev_name, void *data)
{ {
struct ceph_parse_opts_ctx *pctx = fc->fs_private;
struct super_block *sb; struct super_block *sb;
struct ceph_fs_client *fsc; struct ceph_fs_client *fsc;
struct dentry *res; struct dentry *res;
int (*compare_super)(struct super_block *, struct fs_context *) =
ceph_compare_super;
int err; int err;
int (*compare_super)(struct super_block *, void *) = ceph_compare_super;
struct ceph_mount_options *fsopt = NULL;
struct ceph_options *opt = NULL;
dout("ceph_mount\n"); dout("ceph_get_tree\n");
if (!fc->source)
return invalf(fc, "ceph: No source");
#ifdef CONFIG_CEPH_FS_POSIX_ACL #ifdef CONFIG_CEPH_FS_POSIX_ACL
flags |= SB_POSIXACL; fc->sb_flags |= SB_POSIXACL;
#endif #endif
err = parse_mount_options(&fsopt, &opt, flags, data, dev_name);
if (err < 0) {
res = ERR_PTR(err);
goto out_final;
}
/* create client (which we may/may not use) */ /* create client (which we may/may not use) */
fsc = create_fs_client(fsopt, opt); fsc = create_fs_client(pctx->opts, pctx->copts);
pctx->opts = NULL;
pctx->copts = NULL;
if (IS_ERR(fsc)) { if (IS_ERR(fsc)) {
res = ERR_CAST(fsc); err = PTR_ERR(fsc);
goto out_final; goto out_final;
} }
err = ceph_mdsc_init(fsc); err = ceph_mdsc_init(fsc);
if (err < 0) { if (err < 0)
res = ERR_PTR(err);
goto out; goto out;
}
if (ceph_test_opt(fsc->client, NOSHARE)) if (ceph_test_opt(fsc->client, NOSHARE))
compare_super = NULL; compare_super = NULL;
sb = sget(fs_type, compare_super, ceph_set_super, flags, fsc);
fc->s_fs_info = fsc;
sb = sget_fc(fc, compare_super, ceph_set_super);
fc->s_fs_info = NULL;
if (IS_ERR(sb)) { if (IS_ERR(sb)) {
res = ERR_CAST(sb); err = PTR_ERR(sb);
goto out; goto out;
} }
...@@ -1123,18 +1053,19 @@ static struct dentry *ceph_mount(struct file_system_type *fs_type, ...@@ -1123,18 +1053,19 @@ static struct dentry *ceph_mount(struct file_system_type *fs_type,
} else { } else {
dout("get_sb using new client %p\n", fsc); dout("get_sb using new client %p\n", fsc);
err = ceph_setup_bdi(sb, fsc); err = ceph_setup_bdi(sb, fsc);
if (err < 0) { if (err < 0)
res = ERR_PTR(err);
goto out_splat; goto out_splat;
}
} }
res = ceph_real_mount(fsc); res = ceph_real_mount(fsc, fc);
if (IS_ERR(res)) if (IS_ERR(res)) {
err = PTR_ERR(res);
goto out_splat; goto out_splat;
}
dout("root %p inode %p ino %llx.%llx\n", res, dout("root %p inode %p ino %llx.%llx\n", res,
d_inode(res), ceph_vinop(d_inode(res))); d_inode(res), ceph_vinop(d_inode(res)));
return res; fc->root = fsc->sb->s_root;
return 0;
out_splat: out_splat:
ceph_mdsc_close_sessions(fsc->mdsc); ceph_mdsc_close_sessions(fsc->mdsc);
...@@ -1144,8 +1075,79 @@ static struct dentry *ceph_mount(struct file_system_type *fs_type, ...@@ -1144,8 +1075,79 @@ static struct dentry *ceph_mount(struct file_system_type *fs_type,
out: out:
destroy_fs_client(fsc); destroy_fs_client(fsc);
out_final: out_final:
dout("ceph_mount fail %ld\n", PTR_ERR(res)); dout("ceph_get_tree fail %d\n", err);
return res; return err;
}
static void ceph_free_fc(struct fs_context *fc)
{
struct ceph_parse_opts_ctx *pctx = fc->fs_private;
if (pctx) {
destroy_mount_options(pctx->opts);
ceph_destroy_options(pctx->copts);
kfree(pctx);
}
}
static int ceph_reconfigure_fc(struct fs_context *fc)
{
sync_filesystem(fc->root->d_sb);
return 0;
}
static const struct fs_context_operations ceph_context_ops = {
.free = ceph_free_fc,
.parse_param = ceph_parse_mount_param,
.get_tree = ceph_get_tree,
.reconfigure = ceph_reconfigure_fc,
};
/*
* Set up the filesystem mount context.
*/
static int ceph_init_fs_context(struct fs_context *fc)
{
struct ceph_parse_opts_ctx *pctx;
struct ceph_mount_options *fsopt;
pctx = kzalloc(sizeof(*pctx), GFP_KERNEL);
if (!pctx)
return -ENOMEM;
pctx->copts = ceph_alloc_options();
if (!pctx->copts)
goto nomem;
pctx->opts = kzalloc(sizeof(*pctx->opts), GFP_KERNEL);
if (!pctx->opts)
goto nomem;
fsopt = pctx->opts;
fsopt->flags = CEPH_MOUNT_OPT_DEFAULT;
fsopt->wsize = CEPH_MAX_WRITE_SIZE;
fsopt->rsize = CEPH_MAX_READ_SIZE;
fsopt->rasize = CEPH_RASIZE_DEFAULT;
fsopt->snapdir_name = kstrdup(CEPH_SNAPDIRNAME_DEFAULT, GFP_KERNEL);
if (!fsopt->snapdir_name)
goto nomem;
fsopt->caps_wanted_delay_min = CEPH_CAPS_WANTED_DELAY_MIN_DEFAULT;
fsopt->caps_wanted_delay_max = CEPH_CAPS_WANTED_DELAY_MAX_DEFAULT;
fsopt->max_readdir = CEPH_MAX_READDIR_DEFAULT;
fsopt->max_readdir_bytes = CEPH_MAX_READDIR_BYTES_DEFAULT;
fsopt->congestion_kb = default_congestion_kb();
fc->fs_private = pctx;
fc->ops = &ceph_context_ops;
return 0;
nomem:
destroy_mount_options(pctx->opts);
ceph_destroy_options(pctx->copts);
kfree(pctx);
return -ENOMEM;
} }
static void ceph_kill_sb(struct super_block *s) static void ceph_kill_sb(struct super_block *s)
...@@ -1172,7 +1174,7 @@ static void ceph_kill_sb(struct super_block *s) ...@@ -1172,7 +1174,7 @@ static void ceph_kill_sb(struct super_block *s)
static struct file_system_type ceph_fs_type = { static struct file_system_type ceph_fs_type = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.name = "ceph", .name = "ceph",
.mount = ceph_mount, .init_fs_context = ceph_init_fs_context,
.kill_sb = ceph_kill_sb, .kill_sb = ceph_kill_sb,
.fs_flags = FS_RENAME_DOES_D_MOVE, .fs_flags = FS_RENAME_DOES_D_MOVE,
}; };
......
...@@ -74,7 +74,6 @@ ...@@ -74,7 +74,6 @@
struct ceph_mount_options { struct ceph_mount_options {
int flags; int flags;
int sb_flags;
int wsize; /* max write size */ int wsize; /* max write size */
int rsize; /* max read size */ int rsize; /* max read size */
......
...@@ -280,10 +280,12 @@ extern const char *ceph_msg_type_name(int type); ...@@ -280,10 +280,12 @@ extern const char *ceph_msg_type_name(int type);
extern int ceph_check_fsid(struct ceph_client *client, struct ceph_fsid *fsid); extern int ceph_check_fsid(struct ceph_client *client, struct ceph_fsid *fsid);
extern void *ceph_kvmalloc(size_t size, gfp_t flags); extern void *ceph_kvmalloc(size_t size, gfp_t flags);
extern struct ceph_options *ceph_parse_options(char *options, struct fs_parameter;
const char *dev_name, const char *dev_name_end, struct ceph_options *ceph_alloc_options(void);
int (*parse_extra_token)(char *c, void *private), int ceph_parse_mon_ips(const char *buf, size_t len, struct ceph_options *opt,
void *private); struct fs_context *fc);
int ceph_parse_param(struct fs_parameter *param, struct ceph_options *opt,
struct fs_context *fc);
int ceph_print_client_options(struct seq_file *m, struct ceph_client *client, int ceph_print_client_options(struct seq_file *m, struct ceph_client *client,
bool show_all); bool show_all);
extern void ceph_destroy_options(struct ceph_options *opt); extern void ceph_destroy_options(struct ceph_options *opt);
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/mount.h> #include <linux/mount.h>
#include <linux/nsproxy.h> #include <linux/nsproxy.h>
#include <linux/parser.h> #include <linux/fs_parser.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/sched/mm.h> #include <linux/sched/mm.h>
#include <linux/seq_file.h> #include <linux/seq_file.h>
...@@ -254,58 +254,77 @@ enum { ...@@ -254,58 +254,77 @@ enum {
Opt_mount_timeout, Opt_mount_timeout,
Opt_osd_idle_ttl, Opt_osd_idle_ttl,
Opt_osd_request_timeout, Opt_osd_request_timeout,
Opt_last_int,
/* int args above */ /* int args above */
Opt_fsid, Opt_fsid,
Opt_name, Opt_name,
Opt_secret, Opt_secret,
Opt_key, Opt_key,
Opt_ip, Opt_ip,
Opt_last_string,
/* string args above */ /* string args above */
Opt_share, Opt_share,
Opt_noshare,
Opt_crc, Opt_crc,
Opt_nocrc,
Opt_cephx_require_signatures, Opt_cephx_require_signatures,
Opt_nocephx_require_signatures,
Opt_cephx_sign_messages, Opt_cephx_sign_messages,
Opt_nocephx_sign_messages,
Opt_tcp_nodelay, Opt_tcp_nodelay,
Opt_notcp_nodelay,
Opt_abort_on_full, Opt_abort_on_full,
}; };
static match_table_t opt_tokens = { static const struct fs_parameter_spec ceph_param_specs[] = {
{Opt_osdtimeout, "osdtimeout=%d"}, fsparam_flag ("abort_on_full", Opt_abort_on_full),
{Opt_osdkeepalivetimeout, "osdkeepalive=%d"}, fsparam_flag_no ("cephx_require_signatures", Opt_cephx_require_signatures),
{Opt_mount_timeout, "mount_timeout=%d"}, fsparam_flag_no ("cephx_sign_messages", Opt_cephx_sign_messages),
{Opt_osd_idle_ttl, "osd_idle_ttl=%d"}, fsparam_flag_no ("crc", Opt_crc),
{Opt_osd_request_timeout, "osd_request_timeout=%d"}, fsparam_string ("fsid", Opt_fsid),
/* int args above */ fsparam_string ("ip", Opt_ip),
{Opt_fsid, "fsid=%s"}, fsparam_string ("key", Opt_key),
{Opt_name, "name=%s"}, fsparam_u32 ("mount_timeout", Opt_mount_timeout),
{Opt_secret, "secret=%s"}, fsparam_string ("name", Opt_name),
{Opt_key, "key=%s"}, fsparam_u32 ("osd_idle_ttl", Opt_osd_idle_ttl),
{Opt_ip, "ip=%s"}, fsparam_u32 ("osd_request_timeout", Opt_osd_request_timeout),
/* string args above */ fsparam_u32 ("osdkeepalive", Opt_osdkeepalivetimeout),
{Opt_share, "share"}, __fsparam (fs_param_is_s32, "osdtimeout", Opt_osdtimeout,
{Opt_noshare, "noshare"}, fs_param_deprecated),
{Opt_crc, "crc"}, fsparam_string ("secret", Opt_secret),
{Opt_nocrc, "nocrc"}, fsparam_flag_no ("share", Opt_share),
{Opt_cephx_require_signatures, "cephx_require_signatures"}, fsparam_flag_no ("tcp_nodelay", Opt_tcp_nodelay),
{Opt_nocephx_require_signatures, "nocephx_require_signatures"}, {}
{Opt_cephx_sign_messages, "cephx_sign_messages"}, };
{Opt_nocephx_sign_messages, "nocephx_sign_messages"},
{Opt_tcp_nodelay, "tcp_nodelay"}, static const struct fs_parameter_description ceph_parameters = {
{Opt_notcp_nodelay, "notcp_nodelay"}, .name = "libceph",
{Opt_abort_on_full, "abort_on_full"}, .specs = ceph_param_specs,
{-1, NULL}
}; };
struct ceph_options *ceph_alloc_options(void)
{
struct ceph_options *opt;
opt = kzalloc(sizeof(*opt), GFP_KERNEL);
if (!opt)
return NULL;
opt->mon_addr = kcalloc(CEPH_MAX_MON, sizeof(*opt->mon_addr),
GFP_KERNEL);
if (!opt->mon_addr) {
kfree(opt);
return NULL;
}
opt->flags = CEPH_OPT_DEFAULT;
opt->osd_keepalive_timeout = CEPH_OSD_KEEPALIVE_DEFAULT;
opt->mount_timeout = CEPH_MOUNT_TIMEOUT_DEFAULT;
opt->osd_idle_ttl = CEPH_OSD_IDLE_TTL_DEFAULT;
opt->osd_request_timeout = CEPH_OSD_REQUEST_TIMEOUT_DEFAULT;
return opt;
}
EXPORT_SYMBOL(ceph_alloc_options);
void ceph_destroy_options(struct ceph_options *opt) void ceph_destroy_options(struct ceph_options *opt)
{ {
dout("destroy_options %p\n", opt); dout("destroy_options %p\n", opt);
if (!opt)
return;
kfree(opt->name); kfree(opt->name);
if (opt->key) { if (opt->key) {
ceph_crypto_key_destroy(opt->key); ceph_crypto_key_destroy(opt->key);
...@@ -317,7 +336,9 @@ void ceph_destroy_options(struct ceph_options *opt) ...@@ -317,7 +336,9 @@ void ceph_destroy_options(struct ceph_options *opt)
EXPORT_SYMBOL(ceph_destroy_options); EXPORT_SYMBOL(ceph_destroy_options);
/* get secret from key store */ /* get secret from key store */
static int get_secret(struct ceph_crypto_key *dst, const char *name) { static int get_secret(struct ceph_crypto_key *dst, const char *name,
struct fs_context *fc)
{
struct key *ukey; struct key *ukey;
int key_err; int key_err;
int err = 0; int err = 0;
...@@ -330,20 +351,20 @@ static int get_secret(struct ceph_crypto_key *dst, const char *name) { ...@@ -330,20 +351,20 @@ static int get_secret(struct ceph_crypto_key *dst, const char *name) {
key_err = PTR_ERR(ukey); key_err = PTR_ERR(ukey);
switch (key_err) { switch (key_err) {
case -ENOKEY: case -ENOKEY:
pr_warn("ceph: Mount failed due to key not found: %s\n", errorf(fc, "libceph: Failed due to key not found: %s",
name); name);
break; break;
case -EKEYEXPIRED: case -EKEYEXPIRED:
pr_warn("ceph: Mount failed due to expired key: %s\n", errorf(fc, "libceph: Failed due to expired key: %s",
name); name);
break; break;
case -EKEYREVOKED: case -EKEYREVOKED:
pr_warn("ceph: Mount failed due to revoked key: %s\n", errorf(fc, "libceph: Failed due to revoked key: %s",
name); name);
break; break;
default: default:
pr_warn("ceph: Mount failed due to unknown key error %d: %s\n", errorf(fc, "libceph: Failed due to key error %d: %s",
key_err, name); key_err, name);
} }
err = -EPERM; err = -EPERM;
goto out; goto out;
...@@ -361,217 +382,157 @@ static int get_secret(struct ceph_crypto_key *dst, const char *name) { ...@@ -361,217 +382,157 @@ static int get_secret(struct ceph_crypto_key *dst, const char *name) {
return err; return err;
} }
struct ceph_options * int ceph_parse_mon_ips(const char *buf, size_t len, struct ceph_options *opt,
ceph_parse_options(char *options, const char *dev_name, struct fs_context *fc)
const char *dev_name_end,
int (*parse_extra_token)(char *c, void *private),
void *private)
{ {
struct ceph_options *opt; int ret;
const char *c;
int err = -ENOMEM;
substring_t argstr[MAX_OPT_ARGS];
opt = kzalloc(sizeof(*opt), GFP_KERNEL);
if (!opt)
return ERR_PTR(-ENOMEM);
opt->mon_addr = kcalloc(CEPH_MAX_MON, sizeof(*opt->mon_addr),
GFP_KERNEL);
if (!opt->mon_addr)
goto out;
dout("parse_options %p options '%s' dev_name '%s'\n", opt, options,
dev_name);
/* start with defaults */
opt->flags = CEPH_OPT_DEFAULT;
opt->osd_keepalive_timeout = CEPH_OSD_KEEPALIVE_DEFAULT;
opt->mount_timeout = CEPH_MOUNT_TIMEOUT_DEFAULT;
opt->osd_idle_ttl = CEPH_OSD_IDLE_TTL_DEFAULT;
opt->osd_request_timeout = CEPH_OSD_REQUEST_TIMEOUT_DEFAULT;
/* get mon ip(s) */
/* ip1[:port1][,ip2[:port2]...] */ /* ip1[:port1][,ip2[:port2]...] */
err = ceph_parse_ips(dev_name, dev_name_end, opt->mon_addr, ret = ceph_parse_ips(buf, buf + len, opt->mon_addr, CEPH_MAX_MON,
CEPH_MAX_MON, &opt->num_mon); &opt->num_mon);
if (err < 0) if (ret) {
goto out; errorf(fc, "libceph: Failed to parse monitor IPs: %d", ret);
return ret;
}
/* parse mount options */ return 0;
while ((c = strsep(&options, ",")) != NULL) { }
int token, intval; EXPORT_SYMBOL(ceph_parse_mon_ips);
if (!*c)
continue; int ceph_parse_param(struct fs_parameter *param, struct ceph_options *opt,
err = -EINVAL; struct fs_context *fc)
token = match_token((char *)c, opt_tokens, argstr); {
if (token < 0 && parse_extra_token) { struct fs_parse_result result;
/* extra? */ int token, err;
err = parse_extra_token((char *)c, private);
if (err < 0) { token = fs_parse(fc, &ceph_parameters, param, &result);
pr_err("bad option at '%s'\n", c); dout("%s fs_parse '%s' token %d\n", __func__, param->key, token);
goto out; if (token < 0)
} return token;
continue;
} switch (token) {
if (token < Opt_last_int) { case Opt_ip:
err = match_int(&argstr[0], &intval); err = ceph_parse_ips(param->string,
if (err < 0) { param->string + param->size,
pr_err("bad option arg (not int) at '%s'\n", c); &opt->my_addr,
goto out; 1, NULL);
} if (err) {
dout("got int token %d val %d\n", token, intval); errorf(fc, "libceph: Failed to parse ip: %d", err);
} else if (token > Opt_last_int && token < Opt_last_string) { return err;
dout("got string token %d val %s\n", token,
argstr[0].from);
} else {
dout("got token %d\n", token);
} }
switch (token) { opt->flags |= CEPH_OPT_MYIP;
case Opt_ip: break;
err = ceph_parse_ips(argstr[0].from,
argstr[0].to,
&opt->my_addr,
1, NULL);
if (err < 0)
goto out;
opt->flags |= CEPH_OPT_MYIP;
break;
case Opt_fsid: case Opt_fsid:
err = parse_fsid(argstr[0].from, &opt->fsid); err = parse_fsid(param->string, &opt->fsid);
if (err == 0) if (err) {
opt->flags |= CEPH_OPT_FSID; errorf(fc, "libceph: Failed to parse fsid: %d", err);
break; return err;
case Opt_name: }
kfree(opt->name); opt->flags |= CEPH_OPT_FSID;
opt->name = kstrndup(argstr[0].from, break;
argstr[0].to-argstr[0].from, case Opt_name:
GFP_KERNEL); kfree(opt->name);
if (!opt->name) { opt->name = param->string;
err = -ENOMEM; param->string = NULL;
goto out; break;
} case Opt_secret:
break; ceph_crypto_key_destroy(opt->key);
case Opt_secret: kfree(opt->key);
ceph_crypto_key_destroy(opt->key);
kfree(opt->key);
opt->key = kzalloc(sizeof(*opt->key), GFP_KERNEL);
if (!opt->key) {
err = -ENOMEM;
goto out;
}
err = ceph_crypto_key_unarmor(opt->key, argstr[0].from);
if (err < 0)
goto out;
break;
case Opt_key:
ceph_crypto_key_destroy(opt->key);
kfree(opt->key);
opt->key = kzalloc(sizeof(*opt->key), GFP_KERNEL);
if (!opt->key) {
err = -ENOMEM;
goto out;
}
err = get_secret(opt->key, argstr[0].from);
if (err < 0)
goto out;
break;
/* misc */ opt->key = kzalloc(sizeof(*opt->key), GFP_KERNEL);
case Opt_osdtimeout: if (!opt->key)
pr_warn("ignoring deprecated osdtimeout option\n"); return -ENOMEM;
break; err = ceph_crypto_key_unarmor(opt->key, param->string);
case Opt_osdkeepalivetimeout: if (err) {
/* 0 isn't well defined right now, reject it */ errorf(fc, "libceph: Failed to parse secret: %d", err);
if (intval < 1 || intval > INT_MAX / 1000) { return err;
pr_err("osdkeepalive out of range\n"); }
err = -EINVAL; break;
goto out; case Opt_key:
} ceph_crypto_key_destroy(opt->key);
opt->osd_keepalive_timeout = kfree(opt->key);
msecs_to_jiffies(intval * 1000);
break;
case Opt_osd_idle_ttl:
/* 0 isn't well defined right now, reject it */
if (intval < 1 || intval > INT_MAX / 1000) {
pr_err("osd_idle_ttl out of range\n");
err = -EINVAL;
goto out;
}
opt->osd_idle_ttl = msecs_to_jiffies(intval * 1000);
break;
case Opt_mount_timeout:
/* 0 is "wait forever" (i.e. infinite timeout) */
if (intval < 0 || intval > INT_MAX / 1000) {
pr_err("mount_timeout out of range\n");
err = -EINVAL;
goto out;
}
opt->mount_timeout = msecs_to_jiffies(intval * 1000);
break;
case Opt_osd_request_timeout:
/* 0 is "wait forever" (i.e. infinite timeout) */
if (intval < 0 || intval > INT_MAX / 1000) {
pr_err("osd_request_timeout out of range\n");
err = -EINVAL;
goto out;
}
opt->osd_request_timeout = msecs_to_jiffies(intval * 1000);
break;
case Opt_share: opt->key = kzalloc(sizeof(*opt->key), GFP_KERNEL);
if (!opt->key)
return -ENOMEM;
return get_secret(opt->key, param->string, fc);
case Opt_osdtimeout:
warnf(fc, "libceph: Ignoring osdtimeout");
break;
case Opt_osdkeepalivetimeout:
/* 0 isn't well defined right now, reject it */
if (result.uint_32 < 1 || result.uint_32 > INT_MAX / 1000)
goto out_of_range;
opt->osd_keepalive_timeout =
msecs_to_jiffies(result.uint_32 * 1000);
break;
case Opt_osd_idle_ttl:
/* 0 isn't well defined right now, reject it */
if (result.uint_32 < 1 || result.uint_32 > INT_MAX / 1000)
goto out_of_range;
opt->osd_idle_ttl = msecs_to_jiffies(result.uint_32 * 1000);
break;
case Opt_mount_timeout:
/* 0 is "wait forever" (i.e. infinite timeout) */
if (result.uint_32 > INT_MAX / 1000)
goto out_of_range;
opt->mount_timeout = msecs_to_jiffies(result.uint_32 * 1000);
break;
case Opt_osd_request_timeout:
/* 0 is "wait forever" (i.e. infinite timeout) */
if (result.uint_32 > INT_MAX / 1000)
goto out_of_range;
opt->osd_request_timeout =
msecs_to_jiffies(result.uint_32 * 1000);
break;
case Opt_share:
if (!result.negated)
opt->flags &= ~CEPH_OPT_NOSHARE; opt->flags &= ~CEPH_OPT_NOSHARE;
break; else
case Opt_noshare:
opt->flags |= CEPH_OPT_NOSHARE; opt->flags |= CEPH_OPT_NOSHARE;
break; break;
case Opt_crc:
case Opt_crc: if (!result.negated)
opt->flags &= ~CEPH_OPT_NOCRC; opt->flags &= ~CEPH_OPT_NOCRC;
break; else
case Opt_nocrc:
opt->flags |= CEPH_OPT_NOCRC; opt->flags |= CEPH_OPT_NOCRC;
break; break;
case Opt_cephx_require_signatures:
case Opt_cephx_require_signatures: if (!result.negated)
opt->flags &= ~CEPH_OPT_NOMSGAUTH; opt->flags &= ~CEPH_OPT_NOMSGAUTH;
break; else
case Opt_nocephx_require_signatures:
opt->flags |= CEPH_OPT_NOMSGAUTH; opt->flags |= CEPH_OPT_NOMSGAUTH;
break; break;
case Opt_cephx_sign_messages: case Opt_cephx_sign_messages:
if (!result.negated)
opt->flags &= ~CEPH_OPT_NOMSGSIGN; opt->flags &= ~CEPH_OPT_NOMSGSIGN;
break; else
case Opt_nocephx_sign_messages:
opt->flags |= CEPH_OPT_NOMSGSIGN; opt->flags |= CEPH_OPT_NOMSGSIGN;
break; break;
case Opt_tcp_nodelay:
case Opt_tcp_nodelay: if (!result.negated)
opt->flags |= CEPH_OPT_TCP_NODELAY; opt->flags |= CEPH_OPT_TCP_NODELAY;
break; else
case Opt_notcp_nodelay:
opt->flags &= ~CEPH_OPT_TCP_NODELAY; opt->flags &= ~CEPH_OPT_TCP_NODELAY;
break; break;
case Opt_abort_on_full: case Opt_abort_on_full:
opt->flags |= CEPH_OPT_ABORT_ON_FULL; opt->flags |= CEPH_OPT_ABORT_ON_FULL;
break; break;
default: default:
BUG_ON(token); BUG();
}
} }
/* success */ return 0;
return opt;
out: out_of_range:
ceph_destroy_options(opt); return invalf(fc, "libceph: %s out of range", param->key);
return ERR_PTR(err);
} }
EXPORT_SYMBOL(ceph_parse_options); EXPORT_SYMBOL(ceph_parse_param);
int ceph_print_client_options(struct seq_file *m, struct ceph_client *client, int ceph_print_client_options(struct seq_file *m, struct ceph_client *client,
bool show_all) bool show_all)
......
...@@ -2004,10 +2004,8 @@ int ceph_parse_ips(const char *c, const char *end, ...@@ -2004,10 +2004,8 @@ int ceph_parse_ips(const char *c, const char *end,
return 0; return 0;
bad: bad:
pr_err("parse_ips bad ip '%.*s'\n", (int)(end - c), c);
return ret; return ret;
} }
EXPORT_SYMBOL(ceph_parse_ips);
static int process_banner(struct ceph_connection *con) static int process_banner(struct ceph_connection *con)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment