mirror of
https://github.com/versity/scoutfs.git
synced 2026-01-05 11:45:09 +00:00
The default TCP keepalive value is currently 10s, resulting in clients being disconnected after 10 seconds of not replying to a TCP keepalive packet. These keepalive values are reasonable most of the times, but we've seen client disconnects where this timeout has been exceeded, resulting in fencing. The cause for this is unknown at this time, but it is suspected that network intermissions are happening. This change adds a configurable value for this specific client socket timeout. It enforces that its value is above UNRESPONSIVE_PROBES, whose value remains unchanged. The default value of 10000ms (10s) is changed to 60s. This is the value we're assuming is much better suited for customers and has been briefly trialed, showing that it may help to avoid network level interruptions better. Signed-off-by: Auke Kok <auke.kok@versity.com>
668 lines
18 KiB
C
668 lines
18 KiB
C
/*
|
|
* Copyright (C) 2017 Versity Software, Inc. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License v2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/types.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/namei.h>
|
|
|
|
#include <linux/parser.h>
|
|
#include <linux/inet.h>
|
|
#include <linux/string.h>
|
|
#include <linux/in.h>
|
|
|
|
#include "msg.h"
|
|
#include "options.h"
|
|
#include "super.h"
|
|
#include "inode.h"
|
|
#include "alloc.h"
|
|
|
|
enum {
|
|
Opt_acl,
|
|
Opt_data_prealloc_blocks,
|
|
Opt_data_prealloc_contig_only,
|
|
Opt_log_merge_wait_timeout_ms,
|
|
Opt_metadev_path,
|
|
Opt_noacl,
|
|
Opt_orphan_scan_delay_ms,
|
|
Opt_quorum_heartbeat_timeout_ms,
|
|
Opt_quorum_slot_nr,
|
|
Opt_tcp_keepalive_timeout_ms,
|
|
Opt_err,
|
|
};
|
|
|
|
static const match_table_t tokens = {
|
|
{Opt_acl, "acl"},
|
|
{Opt_data_prealloc_blocks, "data_prealloc_blocks=%s"},
|
|
{Opt_data_prealloc_contig_only, "data_prealloc_contig_only=%s"},
|
|
{Opt_log_merge_wait_timeout_ms, "log_merge_wait_timeout_ms=%s"},
|
|
{Opt_metadev_path, "metadev_path=%s"},
|
|
{Opt_noacl, "noacl"},
|
|
{Opt_orphan_scan_delay_ms, "orphan_scan_delay_ms=%s"},
|
|
{Opt_quorum_heartbeat_timeout_ms, "quorum_heartbeat_timeout_ms=%s"},
|
|
{Opt_quorum_slot_nr, "quorum_slot_nr=%s"},
|
|
{Opt_tcp_keepalive_timeout_ms, "tcp_keepalive_timeout_ms=%s"},
|
|
{Opt_err, NULL}
|
|
};
|
|
|
|
struct options_info {
|
|
seqlock_t seqlock;
|
|
struct scoutfs_mount_options opts;
|
|
struct scoutfs_sysfs_attrs sysfs_attrs;
|
|
};
|
|
|
|
#define DECLARE_OPTIONS_INFO(sb, name) \
|
|
struct options_info *name = SCOUTFS_SB(sb)->options_info
|
|
|
|
static int parse_bdev_path(struct super_block *sb, substring_t *substr,
|
|
char **bdev_path_ret)
|
|
{
|
|
char *bdev_path;
|
|
struct inode *bdev_inode;
|
|
struct path path;
|
|
bool got_path = false;
|
|
int ret;
|
|
|
|
bdev_path = match_strdup(substr);
|
|
if (!bdev_path) {
|
|
scoutfs_err(sb, "bdev string dup failed");
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
ret = kern_path(bdev_path, LOOKUP_FOLLOW, &path);
|
|
if (ret) {
|
|
scoutfs_err(sb, "path %s not found for bdev: error %d",
|
|
bdev_path, ret);
|
|
goto out;
|
|
}
|
|
got_path = true;
|
|
|
|
bdev_inode = d_inode(path.dentry);
|
|
if (!S_ISBLK(bdev_inode->i_mode)) {
|
|
scoutfs_err(sb, "path %s for bdev is not a block device",
|
|
bdev_path);
|
|
ret = -ENOTBLK;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (got_path) {
|
|
path_put(&path);
|
|
}
|
|
|
|
if (ret < 0) {
|
|
kfree(bdev_path);
|
|
} else {
|
|
*bdev_path_ret = bdev_path;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void free_options(struct scoutfs_mount_options *opts)
|
|
{
|
|
kfree(opts->metadev_path);
|
|
}
|
|
|
|
#define MIN_LOG_MERGE_WAIT_TIMEOUT_MS 100UL
|
|
#define DEFAULT_LOG_MERGE_WAIT_TIMEOUT_MS 500
|
|
#define MAX_LOG_MERGE_WAIT_TIMEOUT_MS (60 * MSEC_PER_SEC)
|
|
|
|
#define MIN_ORPHAN_SCAN_DELAY_MS 100UL
|
|
#define DEFAULT_ORPHAN_SCAN_DELAY_MS (10 * MSEC_PER_SEC)
|
|
#define MAX_ORPHAN_SCAN_DELAY_MS (60 * MSEC_PER_SEC)
|
|
|
|
#define MIN_DATA_PREALLOC_BLOCKS 1ULL
|
|
#define MAX_DATA_PREALLOC_BLOCKS ((unsigned long long)SCOUTFS_BLOCK_SM_MAX)
|
|
|
|
#define DEFAULT_TCP_KEEPALIVE_TIMEOUT_MS (60 * MSEC_PER_SEC)
|
|
|
|
static void init_default_options(struct scoutfs_mount_options *opts)
|
|
{
|
|
memset(opts, 0, sizeof(*opts));
|
|
|
|
opts->data_prealloc_blocks = SCOUTFS_DATA_PREALLOC_DEFAULT_BLOCKS;
|
|
opts->data_prealloc_contig_only = 1;
|
|
opts->log_merge_wait_timeout_ms = DEFAULT_LOG_MERGE_WAIT_TIMEOUT_MS;
|
|
opts->orphan_scan_delay_ms = -1;
|
|
opts->quorum_heartbeat_timeout_ms = SCOUTFS_QUORUM_DEF_HB_TIMEO_MS;
|
|
opts->quorum_slot_nr = -1;
|
|
opts->tcp_keepalive_timeout_ms = DEFAULT_TCP_KEEPALIVE_TIMEOUT_MS;
|
|
}
|
|
|
|
static int verify_log_merge_wait_timeout_ms(struct super_block *sb, int ret, int val)
|
|
{
|
|
if (ret < 0) {
|
|
scoutfs_err(sb, "failed to parse log_merge_wait_timeout_ms value");
|
|
return -EINVAL;
|
|
}
|
|
if (val < MIN_LOG_MERGE_WAIT_TIMEOUT_MS || val > MAX_LOG_MERGE_WAIT_TIMEOUT_MS) {
|
|
scoutfs_err(sb, "invalid log_merge_wait_timeout_ms value %d, must be between %lu and %lu",
|
|
val, MIN_LOG_MERGE_WAIT_TIMEOUT_MS, MAX_LOG_MERGE_WAIT_TIMEOUT_MS);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int verify_quorum_heartbeat_timeout_ms(struct super_block *sb, int ret, u64 val)
|
|
{
|
|
if (ret < 0) {
|
|
scoutfs_err(sb, "failed to parse quorum_heartbeat_timeout_ms value");
|
|
return -EINVAL;
|
|
}
|
|
if (val < SCOUTFS_QUORUM_MIN_HB_TIMEO_MS || val > SCOUTFS_QUORUM_MAX_HB_TIMEO_MS) {
|
|
scoutfs_err(sb, "invalid quorum_heartbeat_timeout_ms value %llu, must be between %lu and %lu",
|
|
val, SCOUTFS_QUORUM_MIN_HB_TIMEO_MS, SCOUTFS_QUORUM_MAX_HB_TIMEO_MS);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int verify_tcp_keepalive_timeout_ms(struct super_block *sb, int ret, int val)
|
|
{
|
|
if (ret < 0) {
|
|
scoutfs_err(sb, "failed to parse tcp_keepalive_timeout_ms value");
|
|
return -EINVAL;
|
|
}
|
|
if (val <= (UNRESPONSIVE_PROBES * MSEC_PER_SEC)) {
|
|
scoutfs_err(sb, "invalid tcp_keepalive_timeout_ms value %d, must be larger than %lu",
|
|
val, (UNRESPONSIVE_PROBES * MSEC_PER_SEC));
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Parse the option string into our options struct. This can allocate
|
|
* memory in the struct. The caller is responsible for always calling
|
|
* free_options() when the struct is destroyed, including when we return
|
|
* an error.
|
|
*/
|
|
static int parse_options(struct super_block *sb, char *options, struct scoutfs_mount_options *opts)
|
|
{
|
|
substring_t args[MAX_OPT_ARGS];
|
|
u64 nr64;
|
|
int nr;
|
|
int token;
|
|
char *p;
|
|
int ret;
|
|
|
|
while ((p = strsep(&options, ",")) != NULL) {
|
|
if (!*p)
|
|
continue;
|
|
|
|
token = match_token(p, tokens, args);
|
|
switch (token) {
|
|
|
|
case Opt_acl:
|
|
sb->s_flags |= SB_POSIXACL;
|
|
break;
|
|
|
|
case Opt_data_prealloc_blocks:
|
|
ret = match_u64(args, &nr64);
|
|
if (ret < 0 ||
|
|
nr64 < MIN_DATA_PREALLOC_BLOCKS || nr64 > MAX_DATA_PREALLOC_BLOCKS) {
|
|
scoutfs_err(sb, "invalid data_prealloc_blocks option, must be between %llu and %llu",
|
|
MIN_DATA_PREALLOC_BLOCKS, MAX_DATA_PREALLOC_BLOCKS);
|
|
if (ret == 0)
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
opts->data_prealloc_blocks = nr64;
|
|
break;
|
|
|
|
case Opt_data_prealloc_contig_only:
|
|
ret = match_int(args, &nr);
|
|
if (ret < 0 || nr < 0 || nr > 1) {
|
|
scoutfs_err(sb, "invalid data_prealloc_contig_only option, bool must only be 0 or 1");
|
|
if (ret == 0)
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
opts->data_prealloc_contig_only = nr;
|
|
break;
|
|
|
|
case Opt_tcp_keepalive_timeout_ms:
|
|
ret = match_int(args, &nr);
|
|
ret = verify_tcp_keepalive_timeout_ms(sb, ret, nr);
|
|
if (ret < 0)
|
|
return ret;
|
|
opts->tcp_keepalive_timeout_ms = nr;
|
|
break;
|
|
|
|
case Opt_log_merge_wait_timeout_ms:
|
|
ret = match_int(args, &nr);
|
|
ret = verify_log_merge_wait_timeout_ms(sb, ret, nr);
|
|
if (ret < 0)
|
|
return ret;
|
|
opts->log_merge_wait_timeout_ms = nr;
|
|
break;
|
|
|
|
case Opt_metadev_path:
|
|
ret = parse_bdev_path(sb, &args[0], &opts->metadev_path);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
|
|
case Opt_noacl:
|
|
sb->s_flags &= ~SB_POSIXACL;
|
|
break;
|
|
|
|
case Opt_orphan_scan_delay_ms:
|
|
if (opts->orphan_scan_delay_ms != -1) {
|
|
scoutfs_err(sb, "multiple orphan_scan_delay_ms options provided, only provide one.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = match_int(args, &nr);
|
|
if (ret < 0 ||
|
|
nr < MIN_ORPHAN_SCAN_DELAY_MS || nr > MAX_ORPHAN_SCAN_DELAY_MS) {
|
|
scoutfs_err(sb, "invalid orphan_scan_delay_ms option, must be between %lu and %lu",
|
|
MIN_ORPHAN_SCAN_DELAY_MS, MAX_ORPHAN_SCAN_DELAY_MS);
|
|
if (ret == 0)
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
opts->orphan_scan_delay_ms = nr;
|
|
break;
|
|
|
|
case Opt_quorum_heartbeat_timeout_ms:
|
|
ret = match_u64(args, &nr64);
|
|
ret = verify_quorum_heartbeat_timeout_ms(sb, ret, nr64);
|
|
if (ret < 0)
|
|
return ret;
|
|
opts->quorum_heartbeat_timeout_ms = nr64;
|
|
break;
|
|
|
|
case Opt_quorum_slot_nr:
|
|
if (opts->quorum_slot_nr != -1) {
|
|
scoutfs_err(sb, "multiple quorum_slot_nr options provided, only provide one.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = match_int(args, &nr);
|
|
if (ret < 0 || nr < 0 || nr >= SCOUTFS_QUORUM_MAX_SLOTS) {
|
|
scoutfs_err(sb, "invalid quorum_slot_nr option, must be between 0 and %u",
|
|
SCOUTFS_QUORUM_MAX_SLOTS - 1);
|
|
if (ret == 0)
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
opts->quorum_slot_nr = nr;
|
|
break;
|
|
|
|
default:
|
|
scoutfs_err(sb, "Unknown or malformed option, \"%s\"", p);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (opts->orphan_scan_delay_ms == -1)
|
|
opts->orphan_scan_delay_ms = DEFAULT_ORPHAN_SCAN_DELAY_MS;
|
|
|
|
if (!opts->metadev_path) {
|
|
scoutfs_err(sb, "Required mount option \"metadev_path\" not found");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void scoutfs_options_read(struct super_block *sb, struct scoutfs_mount_options *opts)
|
|
{
|
|
DECLARE_OPTIONS_INFO(sb, optinf);
|
|
unsigned int seq;
|
|
|
|
if (WARN_ON_ONCE(optinf == NULL)) {
|
|
/* trying to use options before early setup or after destroy */
|
|
init_default_options(opts);
|
|
return;
|
|
}
|
|
|
|
do {
|
|
seq = read_seqbegin(&optinf->seqlock);
|
|
memcpy(opts, &optinf->opts, sizeof(struct scoutfs_mount_options));
|
|
} while (read_seqretry(&optinf->seqlock, seq));
|
|
}
|
|
|
|
/*
|
|
* Early setup that parses and stores the options so that the rest of
|
|
* setup can use them. Full options setup that relies on other
|
|
* components will be done later.
|
|
*/
|
|
int scoutfs_options_early_setup(struct super_block *sb, char *options)
|
|
{
|
|
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
|
struct scoutfs_mount_options opts;
|
|
struct options_info *optinf;
|
|
int ret;
|
|
|
|
init_default_options(&opts);
|
|
|
|
ret = parse_options(sb, options, &opts);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
optinf = kzalloc(sizeof(struct options_info), GFP_KERNEL);
|
|
if (!optinf) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
seqlock_init(&optinf->seqlock);
|
|
scoutfs_sysfs_init_attrs(sb, &optinf->sysfs_attrs);
|
|
|
|
write_seqlock(&optinf->seqlock);
|
|
optinf->opts = opts;
|
|
write_sequnlock(&optinf->seqlock);
|
|
|
|
sbi->options_info = optinf;
|
|
ret = 0;
|
|
out:
|
|
if (ret < 0)
|
|
free_options(&opts);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int scoutfs_options_show(struct seq_file *seq, struct dentry *root)
|
|
{
|
|
struct super_block *sb = root->d_sb;
|
|
struct scoutfs_mount_options opts;
|
|
const bool is_acl = !!(sb->s_flags & SB_POSIXACL);
|
|
|
|
scoutfs_options_read(sb, &opts);
|
|
|
|
if (is_acl)
|
|
seq_puts(seq, ",acl");
|
|
seq_printf(seq, ",data_prealloc_blocks=%llu", opts.data_prealloc_blocks);
|
|
seq_printf(seq, ",data_prealloc_contig_only=%u", opts.data_prealloc_contig_only);
|
|
seq_printf(seq, ",metadev_path=%s", opts.metadev_path);
|
|
if (!is_acl)
|
|
seq_puts(seq, ",noacl");
|
|
seq_printf(seq, ",orphan_scan_delay_ms=%u", opts.orphan_scan_delay_ms);
|
|
if (opts.quorum_slot_nr >= 0)
|
|
seq_printf(seq, ",quorum_slot_nr=%d", opts.quorum_slot_nr);
|
|
seq_printf(seq, ",tcp_keepalive_timeout_ms=%d", opts.tcp_keepalive_timeout_ms);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t data_prealloc_blocks_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
struct scoutfs_mount_options opts;
|
|
|
|
scoutfs_options_read(sb, &opts);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%llu", opts.data_prealloc_blocks);
|
|
}
|
|
static ssize_t data_prealloc_blocks_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
DECLARE_OPTIONS_INFO(sb, optinf);
|
|
char nullterm[30]; /* more than enough for octal -U64_MAX */
|
|
u64 val;
|
|
int len;
|
|
int ret;
|
|
|
|
len = min(count, sizeof(nullterm) - 1);
|
|
memcpy(nullterm, buf, len);
|
|
nullterm[len] = '\0';
|
|
|
|
ret = kstrtoll(nullterm, 0, &val);
|
|
if (ret < 0 || val < MIN_DATA_PREALLOC_BLOCKS || val > MAX_DATA_PREALLOC_BLOCKS) {
|
|
scoutfs_err(sb, "invalid data_prealloc_blocks option, must be between %llu and %llu",
|
|
MIN_DATA_PREALLOC_BLOCKS, MAX_DATA_PREALLOC_BLOCKS);
|
|
return -EINVAL;
|
|
}
|
|
|
|
write_seqlock(&optinf->seqlock);
|
|
optinf->opts.data_prealloc_blocks = val;
|
|
write_sequnlock(&optinf->seqlock);
|
|
|
|
return count;
|
|
}
|
|
SCOUTFS_ATTR_RW(data_prealloc_blocks);
|
|
|
|
static ssize_t data_prealloc_contig_only_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
struct scoutfs_mount_options opts;
|
|
|
|
scoutfs_options_read(sb, &opts);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u", opts.data_prealloc_contig_only);
|
|
}
|
|
static ssize_t data_prealloc_contig_only_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
DECLARE_OPTIONS_INFO(sb, optinf);
|
|
char nullterm[20]; /* more than enough for octal -U32_MAX */
|
|
long val;
|
|
int len;
|
|
int ret;
|
|
|
|
len = min(count, sizeof(nullterm) - 1);
|
|
memcpy(nullterm, buf, len);
|
|
nullterm[len] = '\0';
|
|
|
|
ret = kstrtol(nullterm, 0, &val);
|
|
if (ret < 0 || val < 0 || val > 1) {
|
|
scoutfs_err(sb, "invalid data_prealloc_contig_only option, bool must be 0 or 1");
|
|
return -EINVAL;
|
|
}
|
|
|
|
write_seqlock(&optinf->seqlock);
|
|
optinf->opts.data_prealloc_contig_only = val;
|
|
write_sequnlock(&optinf->seqlock);
|
|
|
|
return count;
|
|
}
|
|
SCOUTFS_ATTR_RW(data_prealloc_contig_only);
|
|
|
|
static ssize_t log_merge_wait_timeout_ms_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
struct scoutfs_mount_options opts;
|
|
|
|
scoutfs_options_read(sb, &opts);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u", opts.log_merge_wait_timeout_ms);
|
|
}
|
|
static ssize_t log_merge_wait_timeout_ms_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
DECLARE_OPTIONS_INFO(sb, optinf);
|
|
char nullterm[30]; /* more than enough for octal -U64_MAX */
|
|
int val;
|
|
int len;
|
|
int ret;
|
|
|
|
len = min(count, sizeof(nullterm) - 1);
|
|
memcpy(nullterm, buf, len);
|
|
nullterm[len] = '\0';
|
|
|
|
ret = kstrtoint(nullterm, 0, &val);
|
|
ret = verify_log_merge_wait_timeout_ms(sb, ret, val);
|
|
if (ret == 0) {
|
|
write_seqlock(&optinf->seqlock);
|
|
optinf->opts.log_merge_wait_timeout_ms = val;
|
|
write_sequnlock(&optinf->seqlock);
|
|
ret = count;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
SCOUTFS_ATTR_RW(log_merge_wait_timeout_ms);
|
|
|
|
static ssize_t metadev_path_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
struct scoutfs_mount_options opts;
|
|
|
|
scoutfs_options_read(sb, &opts);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s", opts.metadev_path);
|
|
}
|
|
SCOUTFS_ATTR_RO(metadev_path);
|
|
|
|
static ssize_t orphan_scan_delay_ms_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
struct scoutfs_mount_options opts;
|
|
|
|
scoutfs_options_read(sb, &opts);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u", opts.orphan_scan_delay_ms);
|
|
}
|
|
static ssize_t orphan_scan_delay_ms_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
DECLARE_OPTIONS_INFO(sb, optinf);
|
|
char nullterm[20]; /* more than enough for octal -U32_MAX */
|
|
long val;
|
|
int len;
|
|
int ret;
|
|
|
|
len = min(count, sizeof(nullterm) - 1);
|
|
memcpy(nullterm, buf, len);
|
|
nullterm[len] = '\0';
|
|
|
|
ret = kstrtol(nullterm, 0, &val);
|
|
if (ret < 0 || val < MIN_ORPHAN_SCAN_DELAY_MS || val > MAX_ORPHAN_SCAN_DELAY_MS) {
|
|
scoutfs_err(sb, "invalid orphan_scan_delay_ms value written to options sysfs file, must be between %lu and %lu",
|
|
MIN_ORPHAN_SCAN_DELAY_MS, MAX_ORPHAN_SCAN_DELAY_MS);
|
|
return -EINVAL;
|
|
}
|
|
|
|
write_seqlock(&optinf->seqlock);
|
|
optinf->opts.orphan_scan_delay_ms = val;
|
|
write_sequnlock(&optinf->seqlock);
|
|
|
|
scoutfs_inode_schedule_orphan_dwork(sb);
|
|
|
|
return count;
|
|
}
|
|
SCOUTFS_ATTR_RW(orphan_scan_delay_ms);
|
|
|
|
static ssize_t quorum_heartbeat_timeout_ms_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
struct scoutfs_mount_options opts;
|
|
|
|
scoutfs_options_read(sb, &opts);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%llu", opts.quorum_heartbeat_timeout_ms);
|
|
}
|
|
static ssize_t quorum_heartbeat_timeout_ms_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
DECLARE_OPTIONS_INFO(sb, optinf);
|
|
char nullterm[30]; /* more than enough for octal -U64_MAX */
|
|
u64 val;
|
|
int len;
|
|
int ret;
|
|
|
|
len = min(count, sizeof(nullterm) - 1);
|
|
memcpy(nullterm, buf, len);
|
|
nullterm[len] = '\0';
|
|
|
|
ret = kstrtoll(nullterm, 0, &val);
|
|
ret = verify_quorum_heartbeat_timeout_ms(sb, ret, val);
|
|
if (ret == 0) {
|
|
write_seqlock(&optinf->seqlock);
|
|
optinf->opts.quorum_heartbeat_timeout_ms = val;
|
|
write_sequnlock(&optinf->seqlock);
|
|
ret = count;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
SCOUTFS_ATTR_RW(quorum_heartbeat_timeout_ms);
|
|
|
|
static ssize_t quorum_slot_nr_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
|
struct scoutfs_mount_options opts;
|
|
|
|
scoutfs_options_read(sb, &opts);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", opts.quorum_slot_nr);
|
|
}
|
|
SCOUTFS_ATTR_RO(quorum_slot_nr);
|
|
|
|
static struct attribute *options_attrs[] = {
|
|
SCOUTFS_ATTR_PTR(data_prealloc_blocks),
|
|
SCOUTFS_ATTR_PTR(data_prealloc_contig_only),
|
|
SCOUTFS_ATTR_PTR(log_merge_wait_timeout_ms),
|
|
SCOUTFS_ATTR_PTR(metadev_path),
|
|
SCOUTFS_ATTR_PTR(orphan_scan_delay_ms),
|
|
SCOUTFS_ATTR_PTR(quorum_heartbeat_timeout_ms),
|
|
SCOUTFS_ATTR_PTR(quorum_slot_nr),
|
|
NULL,
|
|
};
|
|
|
|
int scoutfs_options_setup(struct super_block *sb)
|
|
{
|
|
DECLARE_OPTIONS_INFO(sb, optinf);
|
|
int ret;
|
|
|
|
ret = scoutfs_sysfs_create_attrs(sb, &optinf->sysfs_attrs, options_attrs, "mount_options");
|
|
if (ret < 0)
|
|
scoutfs_options_destroy(sb);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* We remove the sysfs files early in unmount so that they can't try to call other subsystems
|
|
* as they're being destroyed.
|
|
*/
|
|
void scoutfs_options_stop(struct super_block *sb)
|
|
{
|
|
DECLARE_OPTIONS_INFO(sb, optinf);
|
|
|
|
if (optinf)
|
|
scoutfs_sysfs_destroy_attrs(sb, &optinf->sysfs_attrs);
|
|
}
|
|
|
|
void scoutfs_options_destroy(struct super_block *sb)
|
|
{
|
|
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
|
DECLARE_OPTIONS_INFO(sb, optinf);
|
|
|
|
scoutfs_options_stop(sb);
|
|
|
|
if (optinf) {
|
|
free_options(&optinf->opts);
|
|
kfree(optinf);
|
|
sbi->options_info = NULL;
|
|
}
|
|
}
|