mirror of
https://github.com/versity/scoutfs.git
synced 2026-09-07 08:37:03 +00:00
Merge scoutfs-kmod-dev repo filtered to kmod/
Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
*.o
|
||||
*.ko
|
||||
*.mod.c
|
||||
*.cmd
|
||||
*~
|
||||
src/.tmp_versions/
|
||||
src/Module.symvers
|
||||
src/modules.order
|
||||
cscope.*
|
||||
*.spec
|
||||
*.sw[po]
|
||||
rpmbuild/
|
||||
rpms/
|
||||
|
||||
scoutfs-*.git*/
|
||||
scoutfs-kmod-*.tar
|
||||
@@ -0,0 +1,57 @@
|
||||
ALL: module
|
||||
|
||||
# default to building against the installed source for the running kernel
|
||||
ifeq ($(SK_KSRC),)
|
||||
SK_KSRC := $(shell echo /lib/modules/`uname -r`/build)
|
||||
endif
|
||||
|
||||
# fail if sparse fails if we find it
|
||||
ifeq ($(shell sparse && echo found),found)
|
||||
SP =
|
||||
else
|
||||
SP = @:
|
||||
endif
|
||||
|
||||
SCOUTFS_GIT_DESCRIBE := \
|
||||
$(shell git describe --all --abbrev=6 --long 2>/dev/null || \
|
||||
echo no-git)
|
||||
|
||||
SCOUTFS_FORMAT_HASH := \
|
||||
$(shell cat src/format.h src/ioctl.h | md5sum | cut -b1-16)
|
||||
|
||||
SCOUTFS_ARGS := SCOUTFS_GIT_DESCRIBE=$(SCOUTFS_GIT_DESCRIBE) \
|
||||
SCOUTFS_FORMAT_HASH=$(SCOUTFS_FORMAT_HASH) \
|
||||
CONFIG_SCOUTFS_FS=m -C $(SK_KSRC) M=$(CURDIR)/src \
|
||||
EXTRA_CFLAGS="-Werror"
|
||||
|
||||
# - We use the git describe from tags to set up the RPM versioning
|
||||
RPM_VERSION := $(shell git describe --long --tags | awk -F '-' '{gsub(/^v/,""); print $$1}')
|
||||
RPM_GITHASH := $(shell git rev-parse --short HEAD)
|
||||
TARFILE = scoutfs-kmod-$(RPM_VERSION).tar
|
||||
|
||||
|
||||
.PHONY: .FORCE
|
||||
|
||||
all: module
|
||||
|
||||
module:
|
||||
make $(SCOUTFS_ARGS)
|
||||
$(SP) make C=2 CF="-D__CHECK_ENDIAN__" $(SCOUTFS_ARGS)
|
||||
|
||||
|
||||
modules_install:
|
||||
make $(SCOUTFS_ARGS) modules_install
|
||||
|
||||
|
||||
%.spec: %.spec.in .FORCE
|
||||
sed -e 's/@@VERSION@@/$(RPM_VERSION)/g' \
|
||||
-e 's/@@GITHASH@@/$(RPM_GITHASH)/g' < $< > $@+
|
||||
mv $@+ $@
|
||||
|
||||
|
||||
dist: scoutfs-kmod.spec
|
||||
git archive --format=tar --prefix scoutfs-kmod-$(RPM_VERSION)/ HEAD^{tree} > $(TARFILE)
|
||||
@ tar rf $(TARFILE) --transform="s@\(.*\)@scoutfs-$(RPM_VERSION)/\1@" scoutfs-kmod.spec
|
||||
|
||||
clean:
|
||||
make $(SCOUTFS_ARGS) clean
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
# Introduction
|
||||
|
||||
scoutfs is a clustered in-kernel Linux filesystem designed and built
|
||||
from the ground up to support large archival systems.
|
||||
|
||||
Its key differentiating features are:
|
||||
|
||||
- Integrated consistent indexing accelerates archival maintenance operations
|
||||
- Log-structured commits allow nodes to write concurrently without contention
|
||||
|
||||
It meets best of breed expectations:
|
||||
|
||||
* Fully consistent POSIX semantics between nodes
|
||||
* Rich metadata to ensure the integrity of metadata references
|
||||
* Atomic transactions to maintain consistent persistent structures
|
||||
* First class kernel implementation for high performance and low latency
|
||||
* Open GPLv2 implementation
|
||||
|
||||
Learn more in the [white paper](https://docs.wixstatic.com/ugd/aaa89b_88a5cc84be0b4d1a90f60d8900834d28.pdf).
|
||||
|
||||
# Current Status
|
||||
|
||||
**Alpha Open Source Development**
|
||||
|
||||
scoutfs is under heavy active development. We're developing it in the
|
||||
open to give the community an opportunity to affect the design and
|
||||
implementation.
|
||||
|
||||
The core architectural design elements are in place. Much surrounding
|
||||
functionality hasn't been implemented. It's appropriate for early
|
||||
adopters and interested developers, not for production use.
|
||||
|
||||
In that vein, expect significant incompatible changes to both the format
|
||||
of network messages and persistent structures. To avoid mistakes the
|
||||
implementation currently calculates a hash of the format and ioctl
|
||||
header files in the source tree. The kernel module will refuse to mount
|
||||
a volume created by userspace utilities with a mismatched hash, and it
|
||||
will refuse to connect to a remote node with a mismatched hash. This
|
||||
means having to unmount, mkfs, and remount everything across many
|
||||
functional changes. Once the format is nailed down we'll wire up
|
||||
forward and back compat machinery and remove this temporary safety
|
||||
measure.
|
||||
|
||||
The current kernel module is developed against the RHEL/CentOS 7.x
|
||||
kernel to minimize the friction of developing and testing with partners'
|
||||
existing infrastructure. Once we're happy with the design we'll shift
|
||||
development to the upstream kernel while maintaining distro
|
||||
compatibility branches.
|
||||
|
||||
# Community Mailing List
|
||||
|
||||
Please join us on the open scoutfs-devel@scoutfs.org [mailing list
|
||||
hosted on Google Groups](https://groups.google.com/a/scoutfs.org/forum/#!forum/scoutfs-devel)
|
||||
for all discussion of scoutfs.
|
||||
|
||||
# Quick Start
|
||||
|
||||
**This following a very rough example of the procedure to get up and
|
||||
running, experience will be needed to fill in the gaps. We're happy to
|
||||
help on the mailing list.**
|
||||
|
||||
The requirements for running scoutfs on a small cluster are:
|
||||
|
||||
1. One or more nodes running x86-64 CentOS/RHEL 7.4 (or 7.3)
|
||||
2. Access to two shared block devices
|
||||
3. IPv4 connectivity between the nodes
|
||||
|
||||
The steps for getting scoutfs mounted and operational are:
|
||||
|
||||
1. Get the kernel module running on the nodes
|
||||
2. Make a new filesystem on the devices with the userspace utilities
|
||||
3. Mount the devices on all the nodes
|
||||
|
||||
In this example we run all of these commands on three nodes. The names
|
||||
of the block devices are the same on all the nodes.
|
||||
|
||||
1. Get the Kernel Module and Userspace Binaries
|
||||
|
||||
* Either use snapshot RPMs built from git by Versity:
|
||||
|
||||
```shell
|
||||
rpm -i https://scoutfs.s3-us-west-2.amazonaws.com/scoutfs-repo-0.0.1-1.el7_4.noarch.rpm
|
||||
yum install scoutfs-utils kmod-scoutfs
|
||||
```
|
||||
|
||||
* Or use the binaries built from checked out git repositories:
|
||||
|
||||
```shell
|
||||
yum install kernel-devel
|
||||
git clone git@github.com:versity/scoutfs-kmod-dev.git
|
||||
make -C scoutfs-kmod-dev module
|
||||
modprobe libcrc32c
|
||||
insmod scoutfs-kmod-dev/src/scoutfs.ko
|
||||
|
||||
git clone git@github.com:versity/scoutfs-utils-dev.git
|
||||
make -C scoutfs-utils-dev
|
||||
alias scoutfs=$PWD/scoutfs-utils-dev/src/scoutfs
|
||||
```
|
||||
|
||||
2. Make a New Filesystem (**destroys contents, no questions asked**)
|
||||
|
||||
We specify that two of our three nodes must be present to form a
|
||||
quorum for the system to function.
|
||||
|
||||
```shell
|
||||
scoutfs mkfs -Q 2 /dev/meta_dev /dev/data_dev
|
||||
```
|
||||
|
||||
3. Mount the Filesystem
|
||||
|
||||
Each mounting node provides its local IP address on which it will run
|
||||
an internal server for the other mounts if it is elected the leader by
|
||||
the quorum.
|
||||
|
||||
```shell
|
||||
mkdir /mnt/scoutfs
|
||||
mount -t scoutfs -o server_addr=$NODE_ADDR,metadev_path=/dev/meta_dev /dev/data_dev /mnt/scoutfs
|
||||
```
|
||||
|
||||
4. For Kicks, Observe the Metadata Change Index
|
||||
|
||||
The `meta_seq` index tracks the inodes that are changed in each
|
||||
transaction.
|
||||
|
||||
```shell
|
||||
scoutfs walk-inodes meta_seq 0 -1 /mnt/scoutfs
|
||||
touch /mnt/scoutfs/one; sync
|
||||
scoutfs walk-inodes meta_seq 0 -1 /mnt/scoutfs
|
||||
touch /mnt/scoutfs/two; sync
|
||||
scoutfs walk-inodes meta_seq 0 -1 /mnt/scoutfs
|
||||
touch /mnt/scoutfs/one; sync
|
||||
scoutfs walk-inodes meta_seq 0 -1 /mnt/scoutfs
|
||||
```
|
||||
@@ -0,0 +1,80 @@
|
||||
%define kmod_name scoutfs
|
||||
%define kmod_version @@VERSION@@
|
||||
%define kmod_git_hash @@GITHASH@@
|
||||
%define pkg_date %(date +%%Y%%m%%d)
|
||||
|
||||
# take kernel version or default to uname -r
|
||||
%{!?kversion: %global kversion %(uname -r)}
|
||||
%global kernel_version %{kversion}
|
||||
|
||||
%global kernel_source() /usr/src/kernels/%{kernel_version}.$(arch)
|
||||
%global kernel_release() %{kversion}
|
||||
|
||||
%{!?_release: %global _release 0.%{pkg_date}git%{kmod_git_hash}}
|
||||
|
||||
Name: %{kmod_name}
|
||||
Summary: %{kmod_name} kernel module
|
||||
Version: %{kmod_version}
|
||||
Release: %{_release}%{?dist}
|
||||
License: GPLv2
|
||||
Group: System/Kernel
|
||||
URL: http://scoutfs.org/
|
||||
|
||||
BuildRequires: %{kernel_module_package_buildreqs}
|
||||
BuildRequires: git
|
||||
BuildRequires: kernel-devel-uname-r = %{kernel_version}
|
||||
BuildRequires: module-init-tools
|
||||
|
||||
ExclusiveArch: x86_64
|
||||
|
||||
Source: %{kmod_name}-kmod-%{kmod_version}.tar
|
||||
|
||||
# Build only for standard kernel variant(s); for debug packages, append "debug"
|
||||
# after "default" (separated by space)
|
||||
%kernel_module_package default
|
||||
|
||||
# Disable the building of the debug package(s).
|
||||
%define debug_package %{nil}
|
||||
|
||||
%global install_mod_dir extra/%{name}
|
||||
|
||||
|
||||
%description
|
||||
%{kmod_name} - kernel module
|
||||
|
||||
|
||||
%prep
|
||||
%setup -q -n %{kmod_name}-kmod-%{kmod_version}
|
||||
|
||||
set -- *
|
||||
mkdir source
|
||||
mv "$@" source/
|
||||
mkdir obj
|
||||
|
||||
|
||||
%build
|
||||
echo "Building for kernel: %{kernel_version} flavors: '%{flavors_to_build}'"
|
||||
for flavor in %flavors_to_build; do
|
||||
rm -rf obj/$flavor
|
||||
cp -r source obj/$flavor
|
||||
make SK_KSRC=%{kernel_source $flavor} -C obj/$flavor module
|
||||
done
|
||||
|
||||
%install
|
||||
export INSTALL_MOD_PATH=$RPM_BUILD_ROOT
|
||||
export INSTALL_MOD_DIR=%{install_mod_dir}
|
||||
mkdir -p %{install_mod_dir}
|
||||
for flavor in %{flavors_to_build}; do
|
||||
export KSRC=%{kernel_source $flavor}
|
||||
export KVERSION=%{kernel_release $KSRC}
|
||||
install -d $INSTALL_MOD_PATH/lib/modules/$KVERSION/%{install_mod_dir}
|
||||
cp $PWD/obj/$flavor/src/scoutfs.ko $INSTALL_MOD_PATH/lib/modules/$KVERSION/%{install_mod_dir}/
|
||||
done
|
||||
|
||||
# mark modules executable so that strip-to-file can strip them
|
||||
find %{buildroot} -type f -name \*.ko -exec %{__chmod} u+x \{\} \;
|
||||
|
||||
|
||||
%clean
|
||||
rm -rf %{buildroot}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
config SCOUTFS_FS
|
||||
tristate "scoutfs filesystem"
|
||||
help
|
||||
scoutfs is a clustered file system that stores data in large
|
||||
blocks in shared block storage.
|
||||
|
||||
To compile this file system support as a module, choose M here. The
|
||||
module will be called scoutfs.
|
||||
|
||||
If unsure, say N.
|
||||
@@ -0,0 +1,63 @@
|
||||
obj-$(CONFIG_SCOUTFS_FS) := scoutfs.o
|
||||
|
||||
CFLAGS_super.o = -DSCOUTFS_GIT_DESCRIBE=\"$(SCOUTFS_GIT_DESCRIBE)\" \
|
||||
-DSCOUTFS_FORMAT_HASH=0x$(SCOUTFS_FORMAT_HASH)LLU
|
||||
|
||||
CFLAGS_scoutfs_trace.o = -I$(src) # define_trace.h double include
|
||||
|
||||
# add EXTRA_CFLAGS defines for kernel compat
|
||||
-include $(src)/Makefile.kernelcompat
|
||||
|
||||
scoutfs-y += \
|
||||
avl.o \
|
||||
alloc.o \
|
||||
block.o \
|
||||
btree.o \
|
||||
client.o \
|
||||
counters.o \
|
||||
data.o \
|
||||
dir.o \
|
||||
export.o \
|
||||
ext.o \
|
||||
file.o \
|
||||
forest.o \
|
||||
inode.o \
|
||||
ioctl.o \
|
||||
item.o \
|
||||
lock.o \
|
||||
lock_server.o \
|
||||
msg.o \
|
||||
net.o \
|
||||
options.o \
|
||||
per_task.o \
|
||||
quorum.o \
|
||||
scoutfs_trace.o \
|
||||
server.o \
|
||||
sort_priv.o \
|
||||
spbm.o \
|
||||
srch.o \
|
||||
super.o \
|
||||
sysfs.o \
|
||||
trans.o \
|
||||
triggers.o \
|
||||
tseq.o \
|
||||
xattr.o
|
||||
|
||||
#
|
||||
# The raw types aren't available in userspace headers. Make sure all
|
||||
# the types we use in the headers are the exported __ versions.
|
||||
#
|
||||
# XXX dunno how we're really supposed to do this in kbuild
|
||||
#
|
||||
.PHONY: $(src)/check_exported_types
|
||||
$(src)/check_exported_types:
|
||||
@if egrep '\<[us](8|16|32|64\>)' $(src)/format.h $(src)/ioctl.h; then \
|
||||
echo "no raw types in exported headers, preface with __"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if egrep '\<__packed\>' $(src)/format.h $(src)/ioctl.h; then \
|
||||
echo "no __packed allowed in exported headers"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
extra-y += check_exported_types
|
||||
@@ -0,0 +1,36 @@
|
||||
#
|
||||
# We try to detect the specific api incompatibilities with simple tests
|
||||
# because distros regularly backport features without changing the
|
||||
# version.
|
||||
#
|
||||
|
||||
ccflags-y += -include $(src)/kernelcompat.h
|
||||
|
||||
#
|
||||
# v3.10-rc6-21-gbb6f619b3a49
|
||||
#
|
||||
# _readdir changes from fop->readdir() to fop->iterate() and from
|
||||
# filldir(dirent) to dir_emit(ctx).
|
||||
#
|
||||
ifneq (,$(shell grep 'iterate.*dir_context' include/linux/fs.h))
|
||||
ccflags-y += -DKC_ITERATE_DIR_CONTEXT
|
||||
endif
|
||||
|
||||
#
|
||||
# v3.10-rc6-23-g5f99f4e79abc
|
||||
#
|
||||
# Helpers including dir_emit_dots() are added in the process of
|
||||
# switching dcache_readdir() from fop->readdir() to fop->iterate()
|
||||
#
|
||||
ifneq (,$(shell grep 'dir_emit_dots' include/linux/fs.h))
|
||||
ccflags-y += -DKC_DIR_EMIT_DOTS
|
||||
endif
|
||||
|
||||
#
|
||||
# RHEL extended the fop struct so to use it we have to set
|
||||
# a flag to indicate that the struct is large enough and
|
||||
# contains the pointer.
|
||||
#
|
||||
ifneq (,$(shell grep 'FMODE_KABI_ITERATE' include/linux/fs.h))
|
||||
ccflags-y += -DKC_FMODE_KABI_ITERATE
|
||||
endif
|
||||
+1239
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,135 @@
|
||||
#ifndef _SCOUTFS_ALLOC_H_
|
||||
#define _SCOUTFS_ALLOC_H_
|
||||
|
||||
#include "ext.h"
|
||||
|
||||
/*
|
||||
* These are implementation-specific metrics, they don't need to be
|
||||
* consistent across implementations. They should probably be run-time
|
||||
* knobs.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The largest extent that we'll try to allocate with fallocate. We're
|
||||
* trying not to completely consume a transactions data allocation all
|
||||
* at once. This is only allocation granularity, repeated allocations
|
||||
* can produce large contiguous extents.
|
||||
*/
|
||||
#define SCOUTFS_FALLOCATE_ALLOC_LIMIT \
|
||||
(128ULL * 1024 * 1024 >> SCOUTFS_BLOCK_SM_SHIFT)
|
||||
|
||||
/*
|
||||
* The largest aligned region that we'll try to allocate at the end of
|
||||
* the file as it's extended. This is also limited to the current file
|
||||
* size so we can only waste at most twice the total file size when
|
||||
* files are less than this. We try to keep this around the point of
|
||||
* diminishing returns in streaming performance of common data devices
|
||||
* to limit waste.
|
||||
*/
|
||||
#define SCOUTFS_DATA_EXTEND_PREALLOC_LIMIT \
|
||||
(8ULL * 1024 * 1024 >> SCOUTFS_BLOCK_SM_SHIFT)
|
||||
|
||||
/*
|
||||
* Small data allocations are satisfied by cached extents stored in
|
||||
* the run-time alloc struct to minimize item operations for small
|
||||
* block allocations. Large allocations come directly from btree
|
||||
* extent items, and this defines the threshold beetwen them.
|
||||
*/
|
||||
#define SCOUTFS_ALLOC_DATA_LG_THRESH \
|
||||
(8ULL * 1024 * 1024 >> SCOUTFS_BLOCK_SM_SHIFT)
|
||||
|
||||
/*
|
||||
* Fill client alloc roots to the target when they fall below the lo
|
||||
* threshold.
|
||||
*
|
||||
* We're giving the client the most available meta blocks we can so that
|
||||
* it has the freedom to build large transactions before worrying that
|
||||
* it might run out of meta allocs during commits.
|
||||
*/
|
||||
#define SCOUTFS_SERVER_META_FILL_TARGET \
|
||||
SCOUTFS_ALLOC_LIST_MAX_BLOCKS
|
||||
#define SCOUTFS_SERVER_META_FILL_LO \
|
||||
(SCOUTFS_ALLOC_LIST_MAX_BLOCKS / 2)
|
||||
#define SCOUTFS_SERVER_DATA_FILL_TARGET \
|
||||
(4ULL * 1024 * 1024 * 1024 >> SCOUTFS_BLOCK_SM_SHIFT)
|
||||
#define SCOUTFS_SERVER_DATA_FILL_LO \
|
||||
(1ULL * 1024 * 1024 * 1024 >> SCOUTFS_BLOCK_SM_SHIFT)
|
||||
|
||||
/*
|
||||
* Each of the server meta_alloc roots will try to keep a minimum amount
|
||||
* of free blocks. The server will swap roots when its current avail
|
||||
* falls below the threshold while the freed root is still above it. It
|
||||
* must have room for all the largest allocation attempted in a
|
||||
* transaction on the server.
|
||||
*/
|
||||
#define SCOUTFS_SERVER_META_ALLOC_MIN \
|
||||
(SCOUTFS_SERVER_META_FILL_TARGET * 2)
|
||||
|
||||
/*
|
||||
* A run-time use of a pair of persistent avail/freed roots as a
|
||||
* metadata allocator. It has the machinery needed to lock and avoid
|
||||
* recursion when dirtying the list blocks that are used during the
|
||||
* transaction.
|
||||
*/
|
||||
struct scoutfs_alloc {
|
||||
spinlock_t lock;
|
||||
struct mutex mutex;
|
||||
struct scoutfs_block *dirty_avail_bl;
|
||||
struct scoutfs_block *dirty_freed_bl;
|
||||
struct scoutfs_alloc_list_head avail;
|
||||
struct scoutfs_alloc_list_head freed;
|
||||
};
|
||||
|
||||
void scoutfs_alloc_init(struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_alloc_list_head *avail,
|
||||
struct scoutfs_alloc_list_head *freed);
|
||||
int scoutfs_alloc_prepare_commit(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri);
|
||||
|
||||
int scoutfs_alloc_meta(struct super_block *sb, struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri, u64 *blkno);
|
||||
int scoutfs_free_meta(struct super_block *sb, struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri, u64 blkno);
|
||||
|
||||
int scoutfs_alloc_data(struct super_block *sb, struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_alloc_root *root,
|
||||
struct scoutfs_extent *cached, u64 count,
|
||||
u64 *blkno_ret, u64 *count_ret);
|
||||
int scoutfs_free_data(struct super_block *sb, struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_alloc_root *root, u64 blkno, u64 count);
|
||||
|
||||
int scoutfs_alloc_move(struct super_block *sb, struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_alloc_root *dst,
|
||||
struct scoutfs_alloc_root *src, u64 total);
|
||||
|
||||
int scoutfs_alloc_fill_list(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_alloc_list_head *lhead,
|
||||
struct scoutfs_alloc_root *root,
|
||||
u64 lo, u64 target);
|
||||
int scoutfs_alloc_empty_list(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_alloc_root *root,
|
||||
struct scoutfs_alloc_list_head *lhead);
|
||||
int scoutfs_alloc_splice_list(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_alloc_list_head *dst,
|
||||
struct scoutfs_alloc_list_head *src);
|
||||
|
||||
bool scoutfs_alloc_meta_low(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc, u32 nr);
|
||||
|
||||
typedef int (*scoutfs_alloc_foreach_cb_t)(struct super_block *sb, void *arg,
|
||||
int owner, u64 id,
|
||||
bool meta, bool avail, u64 blocks);
|
||||
int scoutfs_alloc_foreach(struct super_block *sb,
|
||||
scoutfs_alloc_foreach_cb_t cb, void *arg);
|
||||
|
||||
#endif
|
||||
+405
@@ -0,0 +1,405 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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/string.h>
|
||||
|
||||
#include "format.h"
|
||||
#include "avl.h"
|
||||
|
||||
/*
|
||||
* We use a simple avl to index items in btree blocks. The interface
|
||||
* looks a bit like the kernel rbtree interface in that the caller
|
||||
* manages locking and storage for the nodes. Node references are
|
||||
* stored as byte offsets from the root so that the implementation
|
||||
* doesn't have to know anything about the caller's container.
|
||||
*
|
||||
* We store the full height in each node, rather than just 2 bits for
|
||||
* the balance, so that we can use the extra redundancy to verify the
|
||||
* integrity of the tree.
|
||||
*/
|
||||
|
||||
static struct scoutfs_avl_node *node_ptr(struct scoutfs_avl_root *root,
|
||||
__le16 off)
|
||||
{
|
||||
return off ? (void *)root + le16_to_cpu(off) : NULL;
|
||||
}
|
||||
|
||||
static __le16 node_off(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node)
|
||||
{
|
||||
return node ? cpu_to_le16((void *)node - (void *)root) : 0;
|
||||
}
|
||||
|
||||
static __u8 node_height(struct scoutfs_avl_node *node)
|
||||
{
|
||||
return node ? node->height : 0;
|
||||
}
|
||||
|
||||
struct scoutfs_avl_node *
|
||||
scoutfs_avl_search(struct scoutfs_avl_root *root,
|
||||
scoutfs_avl_compare_t compare, void *arg, int *cmp_ret,
|
||||
struct scoutfs_avl_node **par,
|
||||
struct scoutfs_avl_node **next,
|
||||
struct scoutfs_avl_node **prev)
|
||||
{
|
||||
struct scoutfs_avl_node *node = node_ptr(root, root->node);
|
||||
int cmp;
|
||||
|
||||
if (cmp_ret)
|
||||
*cmp_ret = -1;
|
||||
if (par)
|
||||
*par = NULL;
|
||||
if (next)
|
||||
*next = NULL;
|
||||
if (prev)
|
||||
*prev = NULL;
|
||||
|
||||
while (node) {
|
||||
cmp = compare(arg, node);
|
||||
if (par)
|
||||
*par = node;
|
||||
if (cmp_ret)
|
||||
*cmp_ret = cmp;
|
||||
if (cmp < 0) {
|
||||
if (next)
|
||||
*next = node;
|
||||
node = node_ptr(root, node->left);
|
||||
} else if (cmp > 0) {
|
||||
if (prev)
|
||||
*prev = node;
|
||||
node = node_ptr(root, node->right);
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct scoutfs_avl_node *scoutfs_avl_first(struct scoutfs_avl_root *root)
|
||||
{
|
||||
struct scoutfs_avl_node *node = node_ptr(root, root->node);
|
||||
|
||||
while (node && node->left)
|
||||
node = node_ptr(root, node->left);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
struct scoutfs_avl_node *scoutfs_avl_last(struct scoutfs_avl_root *root)
|
||||
{
|
||||
struct scoutfs_avl_node *node = node_ptr(root, root->node);
|
||||
|
||||
while (node && node->right)
|
||||
node = node_ptr(root, node->right);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
struct scoutfs_avl_node *scoutfs_avl_next(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node)
|
||||
{
|
||||
struct scoutfs_avl_node *parent;
|
||||
|
||||
if (node->right) {
|
||||
node = node_ptr(root, node->right);
|
||||
while (node->left)
|
||||
node = node_ptr(root, node->left);
|
||||
return node;
|
||||
}
|
||||
|
||||
while ((parent = node_ptr(root, node->parent)) &&
|
||||
node == node_ptr(root, parent->right))
|
||||
node = parent;
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
struct scoutfs_avl_node *scoutfs_avl_prev(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node)
|
||||
{
|
||||
struct scoutfs_avl_node *parent;
|
||||
|
||||
if (node->left) {
|
||||
node = node_ptr(root, node->left);
|
||||
while (node->right)
|
||||
node = node_ptr(root, node->right);
|
||||
return node;
|
||||
}
|
||||
|
||||
while ((parent = node_ptr(root, node->parent)) &&
|
||||
node == node_ptr(root, parent->left))
|
||||
node = parent;
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
static void set_parent_left_right(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *parent,
|
||||
struct scoutfs_avl_node *old,
|
||||
struct scoutfs_avl_node *new)
|
||||
{
|
||||
__le16 *off;
|
||||
|
||||
if (parent == NULL)
|
||||
off = &root->node;
|
||||
else if (parent->left == node_off(root, old))
|
||||
off = &parent->left;
|
||||
else
|
||||
off = &parent->right;
|
||||
|
||||
*off = node_off(root, new);
|
||||
}
|
||||
|
||||
static void set_height(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node)
|
||||
{
|
||||
struct scoutfs_avl_node *left = node_ptr(root, node->left);
|
||||
struct scoutfs_avl_node *right = node_ptr(root, node->right);
|
||||
|
||||
node->height = 1 + max(node_height(left), node_height(right));
|
||||
}
|
||||
|
||||
static int node_balance(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node)
|
||||
{
|
||||
if (node == NULL)
|
||||
return 0;
|
||||
|
||||
return (int)node_height(node_ptr(root, node->right)) -
|
||||
(int)node_height(node_ptr(root, node->left));
|
||||
}
|
||||
|
||||
/*
|
||||
* d b
|
||||
* / \ rotate right -> / \
|
||||
* b e a d
|
||||
* / \ <- rotate left / \
|
||||
* a c c e
|
||||
*
|
||||
* The rotate functions are always called with the higher node as the
|
||||
* earlier argument. Links to a and e are constant. We have to update
|
||||
* the forward and back refs between parents and nodes for the three links
|
||||
* along root->[db]->[bd]->c.
|
||||
*/
|
||||
static void rotate_right(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *d)
|
||||
{
|
||||
struct scoutfs_avl_node *gpa = node_ptr(root, d->parent);
|
||||
struct scoutfs_avl_node *b = node_ptr(root, d->left);
|
||||
struct scoutfs_avl_node *c = node_ptr(root, b->right);
|
||||
|
||||
set_parent_left_right(root, gpa, d, b);
|
||||
b->parent = node_off(root, gpa);
|
||||
|
||||
b->right = node_off(root, d);
|
||||
d->parent = node_off(root, b);
|
||||
|
||||
d->left = node_off(root, c);
|
||||
if (c)
|
||||
c->parent = node_off(root, d);
|
||||
|
||||
set_height(root, d);
|
||||
set_height(root, b);
|
||||
}
|
||||
|
||||
static void rotate_left(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *b)
|
||||
{
|
||||
struct scoutfs_avl_node *gpa = node_ptr(root, b->parent);
|
||||
struct scoutfs_avl_node *d = node_ptr(root, b->right);
|
||||
struct scoutfs_avl_node *c = node_ptr(root, d->left);
|
||||
|
||||
set_parent_left_right(root, gpa, b, d);
|
||||
d->parent = node_off(root, gpa);
|
||||
|
||||
d->left = node_off(root, b);
|
||||
b->parent = node_off(root, d);
|
||||
|
||||
b->right = node_off(root, c);
|
||||
if (c)
|
||||
c->parent = node_off(root, b);
|
||||
|
||||
set_height(root, b);
|
||||
set_height(root, d);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the balance factor for the given node and perform rotations if
|
||||
* its two child subtrees are too far out of balance. Return either the
|
||||
* node again or the root of the newly balanced subtree.
|
||||
*/
|
||||
static struct scoutfs_avl_node *
|
||||
rotate_imbalance(struct scoutfs_avl_root *root, struct scoutfs_avl_node *node)
|
||||
{
|
||||
int bal = node_balance(root, node);
|
||||
struct scoutfs_avl_node *child;
|
||||
|
||||
if (bal >= -1 && bal <= 1)
|
||||
return node;
|
||||
|
||||
if (bal > 0) {
|
||||
/* turn right-left case into right-right */
|
||||
child = node_ptr(root, node->right);
|
||||
if (node_balance(root, child) < 0)
|
||||
rotate_right(root, child);
|
||||
/* rotate left to address right-right */
|
||||
rotate_left(root, node);
|
||||
|
||||
} else {
|
||||
/* or do the mirror for the left- cases */
|
||||
child = node_ptr(root, node->left);
|
||||
if (node_balance(root, child) > 0)
|
||||
rotate_left(root, child);
|
||||
rotate_right(root, node);
|
||||
}
|
||||
|
||||
return node_ptr(root, node->parent);
|
||||
}
|
||||
|
||||
void scoutfs_avl_insert(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *parent,
|
||||
struct scoutfs_avl_node *node, int cmp)
|
||||
{
|
||||
node->parent = 0;
|
||||
node->left = 0;
|
||||
node->right = 0;
|
||||
set_height(root, node);
|
||||
memset(node->__pad, 0, sizeof(node->__pad));
|
||||
|
||||
if (parent == NULL) {
|
||||
root->node = node_off(root, node);
|
||||
node->parent = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmp < 0)
|
||||
parent->left = node_off(root, node);
|
||||
else
|
||||
parent->right = node_off(root, node);
|
||||
node->parent = node_off(root, parent);
|
||||
|
||||
while (parent) {
|
||||
set_height(root, parent);
|
||||
parent = rotate_imbalance(root, parent);
|
||||
parent = node_ptr(root, parent->parent);
|
||||
}
|
||||
}
|
||||
|
||||
static struct scoutfs_avl_node *avl_successor(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node)
|
||||
{
|
||||
node = node_ptr(root, node->right);
|
||||
while (node->left)
|
||||
node = node_ptr(root, node->left);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find a node next successor and then swap the positions of the two
|
||||
* nodes with each other in the tree. This is only tricky because the
|
||||
* successor can be a direct child of the node and if we weren't careful
|
||||
* we'd be modifying each of the nodes through the pointers between
|
||||
* them.
|
||||
*/
|
||||
static void swap_with_successor(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node)
|
||||
{
|
||||
struct scoutfs_avl_node *succ = avl_successor(root, node);
|
||||
struct scoutfs_avl_node *succ_par = node_ptr(root, succ->parent);
|
||||
struct scoutfs_avl_node *succ_right = node_ptr(root, succ->right);
|
||||
struct scoutfs_avl_node *parent;
|
||||
struct scoutfs_avl_node *left;
|
||||
struct scoutfs_avl_node *right;
|
||||
|
||||
/* Link old node's parent and left child with the successor */
|
||||
succ->parent = node->parent;
|
||||
parent = node_ptr(root, succ->parent);
|
||||
set_parent_left_right(root, parent, node, succ);
|
||||
succ->left = node->left;
|
||||
left = node_ptr(root, succ->left);
|
||||
if (left)
|
||||
left->parent = node_off(root, succ);
|
||||
|
||||
/*
|
||||
* Link the old node's right with successor and the old
|
||||
* successor's parent with the node, they could have pointed to
|
||||
* each other.
|
||||
*/
|
||||
if (succ_par == node) {
|
||||
succ->right = node_off(root, node);
|
||||
node->parent = node_off(root, succ);
|
||||
} else {
|
||||
succ->right = node->right;
|
||||
right = node_ptr(root, succ->right);
|
||||
if (right)
|
||||
right->parent = node_off(root, succ);
|
||||
set_parent_left_right(root, succ_par, succ, node);
|
||||
node->parent = node_off(root, succ_par);
|
||||
}
|
||||
|
||||
/* Link the old successor's right with the node, it can't have left */
|
||||
node->right = node_off(root, succ_right);
|
||||
if (succ_right)
|
||||
succ_right->parent = node_off(root, node);
|
||||
node->left = 0;
|
||||
|
||||
swap(node->height, succ->height);
|
||||
}
|
||||
|
||||
void scoutfs_avl_delete(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node)
|
||||
{
|
||||
struct scoutfs_avl_node *parent;
|
||||
struct scoutfs_avl_node *child;
|
||||
|
||||
if (node->left && node->right)
|
||||
swap_with_successor(root, node);
|
||||
|
||||
parent = node_ptr(root, node->parent);
|
||||
child = node_ptr(root, node->left ?: node->right);
|
||||
|
||||
set_parent_left_right(root, parent, node, child);
|
||||
if (child)
|
||||
child->parent = node->parent;
|
||||
|
||||
while (parent) {
|
||||
set_height(root, parent);
|
||||
parent = rotate_imbalance(root, parent);
|
||||
parent = node_ptr(root, parent->parent);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Move the contents of a node to a new node location in memory. The
|
||||
* logical position of the node in the tree does not change.
|
||||
*/
|
||||
void scoutfs_avl_relocate(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *to,
|
||||
struct scoutfs_avl_node *from)
|
||||
{
|
||||
struct scoutfs_avl_node *parent = node_ptr(root, from->parent);
|
||||
struct scoutfs_avl_node *left = node_ptr(root, from->left);
|
||||
struct scoutfs_avl_node *right = node_ptr(root, from->right);
|
||||
|
||||
set_parent_left_right(root, parent, from, to);
|
||||
to->parent = from->parent;
|
||||
to->left = from->left;
|
||||
if (left)
|
||||
left->parent = node_off(root, to);
|
||||
to->right = from->right;
|
||||
if (right)
|
||||
right->parent = node_off(root, to);
|
||||
to->height = from->height;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef _SCOUTFS_AVL_H_
|
||||
#define _SCOUTFS_AVL_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
typedef int (*scoutfs_avl_compare_t)(void *arg,
|
||||
struct scoutfs_avl_node *node);
|
||||
|
||||
struct scoutfs_avl_node *
|
||||
scoutfs_avl_search(struct scoutfs_avl_root *root,
|
||||
scoutfs_avl_compare_t compare, void *arg, int *cmp_ret,
|
||||
struct scoutfs_avl_node **par,
|
||||
struct scoutfs_avl_node **next,
|
||||
struct scoutfs_avl_node **prev);
|
||||
struct scoutfs_avl_node *scoutfs_avl_first(struct scoutfs_avl_root *root);
|
||||
struct scoutfs_avl_node *scoutfs_avl_last(struct scoutfs_avl_root *root);
|
||||
struct scoutfs_avl_node *scoutfs_avl_next(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node);
|
||||
struct scoutfs_avl_node *scoutfs_avl_prev(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node);
|
||||
void scoutfs_avl_insert(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *parent,
|
||||
struct scoutfs_avl_node *node, int cmp);
|
||||
void scoutfs_avl_delete(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *node);
|
||||
void scoutfs_avl_relocate(struct scoutfs_avl_root *root,
|
||||
struct scoutfs_avl_node *to,
|
||||
struct scoutfs_avl_node *from);
|
||||
|
||||
#endif
|
||||
+1001
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
#ifndef _SCOUTFS_BLOCK_H_
|
||||
#define _SCOUTFS_BLOCK_H_
|
||||
|
||||
struct scoutfs_block_writer {
|
||||
spinlock_t lock;
|
||||
struct list_head dirty_list;
|
||||
u64 nr_dirty_blocks;
|
||||
};
|
||||
|
||||
struct scoutfs_block {
|
||||
u64 blkno;
|
||||
void *data;
|
||||
void *priv;
|
||||
};
|
||||
|
||||
__le32 scoutfs_block_calc_crc(struct scoutfs_block_header *hdr, u32 size);
|
||||
bool scoutfs_block_valid_crc(struct scoutfs_block_header *hdr, u32 size);
|
||||
bool scoutfs_block_valid_ref(struct super_block *sb,
|
||||
struct scoutfs_block_header *hdr,
|
||||
__le64 seq, __le64 blkno);
|
||||
|
||||
struct scoutfs_block *scoutfs_block_create(struct super_block *sb, u64 blkno);
|
||||
struct scoutfs_block *scoutfs_block_read(struct super_block *sb, u64 blkno);
|
||||
void scoutfs_block_invalidate(struct super_block *sb, struct scoutfs_block *bl);
|
||||
bool scoutfs_block_consistent_ref(struct super_block *sb,
|
||||
struct scoutfs_block *bl,
|
||||
__le64 seq, __le64 blkno, u32 magic);
|
||||
void scoutfs_block_put(struct super_block *sb, struct scoutfs_block *bl);
|
||||
|
||||
void scoutfs_block_writer_init(struct super_block *sb,
|
||||
struct scoutfs_block_writer *wri);
|
||||
void scoutfs_block_writer_mark_dirty(struct super_block *sb,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_block *bl);
|
||||
bool scoutfs_block_writer_is_dirty(struct super_block *sb,
|
||||
struct scoutfs_block *bl);
|
||||
int scoutfs_block_writer_write(struct super_block *sb,
|
||||
struct scoutfs_block_writer *wri);
|
||||
void scoutfs_block_writer_forget_all(struct super_block *sb,
|
||||
struct scoutfs_block_writer *wri);
|
||||
void scoutfs_block_writer_forget(struct super_block *sb,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_block *bl);
|
||||
bool scoutfs_block_writer_has_dirty(struct super_block *sb,
|
||||
struct scoutfs_block_writer *wri);
|
||||
u64 scoutfs_block_writer_dirty_bytes(struct super_block *sb,
|
||||
struct scoutfs_block_writer *wri);
|
||||
|
||||
int scoutfs_block_read_sm(struct super_block *sb,
|
||||
struct block_device *bdev, u64 blkno,
|
||||
struct scoutfs_block_header *hdr, size_t len,
|
||||
__le32 *blk_crc);
|
||||
int scoutfs_block_write_sm(struct super_block *sb,
|
||||
struct block_device *bdev, u64 blkno,
|
||||
struct scoutfs_block_header *hdr, size_t len);
|
||||
|
||||
int scoutfs_block_setup(struct super_block *sb);
|
||||
void scoutfs_block_destroy(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
+1842
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
#ifndef _SCOUTFS_BTREE_H_
|
||||
#define _SCOUTFS_BTREE_H_
|
||||
|
||||
#include <linux/uio.h>
|
||||
|
||||
struct scoutfs_alloc;
|
||||
struct scoutfs_block_writer;
|
||||
struct scoutfs_block;
|
||||
|
||||
struct scoutfs_btree_item_ref {
|
||||
struct super_block *sb;
|
||||
struct scoutfs_block *bl;
|
||||
struct scoutfs_key *key;
|
||||
void *val;
|
||||
unsigned val_len;
|
||||
};
|
||||
|
||||
#define SCOUTFS_BTREE_ITEM_REF(name) \
|
||||
struct scoutfs_btree_item_ref name = {NULL,}
|
||||
|
||||
/* caller gives an item to the callback */
|
||||
typedef int (*scoutfs_btree_item_cb)(struct super_block *sb,
|
||||
struct scoutfs_key *key,
|
||||
void *val, int val_len, void *arg);
|
||||
|
||||
/* simple singly-linked list of items */
|
||||
struct scoutfs_btree_item_list {
|
||||
struct scoutfs_btree_item_list *next;
|
||||
struct scoutfs_key key;
|
||||
int val_len;
|
||||
u8 val[0];
|
||||
};
|
||||
|
||||
int scoutfs_btree_lookup(struct super_block *sb,
|
||||
struct scoutfs_btree_root *root,
|
||||
struct scoutfs_key *key,
|
||||
struct scoutfs_btree_item_ref *iref);
|
||||
int scoutfs_btree_insert(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root,
|
||||
struct scoutfs_key *key,
|
||||
void *val, unsigned val_len);
|
||||
int scoutfs_btree_update(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root,
|
||||
struct scoutfs_key *key,
|
||||
void *val, unsigned val_len);
|
||||
int scoutfs_btree_force(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root,
|
||||
struct scoutfs_key *key,
|
||||
void *val, unsigned val_len);
|
||||
int scoutfs_btree_delete(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root,
|
||||
struct scoutfs_key *key);
|
||||
int scoutfs_btree_next(struct super_block *sb, struct scoutfs_btree_root *root,
|
||||
struct scoutfs_key *key,
|
||||
struct scoutfs_btree_item_ref *iref);
|
||||
int scoutfs_btree_prev(struct super_block *sb, struct scoutfs_btree_root *root,
|
||||
struct scoutfs_key *key,
|
||||
struct scoutfs_btree_item_ref *iref);
|
||||
int scoutfs_btree_dirty(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root,
|
||||
struct scoutfs_key *key);
|
||||
|
||||
int scoutfs_btree_read_items(struct super_block *sb,
|
||||
struct scoutfs_btree_root *root,
|
||||
struct scoutfs_key *key,
|
||||
struct scoutfs_key *start,
|
||||
struct scoutfs_key *end,
|
||||
scoutfs_btree_item_cb cb, void *arg);
|
||||
int scoutfs_btree_insert_list(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root,
|
||||
struct scoutfs_btree_item_list *lst);
|
||||
|
||||
void scoutfs_btree_put_iref(struct scoutfs_btree_item_ref *iref);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,564 @@
|
||||
/*
|
||||
* 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/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include <linux/net.h>
|
||||
#include <linux/inet.h>
|
||||
#include <linux/in.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/tcp.h>
|
||||
#include <asm/barrier.h>
|
||||
|
||||
#include "format.h"
|
||||
#include "counters.h"
|
||||
#include "inode.h"
|
||||
#include "btree.h"
|
||||
#include "scoutfs_trace.h"
|
||||
#include "msg.h"
|
||||
#include "client.h"
|
||||
#include "net.h"
|
||||
#include "endian_swap.h"
|
||||
#include "quorum.h"
|
||||
|
||||
/*
|
||||
* The client is responsible for maintaining a connection to the server.
|
||||
* This includes managing quorum elections that determine which client
|
||||
* should run the server that all the clients connect to.
|
||||
*/
|
||||
|
||||
#define CLIENT_CONNECT_DELAY_MS (MSEC_PER_SEC / 10)
|
||||
#define CLIENT_CONNECT_TIMEOUT_MS (1 * MSEC_PER_SEC)
|
||||
#define CLIENT_QUORUM_TIMEOUT_MS (5 * MSEC_PER_SEC)
|
||||
|
||||
struct client_info {
|
||||
struct super_block *sb;
|
||||
|
||||
struct scoutfs_net_connection *conn;
|
||||
atomic_t shutting_down;
|
||||
|
||||
struct workqueue_struct *workq;
|
||||
struct delayed_work connect_dwork;
|
||||
|
||||
u64 server_term;
|
||||
u64 greeting_umb;
|
||||
|
||||
bool sending_farewell;
|
||||
int farewell_error;
|
||||
struct completion farewell_comp;
|
||||
};
|
||||
|
||||
/*
|
||||
* Ask for a new run of allocated inode numbers. The server can return
|
||||
* fewer than @count. It will success with nr == 0 if we've run out.
|
||||
*/
|
||||
int scoutfs_client_alloc_inodes(struct super_block *sb, u64 count,
|
||||
u64 *ino, u64 *nr)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
struct scoutfs_net_inode_alloc ial;
|
||||
__le64 lecount = cpu_to_le64(count);
|
||||
int ret;
|
||||
|
||||
ret = scoutfs_net_sync_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_ALLOC_INODES,
|
||||
&lecount, sizeof(lecount),
|
||||
&ial, sizeof(ial));
|
||||
if (ret == 0) {
|
||||
*ino = le64_to_cpu(ial.ino);
|
||||
*nr = le64_to_cpu(ial.nr);
|
||||
|
||||
if (*nr == 0)
|
||||
ret = -ENOSPC;
|
||||
else if (*ino + *nr < *ino)
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scoutfs_client_get_log_trees(struct super_block *sb,
|
||||
struct scoutfs_log_trees *lt)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
|
||||
return scoutfs_net_sync_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_GET_LOG_TREES,
|
||||
NULL, 0, lt, sizeof(*lt));
|
||||
}
|
||||
|
||||
int scoutfs_client_commit_log_trees(struct super_block *sb,
|
||||
struct scoutfs_log_trees *lt)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
|
||||
return scoutfs_net_sync_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_COMMIT_LOG_TREES,
|
||||
lt, sizeof(*lt), NULL, 0);
|
||||
}
|
||||
|
||||
int scoutfs_client_get_roots(struct super_block *sb,
|
||||
struct scoutfs_net_roots *roots)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
|
||||
return scoutfs_net_sync_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_GET_ROOTS,
|
||||
NULL, 0, roots, sizeof(*roots));
|
||||
}
|
||||
|
||||
int scoutfs_client_advance_seq(struct super_block *sb, u64 *seq)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
__le64 before = cpu_to_le64p(seq);
|
||||
__le64 after;
|
||||
int ret;
|
||||
|
||||
ret = scoutfs_net_sync_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_ADVANCE_SEQ,
|
||||
&before, sizeof(before),
|
||||
&after, sizeof(after));
|
||||
if (ret == 0)
|
||||
*seq = le64_to_cpu(after);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scoutfs_client_get_last_seq(struct super_block *sb, u64 *seq)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
__le64 last_seq;
|
||||
int ret;
|
||||
|
||||
ret = scoutfs_net_sync_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_GET_LAST_SEQ,
|
||||
NULL, 0, &last_seq, sizeof(last_seq));
|
||||
if (ret == 0)
|
||||
*seq = le64_to_cpu(last_seq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* process an incoming grant response from the server */
|
||||
static int client_lock_response(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
void *resp, unsigned int resp_len,
|
||||
int error, void *data)
|
||||
{
|
||||
if (resp_len != sizeof(struct scoutfs_net_lock_grant_response))
|
||||
return -EINVAL;
|
||||
|
||||
/* XXX error? */
|
||||
|
||||
return scoutfs_lock_grant_response(sb, resp);
|
||||
}
|
||||
|
||||
/* Send a lock request to the server. */
|
||||
int scoutfs_client_lock_request(struct super_block *sb,
|
||||
struct scoutfs_net_lock *nl)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
|
||||
return scoutfs_net_submit_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_LOCK,
|
||||
nl, sizeof(*nl),
|
||||
client_lock_response, NULL, NULL);
|
||||
}
|
||||
|
||||
/* Send a lock response to the server. */
|
||||
int scoutfs_client_lock_response(struct super_block *sb, u64 net_id,
|
||||
struct scoutfs_net_lock *nl)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
|
||||
return scoutfs_net_response(sb, client->conn, SCOUTFS_NET_CMD_LOCK,
|
||||
net_id, 0, nl, sizeof(*nl));
|
||||
}
|
||||
|
||||
/* Send a lock recover response to the server. */
|
||||
int scoutfs_client_lock_recover_response(struct super_block *sb, u64 net_id,
|
||||
struct scoutfs_net_lock_recover *nlr)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
u16 bytes = offsetof(struct scoutfs_net_lock_recover,
|
||||
locks[le16_to_cpu(nlr->nr)]);
|
||||
|
||||
return scoutfs_net_response(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_LOCK_RECOVER,
|
||||
net_id, 0, nlr, bytes);
|
||||
}
|
||||
|
||||
/* Find srch files that need to be compacted. */
|
||||
int scoutfs_client_srch_get_compact(struct super_block *sb,
|
||||
struct scoutfs_srch_compact *sc)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
|
||||
return scoutfs_net_sync_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_SRCH_GET_COMPACT,
|
||||
NULL, 0, sc, sizeof(*sc));
|
||||
}
|
||||
|
||||
/* Commit the result of a srch file compaction. */
|
||||
int scoutfs_client_srch_commit_compact(struct super_block *sb,
|
||||
struct scoutfs_srch_compact *res)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
|
||||
return scoutfs_net_sync_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_SRCH_COMMIT_COMPACT,
|
||||
res, sizeof(*res), NULL, 0);
|
||||
}
|
||||
|
||||
/* The client is receiving a invalidation request from the server */
|
||||
static int client_lock(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn, u8 cmd, u64 id,
|
||||
void *arg, u16 arg_len)
|
||||
{
|
||||
if (arg_len != sizeof(struct scoutfs_net_lock))
|
||||
return -EINVAL;
|
||||
|
||||
/* XXX error? */
|
||||
|
||||
return scoutfs_lock_invalidate_request(sb, id, arg);
|
||||
}
|
||||
|
||||
/* The server is asking us for the client's locks starting with the given key */
|
||||
static int client_lock_recover(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
u8 cmd, u64 id, void *arg, u16 arg_len)
|
||||
{
|
||||
if (arg_len != sizeof(struct scoutfs_key))
|
||||
return -EINVAL;
|
||||
|
||||
/* XXX error? */
|
||||
|
||||
return scoutfs_lock_recover_request(sb, id, arg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a greeting response in the client from the server. This is
|
||||
* called for every connected socket on the connection. Each response
|
||||
* contains the remote server's elected term which can be used to
|
||||
* identify server failover.
|
||||
*/
|
||||
static int client_greeting(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
void *resp, unsigned int resp_len, int error,
|
||||
void *data)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super;
|
||||
struct scoutfs_net_greeting *gr = resp;
|
||||
bool new_server;
|
||||
int ret;
|
||||
|
||||
if (error) {
|
||||
ret = error;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (resp_len != sizeof(struct scoutfs_net_greeting)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (gr->fsid != super->hdr.fsid) {
|
||||
scoutfs_warn(sb, "server sent fsid 0x%llx, client has 0x%llx",
|
||||
le64_to_cpu(gr->fsid),
|
||||
le64_to_cpu(super->hdr.fsid));
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (gr->format_hash != super->format_hash) {
|
||||
scoutfs_warn(sb, "server sent format 0x%llx, client has 0x%llx",
|
||||
le64_to_cpu(gr->format_hash),
|
||||
le64_to_cpu(super->format_hash));
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
new_server = le64_to_cpu(gr->server_term) != client->server_term;
|
||||
scoutfs_net_client_greeting(sb, conn, new_server);
|
||||
|
||||
client->server_term = le64_to_cpu(gr->server_term);
|
||||
client->greeting_umb = le64_to_cpu(gr->unmount_barrier);
|
||||
ret = 0;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* This work is responsible for maintaining a connection from the client
|
||||
* to the server. It's queued on mount and disconnect and we requeue
|
||||
* the work if the work fails and we're not shutting down.
|
||||
*
|
||||
* In the typical case a mount reads the super blocks and finds the
|
||||
* address of the currently running server and connects to it.
|
||||
* Non-voting clients who can't connect will keep trying alternating
|
||||
* reading the address and getting connect timeouts.
|
||||
*
|
||||
* Voting mounts will try to elect a leader if they can't connect to the
|
||||
* server. When a quorum can't connect and are able to elect a leader
|
||||
* then a new server is started. The new server will write its address
|
||||
* in the super and everyone will be able to connect.
|
||||
*
|
||||
* There's a tricky bit of coordination required to safely unmount.
|
||||
* Clients need to tell the server that they won't be coming back with a
|
||||
* farewell request. Once a client receives its farewell response it
|
||||
* can exit. But a majority of clients need to stick around to elect a
|
||||
* server to process all their farewell requests. This is coordinated
|
||||
* by having the greeting tell the server that a client is a voter. The
|
||||
* server then holds on to farewell requests from voters until only
|
||||
* requests from the final quorum remain. These farewell responses are
|
||||
* only sent after updating an unmount barrier in the super to indicate
|
||||
* to the final quorum that they can safely exit without having received
|
||||
* a farewell response over the network.
|
||||
*/
|
||||
static void scoutfs_client_connect_worker(struct work_struct *work)
|
||||
{
|
||||
struct client_info *client = container_of(work, struct client_info,
|
||||
connect_dwork.work);
|
||||
struct super_block *sb = client->sb;
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_super_block *super = NULL;
|
||||
struct mount_options *opts = &sbi->opts;
|
||||
const bool am_voter = opts->server_addr.sin_addr.s_addr != 0;
|
||||
struct scoutfs_net_greeting greet;
|
||||
struct sockaddr_in sin;
|
||||
ktime_t timeout_abs;
|
||||
u64 elected_term;
|
||||
int ret;
|
||||
|
||||
super = kmalloc(sizeof(struct scoutfs_super_block), GFP_NOFS);
|
||||
if (!super) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = scoutfs_read_super(sb, super);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* can safely unmount if we see that server processed our farewell */
|
||||
if (am_voter && client->sending_farewell &&
|
||||
(le64_to_cpu(super->unmount_barrier) > client->greeting_umb)) {
|
||||
client->farewell_error = 0;
|
||||
complete(&client->farewell_comp);
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* try to connect to the super's server address */
|
||||
scoutfs_addr_to_sin(&sin, &super->server_addr);
|
||||
if (sin.sin_addr.s_addr != 0 && sin.sin_port != 0)
|
||||
ret = scoutfs_net_connect(sb, client->conn, &sin,
|
||||
CLIENT_CONNECT_TIMEOUT_MS);
|
||||
else
|
||||
ret = -ENOTCONN;
|
||||
|
||||
/* voters try to elect a leader if they couldn't connect */
|
||||
if (ret < 0) {
|
||||
/* non-voters will keep retrying */
|
||||
if (!am_voter)
|
||||
goto out;
|
||||
|
||||
/* make sure local server isn't writing super during votes */
|
||||
scoutfs_server_stop(sb);
|
||||
|
||||
timeout_abs = ktime_add_ms(ktime_get(),
|
||||
CLIENT_QUORUM_TIMEOUT_MS);
|
||||
|
||||
ret = scoutfs_quorum_election(sb, timeout_abs,
|
||||
le64_to_cpu(super->quorum_server_term),
|
||||
&elected_term);
|
||||
/* start the server if we were asked to */
|
||||
if (elected_term > 0)
|
||||
ret = scoutfs_server_start(sb, &opts->server_addr,
|
||||
elected_term);
|
||||
ret = -ENOTCONN;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* send a greeting to verify endpoints of each connection */
|
||||
greet.fsid = super->hdr.fsid;
|
||||
greet.format_hash = super->format_hash;
|
||||
greet.server_term = cpu_to_le64(client->server_term);
|
||||
greet.unmount_barrier = cpu_to_le64(client->greeting_umb);
|
||||
greet.rid = cpu_to_le64(sbi->rid);
|
||||
greet.flags = 0;
|
||||
if (client->sending_farewell)
|
||||
greet.flags |= cpu_to_le64(SCOUTFS_NET_GREETING_FLAG_FAREWELL);
|
||||
if (am_voter)
|
||||
greet.flags |= cpu_to_le64(SCOUTFS_NET_GREETING_FLAG_VOTER);
|
||||
|
||||
ret = scoutfs_net_submit_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_GREETING,
|
||||
&greet, sizeof(greet),
|
||||
client_greeting, NULL, NULL);
|
||||
if (ret)
|
||||
scoutfs_net_shutdown(sb, client->conn);
|
||||
out:
|
||||
kfree(super);
|
||||
|
||||
/* always have a small delay before retrying to avoid storms */
|
||||
if (ret && !atomic_read(&client->shutting_down))
|
||||
queue_delayed_work(client->workq, &client->connect_dwork,
|
||||
msecs_to_jiffies(CLIENT_CONNECT_DELAY_MS));
|
||||
}
|
||||
|
||||
static scoutfs_net_request_t client_req_funcs[] = {
|
||||
[SCOUTFS_NET_CMD_LOCK] = client_lock,
|
||||
[SCOUTFS_NET_CMD_LOCK_RECOVER] = client_lock_recover,
|
||||
};
|
||||
|
||||
/*
|
||||
* Called when either a connect attempt or established connection times
|
||||
* out and fails.
|
||||
*/
|
||||
static void client_notify_down(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn, void *info,
|
||||
u64 rid)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
|
||||
if (!atomic_read(&client->shutting_down))
|
||||
queue_delayed_work(client->workq, &client->connect_dwork, 0);
|
||||
}
|
||||
|
||||
int scoutfs_client_setup(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct client_info *client;
|
||||
int ret;
|
||||
|
||||
client = kzalloc(sizeof(struct client_info), GFP_KERNEL);
|
||||
if (!client) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
sbi->client_info = client;
|
||||
|
||||
client->sb = sb;
|
||||
atomic_set(&client->shutting_down, 0);
|
||||
INIT_DELAYED_WORK(&client->connect_dwork,
|
||||
scoutfs_client_connect_worker);
|
||||
init_completion(&client->farewell_comp);
|
||||
|
||||
client->conn = scoutfs_net_alloc_conn(sb, NULL, client_notify_down, 0,
|
||||
client_req_funcs, "client");
|
||||
if (!client->conn) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
client->workq = alloc_workqueue("scoutfs_client_workq", WQ_UNBOUND, 1);
|
||||
if (!client->workq) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
queue_delayed_work(client->workq, &client->connect_dwork, 0);
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
if (ret)
|
||||
scoutfs_client_destroy(sb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Once we get a response from the server we can shut down */
|
||||
static int client_farewell_response(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
void *resp, unsigned int resp_len,
|
||||
int error, void *data)
|
||||
{
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
|
||||
if (resp_len != 0)
|
||||
return -EINVAL;
|
||||
|
||||
client->farewell_error = error;
|
||||
complete(&client->farewell_comp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* There must be no more callers to the client request functions by the
|
||||
* time we get here.
|
||||
*
|
||||
* If we've connected to a server then we send them a farewell request
|
||||
* so that they don't wait for us to reconnect and trigger a timeout.
|
||||
*
|
||||
* This decision is a little racy. The server considers us connected
|
||||
* when it records a persistent record of our rid as it processes our
|
||||
* greeting. We can disconnect before receiving the greeting response
|
||||
* and leave without sending a farewell. So given that awkward initial
|
||||
* race, we also have a bit of a race where we just test the server_term
|
||||
* to see if we've ever gotten a greeting reply from any server. We
|
||||
* don't try to synchronize with pending connection attempts.
|
||||
*
|
||||
* The consequences of aborting a mount at just the wrong time and
|
||||
* disconnecting without the farewell handshake depend on what the
|
||||
* server does to timed out clients. At best it'll spit out a warning
|
||||
* message that a client disconnected but it won't fence us if we didn't
|
||||
* have any persistent state.
|
||||
*/
|
||||
void scoutfs_client_destroy(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct client_info *client = SCOUTFS_SB(sb)->client_info;
|
||||
struct scoutfs_net_connection *conn;
|
||||
int ret;
|
||||
|
||||
if (client == NULL)
|
||||
return;
|
||||
|
||||
if (client->server_term != 0) {
|
||||
client->sending_farewell = true;
|
||||
ret = scoutfs_net_submit_request(sb, client->conn,
|
||||
SCOUTFS_NET_CMD_FAREWELL,
|
||||
NULL, 0,
|
||||
client_farewell_response,
|
||||
NULL, NULL);
|
||||
if (ret == 0) {
|
||||
ret = wait_for_completion_interruptible(
|
||||
&client->farewell_comp);
|
||||
if (ret == 0)
|
||||
ret = client->farewell_error;
|
||||
}
|
||||
if (ret) {
|
||||
scoutfs_inc_counter(sb, client_farewell_error);
|
||||
scoutfs_warn(sb, "client saw farewell error %d, server might see client connection time out", ret);
|
||||
}
|
||||
}
|
||||
|
||||
/* stop notify_down from queueing connect work */
|
||||
atomic_set(&client->shutting_down, 1);
|
||||
|
||||
/* make sure worker isn't using the conn */
|
||||
cancel_delayed_work_sync(&client->connect_dwork);
|
||||
|
||||
/* make racing conn use explode */
|
||||
conn = client->conn;
|
||||
client->conn = NULL;
|
||||
scoutfs_net_free_conn(sb, conn);
|
||||
|
||||
if (client->workq)
|
||||
destroy_workqueue(client->workq);
|
||||
kfree(client);
|
||||
sbi->client_info = NULL;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef _SCOUTFS_CLIENT_H_
|
||||
#define _SCOUTFS_CLIENT_H_
|
||||
|
||||
int scoutfs_client_alloc_inodes(struct super_block *sb, u64 count,
|
||||
u64 *ino, u64 *nr);
|
||||
int scoutfs_client_get_log_trees(struct super_block *sb,
|
||||
struct scoutfs_log_trees *lt);
|
||||
int scoutfs_client_commit_log_trees(struct super_block *sb,
|
||||
struct scoutfs_log_trees *lt);
|
||||
int scoutfs_client_get_roots(struct super_block *sb,
|
||||
struct scoutfs_net_roots *roots);
|
||||
u64 *scoutfs_client_bulk_alloc(struct super_block *sb);
|
||||
int scoutfs_client_advance_seq(struct super_block *sb, u64 *seq);
|
||||
int scoutfs_client_get_last_seq(struct super_block *sb, u64 *seq);
|
||||
int scoutfs_client_lock_request(struct super_block *sb,
|
||||
struct scoutfs_net_lock *nl);
|
||||
int scoutfs_client_lock_response(struct super_block *sb, u64 net_id,
|
||||
struct scoutfs_net_lock *nl);
|
||||
int scoutfs_client_lock_recover_response(struct super_block *sb, u64 net_id,
|
||||
struct scoutfs_net_lock_recover *nlr);
|
||||
int scoutfs_client_srch_get_compact(struct super_block *sb,
|
||||
struct scoutfs_srch_compact *sc);
|
||||
int scoutfs_client_srch_commit_compact(struct super_block *sb,
|
||||
struct scoutfs_srch_compact *res);
|
||||
|
||||
int scoutfs_client_setup(struct super_block *sb);
|
||||
void scoutfs_client_destroy(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef _SCOUTFS_CMP_H_
|
||||
#define _SCOUTFS_CMP_H_
|
||||
|
||||
/*
|
||||
* A generic ternary comparison macro with strict type checking.
|
||||
*/
|
||||
#define scoutfs_cmp(a, b) \
|
||||
({ \
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
int _ret; \
|
||||
\
|
||||
(void) (&_a == &_b); \
|
||||
_ret = _a < _b ? -1 : _a > _b ? 1 : 0; \
|
||||
_ret; \
|
||||
})
|
||||
|
||||
static inline int scoutfs_cmp_u64s(u64 a, u64 b)
|
||||
{
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,315 @@
|
||||
#ifndef _SCOUTFS_COUNT_H_
|
||||
#define _SCOUTFS_COUNT_H_
|
||||
|
||||
/*
|
||||
* Our estimate of the space consumed while dirtying items is based on
|
||||
* the number of items and the size of their values.
|
||||
*
|
||||
* The estimate is still a read-only input to entering the transaction.
|
||||
* We'd like to use it as a clean rhs arg to hold_trans. We define SIC_
|
||||
* functions which return the count struct. This lets us have a single
|
||||
* arg and avoid bugs in initializing and passing in struct pointers
|
||||
* from callers. The internal __count functions are used compose an
|
||||
* estimate out of the sets of items it manipulates. We program in much
|
||||
* clearer C instead of in the preprocessor.
|
||||
*
|
||||
* Compilers are able to collapse the inlines into constants for the
|
||||
* constant estimates.
|
||||
*/
|
||||
|
||||
struct scoutfs_item_count {
|
||||
signed items;
|
||||
signed vals;
|
||||
};
|
||||
|
||||
/* The caller knows exactly what they're doing. */
|
||||
static inline const struct scoutfs_item_count SIC_EXACT(signed items,
|
||||
signed vals)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {
|
||||
.items = items,
|
||||
.vals = vals,
|
||||
};
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocating an inode creates a new set of indexed items.
|
||||
*/
|
||||
static inline void __count_alloc_inode(struct scoutfs_item_count *cnt)
|
||||
{
|
||||
const int nr_indices = SCOUTFS_INODE_INDEX_NR;
|
||||
|
||||
cnt->items += 1 + nr_indices;
|
||||
cnt->vals += sizeof(struct scoutfs_inode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dirtying an inode dirties the inode item and can delete and create
|
||||
* the full set of indexed items.
|
||||
*/
|
||||
static inline void __count_dirty_inode(struct scoutfs_item_count *cnt)
|
||||
{
|
||||
const int nr_indices = 2 * SCOUTFS_INODE_INDEX_NR;
|
||||
|
||||
cnt->items += 1 + nr_indices;
|
||||
cnt->vals += sizeof(struct scoutfs_inode);
|
||||
}
|
||||
|
||||
static inline const struct scoutfs_item_count SIC_ALLOC_INODE(void)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
|
||||
__count_alloc_inode(&cnt);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static inline const struct scoutfs_item_count SIC_DIRTY_INODE(void)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
|
||||
__count_dirty_inode(&cnt);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Directory entries are stored in three items.
|
||||
*/
|
||||
static inline void __count_dirents(struct scoutfs_item_count *cnt,
|
||||
unsigned name_len)
|
||||
{
|
||||
cnt->items += 3;
|
||||
cnt->vals += 3 * offsetof(struct scoutfs_dirent, name[name_len]);
|
||||
}
|
||||
|
||||
static inline void __count_sym_target(struct scoutfs_item_count *cnt,
|
||||
unsigned size)
|
||||
{
|
||||
unsigned nr = DIV_ROUND_UP(size, SCOUTFS_MAX_VAL_SIZE);
|
||||
|
||||
cnt->items += nr;
|
||||
cnt->vals += size;
|
||||
}
|
||||
|
||||
static inline void __count_orphan(struct scoutfs_item_count *cnt)
|
||||
{
|
||||
|
||||
cnt->items += 1;
|
||||
}
|
||||
|
||||
static inline void __count_mknod(struct scoutfs_item_count *cnt,
|
||||
unsigned name_len)
|
||||
{
|
||||
__count_alloc_inode(cnt);
|
||||
__count_dirents(cnt, name_len);
|
||||
__count_dirty_inode(cnt);
|
||||
}
|
||||
|
||||
static inline const struct scoutfs_item_count SIC_MKNOD(unsigned name_len)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
|
||||
__count_mknod(&cnt, name_len);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dropping the inode deletes all its items. Potentially enormous numbers
|
||||
* of items (data mapping, xattrs) are deleted in their own transactions.
|
||||
*/
|
||||
static inline const struct scoutfs_item_count SIC_DROP_INODE(int mode,
|
||||
u64 size)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
|
||||
if (S_ISLNK(mode))
|
||||
__count_sym_target(&cnt, size);
|
||||
__count_dirty_inode(&cnt);
|
||||
__count_orphan(&cnt);
|
||||
|
||||
cnt.vals = 0;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static inline const struct scoutfs_item_count SIC_LINK(unsigned name_len)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
|
||||
__count_dirents(&cnt, name_len);
|
||||
__count_dirty_inode(&cnt);
|
||||
__count_dirty_inode(&cnt);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unlink can add orphan items.
|
||||
*/
|
||||
static inline const struct scoutfs_item_count SIC_UNLINK(unsigned name_len)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
|
||||
__count_dirents(&cnt, name_len);
|
||||
__count_dirty_inode(&cnt);
|
||||
__count_dirty_inode(&cnt);
|
||||
__count_orphan(&cnt);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static inline const struct scoutfs_item_count SIC_SYMLINK(unsigned name_len,
|
||||
unsigned size)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
|
||||
__count_mknod(&cnt, name_len);
|
||||
__count_sym_target(&cnt, size);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* This assumes the worst case of a rename between directories that
|
||||
* unlinks an existing target. That'll be worse than the common case
|
||||
* by a few hundred bytes.
|
||||
*/
|
||||
static inline const struct scoutfs_item_count SIC_RENAME(unsigned old_len,
|
||||
unsigned new_len)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
|
||||
/* dirty dirs and inodes */
|
||||
__count_dirty_inode(&cnt);
|
||||
__count_dirty_inode(&cnt);
|
||||
__count_dirty_inode(&cnt);
|
||||
__count_dirty_inode(&cnt);
|
||||
|
||||
/* unlink old and new, link new */
|
||||
__count_dirents(&cnt, old_len);
|
||||
__count_dirents(&cnt, new_len);
|
||||
__count_dirents(&cnt, new_len);
|
||||
|
||||
/* orphan the existing target */
|
||||
__count_orphan(&cnt);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creating an xattr results in a dirty set of items with values that
|
||||
* store the xattr header, name, and value. There's always at least one
|
||||
* item with the header and name. Any previously existing items are
|
||||
* deleted which dirties their key but removes their value. The two
|
||||
* sets of items are indexed by different ids so their items don't
|
||||
* overlap.
|
||||
*/
|
||||
static inline const struct scoutfs_item_count SIC_XATTR_SET(unsigned old_parts,
|
||||
bool creating,
|
||||
unsigned name_len,
|
||||
unsigned size)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
unsigned int new_parts;
|
||||
|
||||
__count_dirty_inode(&cnt);
|
||||
|
||||
if (old_parts)
|
||||
cnt.items += old_parts;
|
||||
|
||||
if (creating) {
|
||||
new_parts = SCOUTFS_XATTR_NR_PARTS(name_len, size);
|
||||
|
||||
cnt.items += new_parts;
|
||||
cnt.vals += sizeof(struct scoutfs_xattr) + name_len + size;
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* write_begin can have to allocate all the blocks in the page and can
|
||||
* have to add a big allocation from the server to do so:
|
||||
* - merge added free extents from the server
|
||||
* - remove a free extent per block
|
||||
* - remove an offline extent for every other block
|
||||
* - add a file extent per block
|
||||
*/
|
||||
static inline const struct scoutfs_item_count SIC_WRITE_BEGIN(void)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
unsigned nr_free = (1 + SCOUTFS_BLOCK_SM_PER_PAGE) * 3;
|
||||
unsigned nr_file = (DIV_ROUND_UP(SCOUTFS_BLOCK_SM_PER_PAGE, 2) +
|
||||
SCOUTFS_BLOCK_SM_PER_PAGE) * 3;
|
||||
|
||||
__count_dirty_inode(&cnt);
|
||||
|
||||
cnt.items += nr_free + nr_file;
|
||||
cnt.vals += nr_file;
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Truncating an extent can:
|
||||
* - delete existing file extent,
|
||||
* - create two surrounding file extents,
|
||||
* - add an offline file extent,
|
||||
* - delete two existing free extents
|
||||
* - create a merged free extent
|
||||
*/
|
||||
static inline const struct scoutfs_item_count
|
||||
SIC_TRUNC_EXTENT(struct inode *inode)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
unsigned int nr_file = 1 + 2 + 1;
|
||||
unsigned int nr_free = (2 + 1) * 2;
|
||||
|
||||
if (inode)
|
||||
__count_dirty_inode(&cnt);
|
||||
|
||||
cnt.items += nr_file + nr_free;
|
||||
cnt.vals += nr_file;
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallocating an extent can, at most:
|
||||
* - allocate from the server: delete two free and insert merged
|
||||
* - free an allocated extent: delete one and create two split
|
||||
* - remove an unallocated file extent: delete one and create two split
|
||||
* - add an fallocated flie extent: delete two and inset one merged
|
||||
*/
|
||||
static inline const struct scoutfs_item_count SIC_FALLOCATE_ONE(void)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
unsigned int nr_free = ((1 + 2) * 2) * 2;
|
||||
unsigned int nr_file = (1 + 2) * 2;
|
||||
|
||||
__count_dirty_inode(&cnt);
|
||||
|
||||
cnt.items += nr_free + nr_file;
|
||||
cnt.vals += nr_file;
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* ioc_setattr_more can dirty the inode and add a single offline extent.
|
||||
*/
|
||||
static inline const struct scoutfs_item_count SIC_SETATTR_MORE(void)
|
||||
{
|
||||
struct scoutfs_item_count cnt = {0,};
|
||||
|
||||
__count_dirty_inode(&cnt);
|
||||
|
||||
cnt.items++;
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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/slab.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/percpu_counter.h>
|
||||
|
||||
#include "super.h"
|
||||
#include "sysfs.h"
|
||||
#include "counters.h"
|
||||
|
||||
/*
|
||||
* Maintain simple percpu counters which are always ticking. sysfs
|
||||
* makes this a whole lot more noisy than it needs to be.
|
||||
*/
|
||||
|
||||
#undef EXPAND_COUNTER
|
||||
#define EXPAND_COUNTER(which) { .name = __stringify(which), .mode = 0644 },
|
||||
static struct attribute scoutfs_counter_attrs[] = {
|
||||
EXPAND_EACH_COUNTER
|
||||
};
|
||||
|
||||
/* zero BSS and + 1 makes this null terminated */
|
||||
#define NR_ATTRS ARRAY_SIZE(scoutfs_counter_attrs)
|
||||
static struct attribute *scoutfs_counter_attr_ptrs[NR_ATTRS + 1];
|
||||
|
||||
static ssize_t scoutfs_counter_attr_show(struct kobject *kobj,
|
||||
struct attribute *attr, char *buf)
|
||||
{
|
||||
struct scoutfs_counters *counters;
|
||||
struct percpu_counter *pcpu;
|
||||
size_t index;
|
||||
|
||||
/* use the index in the _attrs array to discover the pcpu pointer */
|
||||
counters = container_of(kobj, struct scoutfs_counters, kobj);
|
||||
index = attr - scoutfs_counter_attrs;
|
||||
pcpu = &counters->FIRST_COUNTER + index;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%lld\n", percpu_counter_sum(pcpu));
|
||||
}
|
||||
|
||||
static void scoutfs_counters_kobj_release(struct kobject *kobj)
|
||||
{
|
||||
struct scoutfs_counters *counters;
|
||||
|
||||
counters = container_of(kobj, struct scoutfs_counters, kobj);
|
||||
|
||||
complete(&counters->comp);
|
||||
}
|
||||
|
||||
static const struct sysfs_ops scoutfs_counter_attr_ops = {
|
||||
.show = scoutfs_counter_attr_show,
|
||||
};
|
||||
|
||||
static struct kobj_type scoutfs_counters_ktype = {
|
||||
.default_attrs = scoutfs_counter_attr_ptrs,
|
||||
.sysfs_ops = &scoutfs_counter_attr_ops,
|
||||
.release = scoutfs_counters_kobj_release,
|
||||
};
|
||||
|
||||
int scoutfs_setup_counters(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_counters *counters;
|
||||
struct percpu_counter *pcpu;
|
||||
int ret;
|
||||
|
||||
counters = kzalloc(sizeof(struct scoutfs_counters), GFP_KERNEL);
|
||||
if (!counters)
|
||||
return -ENOMEM;
|
||||
sbi->counters = counters;
|
||||
|
||||
scoutfs_foreach_counter(sb, pcpu) {
|
||||
ret = percpu_counter_init(pcpu, 0, GFP_KERNEL);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
init_completion(&counters->comp);
|
||||
ret = kobject_init_and_add(&counters->kobj, &scoutfs_counters_ktype,
|
||||
scoutfs_sysfs_sb_dir(sb), "counters");
|
||||
out:
|
||||
if (ret) {
|
||||
/* tear down partial to avoid destroying null kobjs */
|
||||
scoutfs_foreach_counter(sb, pcpu)
|
||||
percpu_counter_destroy(pcpu);
|
||||
kfree(counters);
|
||||
sbi->counters = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void scoutfs_destroy_counters(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_counters *counters = sbi->counters;
|
||||
struct percpu_counter *pcpu;
|
||||
|
||||
/* this only destroys fully initialized counters */
|
||||
if (!counters)
|
||||
return;
|
||||
|
||||
kobject_del(&counters->kobj);
|
||||
kobject_put(&counters->kobj);
|
||||
wait_for_completion(&counters->comp);
|
||||
|
||||
scoutfs_foreach_counter(sb, pcpu)
|
||||
percpu_counter_destroy(pcpu);
|
||||
|
||||
kfree(counters);
|
||||
sbi->counters = NULL;
|
||||
}
|
||||
|
||||
void __init scoutfs_init_counters(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* not ARRAY_SIZE because that would clobber null term */
|
||||
for (i = 0; i < NR_ATTRS; i++)
|
||||
scoutfs_counter_attr_ptrs[i] = &scoutfs_counter_attrs[i];
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
#ifndef _SCOUTFS_COUNTERS_H_
|
||||
#define _SCOUTFS_COUNTERS_H_
|
||||
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/percpu_counter.h>
|
||||
|
||||
#include "super.h"
|
||||
|
||||
/*
|
||||
* We only have to define each counter here and it'll be enumerated in
|
||||
* other places by this macro. Don't forget to update LAST_COUNTER.
|
||||
*/
|
||||
#define EXPAND_EACH_COUNTER \
|
||||
EXPAND_COUNTER(alloc_alloc_data) \
|
||||
EXPAND_COUNTER(alloc_alloc_meta) \
|
||||
EXPAND_COUNTER(alloc_free_data) \
|
||||
EXPAND_COUNTER(alloc_free_meta) \
|
||||
EXPAND_COUNTER(alloc_list_avail_lo) \
|
||||
EXPAND_COUNTER(alloc_list_freed_hi) \
|
||||
EXPAND_COUNTER(alloc_move) \
|
||||
EXPAND_COUNTER(alloc_moved_extent) \
|
||||
EXPAND_COUNTER(alloc_stale_cached_list_block) \
|
||||
EXPAND_COUNTER(block_cache_access) \
|
||||
EXPAND_COUNTER(block_cache_alloc_failure) \
|
||||
EXPAND_COUNTER(block_cache_alloc_page_order) \
|
||||
EXPAND_COUNTER(block_cache_alloc_virt) \
|
||||
EXPAND_COUNTER(block_cache_end_io_error) \
|
||||
EXPAND_COUNTER(block_cache_forget) \
|
||||
EXPAND_COUNTER(block_cache_free) \
|
||||
EXPAND_COUNTER(block_cache_invalidate) \
|
||||
EXPAND_COUNTER(block_cache_lru_move) \
|
||||
EXPAND_COUNTER(block_cache_shrink) \
|
||||
EXPAND_COUNTER(btree_compact_values) \
|
||||
EXPAND_COUNTER(btree_compact_values_enomem) \
|
||||
EXPAND_COUNTER(btree_delete) \
|
||||
EXPAND_COUNTER(btree_dirty) \
|
||||
EXPAND_COUNTER(btree_force) \
|
||||
EXPAND_COUNTER(btree_join) \
|
||||
EXPAND_COUNTER(btree_insert) \
|
||||
EXPAND_COUNTER(btree_leaf_item_hash_search) \
|
||||
EXPAND_COUNTER(btree_lookup) \
|
||||
EXPAND_COUNTER(btree_next) \
|
||||
EXPAND_COUNTER(btree_prev) \
|
||||
EXPAND_COUNTER(btree_read_error) \
|
||||
EXPAND_COUNTER(btree_split) \
|
||||
EXPAND_COUNTER(btree_stale_read) \
|
||||
EXPAND_COUNTER(btree_update) \
|
||||
EXPAND_COUNTER(btree_walk) \
|
||||
EXPAND_COUNTER(btree_walk_restart) \
|
||||
EXPAND_COUNTER(client_farewell_error) \
|
||||
EXPAND_COUNTER(corrupt_btree_block_level) \
|
||||
EXPAND_COUNTER(corrupt_btree_no_child_ref) \
|
||||
EXPAND_COUNTER(corrupt_dirent_backref_name_len) \
|
||||
EXPAND_COUNTER(corrupt_dirent_name_len) \
|
||||
EXPAND_COUNTER(corrupt_dirent_readdir_name_len) \
|
||||
EXPAND_COUNTER(corrupt_inode_block_counts) \
|
||||
EXPAND_COUNTER(corrupt_symlink_inode_size) \
|
||||
EXPAND_COUNTER(corrupt_symlink_missing_item) \
|
||||
EXPAND_COUNTER(corrupt_symlink_not_null_term) \
|
||||
EXPAND_COUNTER(dentry_revalidate_error) \
|
||||
EXPAND_COUNTER(dentry_revalidate_invalid) \
|
||||
EXPAND_COUNTER(dentry_revalidate_locked) \
|
||||
EXPAND_COUNTER(dentry_revalidate_orphan) \
|
||||
EXPAND_COUNTER(dentry_revalidate_rcu) \
|
||||
EXPAND_COUNTER(dentry_revalidate_root) \
|
||||
EXPAND_COUNTER(dentry_revalidate_valid) \
|
||||
EXPAND_COUNTER(dir_backref_excessive_retries) \
|
||||
EXPAND_COUNTER(ext_op_insert) \
|
||||
EXPAND_COUNTER(ext_op_next) \
|
||||
EXPAND_COUNTER(ext_op_remove) \
|
||||
EXPAND_COUNTER(forest_bloom_fail) \
|
||||
EXPAND_COUNTER(forest_bloom_pass) \
|
||||
EXPAND_COUNTER(forest_read_items) \
|
||||
EXPAND_COUNTER(forest_roots_next_hint) \
|
||||
EXPAND_COUNTER(forest_set_bloom_bits) \
|
||||
EXPAND_COUNTER(item_clear_dirty) \
|
||||
EXPAND_COUNTER(item_create) \
|
||||
EXPAND_COUNTER(item_delete) \
|
||||
EXPAND_COUNTER(item_dirty) \
|
||||
EXPAND_COUNTER(item_invalidate) \
|
||||
EXPAND_COUNTER(item_invalidate_page) \
|
||||
EXPAND_COUNTER(item_lookup) \
|
||||
EXPAND_COUNTER(item_mark_dirty) \
|
||||
EXPAND_COUNTER(item_next) \
|
||||
EXPAND_COUNTER(item_page_accessed) \
|
||||
EXPAND_COUNTER(item_page_alloc) \
|
||||
EXPAND_COUNTER(item_page_clear_dirty) \
|
||||
EXPAND_COUNTER(item_page_compact) \
|
||||
EXPAND_COUNTER(item_page_free) \
|
||||
EXPAND_COUNTER(item_page_lru_add) \
|
||||
EXPAND_COUNTER(item_page_lru_remove) \
|
||||
EXPAND_COUNTER(item_page_mark_dirty) \
|
||||
EXPAND_COUNTER(item_page_rbtree_walk) \
|
||||
EXPAND_COUNTER(item_page_split) \
|
||||
EXPAND_COUNTER(item_pcpu_add_replaced) \
|
||||
EXPAND_COUNTER(item_pcpu_page_hit) \
|
||||
EXPAND_COUNTER(item_pcpu_page_miss) \
|
||||
EXPAND_COUNTER(item_pcpu_page_miss_keys) \
|
||||
EXPAND_COUNTER(item_read_pages_split) \
|
||||
EXPAND_COUNTER(item_shrink_page) \
|
||||
EXPAND_COUNTER(item_shrink_page_dirty) \
|
||||
EXPAND_COUNTER(item_shrink_page_reader) \
|
||||
EXPAND_COUNTER(item_shrink_page_trylock) \
|
||||
EXPAND_COUNTER(item_update) \
|
||||
EXPAND_COUNTER(item_write_dirty) \
|
||||
EXPAND_COUNTER(lock_alloc) \
|
||||
EXPAND_COUNTER(lock_free) \
|
||||
EXPAND_COUNTER(lock_grace_extended) \
|
||||
EXPAND_COUNTER(lock_grace_set) \
|
||||
EXPAND_COUNTER(lock_grace_wait) \
|
||||
EXPAND_COUNTER(lock_grant_request) \
|
||||
EXPAND_COUNTER(lock_grant_response) \
|
||||
EXPAND_COUNTER(lock_grant_work) \
|
||||
EXPAND_COUNTER(lock_invalidate_coverage) \
|
||||
EXPAND_COUNTER(lock_invalidate_inode) \
|
||||
EXPAND_COUNTER(lock_invalidate_request) \
|
||||
EXPAND_COUNTER(lock_invalidate_response) \
|
||||
EXPAND_COUNTER(lock_invalidate_sync) \
|
||||
EXPAND_COUNTER(lock_invalidate_work) \
|
||||
EXPAND_COUNTER(lock_lock) \
|
||||
EXPAND_COUNTER(lock_lock_error) \
|
||||
EXPAND_COUNTER(lock_nonblock_eagain) \
|
||||
EXPAND_COUNTER(lock_recover_request) \
|
||||
EXPAND_COUNTER(lock_shrink_attempted) \
|
||||
EXPAND_COUNTER(lock_shrink_aborted) \
|
||||
EXPAND_COUNTER(lock_shrink_work) \
|
||||
EXPAND_COUNTER(lock_unlock) \
|
||||
EXPAND_COUNTER(lock_wait) \
|
||||
EXPAND_COUNTER(net_dropped_response) \
|
||||
EXPAND_COUNTER(net_send_bytes) \
|
||||
EXPAND_COUNTER(net_send_error) \
|
||||
EXPAND_COUNTER(net_send_messages) \
|
||||
EXPAND_COUNTER(net_recv_bytes) \
|
||||
EXPAND_COUNTER(net_recv_dropped_duplicate) \
|
||||
EXPAND_COUNTER(net_recv_error) \
|
||||
EXPAND_COUNTER(net_recv_invalid_message) \
|
||||
EXPAND_COUNTER(net_recv_messages) \
|
||||
EXPAND_COUNTER(net_unknown_request) \
|
||||
EXPAND_COUNTER(quorum_cycle) \
|
||||
EXPAND_COUNTER(quorum_elected_leader) \
|
||||
EXPAND_COUNTER(quorum_election_timeout) \
|
||||
EXPAND_COUNTER(quorum_failure) \
|
||||
EXPAND_COUNTER(quorum_read_block) \
|
||||
EXPAND_COUNTER(quorum_read_block_error) \
|
||||
EXPAND_COUNTER(quorum_read_invalid_block) \
|
||||
EXPAND_COUNTER(quorum_saw_super_leader) \
|
||||
EXPAND_COUNTER(quorum_timedout) \
|
||||
EXPAND_COUNTER(quorum_write_block) \
|
||||
EXPAND_COUNTER(quorum_write_block_error) \
|
||||
EXPAND_COUNTER(quorum_fenced) \
|
||||
EXPAND_COUNTER(server_commit_hold) \
|
||||
EXPAND_COUNTER(server_commit_queue) \
|
||||
EXPAND_COUNTER(server_commit_worker) \
|
||||
EXPAND_COUNTER(srch_add_entry) \
|
||||
EXPAND_COUNTER(srch_compact_dirty_block) \
|
||||
EXPAND_COUNTER(srch_compact_entry) \
|
||||
EXPAND_COUNTER(srch_compact_flush) \
|
||||
EXPAND_COUNTER(srch_compact_log_page) \
|
||||
EXPAND_COUNTER(srch_compact_removed_entry) \
|
||||
EXPAND_COUNTER(srch_inconsistent_ref) \
|
||||
EXPAND_COUNTER(srch_rotate_log) \
|
||||
EXPAND_COUNTER(srch_search_log) \
|
||||
EXPAND_COUNTER(srch_search_log_block) \
|
||||
EXPAND_COUNTER(srch_search_retry_empty) \
|
||||
EXPAND_COUNTER(srch_search_sorted) \
|
||||
EXPAND_COUNTER(srch_search_sorted_block) \
|
||||
EXPAND_COUNTER(srch_search_stale_eio) \
|
||||
EXPAND_COUNTER(srch_search_stale_retry) \
|
||||
EXPAND_COUNTER(srch_search_xattrs) \
|
||||
EXPAND_COUNTER(srch_read_stale) \
|
||||
EXPAND_COUNTER(statfs) \
|
||||
EXPAND_COUNTER(trans_commit_data_alloc_low) \
|
||||
EXPAND_COUNTER(trans_commit_dirty_meta_full) \
|
||||
EXPAND_COUNTER(trans_commit_fsync) \
|
||||
EXPAND_COUNTER(trans_commit_meta_alloc_low) \
|
||||
EXPAND_COUNTER(trans_commit_sync_fs) \
|
||||
EXPAND_COUNTER(trans_commit_timer) \
|
||||
EXPAND_COUNTER(trans_commit_written)
|
||||
|
||||
#define FIRST_COUNTER alloc_alloc_data
|
||||
#define LAST_COUNTER trans_commit_written
|
||||
|
||||
#undef EXPAND_COUNTER
|
||||
#define EXPAND_COUNTER(which) struct percpu_counter which;
|
||||
|
||||
struct scoutfs_counters {
|
||||
/* $sysfs/fs/scoutfs/$id/counters/ */
|
||||
struct kobject kobj;
|
||||
struct completion comp;
|
||||
|
||||
EXPAND_EACH_COUNTER
|
||||
};
|
||||
|
||||
#define scoutfs_foreach_counter(sb, pcpu) \
|
||||
for (pcpu = &SCOUTFS_SB(sb)->counters->FIRST_COUNTER; \
|
||||
pcpu <= &SCOUTFS_SB(sb)->counters->LAST_COUNTER; \
|
||||
pcpu++)
|
||||
|
||||
/*
|
||||
* We always read with _sum, we have no use for the shared count and
|
||||
* certainly don't want to pay the cost of a shared lock to update it.
|
||||
* The default batch of 32 make counter increments show up significantly
|
||||
* in profiles.
|
||||
*/
|
||||
#define SCOUTFS_PCPU_COUNTER_BATCH (1 << 30)
|
||||
|
||||
#define scoutfs_inc_counter(sb, which) \
|
||||
__percpu_counter_add(&SCOUTFS_SB(sb)->counters->which, 1, \
|
||||
SCOUTFS_PCPU_COUNTER_BATCH)
|
||||
|
||||
#define scoutfs_add_counter(sb, which, cnt) \
|
||||
__percpu_counter_add(&SCOUTFS_SB(sb)->counters->which, cnt, \
|
||||
SCOUTFS_PCPU_COUNTER_BATCH)
|
||||
|
||||
void __init scoutfs_init_counters(void);
|
||||
int scoutfs_setup_counters(struct super_block *sb);
|
||||
void scoutfs_destroy_counters(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
+1545
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
||||
#ifndef _SCOUTFS_DATA_H_
|
||||
#define _SCOUTFS_DATA_H_
|
||||
|
||||
struct scoutfs_lock;
|
||||
struct scoutfs_ioctl_data_waiting_entry;
|
||||
|
||||
struct scoutfs_data_wait_root {
|
||||
spinlock_t lock;
|
||||
struct rb_root root;
|
||||
};
|
||||
|
||||
#define DECLARE_DATA_WAIT_ROOT(sb, nm) \
|
||||
struct scoutfs_data_wait_root *nm = &SCOUTFS_SB(sb)->data_wait_root
|
||||
|
||||
struct scoutfs_data_waitq {
|
||||
atomic64_t changed;
|
||||
wait_queue_head_t waitq;
|
||||
};
|
||||
|
||||
#define DECLARE_DATA_WAITQ(in, nm) \
|
||||
struct scoutfs_data_waitq *nm = &SCOUTFS_I(in)->data_waitq
|
||||
|
||||
/*
|
||||
* Tasks can wait for data extents.
|
||||
*/
|
||||
struct scoutfs_data_wait {
|
||||
struct rb_node node;
|
||||
u64 chg;
|
||||
u64 ino;
|
||||
u64 iblock;
|
||||
long err;
|
||||
u8 op;
|
||||
};
|
||||
|
||||
#define DECLARE_DATA_WAIT(nm) \
|
||||
struct scoutfs_data_wait nm = { \
|
||||
.node.__rb_parent_color = (unsigned long)(&nm.node), \
|
||||
.err = 0, \
|
||||
}
|
||||
|
||||
struct scoutfs_traced_extent {
|
||||
u64 iblock;
|
||||
u64 count;
|
||||
u64 blkno;
|
||||
u8 flags;
|
||||
};
|
||||
|
||||
extern const struct address_space_operations scoutfs_file_aops;
|
||||
extern const struct file_operations scoutfs_file_fops;
|
||||
struct scoutfs_alloc;
|
||||
struct scoutfs_block_writer;
|
||||
|
||||
int scoutfs_data_truncate_items(struct super_block *sb, struct inode *inode,
|
||||
u64 ino, u64 iblock, u64 last, bool offline,
|
||||
struct scoutfs_lock *lock);
|
||||
int scoutfs_data_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
|
||||
u64 start, u64 len);
|
||||
long scoutfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len);
|
||||
int scoutfs_data_init_offline_extent(struct inode *inode, u64 size,
|
||||
struct scoutfs_lock *lock);
|
||||
|
||||
int scoutfs_data_wait_check(struct inode *inode, loff_t pos, loff_t len,
|
||||
u8 sef, u8 op, struct scoutfs_data_wait *ow,
|
||||
struct scoutfs_lock *lock);
|
||||
int scoutfs_data_wait_check_iov(struct inode *inode, const struct iovec *iov,
|
||||
unsigned long nr_segs, loff_t pos, u8 sef,
|
||||
u8 op, struct scoutfs_data_wait *ow,
|
||||
struct scoutfs_lock *lock);
|
||||
bool scoutfs_data_wait_found(struct scoutfs_data_wait *ow);
|
||||
int scoutfs_data_wait(struct inode *inode,
|
||||
struct scoutfs_data_wait *ow);
|
||||
void scoutfs_data_wait_changed(struct inode *inode);
|
||||
long scoutfs_data_wait_err(struct inode *inode, u64 sblock, u64 eblock, u64 op,
|
||||
long err);
|
||||
int scoutfs_data_waiting(struct super_block *sb, u64 ino, u64 iblock,
|
||||
struct scoutfs_ioctl_data_waiting_entry *dwe,
|
||||
unsigned int nr);
|
||||
|
||||
void scoutfs_data_init_btrees(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_log_trees *lt);
|
||||
void scoutfs_data_get_btrees(struct super_block *sb,
|
||||
struct scoutfs_log_trees *lt);
|
||||
int scoutfs_data_prepare_commit(struct super_block *sb);
|
||||
u64 scoutfs_data_alloc_free_bytes(struct super_block *sb);
|
||||
|
||||
int scoutfs_data_setup(struct super_block *sb);
|
||||
void scoutfs_data_destroy(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
+1805
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
#ifndef _SCOUTFS_DIR_H_
|
||||
#define _SCOUTFS_DIR_H_
|
||||
|
||||
#include "format.h"
|
||||
#include "lock.h"
|
||||
|
||||
extern const struct file_operations scoutfs_dir_fops;
|
||||
extern const struct inode_operations scoutfs_dir_iops;
|
||||
extern const struct inode_operations scoutfs_symlink_iops;
|
||||
|
||||
struct scoutfs_link_backref_entry {
|
||||
struct list_head head;
|
||||
u64 dir_ino;
|
||||
u64 dir_pos;
|
||||
u16 name_len;
|
||||
struct scoutfs_dirent dent;
|
||||
/* the full name is allocated and stored in dent.name[0] */
|
||||
};
|
||||
|
||||
int scoutfs_dir_get_backref_path(struct super_block *sb, u64 ino, u64 dir_ino,
|
||||
u64 dir_pos, struct list_head *list);
|
||||
void scoutfs_dir_free_backref_path(struct super_block *sb,
|
||||
struct list_head *list);
|
||||
|
||||
int scoutfs_dir_add_next_linkref(struct super_block *sb, u64 ino,
|
||||
u64 dir_ino, u64 dir_pos,
|
||||
struct list_head *list);
|
||||
|
||||
int scoutfs_symlink_drop(struct super_block *sb, u64 ino,
|
||||
struct scoutfs_lock *lock, u64 i_size);
|
||||
|
||||
int scoutfs_dir_init(void);
|
||||
void scoutfs_dir_exit(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef _SCOUTFS_ENDIAN_SWAP_H_
|
||||
#define _SCOUTFS_ENDIAN_SWAP_H_
|
||||
|
||||
#define le64_to_be64(x) cpu_to_be64(le64_to_cpu(x))
|
||||
#define le32_to_be32(x) cpu_to_be32(le32_to_cpu(x))
|
||||
#define le16_to_be16(x) cpu_to_be16(le16_to_cpu(x))
|
||||
|
||||
#define be64_to_le64(x) cpu_to_le64(be64_to_cpu(x))
|
||||
#define be32_to_le32(x) cpu_to_le32(be32_to_cpu(x))
|
||||
#define be16_to_le16(x) cpu_to_le16(be16_to_cpu(x))
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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/slab.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/exportfs.h>
|
||||
|
||||
#include "export.h"
|
||||
#include "inode.h"
|
||||
#include "dir.h"
|
||||
#include "format.h"
|
||||
#include "scoutfs_trace.h"
|
||||
|
||||
/* describe the length of the fileid type in terms of number of u32's used. */
|
||||
static int scoutfs_fileid_len(int fh_type)
|
||||
{
|
||||
switch (fh_type) {
|
||||
case FILEID_SCOUTFS:
|
||||
return 2;
|
||||
case FILEID_SCOUTFS_WITH_PARENT:
|
||||
return 4;
|
||||
}
|
||||
return FILEID_INVALID;
|
||||
}
|
||||
|
||||
static bool scoutfs_valid_fileid(int fh_type)
|
||||
{
|
||||
return scoutfs_fileid_len(fh_type) != FILEID_INVALID;
|
||||
}
|
||||
|
||||
static int scoutfs_encode_fh(struct inode *inode, __u32 *fh, int *max_len,
|
||||
struct inode *parent)
|
||||
{
|
||||
struct scoutfs_fid *fid = (struct scoutfs_fid *)fh;
|
||||
int fh_type = FILEID_SCOUTFS;
|
||||
int len;
|
||||
|
||||
if (parent)
|
||||
fh_type = FILEID_SCOUTFS_WITH_PARENT;
|
||||
|
||||
len = scoutfs_fileid_len(fh_type);
|
||||
|
||||
if (*max_len < len) {
|
||||
*max_len = len;
|
||||
return FILEID_INVALID;
|
||||
}
|
||||
*max_len = len;
|
||||
|
||||
fid->ino = cpu_to_le64(scoutfs_ino(inode));
|
||||
if (parent)
|
||||
fid->parent_ino = cpu_to_le64(scoutfs_ino(parent));
|
||||
|
||||
trace_scoutfs_encode_fh(inode->i_sb, fh_type, fid);
|
||||
|
||||
return fh_type;
|
||||
}
|
||||
|
||||
static struct dentry *scoutfs_fh_to_dentry(struct super_block *sb,
|
||||
struct fid *fid, int fh_len,
|
||||
int fh_type)
|
||||
{
|
||||
struct scoutfs_fid *sfid = (struct scoutfs_fid *)fid;
|
||||
struct inode *inode = NULL;
|
||||
|
||||
if (fh_len < scoutfs_fileid_len(fh_type))
|
||||
return NULL;
|
||||
|
||||
trace_scoutfs_fh_to_dentry(sb, fh_type, sfid);
|
||||
|
||||
if (scoutfs_valid_fileid(fh_type))
|
||||
inode = scoutfs_iget(sb, le64_to_cpu(sfid->ino));
|
||||
|
||||
return d_obtain_alias(inode);
|
||||
}
|
||||
|
||||
static struct dentry *scoutfs_fh_to_parent(struct super_block *sb,
|
||||
struct fid *fid, int fh_len,
|
||||
int fh_type)
|
||||
{
|
||||
struct scoutfs_fid *sfid = (struct scoutfs_fid *)fid;
|
||||
struct inode *inode = NULL;
|
||||
|
||||
if (fh_len < scoutfs_fileid_len(fh_type))
|
||||
return NULL;
|
||||
|
||||
trace_scoutfs_fh_to_parent(sb, fh_type, sfid);
|
||||
|
||||
if (scoutfs_valid_fileid(fh_type) &&
|
||||
fh_type == FILEID_SCOUTFS_WITH_PARENT)
|
||||
inode = scoutfs_iget(sb, le64_to_cpu(sfid->parent_ino));
|
||||
|
||||
return d_obtain_alias(inode);
|
||||
}
|
||||
|
||||
static struct dentry *scoutfs_get_parent(struct dentry *child)
|
||||
{
|
||||
struct inode *inode = child->d_inode;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_link_backref_entry *ent;
|
||||
LIST_HEAD(list);
|
||||
int ret;
|
||||
u64 ino;
|
||||
|
||||
ret = scoutfs_dir_add_next_linkref(sb, scoutfs_ino(inode), 0, 0, &list);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
ent = list_first_entry(&list, struct scoutfs_link_backref_entry, head);
|
||||
ino = ent->dir_ino;
|
||||
scoutfs_dir_free_backref_path(sb, &list);
|
||||
trace_scoutfs_get_parent(sb, inode, ino);
|
||||
|
||||
inode = scoutfs_iget(sb, ino);
|
||||
|
||||
return d_obtain_alias(inode);
|
||||
}
|
||||
|
||||
static int scoutfs_get_name(struct dentry *parent, char *name,
|
||||
struct dentry *child)
|
||||
{
|
||||
u64 dir_ino = scoutfs_ino(parent->d_inode);
|
||||
struct scoutfs_link_backref_entry *ent;
|
||||
struct inode *inode = child->d_inode;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
LIST_HEAD(list);
|
||||
int ret;
|
||||
|
||||
ret = scoutfs_dir_add_next_linkref(sb, scoutfs_ino(inode), dir_ino,
|
||||
0, &list);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = -ENOENT;
|
||||
ent = list_first_entry(&list, struct scoutfs_link_backref_entry, head);
|
||||
if (le64_to_cpu(ent->dent.ino) == scoutfs_ino(inode) &&
|
||||
ent->dir_ino == dir_ino &&
|
||||
ent->name_len <= NAME_MAX) {
|
||||
memcpy(name, ent->dent.name, ent->name_len);
|
||||
name[ent->name_len] = '\0';
|
||||
ret = 0;
|
||||
trace_scoutfs_get_name(sb, parent->d_inode, inode, name);
|
||||
}
|
||||
scoutfs_dir_free_backref_path(sb, &list);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const struct export_operations scoutfs_export_ops = {
|
||||
.encode_fh = scoutfs_encode_fh,
|
||||
.fh_to_dentry = scoutfs_fh_to_dentry,
|
||||
.fh_to_parent = scoutfs_fh_to_parent,
|
||||
.get_parent = scoutfs_get_parent,
|
||||
.get_name = scoutfs_get_name,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef _SCOUTFS_EXPORT_H_
|
||||
#define _SCOUTFS_EXPORT_H_
|
||||
|
||||
#include <linux/exportfs.h>
|
||||
|
||||
extern const struct export_operations scoutfs_export_ops;
|
||||
|
||||
#endif
|
||||
+394
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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/fs.h>
|
||||
|
||||
#include "ext.h"
|
||||
#include "counters.h"
|
||||
#include "scoutfs_trace.h"
|
||||
|
||||
/*
|
||||
* Extents are used to track free block regions and to map logical file
|
||||
* regions to device blocks. Extents can be split and merged as
|
||||
* they're modified. These helpers implement all the fiddly extent
|
||||
* manipulations. Callers provide callbacks which implement the actual
|
||||
* storage of extents in either the item cache or btree items.
|
||||
*/
|
||||
|
||||
static void ext_zero(struct scoutfs_extent *ext)
|
||||
{
|
||||
memset(ext, 0, sizeof(struct scoutfs_extent));
|
||||
}
|
||||
|
||||
static bool ext_overlap(struct scoutfs_extent *ext, u64 start, u64 len)
|
||||
{
|
||||
u64 e_end = ext->start + ext->len - 1;
|
||||
u64 end = start + len - 1;
|
||||
|
||||
return !(e_end < start || ext->start > end);
|
||||
}
|
||||
|
||||
static bool ext_inside(u64 start, u64 len, struct scoutfs_extent *out)
|
||||
{
|
||||
u64 in_end = start + len - 1;
|
||||
u64 out_end = out->start + out->len - 1;
|
||||
|
||||
return out->start <= start && out_end >= in_end;
|
||||
}
|
||||
|
||||
/* we only translate mappings when they exist */
|
||||
static inline u64 ext_map_add(u64 map, u64 diff)
|
||||
{
|
||||
return map ? map + diff : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extents can merge if they're logically contiguous, both don't have
|
||||
* mappings or have mappings which are also contiguous, and have
|
||||
* matching flags.
|
||||
*/
|
||||
bool scoutfs_ext_can_merge(struct scoutfs_extent *left,
|
||||
struct scoutfs_extent *right)
|
||||
{
|
||||
return (left->start + left->len == right->start) &&
|
||||
((!left->map && !right->map) ||
|
||||
(left->map + left->len == right->map)) &&
|
||||
(left->flags == right->flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Split an existing extent in to left and right extents by removing
|
||||
* an interior range. The split extents are all zeros if the range
|
||||
* extends to their end of the extent.
|
||||
*/
|
||||
static void ext_split(struct scoutfs_extent *ext, u64 start, u64 len,
|
||||
struct scoutfs_extent *left,
|
||||
struct scoutfs_extent *right)
|
||||
{
|
||||
if (ext->start < start) {
|
||||
left->start = ext->start;
|
||||
left->len = start - ext->start;
|
||||
left->map = ext->map;
|
||||
left->flags = ext->flags;
|
||||
} else {
|
||||
ext_zero(left);
|
||||
}
|
||||
|
||||
if (ext->start + ext->len > start + len) {
|
||||
right->start = start + len;
|
||||
right->len = ext->start + ext->len - right->start;
|
||||
right->map = ext_map_add(ext->map, right->start - ext->start);
|
||||
right->flags = ext->flags;
|
||||
} else {
|
||||
ext_zero(right);
|
||||
}
|
||||
}
|
||||
|
||||
#define op_call(sb, ops, arg, which, args...) \
|
||||
({ \
|
||||
int _ret; \
|
||||
_ret = ops->which(sb, arg, ##args); \
|
||||
scoutfs_inc_counter(sb, ext_op_##which); \
|
||||
trace_scoutfs_ext_op_##which(sb, ##args, _ret); \
|
||||
_ret; \
|
||||
})
|
||||
|
||||
struct extent_changes {
|
||||
struct scoutfs_extent exts[4];
|
||||
bool ins[4];
|
||||
u8 nr;
|
||||
};
|
||||
|
||||
static void add_change(struct extent_changes *chg,
|
||||
struct scoutfs_extent *ext, bool ins)
|
||||
{
|
||||
BUILD_BUG_ON(ARRAY_SIZE(chg->ins) != ARRAY_SIZE(chg->exts));
|
||||
|
||||
if (ext->len) {
|
||||
BUG_ON(chg->nr == ARRAY_SIZE(chg->exts));
|
||||
chg->exts[chg->nr] = *ext;
|
||||
chg->ins[chg->nr] = !!ins;
|
||||
chg->nr++;
|
||||
}
|
||||
}
|
||||
|
||||
static int apply_changes(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, struct extent_changes *chg)
|
||||
{
|
||||
int ret = 0;
|
||||
int err;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < chg->nr; i++) {
|
||||
if (chg->ins[i])
|
||||
ret = op_call(sb, ops, arg, insert, chg->exts[i].start,
|
||||
chg->exts[i].len, chg->exts[i].map,
|
||||
chg->exts[i].flags);
|
||||
else
|
||||
ret = op_call(sb, ops, arg, remove, chg->exts[i].start,
|
||||
chg->exts[i].len, chg->exts[i].map,
|
||||
chg->exts[i].flags);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
while (ret < 0 && --i >= 0) {
|
||||
if (chg->ins[i])
|
||||
err = op_call(sb, ops, arg, remove, chg->exts[i].start,
|
||||
chg->exts[i].len, chg->exts[i].map,
|
||||
chg->exts[i].flags);
|
||||
else
|
||||
err = op_call(sb, ops, arg, insert, chg->exts[i].start,
|
||||
chg->exts[i].len, chg->exts[i].map,
|
||||
chg->exts[i].flags);
|
||||
BUG_ON(err); /* inconsistent */
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scoutfs_ext_next(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, u64 start, u64 len, struct scoutfs_extent *ext)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = op_call(sb, ops, arg, next, start, len, ext);
|
||||
trace_scoutfs_ext_next(sb, start, len, ext, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert the given extent. EINVAL is returned if there's already an existing
|
||||
* overlapping extent. This can merge with its neighbours.
|
||||
*/
|
||||
int scoutfs_ext_insert(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, u64 start, u64 len, u64 map, u8 flags)
|
||||
{
|
||||
struct extent_changes chg = { .nr = 0 };
|
||||
struct scoutfs_extent found;
|
||||
struct scoutfs_extent ins;
|
||||
int ret;
|
||||
|
||||
ins.start = start;
|
||||
ins.len = len;
|
||||
ins.map = map;
|
||||
ins.flags = flags;
|
||||
|
||||
/* find right neighbour and check for overlap */
|
||||
ret = op_call(sb, ops, arg, next, start, 1, &found);
|
||||
if (ret < 0 && ret != -ENOENT)
|
||||
goto out;
|
||||
|
||||
/* inserting extent must not overlap */
|
||||
if (found.len && ext_overlap(&ins, found.start, found.len)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* merge with right if we can */
|
||||
if (found.len && scoutfs_ext_can_merge(&ins, &found)) {
|
||||
ins.len += found.len;
|
||||
add_change(&chg, &found, false);
|
||||
}
|
||||
|
||||
/* see if we can merge with a left neighbour */
|
||||
if (start > 0) {
|
||||
ret = op_call(sb, ops, arg, next, start - 1, 1, &found);
|
||||
if (ret < 0 && ret != -ENOENT)
|
||||
goto out;
|
||||
|
||||
if (ret == 0 && scoutfs_ext_can_merge(&found, &ins)) {
|
||||
ins.start = found.start;
|
||||
ins.map = found.map;
|
||||
ins.len += found.len;
|
||||
add_change(&chg, &found, false);
|
||||
}
|
||||
}
|
||||
|
||||
add_change(&chg, &ins, true);
|
||||
ret = apply_changes(sb, ops, arg, &chg);
|
||||
out:
|
||||
trace_scoutfs_ext_insert(sb, start, len, map, flags, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the given extent. The extent to remove must be found entirely
|
||||
* in an existing extent. If the existing extent is larger then we leave
|
||||
* behind the remaining extent. The existing extent can be split.
|
||||
*/
|
||||
int scoutfs_ext_remove(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, u64 start, u64 len)
|
||||
{
|
||||
struct extent_changes chg = { .nr = 0 };
|
||||
struct scoutfs_extent found;
|
||||
struct scoutfs_extent left;
|
||||
struct scoutfs_extent right;
|
||||
int ret;
|
||||
|
||||
ret = op_call(sb, ops, arg, next, start, 1, &found);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* removed extent must be entirely within found */
|
||||
if (!ext_inside(start, len, &found)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ext_split(&found, start, len, &left, &right);
|
||||
|
||||
add_change(&chg, &found, false);
|
||||
add_change(&chg, &left, true);
|
||||
add_change(&chg, &right, true);
|
||||
|
||||
ret = apply_changes(sb, ops, arg, &chg);
|
||||
out:
|
||||
trace_scoutfs_ext_remove(sb, start, len, 0, 0, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find and remove the next extent, removing only a portion if the
|
||||
* extent is larger than the count. Returns ENOENT if it didn't
|
||||
* find any extents.
|
||||
*
|
||||
* This does not search for merge candidates so it's safe to call with
|
||||
* extents indexed by length.
|
||||
*/
|
||||
int scoutfs_ext_alloc(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, u64 start, u64 len, u64 count,
|
||||
struct scoutfs_extent *ext)
|
||||
{
|
||||
struct extent_changes chg = { .nr = 0 };
|
||||
struct scoutfs_extent found;
|
||||
struct scoutfs_extent ins;
|
||||
int ret;
|
||||
|
||||
ret = op_call(sb, ops, arg, next, start, len, &found);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
add_change(&chg, &found, false);
|
||||
|
||||
if (found.len > count) {
|
||||
ins.start = found.start + count;
|
||||
ins.len = found.len - count;
|
||||
ins.map = ext_map_add(found.map, count);
|
||||
ins.flags = found.flags;
|
||||
|
||||
add_change(&chg, &ins, true);
|
||||
}
|
||||
|
||||
ret = apply_changes(sb, ops, arg, &chg);
|
||||
out:
|
||||
if (ret == 0) {
|
||||
ext->start = found.start;
|
||||
ext->len = min(found.len, count);
|
||||
ext->map = found.map;
|
||||
ext->flags = found.flags;
|
||||
} else {
|
||||
ext_zero(ext);
|
||||
}
|
||||
|
||||
trace_scoutfs_ext_alloc(sb, start, len, count, ext, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the map and flags for an extent region, with the magical property
|
||||
* that extents with map and flags set to 0 are removed.
|
||||
*
|
||||
* If we're modifying an existing extent then the modification must be
|
||||
* fully inside the existing extent. The modification can leave edges
|
||||
* of the extent which need to be inserted. If the modification extends
|
||||
* to the end of the existing extent then we need to check for adjacent
|
||||
* neighbouring extents which might now be able to be merged.
|
||||
*
|
||||
* Inserting a new extent is like the case of modifying the entire
|
||||
* existing extent. We need to check neighbours of the inserted extent
|
||||
* to see if they can be merged.
|
||||
*/
|
||||
int scoutfs_ext_set(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, u64 start, u64 len, u64 map, u8 flags)
|
||||
{
|
||||
struct extent_changes chg = { .nr = 0 };
|
||||
struct scoutfs_extent found;
|
||||
struct scoutfs_extent left;
|
||||
struct scoutfs_extent right;
|
||||
struct scoutfs_extent set;
|
||||
int ret;
|
||||
|
||||
set.start = start;
|
||||
set.len = len;
|
||||
set.map = map;
|
||||
set.flags = flags;
|
||||
|
||||
/* find extent to remove */
|
||||
ret = op_call(sb, ops, arg, next, start, 1, &found);
|
||||
if (ret < 0 && ret != -ENOENT)
|
||||
goto out;
|
||||
|
||||
if (ret == 0 && ext_overlap(&found, start, len)) {
|
||||
/* set extent must be entirely within found */
|
||||
if (!ext_inside(start, len, &found)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
add_change(&chg, &found, false);
|
||||
ext_split(&found, start, len, &left, &right);
|
||||
} else {
|
||||
ext_zero(&found);
|
||||
ext_zero(&left);
|
||||
ext_zero(&right);
|
||||
}
|
||||
|
||||
if (left.len) {
|
||||
/* inserting split left, won't merge */
|
||||
add_change(&chg, &left, true);
|
||||
} else if (start > 0) {
|
||||
ret = op_call(sb, ops, arg, next, start - 1, 1, &left);
|
||||
if (ret < 0 && ret != -ENOENT)
|
||||
goto out;
|
||||
else if (ret == 0 && scoutfs_ext_can_merge(&left, &set)) {
|
||||
/* remove found left, merging */
|
||||
set.start = left.start;
|
||||
set.map = left.map;
|
||||
set.len += left.len;
|
||||
add_change(&chg, &left, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (right.len) {
|
||||
/* inserting split right, won't merge */
|
||||
add_change(&chg, &right, true);
|
||||
} else {
|
||||
ret = op_call(sb, ops, arg, next, start + len, 1, &right);
|
||||
if (ret < 0 && ret != -ENOENT)
|
||||
goto out;
|
||||
else if (ret == 0 && scoutfs_ext_can_merge(&set, &right)) {
|
||||
/* remove found right, merging */
|
||||
set.len += right.len;
|
||||
add_change(&chg, &right, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (set.flags || set.map)
|
||||
add_change(&chg, &set, true);
|
||||
|
||||
ret = apply_changes(sb, ops, arg, &chg);
|
||||
out:
|
||||
trace_scoutfs_ext_set(sb, start, len, map, flags, ret);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef _SCOUTFS_EXT_H_
|
||||
#define _SCOUTFS_EXT_H_
|
||||
|
||||
struct scoutfs_extent {
|
||||
u64 start;
|
||||
u64 len;
|
||||
u64 map;
|
||||
u8 flags;
|
||||
};
|
||||
|
||||
struct scoutfs_ext_ops {
|
||||
int (*next)(struct super_block *sb, void *arg,
|
||||
u64 start, u64 len, struct scoutfs_extent *ext);
|
||||
int (*insert)(struct super_block *sb, void *arg,
|
||||
u64 start, u64 len, u64 map, u8 flags);
|
||||
int (*remove)(struct super_block *sb, void *arg, u64 start, u64 len,
|
||||
u64 map, u8 flags);
|
||||
};
|
||||
|
||||
bool scoutfs_ext_can_merge(struct scoutfs_extent *left,
|
||||
struct scoutfs_extent *right);
|
||||
|
||||
int scoutfs_ext_next(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, u64 start, u64 len, struct scoutfs_extent *ext);
|
||||
int scoutfs_ext_insert(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, u64 start, u64 len, u64 map, u8 flags);
|
||||
int scoutfs_ext_remove(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, u64 start, u64 len);
|
||||
int scoutfs_ext_alloc(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, u64 start, u64 len, u64 limit,
|
||||
struct scoutfs_extent *ext);
|
||||
int scoutfs_ext_set(struct super_block *sb, struct scoutfs_ext_ops *ops,
|
||||
void *arg, u64 start, u64 len, u64 map, u8 flags);
|
||||
|
||||
#endif
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/mpage.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/aio.h>
|
||||
|
||||
#include "format.h"
|
||||
#include "super.h"
|
||||
#include "data.h"
|
||||
#include "scoutfs_trace.h"
|
||||
#include "lock.h"
|
||||
#include "file.h"
|
||||
#include "inode.h"
|
||||
#include "per_task.h"
|
||||
|
||||
/* TODO: Direct I/O, AIO */
|
||||
ssize_t scoutfs_file_aio_read(struct kiocb *iocb, const struct iovec *iov,
|
||||
unsigned long nr_segs, loff_t pos)
|
||||
{
|
||||
struct file *file = iocb->ki_filp;
|
||||
struct inode *inode = file_inode(file);
|
||||
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_lock *inode_lock = NULL;
|
||||
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_ent);
|
||||
DECLARE_DATA_WAIT(dw);
|
||||
int ret;
|
||||
|
||||
retry:
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
|
||||
SCOUTFS_LKF_REFRESH_INODE, inode, &inode_lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, inode_lock)) {
|
||||
/* protect checked extents from stage/release */
|
||||
mutex_lock(&inode->i_mutex);
|
||||
atomic_inc(&inode->i_dio_count);
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
|
||||
ret = scoutfs_data_wait_check_iov(inode, iov, nr_segs, pos,
|
||||
SEF_OFFLINE,
|
||||
SCOUTFS_IOC_DWO_READ,
|
||||
&dw, inode_lock);
|
||||
if (ret != 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = generic_file_aio_read(iocb, iov, nr_segs, pos);
|
||||
|
||||
out:
|
||||
if (scoutfs_per_task_del(&si->pt_data_lock, &pt_ent))
|
||||
inode_dio_done(inode);
|
||||
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_READ);
|
||||
|
||||
if (scoutfs_data_wait_found(&dw)) {
|
||||
ret = scoutfs_data_wait(inode, &dw);
|
||||
if (ret == 0)
|
||||
goto retry;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t scoutfs_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
|
||||
unsigned long nr_segs, loff_t pos)
|
||||
{
|
||||
struct file *file = iocb->ki_filp;
|
||||
struct inode *inode = file_inode(file);
|
||||
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_lock *inode_lock = NULL;
|
||||
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_ent);
|
||||
DECLARE_DATA_WAIT(dw);
|
||||
int ret;
|
||||
|
||||
if (iocb->ki_left == 0) /* Does this even happen? */
|
||||
return 0;
|
||||
|
||||
retry:
|
||||
mutex_lock(&inode->i_mutex);
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_WRITE,
|
||||
SCOUTFS_LKF_REFRESH_INODE, inode, &inode_lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = scoutfs_complete_truncate(inode, inode_lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, inode_lock)) {
|
||||
/* data_version is per inode, whole file must be online */
|
||||
ret = scoutfs_data_wait_check(inode, 0, i_size_read(inode),
|
||||
SEF_OFFLINE,
|
||||
SCOUTFS_IOC_DWO_WRITE,
|
||||
&dw, inode_lock);
|
||||
if (ret != 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* XXX: remove SUID bit */
|
||||
|
||||
ret = __generic_file_aio_write(iocb, iov, nr_segs, &iocb->ki_pos);
|
||||
|
||||
out:
|
||||
scoutfs_per_task_del(&si->pt_data_lock, &pt_ent);
|
||||
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_WRITE);
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
|
||||
if (scoutfs_data_wait_found(&dw)) {
|
||||
ret = scoutfs_data_wait(inode, &dw);
|
||||
if (ret == 0)
|
||||
goto retry;
|
||||
}
|
||||
|
||||
if (ret > 0 || ret == -EIOCBQUEUED) {
|
||||
ssize_t err;
|
||||
|
||||
err = generic_write_sync(file, pos, ret);
|
||||
if (err < 0 && ret > 0)
|
||||
ret = err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scoutfs_permission(struct inode *inode, int mask)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_lock *inode_lock = NULL;
|
||||
int ret;
|
||||
|
||||
if (mask & MAY_NOT_BLOCK)
|
||||
return -ECHILD;
|
||||
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
|
||||
SCOUTFS_LKF_REFRESH_INODE, inode, &inode_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = generic_permission(inode, mask);
|
||||
|
||||
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_READ);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
loff_t scoutfs_file_llseek(struct file *file, loff_t offset, int whence)
|
||||
{
|
||||
struct inode *inode = file->f_mapping->host;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_lock *lock = NULL;
|
||||
int ret = 0;
|
||||
|
||||
switch (whence) {
|
||||
case SEEK_END:
|
||||
case SEEK_DATA:
|
||||
case SEEK_HOLE:
|
||||
/*
|
||||
* These require a lock and inode refresh as they
|
||||
* reference i_size.
|
||||
*
|
||||
* XXX: SEEK_DATA/SEEK_HOLE can search our extent
|
||||
* items instead of relying on generic_file_llseek()
|
||||
* trickery.
|
||||
*/
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
|
||||
SCOUTFS_LKF_REFRESH_INODE, inode,
|
||||
&lock);
|
||||
case SEEK_SET:
|
||||
case SEEK_CUR:
|
||||
/* No lock required, fall through to the generic helper */
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
offset = generic_file_llseek(file, offset, whence);
|
||||
|
||||
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
|
||||
|
||||
return ret ? ret : offset;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef _SCOUTFS_FILE_H_
|
||||
#define _SCOUTFS_FILE_H_
|
||||
|
||||
ssize_t scoutfs_file_aio_read(struct kiocb *iocb, const struct iovec *iov,
|
||||
unsigned long nr_segs, loff_t pos);
|
||||
ssize_t scoutfs_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
|
||||
unsigned long nr_segs, loff_t pos);
|
||||
int scoutfs_permission(struct inode *inode, int mask);
|
||||
loff_t scoutfs_file_llseek(struct file *file, loff_t offset, int whence);
|
||||
|
||||
#endif /* _SCOUTFS_FILE_H_ */
|
||||
@@ -0,0 +1,668 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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/slab.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/random.h>
|
||||
|
||||
#include "super.h"
|
||||
#include "format.h"
|
||||
#include "lock.h"
|
||||
#include "btree.h"
|
||||
#include "client.h"
|
||||
#include "alloc.h"
|
||||
#include "block.h"
|
||||
#include "forest.h"
|
||||
#include "hash.h"
|
||||
#include "srch.h"
|
||||
#include "counters.h"
|
||||
#include "scoutfs_trace.h"
|
||||
|
||||
/*
|
||||
* scoutfs items are stored in a forest of btrees. Each mount writes
|
||||
* items into its own relatively small log btree. Each mount can also
|
||||
* have a few finalized log btrees sitting around that it is no longer
|
||||
* writing to. Finally a much larger core fs btree is the final home
|
||||
* for metadata.
|
||||
*
|
||||
* The log btrees are modified by multiple transactions over time so
|
||||
* there is no consistent ordering relationship between the items in
|
||||
* different btrees. Each item in a log btree stores a version number
|
||||
* for the item. Readers check log btrees for the most recent version
|
||||
* that it should use.
|
||||
*
|
||||
* The item cache reads items in bulk from stable btrees, and writes a
|
||||
* transaction's worth of dirty items into the item log btree.
|
||||
*
|
||||
* Log btrees are typically very sparse. It would be wasteful for
|
||||
* readers to read every log btree looking for an item. Each log btree
|
||||
* contains a bloom filter keyed on the starting key of locks. This
|
||||
* lets lock holders quickly eliminate log trees that cannot contain
|
||||
* keys protected by their lock.
|
||||
*/
|
||||
|
||||
struct forest_info {
|
||||
struct mutex mutex;
|
||||
struct scoutfs_alloc *alloc;
|
||||
struct scoutfs_block_writer *wri;
|
||||
struct scoutfs_log_trees our_log;
|
||||
|
||||
struct mutex srch_mutex;
|
||||
struct scoutfs_srch_file srch_file;
|
||||
struct scoutfs_block *srch_bl;
|
||||
};
|
||||
|
||||
#define DECLARE_FOREST_INFO(sb, name) \
|
||||
struct forest_info *name = SCOUTFS_SB(sb)->forest_info
|
||||
|
||||
struct forest_refs {
|
||||
struct scoutfs_btree_ref fs_ref;
|
||||
struct scoutfs_btree_ref logs_ref;
|
||||
};
|
||||
|
||||
/* initialize some refs that initially aren't equal */
|
||||
#define DECLARE_STALE_TRACKING_SUPER_REFS(a, b) \
|
||||
struct forest_refs a = {{cpu_to_le64(0),}}; \
|
||||
struct forest_refs b = {{cpu_to_le64(1),}}
|
||||
|
||||
struct forest_bloom_nrs {
|
||||
unsigned int nrs[SCOUTFS_FOREST_BLOOM_NRS];
|
||||
};
|
||||
|
||||
static void calc_bloom_nrs(struct forest_bloom_nrs *bloom,
|
||||
struct scoutfs_key *key)
|
||||
{
|
||||
u64 hash;
|
||||
int i;
|
||||
|
||||
BUILD_BUG_ON((SCOUTFS_FOREST_BLOOM_FUNC_BITS *
|
||||
SCOUTFS_FOREST_BLOOM_NRS) > 64);
|
||||
|
||||
hash = scoutfs_hash64(key, sizeof(struct scoutfs_key));
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bloom->nrs); i++) {
|
||||
bloom->nrs[i] = (u32)hash % SCOUTFS_FOREST_BLOOM_BITS;
|
||||
hash >>= SCOUTFS_FOREST_BLOOM_FUNC_BITS;
|
||||
}
|
||||
}
|
||||
|
||||
static struct scoutfs_block *read_bloom_ref(struct super_block *sb,
|
||||
struct scoutfs_btree_ref *ref)
|
||||
{
|
||||
struct scoutfs_block *bl;
|
||||
|
||||
bl = scoutfs_block_read(sb, le64_to_cpu(ref->blkno));
|
||||
if (IS_ERR(bl))
|
||||
return bl;
|
||||
|
||||
if (!scoutfs_block_consistent_ref(sb, bl, ref->seq, ref->blkno,
|
||||
SCOUTFS_BLOCK_MAGIC_BLOOM)) {
|
||||
scoutfs_block_invalidate(sb, bl);
|
||||
scoutfs_block_put(sb, bl);
|
||||
return ERR_PTR(-ESTALE);
|
||||
}
|
||||
|
||||
return bl;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is an unlocked iteration across all the btrees to find a hint at
|
||||
* the next key that the caller could read. It's used to find out what
|
||||
* next key range to lock, presuming you're allowed to only see items
|
||||
* that have been synced. We ask the server for the current roots to
|
||||
* check.
|
||||
*
|
||||
* We don't bother skipping deletion items here. The caller will safely
|
||||
* skip over them when really reading from their locked region and will
|
||||
* call again after them to find the next hint.
|
||||
*
|
||||
* We're reading from stable persistent trees so we don't need to lock
|
||||
* against writers, their writes are cow into free blocks.
|
||||
*/
|
||||
int scoutfs_forest_next_hint(struct super_block *sb, struct scoutfs_key *key,
|
||||
struct scoutfs_key *next)
|
||||
{
|
||||
DECLARE_STALE_TRACKING_SUPER_REFS(prev_refs, refs);
|
||||
struct scoutfs_net_roots roots;
|
||||
struct scoutfs_btree_root item_root;
|
||||
struct scoutfs_log_trees *lt;
|
||||
SCOUTFS_BTREE_ITEM_REF(iref);
|
||||
struct scoutfs_key found;
|
||||
struct scoutfs_key ltk;
|
||||
bool checked_fs;
|
||||
bool have_next;
|
||||
int ret;
|
||||
|
||||
scoutfs_inc_counter(sb, forest_roots_next_hint);
|
||||
|
||||
retry:
|
||||
ret = scoutfs_client_get_roots(sb, &roots);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
trace_scoutfs_forest_using_roots(sb, &roots.fs_root, &roots.logs_root);
|
||||
refs.fs_ref = roots.fs_root.ref;
|
||||
refs.logs_ref = roots.logs_root.ref;
|
||||
|
||||
scoutfs_key_init_log_trees(<k, 0, 0);
|
||||
checked_fs = false;
|
||||
have_next = false;
|
||||
|
||||
for (;;) {
|
||||
if (!checked_fs) {
|
||||
checked_fs = true;
|
||||
item_root = roots.fs_root;
|
||||
} else {
|
||||
ret = scoutfs_btree_next(sb, &roots.logs_root, <k,
|
||||
&iref);
|
||||
if (ret == -ENOENT) {
|
||||
if (have_next)
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
if (ret == -ESTALE)
|
||||
break;
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (iref.val_len == sizeof(*lt)) {
|
||||
ltk = *iref.key;
|
||||
scoutfs_key_inc(<k);
|
||||
lt = iref.val;
|
||||
item_root = lt->item_root;
|
||||
} else {
|
||||
ret = -EIO;
|
||||
}
|
||||
scoutfs_btree_put_iref(&iref);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (item_root.ref.blkno == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = scoutfs_btree_next(sb, &item_root, key, &iref);
|
||||
if (ret == -ENOENT)
|
||||
continue;
|
||||
if (ret == -ESTALE)
|
||||
break;
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
found = *iref.key;
|
||||
scoutfs_btree_put_iref(&iref);
|
||||
|
||||
if (!have_next || scoutfs_key_compare(&found, next) < 0) {
|
||||
have_next = true;
|
||||
*next = found;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == -ESTALE) {
|
||||
if (memcmp(&prev_refs, &refs, sizeof(refs)) == 0)
|
||||
return -EIO;
|
||||
prev_refs = refs;
|
||||
goto retry;
|
||||
}
|
||||
out:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct forest_read_items_data {
|
||||
bool is_fs;
|
||||
scoutfs_forest_item_cb cb;
|
||||
void *cb_arg;
|
||||
};
|
||||
|
||||
static int forest_read_items(struct super_block *sb, struct scoutfs_key *key,
|
||||
void *val, int val_len, void *arg)
|
||||
{
|
||||
struct forest_read_items_data *rid = arg;
|
||||
struct scoutfs_log_item_value _liv = {0,};
|
||||
struct scoutfs_log_item_value *liv = &_liv;
|
||||
|
||||
if (!rid->is_fs) {
|
||||
liv = val;
|
||||
val += sizeof(struct scoutfs_log_item_value);
|
||||
val_len -= sizeof(struct scoutfs_log_item_value);
|
||||
}
|
||||
|
||||
return rid->cb(sb, key, liv, val, val_len, rid->cb_arg);
|
||||
}
|
||||
|
||||
/*
|
||||
* For each forest btree whose bloom block indicates that the lock might
|
||||
* have items stored, call the caller's callback for every item in the
|
||||
* leaf block in each tree which contains the key.
|
||||
*
|
||||
* The btree iter calls clamp the caller's range to the tightest range
|
||||
* that covers all the blocks. Any keys outside of this range can't be
|
||||
* trusted because we didn't visit all the trees to check their items.
|
||||
*
|
||||
* If we hit stale blocks and retry we can call the callback for
|
||||
* duplicate items. This is harmless because the items are stable while
|
||||
* the caller holds their cluster lock and the caller has to filter out
|
||||
* item versions anyway.
|
||||
*/
|
||||
int scoutfs_forest_read_items(struct super_block *sb,
|
||||
struct scoutfs_lock *lock,
|
||||
struct scoutfs_key *key,
|
||||
struct scoutfs_key *start,
|
||||
struct scoutfs_key *end,
|
||||
scoutfs_forest_item_cb cb, void *arg)
|
||||
{
|
||||
DECLARE_STALE_TRACKING_SUPER_REFS(prev_refs, refs);
|
||||
struct forest_read_items_data rid = {
|
||||
.cb = cb,
|
||||
.cb_arg = arg,
|
||||
};
|
||||
struct scoutfs_log_trees lt;
|
||||
struct scoutfs_net_roots roots;
|
||||
struct scoutfs_bloom_block *bb;
|
||||
struct forest_bloom_nrs bloom;
|
||||
SCOUTFS_BTREE_ITEM_REF(iref);
|
||||
struct scoutfs_block *bl;
|
||||
struct scoutfs_key ltk;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
scoutfs_inc_counter(sb, forest_read_items);
|
||||
calc_bloom_nrs(&bloom, &lock->start);
|
||||
|
||||
roots = lock->roots;
|
||||
retry:
|
||||
ret = scoutfs_client_get_roots(sb, &roots);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
trace_scoutfs_forest_using_roots(sb, &roots.fs_root, &roots.logs_root);
|
||||
refs.fs_ref = roots.fs_root.ref;
|
||||
refs.logs_ref = roots.logs_root.ref;
|
||||
|
||||
*start = lock->start;
|
||||
*end = lock->end;
|
||||
|
||||
/* start with fs root items */
|
||||
rid.is_fs = true;
|
||||
ret = scoutfs_btree_read_items(sb, &roots.fs_root, key, start, end,
|
||||
forest_read_items, &rid);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
rid.is_fs = false;
|
||||
|
||||
scoutfs_key_init_log_trees(<k, 0, 0);
|
||||
for (;; scoutfs_key_inc(<k)) {
|
||||
ret = scoutfs_btree_next(sb, &roots.logs_root, <k, &iref);
|
||||
if (ret == 0) {
|
||||
if (iref.val_len == sizeof(lt)) {
|
||||
ltk = *iref.key;
|
||||
memcpy(<, iref.val, sizeof(lt));
|
||||
} else {
|
||||
ret = -EIO;
|
||||
}
|
||||
scoutfs_btree_put_iref(&iref);
|
||||
}
|
||||
if (ret < 0) {
|
||||
if (ret == -ENOENT)
|
||||
break;
|
||||
goto out; /* including stale */
|
||||
}
|
||||
|
||||
if (lt.bloom_ref.blkno == 0)
|
||||
continue;
|
||||
|
||||
bl = read_bloom_ref(sb, <.bloom_ref);
|
||||
if (IS_ERR(bl)) {
|
||||
ret = PTR_ERR(bl);
|
||||
goto out;
|
||||
}
|
||||
bb = bl->data;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bloom.nrs); i++) {
|
||||
if (!test_bit_le(bloom.nrs[i], bb->bits))
|
||||
break;
|
||||
}
|
||||
|
||||
scoutfs_block_put(sb, bl);
|
||||
|
||||
/* one of the bloom bits wasn't set */
|
||||
if (i != ARRAY_SIZE(bloom.nrs)) {
|
||||
scoutfs_inc_counter(sb, forest_bloom_fail);
|
||||
continue;
|
||||
}
|
||||
|
||||
scoutfs_inc_counter(sb, forest_bloom_pass);
|
||||
|
||||
ret = scoutfs_btree_read_items(sb, <.item_root, key, start,
|
||||
end, forest_read_items, &rid);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
if (ret == -ESTALE) {
|
||||
if (memcmp(&prev_refs, &refs, sizeof(refs)) == 0) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
prev_refs = refs;
|
||||
|
||||
ret = scoutfs_client_get_roots(sb, &roots);
|
||||
if (ret)
|
||||
goto out;
|
||||
goto retry;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure that the bloom bits for the lock's start key are all set in
|
||||
* the current log's bloom block. We record the nr of our log tree in
|
||||
* the lock so that we only try to cow and set the bits once per tree
|
||||
* across multiple commits as long as the lock isn't purged.
|
||||
*
|
||||
* This is using a coarse mutex to serialize cowing the block. It could
|
||||
* be much finer grained, but it's infrequent. We'll keep an eye on if
|
||||
* it gets expensive enough to warrant fixing.
|
||||
*/
|
||||
int scoutfs_forest_set_bloom_bits(struct super_block *sb,
|
||||
struct scoutfs_lock *lock)
|
||||
{
|
||||
struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super;
|
||||
DECLARE_FOREST_INFO(sb, finf);
|
||||
struct scoutfs_block *new_bl = NULL;
|
||||
struct scoutfs_block *bl = NULL;
|
||||
struct scoutfs_bloom_block *bb;
|
||||
struct scoutfs_btree_ref *ref;
|
||||
struct forest_bloom_nrs bloom;
|
||||
int nr_set = 0;
|
||||
u64 blkno;
|
||||
u64 nr;
|
||||
int ret;
|
||||
int err;
|
||||
int i;
|
||||
|
||||
nr = le64_to_cpu(finf->our_log.nr);
|
||||
|
||||
/* our rid is constant */
|
||||
if (atomic64_read(&lock->forest_bloom_nr) == nr) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mutex_lock(&finf->mutex);
|
||||
|
||||
scoutfs_inc_counter(sb, forest_set_bloom_bits);
|
||||
calc_bloom_nrs(&bloom, &lock->start);
|
||||
|
||||
ref = &finf->our_log.bloom_ref;
|
||||
|
||||
if (ref->blkno) {
|
||||
bl = read_bloom_ref(sb, ref);
|
||||
if (IS_ERR(bl)) {
|
||||
ret = PTR_ERR(bl);
|
||||
goto unlock;
|
||||
}
|
||||
bb = bl->data;
|
||||
}
|
||||
|
||||
if (!ref->blkno || !scoutfs_block_writer_is_dirty(sb, bl)) {
|
||||
|
||||
ret = scoutfs_alloc_meta(sb, finf->alloc, finf->wri, &blkno);
|
||||
if (ret < 0)
|
||||
goto unlock;
|
||||
|
||||
new_bl = scoutfs_block_create(sb, blkno);
|
||||
if (IS_ERR(new_bl)) {
|
||||
err = scoutfs_free_meta(sb, finf->alloc, finf->wri,
|
||||
blkno);
|
||||
BUG_ON(err); /* could have dirtied */
|
||||
ret = PTR_ERR(new_bl);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (bl) {
|
||||
err = scoutfs_free_meta(sb, finf->alloc, finf->wri,
|
||||
le64_to_cpu(ref->blkno));
|
||||
BUG_ON(err); /* could have dirtied */
|
||||
memcpy(new_bl->data, bl->data, SCOUTFS_BLOCK_LG_SIZE);
|
||||
} else {
|
||||
memset(new_bl->data, 0, SCOUTFS_BLOCK_LG_SIZE);
|
||||
}
|
||||
|
||||
scoutfs_block_writer_mark_dirty(sb, finf->wri, new_bl);
|
||||
|
||||
scoutfs_block_put(sb, bl);
|
||||
bl = new_bl;
|
||||
bb = bl->data;
|
||||
new_bl = NULL;
|
||||
|
||||
bb->hdr.magic = cpu_to_le32(SCOUTFS_BLOCK_MAGIC_BLOOM);
|
||||
bb->hdr.fsid = super->hdr.fsid;
|
||||
bb->hdr.blkno = cpu_to_le64(blkno);
|
||||
prandom_bytes(&bb->hdr.seq, sizeof(bb->hdr.seq));
|
||||
ref->blkno = bb->hdr.blkno;
|
||||
ref->seq = bb->hdr.seq;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bloom.nrs); i++) {
|
||||
if (!test_and_set_bit_le(bloom.nrs[i], bb->bits)) {
|
||||
le64_add_cpu(&bb->total_set, 1);
|
||||
nr_set++;
|
||||
}
|
||||
}
|
||||
|
||||
trace_scoutfs_forest_bloom_set(sb, &lock->start,
|
||||
le64_to_cpu(finf->our_log.rid),
|
||||
le64_to_cpu(finf->our_log.nr),
|
||||
le64_to_cpu(finf->our_log.bloom_ref.blkno),
|
||||
le64_to_cpu(finf->our_log.bloom_ref.seq),
|
||||
nr_set);
|
||||
|
||||
atomic64_set(&lock->forest_bloom_nr, nr);
|
||||
ret = 0;
|
||||
unlock:
|
||||
mutex_unlock(&finf->mutex);
|
||||
out:
|
||||
scoutfs_block_put(sb, bl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The caller is commiting items in the transaction and has found the
|
||||
* greatest item version amongst them. We store it in the log_trees root
|
||||
* to send to the server.
|
||||
*/
|
||||
void scoutfs_forest_set_max_vers(struct super_block *sb, u64 max_vers)
|
||||
{
|
||||
DECLARE_FOREST_INFO(sb, finf);
|
||||
|
||||
finf->our_log.max_item_vers = cpu_to_le64(max_vers);
|
||||
}
|
||||
|
||||
/*
|
||||
* The server is calling during setup to find the greatest item version
|
||||
* amongst all the log tree roots. They have the authoritative current
|
||||
* super.
|
||||
*
|
||||
* Item versions are only used to compare items in log trees, not in the
|
||||
* main fs tree. All we have to do is find the greatest version amongst
|
||||
* the log_trees so that new locks will have a write_version greater
|
||||
* than all the items in the log_trees.
|
||||
*/
|
||||
int scoutfs_forest_get_max_vers(struct super_block *sb,
|
||||
struct scoutfs_super_block *super,
|
||||
u64 *vers)
|
||||
{
|
||||
struct scoutfs_log_trees *lt;
|
||||
SCOUTFS_BTREE_ITEM_REF(iref);
|
||||
struct scoutfs_key ltk;
|
||||
int ret;
|
||||
|
||||
scoutfs_key_init_log_trees(<k, 0, 0);
|
||||
*vers = 0;
|
||||
|
||||
for (;; scoutfs_key_inc(<k)) {
|
||||
ret = scoutfs_btree_next(sb, &super->logs_root, <k, &iref);
|
||||
if (ret == 0) {
|
||||
if (iref.val_len == sizeof(struct scoutfs_log_trees)) {
|
||||
ltk = *iref.key;
|
||||
lt = iref.val;
|
||||
*vers = max(*vers,
|
||||
le64_to_cpu(lt->max_item_vers));
|
||||
} else {
|
||||
ret = -EIO;
|
||||
}
|
||||
scoutfs_btree_put_iref(&iref);
|
||||
}
|
||||
if (ret < 0) {
|
||||
if (ret == -ENOENT)
|
||||
break;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scoutfs_forest_insert_list(struct super_block *sb,
|
||||
struct scoutfs_btree_item_list *lst)
|
||||
{
|
||||
DECLARE_FOREST_INFO(sb, finf);
|
||||
|
||||
return scoutfs_btree_insert_list(sb, finf->alloc, finf->wri,
|
||||
&finf->our_log.item_root, lst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a srch entry to the current transaction's log file. It will be
|
||||
* committed in a transaction along with the dirty btree blocks that
|
||||
* hold dirty items. The srch entries aren't governed by lock
|
||||
* consistency.
|
||||
*
|
||||
* We lock here because of the shared file and block reference.
|
||||
* Typically these calls are a quick appending to the end of the block,
|
||||
* but they will allocate or cow blocks every few thousand calls.
|
||||
*/
|
||||
int scoutfs_forest_srch_add(struct super_block *sb, u64 hash, u64 ino, u64 id)
|
||||
{
|
||||
DECLARE_FOREST_INFO(sb, finf);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&finf->srch_mutex);
|
||||
ret = scoutfs_srch_add(sb, finf->alloc, finf->wri, &finf->srch_file,
|
||||
&finf->srch_bl, hash, ino, id);
|
||||
mutex_unlock(&finf->srch_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is called from transactions as a new transaction opens and is
|
||||
* serialized with all writers.
|
||||
*/
|
||||
void scoutfs_forest_init_btrees(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_log_trees *lt)
|
||||
{
|
||||
DECLARE_FOREST_INFO(sb, finf);
|
||||
|
||||
mutex_lock(&finf->mutex);
|
||||
|
||||
finf->alloc = alloc;
|
||||
finf->wri = wri;
|
||||
|
||||
/* the lt allocator fields have been used by the caller */
|
||||
memset(&finf->our_log, 0, sizeof(finf->our_log));
|
||||
finf->our_log.item_root = lt->item_root;
|
||||
finf->our_log.bloom_ref = lt->bloom_ref;
|
||||
finf->our_log.max_item_vers = lt->max_item_vers;
|
||||
finf->our_log.rid = lt->rid;
|
||||
finf->our_log.nr = lt->nr;
|
||||
finf->srch_file = lt->srch_file;
|
||||
|
||||
WARN_ON_ONCE(finf->srch_bl); /* commiting should have put the block */
|
||||
finf->srch_bl = NULL;
|
||||
|
||||
trace_scoutfs_forest_init_our_log(sb, le64_to_cpu(lt->rid),
|
||||
le64_to_cpu(lt->nr),
|
||||
le64_to_cpu(lt->item_root.ref.blkno),
|
||||
le64_to_cpu(lt->item_root.ref.seq));
|
||||
|
||||
mutex_unlock(&finf->mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* This is called during transaction commit which excludes forest writer
|
||||
* calls. The caller has already written all the dirty blocks that the
|
||||
* forest roots reference. They're getting the roots to send to the server
|
||||
* for the commit.
|
||||
*/
|
||||
void scoutfs_forest_get_btrees(struct super_block *sb,
|
||||
struct scoutfs_log_trees *lt)
|
||||
{
|
||||
DECLARE_FOREST_INFO(sb, finf);
|
||||
|
||||
lt->item_root = finf->our_log.item_root;
|
||||
lt->bloom_ref = finf->our_log.bloom_ref;
|
||||
lt->srch_file = finf->srch_file;
|
||||
lt->max_item_vers = finf->our_log.max_item_vers;
|
||||
|
||||
scoutfs_block_put(sb, finf->srch_bl);
|
||||
finf->srch_bl = NULL;
|
||||
|
||||
trace_scoutfs_forest_prepare_commit(sb, <->item_root.ref,
|
||||
<->bloom_ref);
|
||||
}
|
||||
|
||||
int scoutfs_forest_setup(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct forest_info *finf;
|
||||
int ret;
|
||||
|
||||
finf = kzalloc(sizeof(struct forest_info), GFP_KERNEL);
|
||||
if (!finf) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* the finf fields will be setup as we open a transaction */
|
||||
mutex_init(&finf->mutex);
|
||||
mutex_init(&finf->srch_mutex);
|
||||
|
||||
sbi->forest_info = finf;
|
||||
ret = 0;
|
||||
out:
|
||||
if (ret)
|
||||
scoutfs_forest_destroy(sb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void scoutfs_forest_destroy(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct forest_info *finf = SCOUTFS_SB(sb)->forest_info;
|
||||
|
||||
if (finf) {
|
||||
scoutfs_block_put(sb, finf->srch_bl);
|
||||
kfree(finf);
|
||||
sbi->forest_info = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef _SCOUTFS_FOREST_H_
|
||||
#define _SCOUTFS_FOREST_H_
|
||||
|
||||
struct scoutfs_alloc;
|
||||
struct scoutfs_block_writer;
|
||||
struct scoutfs_block;
|
||||
|
||||
#include "btree.h"
|
||||
|
||||
/* caller gives an item to the callback */
|
||||
typedef int (*scoutfs_forest_item_cb)(struct super_block *sb,
|
||||
struct scoutfs_key *key,
|
||||
struct scoutfs_log_item_value *liv,
|
||||
void *val, int val_len, void *arg);
|
||||
|
||||
int scoutfs_forest_next_hint(struct super_block *sb, struct scoutfs_key *key,
|
||||
struct scoutfs_key *next);
|
||||
int scoutfs_forest_read_items(struct super_block *sb,
|
||||
struct scoutfs_lock *lock,
|
||||
struct scoutfs_key *key,
|
||||
struct scoutfs_key *start,
|
||||
struct scoutfs_key *end,
|
||||
scoutfs_forest_item_cb cb, void *arg);
|
||||
int scoutfs_forest_set_bloom_bits(struct super_block *sb,
|
||||
struct scoutfs_lock *lock);
|
||||
void scoutfs_forest_set_max_vers(struct super_block *sb, u64 max_vers);
|
||||
int scoutfs_forest_get_max_vers(struct super_block *sb,
|
||||
struct scoutfs_super_block *super,
|
||||
u64 *vers);
|
||||
int scoutfs_forest_insert_list(struct super_block *sb,
|
||||
struct scoutfs_btree_item_list *lst);
|
||||
int scoutfs_forest_srch_add(struct super_block *sb, u64 hash, u64 ino, u64 id);
|
||||
|
||||
void scoutfs_forest_init_btrees(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_log_trees *lt);
|
||||
void scoutfs_forest_get_btrees(struct super_block *sb,
|
||||
struct scoutfs_log_trees *lt);
|
||||
|
||||
int scoutfs_forest_setup(struct super_block *sb);
|
||||
void scoutfs_forest_destroy(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,946 @@
|
||||
#ifndef _SCOUTFS_FORMAT_H_
|
||||
#define _SCOUTFS_FORMAT_H_
|
||||
|
||||
/* statfs(2) f_type */
|
||||
#define SCOUTFS_SUPER_MAGIC 0x554f4353 /* "SCOU" */
|
||||
|
||||
/* block header magic values, chosen at random */
|
||||
#define SCOUTFS_BLOCK_MAGIC_SUPER 0x103c428b
|
||||
#define SCOUTFS_BLOCK_MAGIC_BTREE 0xe597f96d
|
||||
#define SCOUTFS_BLOCK_MAGIC_BLOOM 0x31995604
|
||||
#define SCOUTFS_BLOCK_MAGIC_SRCH_BLOCK 0x897e4a7d
|
||||
#define SCOUTFS_BLOCK_MAGIC_SRCH_PARENT 0xb23a2a05
|
||||
#define SCOUTFS_BLOCK_MAGIC_ALLOC_LIST 0x8a93ac83
|
||||
|
||||
/*
|
||||
* The super block, quorum block, and file data allocation granularity
|
||||
* use the smaller 4KB block.
|
||||
*/
|
||||
#define SCOUTFS_BLOCK_SM_SHIFT 12
|
||||
#define SCOUTFS_BLOCK_SM_SIZE (1 << SCOUTFS_BLOCK_SM_SHIFT)
|
||||
#define SCOUTFS_BLOCK_SM_MASK (SCOUTFS_BLOCK_SM_SIZE - 1)
|
||||
#define SCOUTFS_BLOCK_SM_PER_PAGE (PAGE_SIZE / SCOUTFS_BLOCK_SM_SIZE)
|
||||
#define SCOUTFS_BLOCK_SM_SECTOR_SHIFT (SCOUTFS_BLOCK_SM_SHIFT - 9)
|
||||
#define SCOUTFS_BLOCK_SM_SECTORS (1 << SCOUTFS_BLOCK_SM_SECTOR_SHIFT)
|
||||
#define SCOUTFS_BLOCK_SM_MAX (U64_MAX >> SCOUTFS_BLOCK_SM_SHIFT)
|
||||
#define SCOUTFS_BLOCK_SM_PAGES_PER (SCOUTFS_BLOCK_SM_SIZE / PAGE_SIZE)
|
||||
#define SCOUTFS_BLOCK_SM_PAGE_ORDER (SCOUTFS_BLOCK_SM_SHIFT - PAGE_SHIFT)
|
||||
|
||||
/*
|
||||
* The radix and btree structures, and the forest bloom block, use the
|
||||
* larger 64KB metadata block size.
|
||||
*/
|
||||
#define SCOUTFS_BLOCK_LG_SHIFT 16
|
||||
#define SCOUTFS_BLOCK_LG_SIZE (1 << SCOUTFS_BLOCK_LG_SHIFT)
|
||||
#define SCOUTFS_BLOCK_LG_MASK (SCOUTFS_BLOCK_LG_SIZE - 1)
|
||||
#define SCOUTFS_BLOCK_LG_PER_PAGE (PAGE_SIZE / SCOUTFS_BLOCK_LG_SIZE)
|
||||
#define SCOUTFS_BLOCK_LG_SECTOR_SHIFT (SCOUTFS_BLOCK_LG_SHIFT - 9)
|
||||
#define SCOUTFS_BLOCK_LG_SECTORS (1 << SCOUTFS_BLOCK_LG_SECTOR_SHIFT)
|
||||
#define SCOUTFS_BLOCK_LG_MAX (U64_MAX >> SCOUTFS_BLOCK_LG_SHIFT)
|
||||
#define SCOUTFS_BLOCK_LG_PAGES_PER (SCOUTFS_BLOCK_LG_SIZE / PAGE_SIZE)
|
||||
#define SCOUTFS_BLOCK_LG_PAGE_ORDER (SCOUTFS_BLOCK_LG_SHIFT - PAGE_SHIFT)
|
||||
|
||||
#define SCOUTFS_BLOCK_SM_LG_SHIFT (SCOUTFS_BLOCK_LG_SHIFT - \
|
||||
SCOUTFS_BLOCK_SM_SHIFT)
|
||||
|
||||
|
||||
/*
|
||||
* The super block leaves some room before the first block for platform
|
||||
* structures like boot loaders.
|
||||
*/
|
||||
#define SCOUTFS_SUPER_BLKNO ((64ULL * 1024) >> SCOUTFS_BLOCK_SM_SHIFT)
|
||||
|
||||
/*
|
||||
* A reasonably large region of aligned quorum blocks follow the super
|
||||
* block. Each voting cycle reads the entire region so we don't want it
|
||||
* to be too enormous. 256K seems like a reasonably chunky single IO.
|
||||
* The number of blocks in the region also determines the number of
|
||||
* mounts that have a reasonable probability of not overwriting each
|
||||
* other's random block locations.
|
||||
*/
|
||||
#define SCOUTFS_QUORUM_BLKNO ((256ULL * 1024) >> SCOUTFS_BLOCK_SM_SHIFT)
|
||||
#define SCOUTFS_QUORUM_BLOCKS ((256ULL * 1024) >> SCOUTFS_BLOCK_SM_SHIFT)
|
||||
|
||||
/*
|
||||
* Start data on the data device aligned as well.
|
||||
*/
|
||||
#define SCOUTFS_DATA_DEV_START_BLKNO ((256ULL * 1024) >> SCOUTFS_BLOCK_SM_SHIFT)
|
||||
|
||||
|
||||
#define SCOUTFS_UNIQUE_NAME_MAX_BYTES 64 /* includes null */
|
||||
|
||||
/*
|
||||
* Base types used by other structures.
|
||||
*/
|
||||
struct scoutfs_timespec {
|
||||
__le64 sec;
|
||||
__le32 nsec;
|
||||
__u8 __pad[4];
|
||||
};
|
||||
|
||||
/* XXX ipv6 */
|
||||
struct scoutfs_inet_addr {
|
||||
__le32 addr;
|
||||
__le16 port;
|
||||
__u8 __pad[2];
|
||||
};
|
||||
|
||||
/*
|
||||
* This header is stored at the start of btree blocks and the super
|
||||
* block for verification. The crc field is not included in the
|
||||
* calculation of the crc.
|
||||
*/
|
||||
struct scoutfs_block_header {
|
||||
__le32 crc;
|
||||
__le32 magic;
|
||||
__le64 fsid;
|
||||
__le64 seq;
|
||||
__le64 blkno;
|
||||
};
|
||||
|
||||
/*
|
||||
* scoutfs identifies all file system metadata items by a small key
|
||||
* struct.
|
||||
*
|
||||
* Each item type maps their logical structures to the fixed fields in
|
||||
* sort order. This lets us print keys without needing per-type
|
||||
* formats.
|
||||
*
|
||||
* The keys are compared by considering the fields in struct order from
|
||||
* most to least significant. They are considered a multi precision
|
||||
* value when navigating the keys in ordered key space. We can
|
||||
* increment them, subtract them from each other, etc.
|
||||
*/
|
||||
struct scoutfs_key {
|
||||
__le64 _sk_first;
|
||||
__le64 _sk_second;
|
||||
__le64 _sk_third;
|
||||
__u8 _sk_fourth;
|
||||
__u8 sk_zone;
|
||||
__u8 sk_type;
|
||||
__u8 __pad[5];
|
||||
};
|
||||
|
||||
/* inode index */
|
||||
#define skii_major _sk_second
|
||||
#define skii_ino _sk_third
|
||||
|
||||
/* node orphan inode */
|
||||
#define sko_rid _sk_first
|
||||
#define sko_ino _sk_second
|
||||
|
||||
/* inode */
|
||||
#define ski_ino _sk_first
|
||||
|
||||
/* xattr parts */
|
||||
#define skx_ino _sk_first
|
||||
#define skx_name_hash _sk_second
|
||||
#define skx_id _sk_third
|
||||
#define skx_part _sk_fourth
|
||||
|
||||
/* directory entries */
|
||||
#define skd_ino _sk_first
|
||||
#define skd_major _sk_second
|
||||
#define skd_minor _sk_third
|
||||
|
||||
/* symlink target */
|
||||
#define sks_ino _sk_first
|
||||
#define sks_nr _sk_second
|
||||
|
||||
/* data extents */
|
||||
#define skdx_ino _sk_first
|
||||
#define skdx_end _sk_second
|
||||
#define skdx_len _sk_third
|
||||
|
||||
/* log trees */
|
||||
#define sklt_rid _sk_first
|
||||
#define sklt_nr _sk_second
|
||||
|
||||
/* lock clients */
|
||||
#define sklc_rid _sk_first
|
||||
|
||||
/* seqs */
|
||||
#define skts_trans_seq _sk_first
|
||||
#define skts_rid _sk_second
|
||||
|
||||
/* mounted clients */
|
||||
#define skmc_rid _sk_first
|
||||
|
||||
/* free extents by blkno */
|
||||
#define skfb_end _sk_second
|
||||
#define skfb_len _sk_third
|
||||
/* free extents by len */
|
||||
#define skfl_neglen _sk_second
|
||||
#define skfl_blkno _sk_third
|
||||
|
||||
struct scoutfs_radix_block {
|
||||
struct scoutfs_block_header hdr;
|
||||
union {
|
||||
struct scoutfs_radix_ref {
|
||||
__le64 blkno;
|
||||
__le64 seq;
|
||||
__le64 sm_total;
|
||||
__le64 lg_total;
|
||||
} refs[0];
|
||||
__le64 bits[0];
|
||||
};
|
||||
};
|
||||
|
||||
struct scoutfs_avl_root {
|
||||
__le16 node;
|
||||
};
|
||||
|
||||
struct scoutfs_avl_node {
|
||||
__le16 parent;
|
||||
__le16 left;
|
||||
__le16 right;
|
||||
__u8 height;
|
||||
__u8 __pad[1];
|
||||
};
|
||||
|
||||
/* when we split we want to have multiple items on each side */
|
||||
#define SCOUTFS_BTREE_MAX_VAL_LEN 896
|
||||
|
||||
/*
|
||||
* A 4EB test image measured a worst case height of 17. This is plenty
|
||||
* generous.
|
||||
*/
|
||||
#define SCOUTFS_BTREE_MAX_HEIGHT 20
|
||||
|
||||
struct scoutfs_btree_ref {
|
||||
__le64 blkno;
|
||||
__le64 seq;
|
||||
};
|
||||
|
||||
/*
|
||||
* A height of X means that the first block read will have level X-1 and
|
||||
* the leaves will have level 0.
|
||||
*/
|
||||
struct scoutfs_btree_root {
|
||||
struct scoutfs_btree_ref ref;
|
||||
__u8 height;
|
||||
__u8 __pad[7];
|
||||
};
|
||||
|
||||
struct scoutfs_btree_item {
|
||||
struct scoutfs_avl_node node;
|
||||
struct scoutfs_key key;
|
||||
__le16 val_off;
|
||||
__le16 val_len;
|
||||
__u8 __pad[4];
|
||||
};
|
||||
|
||||
struct scoutfs_btree_block {
|
||||
struct scoutfs_block_header hdr;
|
||||
struct scoutfs_avl_root item_root;
|
||||
__le16 nr_items;
|
||||
__le16 total_item_bytes;
|
||||
__le16 mid_free_len;
|
||||
__u8 level;
|
||||
__u8 __pad[7];
|
||||
struct scoutfs_btree_item items[0];
|
||||
/* leaf blocks have a fixed size item offset hash table at the end */
|
||||
};
|
||||
|
||||
#define SCOUTFS_BTREE_VALUE_ALIGN 8
|
||||
|
||||
/*
|
||||
* Try to aim for a 75% load in a leaf full of items with no value.
|
||||
* We'll almost never see this because most items have values and most
|
||||
* blocks aren't full.
|
||||
*/
|
||||
#define SCOUTFS_BTREE_LEAF_ITEM_HASH_NR_UNALIGNED \
|
||||
((SCOUTFS_BLOCK_LG_SIZE - sizeof(struct scoutfs_btree_block)) / \
|
||||
(sizeof(struct scoutfs_btree_item) + (sizeof(__le16))) * 100 / 75)
|
||||
#define SCOUTFS_BTREE_LEAF_ITEM_HASH_NR \
|
||||
(round_up(SCOUTFS_BTREE_LEAF_ITEM_HASH_NR_UNALIGNED, \
|
||||
SCOUTFS_BTREE_VALUE_ALIGN))
|
||||
#define SCOUTFS_BTREE_LEAF_ITEM_HASH_BYTES \
|
||||
(SCOUTFS_BTREE_LEAF_ITEM_HASH_NR * sizeof(__le16))
|
||||
|
||||
struct scoutfs_alloc_list_ref {
|
||||
__le64 blkno;
|
||||
__le64 seq;
|
||||
};
|
||||
|
||||
/*
|
||||
* first_nr tracks the nr of the first block in the list and is used for
|
||||
* allocation sizing. total_nr is the sum of the nr of all the blocks in
|
||||
* the list and is used for calculating total free block counts.
|
||||
*/
|
||||
struct scoutfs_alloc_list_head {
|
||||
struct scoutfs_alloc_list_ref ref;
|
||||
__le64 total_nr;
|
||||
__le32 first_nr;
|
||||
__u8 __pad[4];
|
||||
};
|
||||
|
||||
/*
|
||||
* While the main allocator uses extent items in btree blocks, metadata
|
||||
* allocations for a single transaction are recorded in arrays in
|
||||
* blocks. This limits the number of allocations and frees needed to
|
||||
* cow and modify the structure. The blocks can be stored in a list
|
||||
* which lets us create a persistent log of pending frees that are
|
||||
* generated as we cow btree blocks to insert freed extents.
|
||||
*
|
||||
* The array floats in the block so that both adding and removing blknos
|
||||
* only modifies an index.
|
||||
*/
|
||||
struct scoutfs_alloc_list_block {
|
||||
struct scoutfs_block_header hdr;
|
||||
struct scoutfs_alloc_list_ref next;
|
||||
__le32 start;
|
||||
__le32 nr;
|
||||
__le64 blknos[0]; /* naturally aligned for sorting */
|
||||
};
|
||||
|
||||
#define SCOUTFS_ALLOC_LIST_MAX_BLOCKS \
|
||||
((SCOUTFS_BLOCK_LG_SIZE - sizeof(struct scoutfs_alloc_list_block)) / \
|
||||
(member_sizeof(struct scoutfs_alloc_list_block, blknos[0])))
|
||||
|
||||
/*
|
||||
* These can safely be initialized to all-zeros.
|
||||
*/
|
||||
struct scoutfs_alloc_root {
|
||||
__le64 total_len;
|
||||
struct scoutfs_btree_root root;
|
||||
};
|
||||
|
||||
/* types of allocators, exposed to alloc_detail ioctl */
|
||||
#define SCOUTFS_ALLOC_OWNER_NONE 0
|
||||
#define SCOUTFS_ALLOC_OWNER_SERVER 1
|
||||
#define SCOUTFS_ALLOC_OWNER_MOUNT 2
|
||||
#define SCOUTFS_ALLOC_OWNER_SRCH 3
|
||||
|
||||
struct scoutfs_mounted_client_btree_val {
|
||||
__u8 flags;
|
||||
};
|
||||
|
||||
#define SCOUTFS_MOUNTED_CLIENT_VOTER (1 << 0)
|
||||
|
||||
/*
|
||||
* srch files are a contiguous run of blocks with compressed entries
|
||||
* described by a dense parent radix. The files can be stored in
|
||||
* log_tree items when the files contain unsorted entries written by
|
||||
* mounts during their transactions. Sorted files of increasing size
|
||||
* are kept in a btree off the super for searching and further
|
||||
* compacting.
|
||||
*/
|
||||
struct scoutfs_srch_entry {
|
||||
__le64 hash;
|
||||
__le64 ino;
|
||||
__le64 id;
|
||||
};
|
||||
|
||||
#define SCOUTFS_SRCH_ENTRY_MAX_BYTES (2 + (sizeof(__u64) * 3))
|
||||
|
||||
struct scoutfs_srch_ref {
|
||||
__le64 blkno;
|
||||
__le64 seq;
|
||||
};
|
||||
|
||||
struct scoutfs_srch_file {
|
||||
struct scoutfs_srch_entry first;
|
||||
struct scoutfs_srch_entry last;
|
||||
struct scoutfs_srch_ref ref;
|
||||
__le64 blocks;
|
||||
__le64 entries;
|
||||
__u8 height;
|
||||
__u8 __pad[7];
|
||||
};
|
||||
|
||||
struct scoutfs_srch_parent {
|
||||
struct scoutfs_block_header hdr;
|
||||
struct scoutfs_srch_ref refs[0];
|
||||
};
|
||||
|
||||
#define SCOUTFS_SRCH_PARENT_REFS \
|
||||
((SCOUTFS_BLOCK_LG_SIZE - \
|
||||
offsetof(struct scoutfs_srch_parent, refs)) / \
|
||||
sizeof(struct scoutfs_srch_ref))
|
||||
|
||||
struct scoutfs_srch_block {
|
||||
struct scoutfs_block_header hdr;
|
||||
struct scoutfs_srch_entry first;
|
||||
struct scoutfs_srch_entry last;
|
||||
struct scoutfs_srch_entry tail;
|
||||
__le32 entry_nr;
|
||||
__le32 entry_bytes;
|
||||
__u8 entries[0];
|
||||
};
|
||||
|
||||
/*
|
||||
* Decoding loads final small deltas with full __u64 loads. Rather than
|
||||
* check the size before each load we stop coding entries past the point
|
||||
* where a full size entry could overflow the block. A final entry can
|
||||
* start at this byte count and consume the rest of the block, though
|
||||
* its unlikely.
|
||||
*/
|
||||
#define SCOUTFS_SRCH_BLOCK_SAFE_BYTES \
|
||||
(SCOUTFS_BLOCK_LG_SIZE - sizeof(struct scoutfs_srch_block) - \
|
||||
SCOUTFS_SRCH_ENTRY_MAX_BYTES)
|
||||
|
||||
#define SCOUTFS_SRCH_LOG_BLOCK_LIMIT (1024 * 1024 / SCOUTFS_BLOCK_LG_SIZE)
|
||||
#define SCOUTFS_SRCH_COMPACT_ORDER 2
|
||||
#define SCOUTFS_SRCH_COMPACT_NR (1 << SCOUTFS_SRCH_COMPACT_ORDER)
|
||||
|
||||
/*
|
||||
* A persistent record of a srch file compaction operation in progress.
|
||||
*
|
||||
* When compacting log files blk and pos aren't used. When compacting
|
||||
* sorted files blk is the logical block number and pos is the byte
|
||||
* offset of the next entry. When deleting files pos is the height of
|
||||
* the level that we're deleting, and blk is the logical block offset of
|
||||
* the next parent ref array index to descend through.
|
||||
*/
|
||||
struct scoutfs_srch_compact {
|
||||
struct scoutfs_alloc_list_head meta_avail;
|
||||
struct scoutfs_alloc_list_head meta_freed;
|
||||
__le64 id;
|
||||
__u8 nr;
|
||||
__u8 flags;
|
||||
__u8 __pad[6];
|
||||
struct scoutfs_srch_file out;
|
||||
struct scoutfs_srch_compact_input {
|
||||
struct scoutfs_srch_file sfl;
|
||||
__le64 blk;
|
||||
__le64 pos;
|
||||
} in[SCOUTFS_SRCH_COMPACT_NR];
|
||||
};
|
||||
|
||||
/* server -> client: combine input log file entries into output file */
|
||||
#define SCOUTFS_SRCH_COMPACT_FLAG_LOG (1 << 0)
|
||||
/* server -> client: combine input sorted file entries into output file */
|
||||
#define SCOUTFS_SRCH_COMPACT_FLAG_SORTED (1 << 1)
|
||||
/* server -> client: delete input files */
|
||||
#define SCOUTFS_SRCH_COMPACT_FLAG_DELETE (1 << 2)
|
||||
/* client -> server: compaction phase (LOG,SORTED,DELETE) done */
|
||||
#define SCOUTFS_SRCH_COMPACT_FLAG_DONE (1 << 4)
|
||||
/* client -> server: compaction failed */
|
||||
#define SCOUTFS_SRCH_COMPACT_FLAG_ERROR (1 << 5)
|
||||
|
||||
/*
|
||||
* XXX I imagine we should rename these now that they've evolved to track
|
||||
* all the btrees that clients use during a transaction. It's not just
|
||||
* about item logs, it's about clients making changes to trees.
|
||||
*/
|
||||
struct scoutfs_log_trees {
|
||||
struct scoutfs_alloc_list_head meta_avail;
|
||||
struct scoutfs_alloc_list_head meta_freed;
|
||||
struct scoutfs_btree_root item_root;
|
||||
struct scoutfs_btree_ref bloom_ref;
|
||||
struct scoutfs_alloc_root data_avail;
|
||||
struct scoutfs_alloc_root data_freed;
|
||||
struct scoutfs_srch_file srch_file;
|
||||
__le64 max_item_vers;
|
||||
__le64 rid;
|
||||
__le64 nr;
|
||||
};
|
||||
|
||||
struct scoutfs_log_item_value {
|
||||
__le64 vers;
|
||||
__u8 flags;
|
||||
__u8 __pad[7];
|
||||
__u8 data[0];
|
||||
};
|
||||
|
||||
/*
|
||||
* FS items are limited by the max btree value length with the log item
|
||||
* value header.
|
||||
*/
|
||||
#define SCOUTFS_MAX_VAL_SIZE \
|
||||
(SCOUTFS_BTREE_MAX_VAL_LEN - sizeof(struct scoutfs_log_item_value))
|
||||
|
||||
#define SCOUTFS_LOG_ITEM_FLAG_DELETION (1 << 0)
|
||||
|
||||
struct scoutfs_bloom_block {
|
||||
struct scoutfs_block_header hdr;
|
||||
__le64 total_set;
|
||||
__le64 bits[0];
|
||||
};
|
||||
|
||||
/*
|
||||
* Item log trees are accompanied by a block of bits that make up a
|
||||
* bloom filter which indicate if the item log trees may contain items
|
||||
* covered by a lock. The log trees should be finalized and merged long
|
||||
* before the bloom filters fill up and start returning excessive false
|
||||
* positives.
|
||||
*/
|
||||
#define SCOUTFS_FOREST_BLOOM_NRS 3
|
||||
#define SCOUTFS_FOREST_BLOOM_BITS \
|
||||
(((SCOUTFS_BLOCK_LG_SIZE - sizeof(struct scoutfs_bloom_block)) / \
|
||||
member_sizeof(struct scoutfs_bloom_block, bits[0])) * \
|
||||
member_sizeof(struct scoutfs_bloom_block, bits[0]) * 8)
|
||||
#define SCOUTFS_FOREST_BLOOM_FUNC_BITS (SCOUTFS_BLOCK_LG_SHIFT + 3)
|
||||
|
||||
/*
|
||||
* Keys are first sorted by major key zones.
|
||||
*/
|
||||
#define SCOUTFS_INODE_INDEX_ZONE 1
|
||||
#define SCOUTFS_RID_ZONE 2
|
||||
#define SCOUTFS_FS_ZONE 3
|
||||
#define SCOUTFS_LOCK_ZONE 4
|
||||
/* Items only stored in server btrees */
|
||||
#define SCOUTFS_LOG_TREES_ZONE 6
|
||||
#define SCOUTFS_LOCK_CLIENTS_ZONE 7
|
||||
#define SCOUTFS_TRANS_SEQ_ZONE 8
|
||||
#define SCOUTFS_MOUNTED_CLIENT_ZONE 9
|
||||
#define SCOUTFS_SRCH_ZONE 10
|
||||
#define SCOUTFS_FREE_EXTENT_ZONE 11
|
||||
|
||||
/* inode index zone */
|
||||
#define SCOUTFS_INODE_INDEX_META_SEQ_TYPE 1
|
||||
#define SCOUTFS_INODE_INDEX_DATA_SEQ_TYPE 2
|
||||
#define SCOUTFS_INODE_INDEX_NR 3 /* don't forget to update */
|
||||
|
||||
/* rid zone (also used in server alloc btree) */
|
||||
#define SCOUTFS_ORPHAN_TYPE 1
|
||||
|
||||
/* fs zone */
|
||||
#define SCOUTFS_INODE_TYPE 1
|
||||
#define SCOUTFS_XATTR_TYPE 2
|
||||
#define SCOUTFS_DIRENT_TYPE 3
|
||||
#define SCOUTFS_READDIR_TYPE 4
|
||||
#define SCOUTFS_LINK_BACKREF_TYPE 5
|
||||
#define SCOUTFS_SYMLINK_TYPE 6
|
||||
#define SCOUTFS_DATA_EXTENT_TYPE 7
|
||||
|
||||
/* lock zone, only ever found in lock ranges, never in persistent items */
|
||||
#define SCOUTFS_RENAME_TYPE 1
|
||||
|
||||
/* srch zone, only in server btrees */
|
||||
#define SCOUTFS_SRCH_LOG_TYPE 1
|
||||
#define SCOUTFS_SRCH_BLOCKS_TYPE 2
|
||||
#define SCOUTFS_SRCH_PENDING_TYPE 3
|
||||
#define SCOUTFS_SRCH_BUSY_TYPE 4
|
||||
|
||||
/* free extents in allocator btrees in client and server, by blkno or len */
|
||||
#define SCOUTFS_FREE_EXTENT_BLKNO_TYPE 1
|
||||
#define SCOUTFS_FREE_EXTENT_LEN_TYPE 2
|
||||
|
||||
/* file data extents have start and len in key */
|
||||
struct scoutfs_data_extent_val {
|
||||
__le64 blkno;
|
||||
__u8 flags;
|
||||
__u8 __pad[7];
|
||||
};
|
||||
|
||||
#define SEF_OFFLINE (1 << 0)
|
||||
#define SEF_UNWRITTEN (1 << 1)
|
||||
#define SEF_UNKNOWN (U8_MAX << 2)
|
||||
|
||||
/*
|
||||
* The first xattr part item has a header that describes the xattr. The
|
||||
* name and value are then packed into the following bytes in the first
|
||||
* part item and overflow into the values of the rest of the part items.
|
||||
*/
|
||||
struct scoutfs_xattr {
|
||||
__le16 val_len;
|
||||
__u8 name_len;
|
||||
__u8 __pad[5];
|
||||
__u8 name[0];
|
||||
};
|
||||
|
||||
|
||||
/* XXX does this exist upstream somewhere? */
|
||||
#define member_sizeof(TYPE, MEMBER) (sizeof(((TYPE *)0)->MEMBER))
|
||||
|
||||
#define SCOUTFS_UUID_BYTES 16
|
||||
|
||||
/*
|
||||
* Mounts read all the quorum blocks and write to one random quorum
|
||||
* block during a cycle. The min cycle time limits the per-mount iop
|
||||
* load during elections. The random cycle delay makes it less likely
|
||||
* that mounts will read and write at the same time and miss each
|
||||
* other's writes. An election only completes if a quorum of mounts
|
||||
* vote for a leader before any of their elections timeout. This is
|
||||
* made less likely by the probability that mounts will overwrite each
|
||||
* others random block locations. The max quorum count limits that
|
||||
* probability. 9 mounts only have a 55% chance of writing to unique 4k
|
||||
* blocks in a 256k region. The election timeout is set to include
|
||||
* enough cycles to usually complete the election. Once a leader is
|
||||
* elected it spends a number of cycles writing out blocks with itself
|
||||
* logged as a leader. This reduces the possibility that servers
|
||||
* will have their log entries overwritten and not be fenced.
|
||||
*/
|
||||
#define SCOUTFS_QUORUM_MAX_COUNT 9
|
||||
#define SCOUTFS_QUORUM_CYCLE_LO_MS 10
|
||||
#define SCOUTFS_QUORUM_CYCLE_HI_MS 20
|
||||
#define SCOUTFS_QUORUM_TERM_LO_MS 250
|
||||
#define SCOUTFS_QUORUM_TERM_HI_MS 500
|
||||
#define SCOUTFS_QUORUM_ELECTED_LOG_CYCLES 10
|
||||
|
||||
struct scoutfs_quorum_block {
|
||||
__le64 fsid;
|
||||
__le64 blkno;
|
||||
__le64 term;
|
||||
__le64 write_nr;
|
||||
__le64 voter_rid;
|
||||
__le64 vote_for_rid;
|
||||
__le32 crc;
|
||||
__u8 log_nr;
|
||||
__u8 __pad[3];
|
||||
struct scoutfs_quorum_log {
|
||||
__le64 term;
|
||||
__le64 rid;
|
||||
struct scoutfs_inet_addr addr;
|
||||
} log[0];
|
||||
};
|
||||
|
||||
#define SCOUTFS_QUORUM_LOG_MAX \
|
||||
((SCOUTFS_BLOCK_SM_SIZE - sizeof(struct scoutfs_quorum_block)) / \
|
||||
sizeof(struct scoutfs_quorum_log))
|
||||
|
||||
#define SCOUTFS_FLAG_IS_META_BDEV 0x01
|
||||
|
||||
struct scoutfs_super_block {
|
||||
struct scoutfs_block_header hdr;
|
||||
__le64 id;
|
||||
__le64 format_hash;
|
||||
__le64 flags;
|
||||
__u8 uuid[SCOUTFS_UUID_BYTES];
|
||||
__le64 next_ino;
|
||||
__le64 next_trans_seq;
|
||||
__le64 total_meta_blocks; /* both static and dynamic */
|
||||
__le64 first_meta_blkno; /* first dynamically allocated */
|
||||
__le64 last_meta_blkno;
|
||||
__le64 total_data_blocks;
|
||||
__le64 first_data_blkno;
|
||||
__le64 last_data_blkno;
|
||||
__le64 quorum_fenced_term;
|
||||
__le64 quorum_server_term;
|
||||
__le64 unmount_barrier;
|
||||
__u8 quorum_count;
|
||||
__u8 __pad[7];
|
||||
struct scoutfs_inet_addr server_addr;
|
||||
struct scoutfs_alloc_root meta_alloc[2];
|
||||
struct scoutfs_alloc_root data_alloc;
|
||||
struct scoutfs_alloc_list_head server_meta_avail[2];
|
||||
struct scoutfs_alloc_list_head server_meta_freed[2];
|
||||
struct scoutfs_btree_root fs_root;
|
||||
struct scoutfs_btree_root logs_root;
|
||||
struct scoutfs_btree_root lock_clients;
|
||||
struct scoutfs_btree_root trans_seqs;
|
||||
struct scoutfs_btree_root mounted_clients;
|
||||
struct scoutfs_btree_root srch_root;
|
||||
};
|
||||
|
||||
#define SCOUTFS_ROOT_INO 1
|
||||
|
||||
|
||||
/*
|
||||
* @meta_seq: advanced the first time an inode is updated in a given
|
||||
* transaction. It can only advance again after the inode is written
|
||||
* and a new transaction opens.
|
||||
*
|
||||
* @data_seq: advanced the first time a file's data (or size) is
|
||||
* modified in a given transaction. It can only advance again after the
|
||||
* file is written and a new transaction opens.
|
||||
*
|
||||
* @data_version: incremented every time the contents of a file could
|
||||
* have changed. It is exposed via an ioctl and is then provided as an
|
||||
* argument to data functions to protect racing modification.
|
||||
*
|
||||
* @online_blocks: The number of fixed 4k blocks currently allocated and
|
||||
* storing data in the volume.
|
||||
*
|
||||
* @offline_blocks: The number of fixed 4k blocks that could be made
|
||||
* online by staging.
|
||||
*
|
||||
* XXX
|
||||
* - otime?
|
||||
* - compat flags?
|
||||
* - version?
|
||||
* - generation?
|
||||
* - be more careful with rdev?
|
||||
*/
|
||||
struct scoutfs_inode {
|
||||
__le64 size;
|
||||
__le64 meta_seq;
|
||||
__le64 data_seq;
|
||||
__le64 data_version;
|
||||
__le64 online_blocks;
|
||||
__le64 offline_blocks;
|
||||
__le64 next_readdir_pos;
|
||||
__le64 next_xattr_id;
|
||||
__le32 nlink;
|
||||
__le32 uid;
|
||||
__le32 gid;
|
||||
__le32 mode;
|
||||
__le32 rdev;
|
||||
__le32 flags;
|
||||
struct scoutfs_timespec atime;
|
||||
struct scoutfs_timespec ctime;
|
||||
struct scoutfs_timespec mtime;
|
||||
};
|
||||
|
||||
#define SCOUTFS_INO_FLAG_TRUNCATE 0x1
|
||||
|
||||
#define SCOUTFS_ROOT_INO 1
|
||||
|
||||
/* like the block size, a reasonable min PATH_MAX across platforms */
|
||||
#define SCOUTFS_SYMLINK_MAX_SIZE 4096
|
||||
|
||||
/*
|
||||
* Dirents are stored in multiple places to isolate contention when
|
||||
* performing different operations: hashed by name for creation and
|
||||
* lookup, at incrementing positions for readdir and resolving inodes to
|
||||
* paths. Each entry has all the metadata needed to reference all the
|
||||
* items (so an entry cached by lookup can be used to unlink all the
|
||||
* items).
|
||||
*/
|
||||
struct scoutfs_dirent {
|
||||
__le64 ino;
|
||||
__le64 hash;
|
||||
__le64 pos;
|
||||
__u8 type;
|
||||
__u8 __pad[7];
|
||||
__u8 name[0];
|
||||
};
|
||||
|
||||
#define SCOUTFS_NAME_LEN 255
|
||||
|
||||
/* S32_MAX avoids the (int) sign bit and might avoid sloppy bugs */
|
||||
#define SCOUTFS_LINK_MAX S32_MAX
|
||||
|
||||
/* entries begin after . and .. */
|
||||
#define SCOUTFS_DIRENT_FIRST_POS 2
|
||||
/* getdents returns next pos with an entry, no entry at (f_pos)~0 */
|
||||
#define SCOUTFS_DIRENT_LAST_POS (U64_MAX - 1)
|
||||
|
||||
enum scoutfs_dentry_type {
|
||||
SCOUTFS_DT_FIFO = 0,
|
||||
SCOUTFS_DT_CHR,
|
||||
SCOUTFS_DT_DIR,
|
||||
SCOUTFS_DT_BLK,
|
||||
SCOUTFS_DT_REG,
|
||||
SCOUTFS_DT_LNK,
|
||||
SCOUTFS_DT_SOCK,
|
||||
SCOUTFS_DT_WHT,
|
||||
};
|
||||
|
||||
|
||||
#define SCOUTFS_XATTR_MAX_NAME_LEN 255
|
||||
#define SCOUTFS_XATTR_MAX_VAL_LEN 65535
|
||||
#define SCOUTFS_XATTR_MAX_PART_SIZE SCOUTFS_MAX_VAL_SIZE
|
||||
|
||||
#define SCOUTFS_XATTR_NR_PARTS(name_len, val_len) \
|
||||
DIV_ROUND_UP(sizeof(struct scoutfs_xattr) + name_len + val_len, \
|
||||
(unsigned int)SCOUTFS_XATTR_MAX_PART_SIZE)
|
||||
|
||||
#define SCOUTFS_LOCK_INODE_GROUP_NR 1024
|
||||
#define SCOUTFS_LOCK_INODE_GROUP_MASK (SCOUTFS_LOCK_INODE_GROUP_NR - 1)
|
||||
#define SCOUTFS_LOCK_SEQ_GROUP_MASK ((1ULL << 10) - 1)
|
||||
|
||||
/*
|
||||
* messages over the wire.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Greetings verify identity of communicating nodes. The sender sends
|
||||
* their credentials and the receiver verifies them.
|
||||
*
|
||||
* @server_term: The raft term that elected the server. Initially 0
|
||||
* from the client, sent by the server, then sent by the client as it
|
||||
* tries to reconnect. Used to identify a client reconnecting to both
|
||||
* the same serer after receiving a greeting response and to a new
|
||||
* server after failover.
|
||||
*
|
||||
* @unmount_barrier: Incremented every time the remaining majority of
|
||||
* quorum members all agree to leave. The server tells a quorum member
|
||||
* the value that it's connecting under so that if the client sees the
|
||||
* value increase in the super block then it knows that the server has
|
||||
* processed its farewell and can safely unmount.
|
||||
*
|
||||
* @rid: The client's random id that was generated once as the mount
|
||||
* started up. This identifies a specific remote mount across
|
||||
* connections and servers. It's set to the client's rid in both the
|
||||
* request and response for consistency.
|
||||
*/
|
||||
struct scoutfs_net_greeting {
|
||||
__le64 fsid;
|
||||
__le64 format_hash;
|
||||
__le64 server_term;
|
||||
__le64 unmount_barrier;
|
||||
__le64 rid;
|
||||
__le64 flags;
|
||||
};
|
||||
|
||||
#define SCOUTFS_NET_GREETING_FLAG_FAREWELL (1 << 0)
|
||||
#define SCOUTFS_NET_GREETING_FLAG_VOTER (1 << 1)
|
||||
#define SCOUTFS_NET_GREETING_FLAG_INVALID (~(__u64)0 << 2)
|
||||
|
||||
/*
|
||||
* This header precedes and describes all network messages sent over
|
||||
* sockets.
|
||||
*
|
||||
* @seq: A sequence number that is increased for each message queued for
|
||||
* send on the sender. The sender will never reorder messages in the
|
||||
* send queue so this will always increase in recv on the receiver. The
|
||||
* receiver can use this to drop messages that arrived twice after being
|
||||
* resent across a newly connected socket for a given connection.
|
||||
*
|
||||
* @recv_seq: The sequence number of the last received message. The
|
||||
* receiver is sending this to the sender in every message. The sender
|
||||
* uses them to drop responses which have been delivered.
|
||||
*
|
||||
* @id: An increasing identifier that is set in each request. Responses
|
||||
* specify the request that they're responding to.
|
||||
*
|
||||
* Error is only set to a translated errno and will only be found in
|
||||
* response messages.
|
||||
*/
|
||||
struct scoutfs_net_header {
|
||||
__le64 clock_sync_id;
|
||||
__le64 seq;
|
||||
__le64 recv_seq;
|
||||
__le64 id;
|
||||
__le16 data_len;
|
||||
__u8 cmd;
|
||||
__u8 flags;
|
||||
__u8 error;
|
||||
__u8 __pad[3];
|
||||
__u8 data[0];
|
||||
};
|
||||
|
||||
#define SCOUTFS_NET_FLAG_RESPONSE (1 << 0)
|
||||
#define SCOUTFS_NET_FLAGS_UNKNOWN (U8_MAX << 1)
|
||||
|
||||
enum scoutfs_net_cmd {
|
||||
SCOUTFS_NET_CMD_GREETING = 0,
|
||||
SCOUTFS_NET_CMD_ALLOC_INODES,
|
||||
SCOUTFS_NET_CMD_GET_LOG_TREES,
|
||||
SCOUTFS_NET_CMD_COMMIT_LOG_TREES,
|
||||
SCOUTFS_NET_CMD_GET_ROOTS,
|
||||
SCOUTFS_NET_CMD_ADVANCE_SEQ,
|
||||
SCOUTFS_NET_CMD_GET_LAST_SEQ,
|
||||
SCOUTFS_NET_CMD_LOCK,
|
||||
SCOUTFS_NET_CMD_LOCK_RECOVER,
|
||||
SCOUTFS_NET_CMD_SRCH_GET_COMPACT,
|
||||
SCOUTFS_NET_CMD_SRCH_COMMIT_COMPACT,
|
||||
SCOUTFS_NET_CMD_FAREWELL,
|
||||
SCOUTFS_NET_CMD_UNKNOWN,
|
||||
};
|
||||
|
||||
/*
|
||||
* Define a macro to evaluate another macro for each of the errnos we
|
||||
* translate over the wire. This lets us keep our enum in sync with the
|
||||
* mapping arrays to and from host errnos.
|
||||
*/
|
||||
#define EXPAND_EACH_NET_ERRNO \
|
||||
EXPAND_NET_ERRNO(ENOENT) \
|
||||
EXPAND_NET_ERRNO(ENOMEM) \
|
||||
EXPAND_NET_ERRNO(EIO) \
|
||||
EXPAND_NET_ERRNO(ENOSPC) \
|
||||
EXPAND_NET_ERRNO(EINVAL)
|
||||
|
||||
#undef EXPAND_NET_ERRNO
|
||||
#define EXPAND_NET_ERRNO(which) SCOUTFS_NET_ERR_##which,
|
||||
enum scoutfs_net_errors {
|
||||
SCOUTFS_NET_ERR_NONE = 0,
|
||||
EXPAND_EACH_NET_ERRNO
|
||||
SCOUTFS_NET_ERR_UNKNOWN,
|
||||
};
|
||||
|
||||
/* arbitrarily chosen to be safely less than mss and allow 1k with header */
|
||||
#define SCOUTFS_NET_MAX_DATA_LEN 1100
|
||||
|
||||
/*
|
||||
* When there's no more free inodes this will be sent with ino = ~0 and
|
||||
* nr = 0.
|
||||
*/
|
||||
struct scoutfs_net_inode_alloc {
|
||||
__le64 ino;
|
||||
__le64 nr;
|
||||
};
|
||||
|
||||
struct scoutfs_net_roots {
|
||||
struct scoutfs_btree_root fs_root;
|
||||
struct scoutfs_btree_root logs_root;
|
||||
struct scoutfs_btree_root srch_root;
|
||||
};
|
||||
|
||||
struct scoutfs_net_lock {
|
||||
struct scoutfs_key key;
|
||||
__le64 write_version;
|
||||
__u8 old_mode;
|
||||
__u8 new_mode;
|
||||
__u8 __pad[6];
|
||||
};
|
||||
|
||||
struct scoutfs_net_lock_grant_response {
|
||||
struct scoutfs_net_lock nl;
|
||||
struct scoutfs_net_roots roots;
|
||||
};
|
||||
|
||||
struct scoutfs_net_lock_recover {
|
||||
__le16 nr;
|
||||
__u8 __pad[6];
|
||||
struct scoutfs_net_lock locks[0];
|
||||
};
|
||||
|
||||
#define SCOUTFS_NET_LOCK_MAX_RECOVER_NR \
|
||||
((SCOUTFS_NET_MAX_DATA_LEN - sizeof(struct scoutfs_net_lock_recover)) /\
|
||||
sizeof(struct scoutfs_net_lock))
|
||||
|
||||
/* some enums for tracing */
|
||||
enum scoutfs_lock_trace {
|
||||
SLT_CLIENT,
|
||||
SLT_SERVER,
|
||||
SLT_GRANT,
|
||||
SLT_INVALIDATE,
|
||||
SLT_REQUEST,
|
||||
SLT_RESPONSE,
|
||||
};
|
||||
|
||||
/*
|
||||
* Read and write locks operate as you'd expect. Multiple readers can
|
||||
* hold read locks while writers are excluded. A single writer can hold
|
||||
* a write lock which excludes other readers and writers. Writers can
|
||||
* read while holding a write lock.
|
||||
*
|
||||
* Multiple writers can hold write only locks but they can not read,
|
||||
* they can only generate dirty items. It's used when the system has
|
||||
* other means of knowing that it's safe to overwrite items.
|
||||
*
|
||||
* The null mode provides no access and is used to destroy locks.
|
||||
*/
|
||||
enum scoutfs_lock_mode {
|
||||
SCOUTFS_LOCK_NULL = 0,
|
||||
SCOUTFS_LOCK_READ,
|
||||
SCOUTFS_LOCK_WRITE,
|
||||
SCOUTFS_LOCK_WRITE_ONLY,
|
||||
SCOUTFS_LOCK_INVALID,
|
||||
};
|
||||
|
||||
/*
|
||||
* Scoutfs file handle structure - this can be copied out to userspace
|
||||
* via open by handle or put on the wire from NFS.
|
||||
*/
|
||||
struct scoutfs_fid {
|
||||
__le64 ino;
|
||||
__le64 parent_ino;
|
||||
};
|
||||
|
||||
#define FILEID_SCOUTFS 0x81
|
||||
#define FILEID_SCOUTFS_WITH_PARENT 0x82
|
||||
|
||||
/*
|
||||
* Identifiers for sources of corruption that can generate messages.
|
||||
*/
|
||||
enum scoutfs_corruption_sources {
|
||||
SC_DIRENT_NAME_LEN = 0,
|
||||
SC_DIRENT_BACKREF_NAME_LEN,
|
||||
SC_DIRENT_READDIR_NAME_LEN,
|
||||
SC_SYMLINK_INODE_SIZE,
|
||||
SC_SYMLINK_MISSING_ITEM,
|
||||
SC_SYMLINK_NOT_NULL_TERM,
|
||||
SC_BTREE_BLOCK_LEVEL,
|
||||
SC_BTREE_NO_CHILD_REF,
|
||||
SC_INODE_BLOCK_COUNTS,
|
||||
SC_NR_SOURCES,
|
||||
};
|
||||
|
||||
#define SC_NR_LONGS DIV_ROUND_UP(SC_NR_SOURCES, BITS_PER_LONG)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef _SCOUTFS_HASH_H_
|
||||
#define _SCOUTFS_HASH_H_
|
||||
|
||||
/*
|
||||
* We're using FNV1a for now. It's fine. Ish.
|
||||
*
|
||||
* The longer term plan is xxh3 but it looks like it'll take just a bit
|
||||
* more time to be declared stable and then it needs to be ported to the
|
||||
* kernel.
|
||||
*
|
||||
* - https://fastcompression.blogspot.com/2019/03/presenting-xxh3.html
|
||||
* - https://github.com/Cyan4973/xxHash/releases/tag/v0.7.4
|
||||
*/
|
||||
|
||||
static inline u32 fnv1a32(const void *data, unsigned int len)
|
||||
{
|
||||
u32 hash = 0x811c9dc5;
|
||||
|
||||
while (len--) {
|
||||
hash ^= *(u8 *)(data++);
|
||||
hash *= 0x01000193;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static inline u64 fnv1a64(const void *data, unsigned int len)
|
||||
{
|
||||
u64 hash = 0xcbf29ce484222325ULL;
|
||||
|
||||
while (len--) {
|
||||
hash ^= *(u8 *)(data++);
|
||||
hash *= 0x100000001b3ULL;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static inline u32 scoutfs_hash32(const void *data, unsigned int len)
|
||||
{
|
||||
return fnv1a32(data, len);
|
||||
}
|
||||
|
||||
static inline u64 scoutfs_hash64(const void *data, unsigned int len)
|
||||
{
|
||||
return fnv1a64(data, len);
|
||||
}
|
||||
|
||||
#endif
|
||||
+1761
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,124 @@
|
||||
#ifndef _SCOUTFS_INODE_H_
|
||||
#define _SCOUTFS_INODE_H_
|
||||
|
||||
#include "key.h"
|
||||
#include "lock.h"
|
||||
#include "per_task.h"
|
||||
#include "count.h"
|
||||
#include "format.h"
|
||||
#include "data.h"
|
||||
|
||||
struct scoutfs_lock;
|
||||
|
||||
struct scoutfs_inode_info {
|
||||
/* read or initialized for each inode instance */
|
||||
u64 ino;
|
||||
u64 next_readdir_pos;
|
||||
u64 next_xattr_id;
|
||||
u64 meta_seq;
|
||||
u64 data_seq;
|
||||
u64 data_version;
|
||||
u64 online_blocks;
|
||||
u64 offline_blocks;
|
||||
u32 flags;
|
||||
|
||||
/*
|
||||
* The in-memory item info caches the current index item values
|
||||
* so that we can decide to update them with comparisons instead
|
||||
* of by maintaining state that tracks the inode differing from
|
||||
* the item. The "item_" prefix is a bit clumsy :/.
|
||||
*/
|
||||
struct mutex item_mutex;
|
||||
bool have_item;
|
||||
u64 item_majors[SCOUTFS_INODE_INDEX_NR];
|
||||
u32 item_minors[SCOUTFS_INODE_INDEX_NR];
|
||||
|
||||
/* updated at on each new lock acquisition */
|
||||
atomic64_t last_refreshed;
|
||||
|
||||
/* initialized once for slab object */
|
||||
seqcount_t seqcount;
|
||||
bool staging; /* holder of i_mutex is staging */
|
||||
struct scoutfs_per_task pt_data_lock;
|
||||
struct scoutfs_data_waitq data_waitq;
|
||||
struct rw_semaphore xattr_rwsem;
|
||||
struct rb_node writeback_node;
|
||||
|
||||
struct inode inode;
|
||||
};
|
||||
|
||||
static inline struct scoutfs_inode_info *SCOUTFS_I(struct inode *inode)
|
||||
{
|
||||
return container_of(inode, struct scoutfs_inode_info, inode);
|
||||
}
|
||||
|
||||
static inline u64 scoutfs_ino(struct inode *inode)
|
||||
{
|
||||
return SCOUTFS_I(inode)->ino;
|
||||
}
|
||||
|
||||
struct inode *scoutfs_alloc_inode(struct super_block *sb);
|
||||
void scoutfs_destroy_inode(struct inode *inode);
|
||||
int scoutfs_drop_inode(struct inode *inode);
|
||||
void scoutfs_evict_inode(struct inode *inode);
|
||||
int scoutfs_orphan_inode(struct inode *inode);
|
||||
|
||||
struct inode *scoutfs_iget(struct super_block *sb, u64 ino);
|
||||
struct inode *scoutfs_ilookup(struct super_block *sb, u64 ino);
|
||||
|
||||
void scoutfs_inode_init_index_key(struct scoutfs_key *key, u8 type, u64 major,
|
||||
u32 minor, u64 ino);
|
||||
int scoutfs_inode_index_start(struct super_block *sb, u64 *seq);
|
||||
int scoutfs_inode_index_prepare(struct super_block *sb, struct list_head *list,
|
||||
struct inode *inode, bool set_data_seq);
|
||||
int scoutfs_inode_index_prepare_ino(struct super_block *sb,
|
||||
struct list_head *list, u64 ino,
|
||||
umode_t mode);
|
||||
int scoutfs_inode_index_try_lock_hold(struct super_block *sb,
|
||||
struct list_head *list, u64 seq,
|
||||
const struct scoutfs_item_count cnt);
|
||||
int scoutfs_inode_index_lock_hold(struct inode *inode, struct list_head *list,
|
||||
bool set_data_seq,
|
||||
const struct scoutfs_item_count cnt);
|
||||
void scoutfs_inode_index_unlock(struct super_block *sb, struct list_head *list);
|
||||
|
||||
int scoutfs_dirty_inode_item(struct inode *inode, struct scoutfs_lock *lock);
|
||||
void scoutfs_update_inode_item(struct inode *inode, struct scoutfs_lock *lock,
|
||||
struct list_head *ind_locks);
|
||||
|
||||
int scoutfs_alloc_ino(struct super_block *sb, bool is_dir, u64 *ino_ret);
|
||||
struct inode *scoutfs_new_inode(struct super_block *sb, struct inode *dir,
|
||||
umode_t mode, dev_t rdev, u64 ino,
|
||||
struct scoutfs_lock *lock);
|
||||
|
||||
void scoutfs_inode_set_meta_seq(struct inode *inode);
|
||||
void scoutfs_inode_set_data_seq(struct inode *inode);
|
||||
void scoutfs_inode_inc_data_version(struct inode *inode);
|
||||
void scoutfs_inode_set_data_version(struct inode *inode, u64 data_version);
|
||||
void scoutfs_inode_add_onoff(struct inode *inode, s64 on, s64 off);
|
||||
u64 scoutfs_inode_meta_seq(struct inode *inode);
|
||||
u64 scoutfs_inode_data_seq(struct inode *inode);
|
||||
u64 scoutfs_inode_data_version(struct inode *inode);
|
||||
void scoutfs_inode_get_onoff(struct inode *inode, s64 *on, s64 *off);
|
||||
int scoutfs_complete_truncate(struct inode *inode, struct scoutfs_lock *lock);
|
||||
|
||||
int scoutfs_inode_refresh(struct inode *inode, struct scoutfs_lock *lock,
|
||||
int flags);
|
||||
int scoutfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
|
||||
struct kstat *stat);
|
||||
int scoutfs_setattr(struct dentry *dentry, struct iattr *attr);
|
||||
|
||||
int scoutfs_scan_orphans(struct super_block *sb);
|
||||
|
||||
void scoutfs_inode_queue_writeback(struct inode *inode);
|
||||
int scoutfs_inode_walk_writeback(struct super_block *sb, bool write);
|
||||
|
||||
u64 scoutfs_last_ino(struct super_block *sb);
|
||||
|
||||
void scoutfs_inode_exit(void);
|
||||
int scoutfs_inode_init(void);
|
||||
|
||||
int scoutfs_inode_setup(struct super_block *sb);
|
||||
void scoutfs_inode_destroy(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,966 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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/fs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/uio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/aio.h>
|
||||
|
||||
#include "format.h"
|
||||
#include "key.h"
|
||||
#include "dir.h"
|
||||
#include "ioctl.h"
|
||||
#include "super.h"
|
||||
#include "inode.h"
|
||||
#include "item.h"
|
||||
#include "forest.h"
|
||||
#include "data.h"
|
||||
#include "client.h"
|
||||
#include "lock.h"
|
||||
#include "trans.h"
|
||||
#include "xattr.h"
|
||||
#include "hash.h"
|
||||
#include "srch.h"
|
||||
#include "alloc.h"
|
||||
#include "scoutfs_trace.h"
|
||||
|
||||
/*
|
||||
* We make inode index items coherent by locking fixed size regions of
|
||||
* the key space. But the inode index item key space is vast and can
|
||||
* have huge sparse regions. To avoid trying every possible lock in the
|
||||
* sparse regions we use the manifest to find the next stable key in the
|
||||
* key space after we find no items in a given lock region. This is
|
||||
* relatively cheap because reading is going to check the segments
|
||||
* anyway.
|
||||
*
|
||||
* This is copying to userspace while holding a read lock. This is safe
|
||||
* because faulting can send a request for a write lock while the read
|
||||
* lock is being used. The cluster locks don't block tasks in a node,
|
||||
* they match and the tasks fall back to local locking. In this case
|
||||
* the spin locks around the item cache.
|
||||
*/
|
||||
static long scoutfs_ioc_walk_inodes(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct super_block *sb = file_inode(file)->i_sb;
|
||||
struct scoutfs_ioctl_walk_inodes __user *uwalk = (void __user *)arg;
|
||||
struct scoutfs_ioctl_walk_inodes walk;
|
||||
struct scoutfs_ioctl_walk_inodes_entry ent;
|
||||
struct scoutfs_key next_key;
|
||||
struct scoutfs_key last_key;
|
||||
struct scoutfs_key key;
|
||||
struct scoutfs_lock *lock;
|
||||
u64 last_seq;
|
||||
int ret = 0;
|
||||
u32 nr = 0;
|
||||
u8 type;
|
||||
|
||||
if (copy_from_user(&walk, uwalk, sizeof(walk)))
|
||||
return -EFAULT;
|
||||
|
||||
trace_scoutfs_ioc_walk_inodes(sb, &walk);
|
||||
|
||||
if (walk.index == SCOUTFS_IOC_WALK_INODES_META_SEQ)
|
||||
type = SCOUTFS_INODE_INDEX_META_SEQ_TYPE;
|
||||
else if (walk.index == SCOUTFS_IOC_WALK_INODES_DATA_SEQ)
|
||||
type = SCOUTFS_INODE_INDEX_DATA_SEQ_TYPE;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
/* clamp results to the inodes in the farthest stable seq */
|
||||
if (type == SCOUTFS_INODE_INDEX_META_SEQ_TYPE ||
|
||||
type == SCOUTFS_INODE_INDEX_DATA_SEQ_TYPE) {
|
||||
|
||||
ret = scoutfs_client_get_last_seq(sb, &last_seq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (last_seq < walk.last.major) {
|
||||
walk.last.major = last_seq;
|
||||
walk.last.minor = ~0;
|
||||
walk.last.ino = ~0ULL;
|
||||
}
|
||||
}
|
||||
|
||||
scoutfs_inode_init_index_key(&key, type, walk.first.major,
|
||||
walk.first.minor, walk.first.ino);
|
||||
scoutfs_inode_init_index_key(&last_key, type, walk.last.major,
|
||||
walk.last.minor, walk.last.ino);
|
||||
|
||||
/* cap nr to the max the ioctl can return to a compat task */
|
||||
walk.nr_entries = min_t(u64, walk.nr_entries, INT_MAX);
|
||||
|
||||
ret = scoutfs_lock_inode_index(sb, SCOUTFS_LOCK_READ, type,
|
||||
walk.first.major, walk.first.ino,
|
||||
&lock);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
for (nr = 0; nr < walk.nr_entries; ) {
|
||||
|
||||
ret = scoutfs_item_next(sb, &key, &last_key, NULL, 0, lock);
|
||||
if (ret < 0 && ret != -ENOENT)
|
||||
break;
|
||||
|
||||
if (ret == -ENOENT) {
|
||||
|
||||
/* done if lock covers last iteration key */
|
||||
if (scoutfs_key_compare(&last_key, &lock->end) <= 0) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* continue iterating after locked empty region */
|
||||
key = lock->end;
|
||||
scoutfs_key_inc(&key);
|
||||
|
||||
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
|
||||
|
||||
ret = scoutfs_forest_next_hint(sb, &key, &next_key);
|
||||
if (ret < 0 && ret != -ENOENT)
|
||||
goto out;
|
||||
|
||||
if (ret == -ENOENT ||
|
||||
scoutfs_key_compare(&next_key, &last_key) > 0) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
key = next_key;
|
||||
|
||||
ret = scoutfs_lock_inode_index(sb, SCOUTFS_LOCK_READ,
|
||||
key.sk_type,
|
||||
le64_to_cpu(key.skii_major),
|
||||
le64_to_cpu(key.skii_ino),
|
||||
&lock);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ent.major = le64_to_cpu(key.skii_major);
|
||||
ent.minor = 0;
|
||||
ent.ino = le64_to_cpu(key.skii_ino);
|
||||
|
||||
if (copy_to_user((void __user *)walk.entries_ptr, &ent,
|
||||
sizeof(ent))) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
nr++;
|
||||
walk.entries_ptr += sizeof(ent);
|
||||
|
||||
scoutfs_key_inc(&key);
|
||||
}
|
||||
|
||||
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
|
||||
|
||||
out:
|
||||
if (nr > 0)
|
||||
ret = nr;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* See the comment above the definition of struct scoutfs_ioctl_ino_path
|
||||
* for ioctl semantics.
|
||||
*/
|
||||
static long scoutfs_ioc_ino_path(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct super_block *sb = file_inode(file)->i_sb;
|
||||
struct scoutfs_ioctl_ino_path_result __user *ures;
|
||||
struct scoutfs_link_backref_entry *last_ent;
|
||||
struct scoutfs_link_backref_entry *ent;
|
||||
struct scoutfs_ioctl_ino_path args;
|
||||
LIST_HEAD(list);
|
||||
u16 copied;
|
||||
char term;
|
||||
int ret;
|
||||
|
||||
if (!capable(CAP_DAC_READ_SEARCH))
|
||||
return -EPERM;
|
||||
|
||||
if (copy_from_user(&args, (void __user *)arg, sizeof(args)))
|
||||
return -EFAULT;
|
||||
|
||||
ures = (void __user *)(unsigned long)args.result_ptr;
|
||||
|
||||
ret = scoutfs_dir_get_backref_path(sb, args.ino, args.dir_ino,
|
||||
args.dir_pos, &list);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
last_ent = list_last_entry(&list, struct scoutfs_link_backref_entry,
|
||||
head);
|
||||
copied = 0;
|
||||
list_for_each_entry(ent, &list, head) {
|
||||
|
||||
if (offsetof(struct scoutfs_ioctl_ino_path_result,
|
||||
path[copied + ent->name_len + 1])
|
||||
> args.result_bytes) {
|
||||
ret = -ENAMETOOLONG;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (copy_to_user(&ures->path[copied],
|
||||
ent->dent.name, ent->name_len)) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
copied += ent->name_len;
|
||||
|
||||
if (ent == last_ent)
|
||||
term = '\0';
|
||||
else
|
||||
term = '/';
|
||||
|
||||
if (put_user(term, &ures->path[copied])) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
copied++;
|
||||
}
|
||||
|
||||
/* fill the result header now that we know the copied path length */
|
||||
if (put_user(last_ent->dir_ino, &ures->dir_ino) ||
|
||||
put_user(last_ent->dir_pos, &ures->dir_pos) ||
|
||||
put_user(copied, &ures->path_bytes)) {
|
||||
ret = -EFAULT;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
out:
|
||||
scoutfs_dir_free_backref_path(sb, &list);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The caller has a version of the data available in the given byte
|
||||
* range in an external archive. As long as the data version still
|
||||
* matches we free the blocks fully contained in the range and mark them
|
||||
* offline. Attempts to use the blocks in the future will trigger
|
||||
* recall from the archive.
|
||||
*
|
||||
* If the file's online blocks drop to 0 then we also truncate any
|
||||
* blocks beyond i_size. This honors the intent of fully releasing a file
|
||||
* without the user needing to know to release past i_size or truncate.
|
||||
*
|
||||
* XXX permissions?
|
||||
* XXX a lot of this could be generic file write prep
|
||||
*/
|
||||
static long scoutfs_ioc_release(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_ioctl_release args;
|
||||
struct scoutfs_lock *lock = NULL;
|
||||
loff_t start;
|
||||
loff_t end_inc;
|
||||
u64 online;
|
||||
u64 offline;
|
||||
u64 isize;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&args, (void __user *)arg, sizeof(args)))
|
||||
return -EFAULT;
|
||||
|
||||
trace_scoutfs_ioc_release(sb, scoutfs_ino(inode), &args);
|
||||
|
||||
if (args.count == 0)
|
||||
return 0;
|
||||
if ((args.block + args.count) < args.block)
|
||||
return -EINVAL;
|
||||
|
||||
|
||||
ret = mnt_want_write_file(file);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&inode->i_mutex);
|
||||
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_WRITE,
|
||||
SCOUTFS_LKF_REFRESH_INODE, inode, &lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (!S_ISREG(inode->i_mode)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(file->f_mode & FMODE_WRITE)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (scoutfs_inode_data_version(inode) != args.data_version) {
|
||||
ret = -ESTALE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
inode_dio_wait(inode);
|
||||
|
||||
/* drop all clean and dirty cached blocks in the range */
|
||||
start = args.block << SCOUTFS_BLOCK_SM_SHIFT;
|
||||
end_inc = ((args.block + args.count) << SCOUTFS_BLOCK_SM_SHIFT) - 1;
|
||||
truncate_inode_pages_range(&inode->i_data, start, end_inc);
|
||||
|
||||
ret = scoutfs_data_truncate_items(sb, inode, scoutfs_ino(inode),
|
||||
args.block,
|
||||
args.block + args.count - 1, true,
|
||||
lock);
|
||||
if (ret == 0) {
|
||||
scoutfs_inode_get_onoff(inode, &online, &offline);
|
||||
isize = i_size_read(inode);
|
||||
if (online == 0 && isize) {
|
||||
start = (isize + SCOUTFS_BLOCK_SM_SIZE - 1)
|
||||
>> SCOUTFS_BLOCK_SM_SHIFT;
|
||||
ret = scoutfs_data_truncate_items(sb, inode,
|
||||
scoutfs_ino(inode),
|
||||
start, U64_MAX,
|
||||
false, lock);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE);
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
mnt_drop_write_file(file);
|
||||
|
||||
trace_scoutfs_ioc_release_ret(sb, scoutfs_ino(inode), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long scoutfs_ioc_data_wait_err(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct super_block *sb = file_inode(file)->i_sb;
|
||||
struct scoutfs_ioctl_data_wait_err args;
|
||||
struct scoutfs_lock *lock = NULL;
|
||||
struct inode *inode = NULL;
|
||||
u64 sblock;
|
||||
u64 eblock;
|
||||
long ret;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
if (copy_from_user(&args, (void __user *)arg, sizeof(args)))
|
||||
return -EFAULT;
|
||||
if (args.count == 0)
|
||||
return 0;
|
||||
if ((args.op & SCOUTFS_IOC_DWO_UNKNOWN) || !IS_ERR_VALUE(args.err))
|
||||
return -EINVAL;
|
||||
if ((args.op & SCOUTFS_IOC_DWO_UNKNOWN) || !IS_ERR_VALUE(args.err))
|
||||
return -EINVAL;
|
||||
|
||||
trace_scoutfs_ioc_data_wait_err(sb, &args);
|
||||
|
||||
sblock = args.offset >> SCOUTFS_BLOCK_SM_SHIFT;
|
||||
eblock = (args.offset + args.count - 1) >> SCOUTFS_BLOCK_SM_SHIFT;
|
||||
|
||||
if (sblock > eblock)
|
||||
return -EINVAL;
|
||||
|
||||
inode = scoutfs_ilookup(sb, args.ino);
|
||||
if (!inode) {
|
||||
ret = -ESTALE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mutex_lock(&inode->i_mutex);
|
||||
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
|
||||
SCOUTFS_LKF_REFRESH_INODE, inode, &lock);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
if (!S_ISREG(inode->i_mode)) {
|
||||
ret = -EINVAL;
|
||||
} else if (scoutfs_inode_data_version(inode) != args.data_version) {
|
||||
ret = -ESTALE;
|
||||
} else {
|
||||
ret = scoutfs_data_wait_err(inode, sblock, eblock, args.op,
|
||||
args.err);
|
||||
}
|
||||
|
||||
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
|
||||
unlock:
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
iput(inode);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the archived contents of the file back if the data_version
|
||||
* still matches.
|
||||
*
|
||||
* This is a data plane operation only. We don't want the write to
|
||||
* change any fields in the inode. It only changes the file contents.
|
||||
*
|
||||
* Keep in mind that the staging writes can easily span transactions and
|
||||
* can crash partway through. If we called the normal write path and
|
||||
* restored the inode afterwards the modified inode could be commited
|
||||
* partway through by a transaction and then left that way by a crash
|
||||
* before the write finishes and we restore the fields. It also
|
||||
* wouldn't be great if the temporarily updated inode was visible to
|
||||
* paths that don't serialize with write.
|
||||
*
|
||||
* We're implementing the buffered write path down to the start of
|
||||
* generic_file_buffered_writes() without all the stuff that would
|
||||
* change the inode: file_remove_suid(), file_update_time(). The
|
||||
* easiest way to do that is to call generic_file_buffered_write().
|
||||
* We're careful to only allow staging writes inside i_size.
|
||||
*
|
||||
* We set a bool on the inode which tells our code to update the
|
||||
* offline extents and to not update the data_version counter.
|
||||
*
|
||||
* This doesn't support any fancy write modes or side-effects: aio,
|
||||
* direct, append, sync, breaking suid, sending rlimit signals.
|
||||
*/
|
||||
static long scoutfs_ioc_stage(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct address_space *mapping = inode->i_mapping;
|
||||
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
|
||||
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_ent);
|
||||
struct scoutfs_ioctl_stage args;
|
||||
struct scoutfs_lock *lock = NULL;
|
||||
struct kiocb kiocb;
|
||||
struct iovec iov;
|
||||
size_t written;
|
||||
loff_t end_size;
|
||||
loff_t isize;
|
||||
loff_t pos;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&args, (void __user *)arg, sizeof(args)))
|
||||
return -EFAULT;
|
||||
|
||||
trace_scoutfs_ioc_stage(sb, scoutfs_ino(inode), &args);
|
||||
|
||||
end_size = args.offset + args.count;
|
||||
|
||||
/* verify arg constraints that aren't dependent on file */
|
||||
if (args.count < 0 || (end_size < args.offset) ||
|
||||
args.offset & SCOUTFS_BLOCK_SM_MASK)
|
||||
return -EINVAL;
|
||||
|
||||
if (args.count == 0)
|
||||
return 0;
|
||||
|
||||
/* the iocb is really only used for the file pointer :P */
|
||||
init_sync_kiocb(&kiocb, file);
|
||||
kiocb.ki_pos = args.offset;
|
||||
kiocb.ki_left = args.count;
|
||||
kiocb.ki_nbytes = args.count;
|
||||
iov.iov_base = (void __user *)(unsigned long)args.buf_ptr;
|
||||
iov.iov_len = args.count;
|
||||
|
||||
ret = mnt_want_write_file(file);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&inode->i_mutex);
|
||||
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_WRITE,
|
||||
SCOUTFS_LKF_REFRESH_INODE, inode, &lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
scoutfs_per_task_add(&si->pt_data_lock, &pt_ent, lock);
|
||||
|
||||
isize = i_size_read(inode);
|
||||
|
||||
if (!S_ISREG(inode->i_mode) ||
|
||||
!(file->f_mode & FMODE_WRITE) ||
|
||||
(file->f_flags & (O_APPEND | O_DIRECT | O_DSYNC)) ||
|
||||
IS_SYNC(file->f_mapping->host) ||
|
||||
(end_size > isize) ||
|
||||
((end_size & SCOUTFS_BLOCK_SM_MASK) && (end_size != isize))) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (scoutfs_inode_data_version(inode) != args.data_version) {
|
||||
ret = -ESTALE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
si->staging = true;
|
||||
current->backing_dev_info = mapping->backing_dev_info;
|
||||
|
||||
pos = args.offset;
|
||||
written = 0;
|
||||
do {
|
||||
ret = generic_file_buffered_write(&kiocb, &iov, 1, pos, &pos,
|
||||
args.count, written);
|
||||
BUG_ON(ret == -EIOCBQUEUED);
|
||||
if (ret > 0)
|
||||
written += ret;
|
||||
} while (ret > 0 && written < args.count);
|
||||
|
||||
si->staging = false;
|
||||
current->backing_dev_info = NULL;
|
||||
out:
|
||||
scoutfs_per_task_del(&si->pt_data_lock, &pt_ent);
|
||||
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE);
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
mnt_drop_write_file(file);
|
||||
|
||||
trace_scoutfs_ioc_stage_ret(sb, scoutfs_ino(inode), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long scoutfs_ioc_stat_more(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
struct scoutfs_ioctl_stat_more stm;
|
||||
|
||||
if (get_user(stm.valid_bytes, (__u64 __user *)arg))
|
||||
return -EFAULT;
|
||||
|
||||
stm.valid_bytes = min_t(u64, stm.valid_bytes,
|
||||
sizeof(struct scoutfs_ioctl_stat_more));
|
||||
stm.meta_seq = scoutfs_inode_meta_seq(inode);
|
||||
stm.data_seq = scoutfs_inode_data_seq(inode);
|
||||
stm.data_version = scoutfs_inode_data_version(inode);
|
||||
scoutfs_inode_get_onoff(inode, &stm.online_blocks, &stm.offline_blocks);
|
||||
|
||||
if (copy_to_user((void __user *)arg, &stm, stm.valid_bytes))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool inc_wrapped(u64 *ino, u64 *iblock)
|
||||
{
|
||||
return (++(*iblock) == 0) && (++(*ino) == 0);
|
||||
}
|
||||
|
||||
static long scoutfs_ioc_data_waiting(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct super_block *sb = file_inode(file)->i_sb;
|
||||
struct scoutfs_ioctl_data_waiting idw;
|
||||
struct scoutfs_ioctl_data_waiting_entry __user *udwe;
|
||||
struct scoutfs_ioctl_data_waiting_entry dwe[16];
|
||||
unsigned int nr;
|
||||
int total;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&idw, (void __user *)arg, sizeof(idw)))
|
||||
return -EFAULT;
|
||||
|
||||
if (idw.flags & SCOUTFS_IOC_DATA_WAITING_FLAGS_UNKNOWN)
|
||||
return -EINVAL;
|
||||
|
||||
udwe = (void __user *)(long)idw.ents_ptr;
|
||||
total = 0;
|
||||
ret = 0;
|
||||
while (idw.ents_nr && !inc_wrapped(&idw.after_ino, &idw.after_iblock)) {
|
||||
nr = min_t(size_t, idw.ents_nr, ARRAY_SIZE(dwe));
|
||||
|
||||
ret = scoutfs_data_waiting(sb, idw.after_ino, idw.after_iblock,
|
||||
dwe, nr);
|
||||
BUG_ON(ret > nr); /* stack overflow \o/ */
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
if (copy_to_user(udwe, dwe, ret * sizeof(dwe[0]))) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
idw.after_ino = dwe[ret - 1].ino;
|
||||
idw.after_iblock = dwe[ret - 1].iblock;
|
||||
|
||||
udwe += ret;
|
||||
idw.ents_nr -= ret;
|
||||
total += ret;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret ?: total;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is used when restoring files, it lets the caller set all the
|
||||
* inode attributes which are otherwise unreachable. Changing the file
|
||||
* size can only be done for regular files with a data_version of 0.
|
||||
*/
|
||||
static long scoutfs_ioc_setattr_more(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct inode *inode = file->f_inode;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_ioctl_setattr_more __user *usm = (void __user *)arg;
|
||||
struct scoutfs_ioctl_setattr_more sm;
|
||||
struct scoutfs_lock *lock = NULL;
|
||||
LIST_HEAD(ind_locks);
|
||||
bool set_data_seq;
|
||||
int ret;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN)) {
|
||||
ret = -EPERM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(file->f_mode & FMODE_WRITE)) {
|
||||
ret = -EBADF;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (copy_from_user(&sm, usm, sizeof(sm))) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((sm.i_size > 0 && sm.data_version == 0) ||
|
||||
((sm.flags & SCOUTFS_IOC_SETATTR_MORE_OFFLINE) && !sm.i_size) ||
|
||||
(sm.flags & SCOUTFS_IOC_SETATTR_MORE_UNKNOWN)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = mnt_want_write_file(file);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
mutex_lock(&inode->i_mutex);
|
||||
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_WRITE,
|
||||
SCOUTFS_LKF_REFRESH_INODE, inode, &lock);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
/* can only change size/dv on untouched regular files */
|
||||
if ((sm.i_size != 0 || sm.data_version != 0) &&
|
||||
((!S_ISREG(inode->i_mode) ||
|
||||
scoutfs_inode_data_version(inode) != 0))) {
|
||||
ret = -EINVAL;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* create offline extents in potentially many transactions */
|
||||
if (sm.flags & SCOUTFS_IOC_SETATTR_MORE_OFFLINE) {
|
||||
ret = scoutfs_data_init_offline_extent(inode, sm.i_size, lock);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* setting only so we don't see 0 data seq with nonzero data_version */
|
||||
set_data_seq = sm.data_version != 0 ? true : false;
|
||||
ret = scoutfs_inode_index_lock_hold(inode, &ind_locks, set_data_seq,
|
||||
SIC_SETATTR_MORE());
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
if (sm.data_version)
|
||||
scoutfs_inode_set_data_version(inode, sm.data_version);
|
||||
if (sm.i_size)
|
||||
i_size_write(inode, sm.i_size);
|
||||
inode->i_ctime.tv_sec = sm.ctime_sec;
|
||||
inode->i_ctime.tv_nsec = sm.ctime_nsec;
|
||||
|
||||
scoutfs_update_inode_item(inode, lock, &ind_locks);
|
||||
ret = 0;
|
||||
|
||||
scoutfs_release_trans(sb);
|
||||
unlock:
|
||||
scoutfs_inode_index_unlock(sb, &ind_locks);
|
||||
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE);
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
mnt_drop_write_file(file);
|
||||
out:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* This lists .hide. attributes on the inode. It doesn't include normal
|
||||
* xattrs that are visible to listxattr because we don't perform as
|
||||
* rigorous security access checks as normal vfs listxattr does.
|
||||
*/
|
||||
static long scoutfs_ioc_listxattr_hidden(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct inode *inode = file->f_inode;
|
||||
struct scoutfs_ioctl_listxattr_hidden __user *ulxr = (void __user *)arg;
|
||||
struct scoutfs_ioctl_listxattr_hidden lxh;
|
||||
struct page *page = NULL;
|
||||
unsigned int bytes;
|
||||
int total = 0;
|
||||
int ret;
|
||||
|
||||
ret = inode_permission(inode, MAY_READ);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (copy_from_user(&lxh, ulxr, sizeof(lxh))) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
page = alloc_page(GFP_KERNEL);
|
||||
if (!page) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (lxh.buf_bytes) {
|
||||
bytes = min_t(int, lxh.buf_bytes, PAGE_SIZE);
|
||||
ret = scoutfs_list_xattrs(inode, page_address(page), bytes,
|
||||
&lxh.hash_pos, &lxh.id_pos,
|
||||
false, true);
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
if (copy_to_user((void __user *)lxh.buf_ptr,
|
||||
page_address(page), ret)) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
lxh.buf_ptr += ret;
|
||||
lxh.buf_bytes -= ret;
|
||||
total += ret;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
out:
|
||||
if (page)
|
||||
__free_page(page);
|
||||
|
||||
if (ret == 0 && (__put_user(lxh.hash_pos, &ulxr->hash_pos) ||
|
||||
__put_user(lxh.id_pos, &ulxr->id_pos)))
|
||||
ret = -EFAULT;
|
||||
|
||||
return ret ?: total;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the inode numbers of inodes which might contain the given
|
||||
* named xattr. This will only find scoutfs xattrs with the index tag
|
||||
* but we don't check that the callers xattr name contains the tag and
|
||||
* search for it regardless.
|
||||
*/
|
||||
static long scoutfs_ioc_search_xattrs(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct super_block *sb = file_inode(file)->i_sb;
|
||||
struct scoutfs_ioctl_search_xattrs __user *usx = (void __user *)arg;
|
||||
struct scoutfs_ioctl_search_xattrs sx;
|
||||
struct scoutfs_xattr_prefix_tags tgs;
|
||||
struct scoutfs_srch_rb_root sroot;
|
||||
struct scoutfs_srch_rb_node *snode;
|
||||
u64 __user *uinos;
|
||||
struct rb_node *node;
|
||||
char *name = NULL;
|
||||
bool done = false;
|
||||
u64 prev_ino = 0;
|
||||
u64 total = 0;
|
||||
int ret;
|
||||
|
||||
if (!(file->f_mode & FMODE_READ)) {
|
||||
ret = -EBADF;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN)) {
|
||||
ret = -EPERM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (copy_from_user(&sx, usx, sizeof(sx))) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
uinos = (u64 __user *)sx.inodes_ptr;
|
||||
|
||||
if (sx.name_bytes > SCOUTFS_XATTR_MAX_NAME_LEN) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (sx.nr_inodes == 0 || sx.last_ino < sx.next_ino) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
name = kmalloc(sx.name_bytes, GFP_KERNEL);
|
||||
if (!name) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (copy_from_user(name, (void __user *)sx.name_ptr, sx.name_bytes)) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (scoutfs_xattr_parse_tags(name, sx.name_bytes, &tgs) < 0 ||
|
||||
!tgs.srch) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = scoutfs_srch_search_xattrs(sb, &sroot,
|
||||
scoutfs_hash64(name, sx.name_bytes),
|
||||
sx.next_ino, sx.last_ino, &done);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
prev_ino = 0;
|
||||
scoutfs_srch_foreach_rb_node(snode, node, &sroot) {
|
||||
if (prev_ino == snode->ino)
|
||||
continue;
|
||||
|
||||
if (put_user(snode->ino, uinos + total)) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
prev_ino = snode->ino;
|
||||
|
||||
if (++total == sx.nr_inodes)
|
||||
break;
|
||||
}
|
||||
|
||||
sx.output_flags = 0;
|
||||
if (done && total == sroot.nr)
|
||||
sx.output_flags |= SCOUTFS_SEARCH_XATTRS_OFLAG_END;
|
||||
|
||||
if (put_user(sx.output_flags, &usx->output_flags))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
scoutfs_srch_destroy_rb_root(&sroot);
|
||||
|
||||
out:
|
||||
kfree(name);
|
||||
return ret ?: total;
|
||||
}
|
||||
|
||||
static long scoutfs_ioc_statfs_more(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct super_block *sb = file_inode(file)->i_sb;
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_super_block *super = &sbi->super;
|
||||
struct scoutfs_ioctl_statfs_more sfm;
|
||||
int ret;
|
||||
|
||||
if (get_user(sfm.valid_bytes, (__u64 __user *)arg))
|
||||
return -EFAULT;
|
||||
|
||||
sfm.valid_bytes = min_t(u64, sfm.valid_bytes,
|
||||
sizeof(struct scoutfs_ioctl_statfs_more));
|
||||
sfm.fsid = le64_to_cpu(super->hdr.fsid);
|
||||
sfm.rid = sbi->rid;
|
||||
sfm.total_meta_blocks = le64_to_cpu(super->total_meta_blocks);
|
||||
sfm.total_data_blocks = le64_to_cpu(super->total_data_blocks);
|
||||
|
||||
ret = scoutfs_client_get_last_seq(sb, &sfm.committed_seq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (copy_to_user((void __user *)arg, &sfm, sfm.valid_bytes))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct copy_alloc_detail_args {
|
||||
struct scoutfs_ioctl_alloc_detail_entry __user *uade;
|
||||
u64 nr;
|
||||
u64 copied;
|
||||
};
|
||||
|
||||
static int copy_alloc_detail_to_user(struct super_block *sb, void *arg,
|
||||
int owner, u64 id, bool meta, bool avail,
|
||||
u64 blocks)
|
||||
{
|
||||
struct copy_alloc_detail_args *args = arg;
|
||||
struct scoutfs_ioctl_alloc_detail_entry ade;
|
||||
|
||||
if (args->copied == args->nr)
|
||||
return -EOVERFLOW;
|
||||
|
||||
ade.blocks = blocks;
|
||||
ade.id = id;
|
||||
ade.meta = !!meta;
|
||||
ade.avail = !!avail;
|
||||
|
||||
if (copy_to_user(&args->uade[args->copied], &ade, sizeof(ade)))
|
||||
return -EFAULT;
|
||||
|
||||
args->copied++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long scoutfs_ioc_alloc_detail(struct file *file, unsigned long arg)
|
||||
{
|
||||
struct super_block *sb = file_inode(file)->i_sb;
|
||||
struct scoutfs_ioctl_alloc_detail __user *uad = (void __user *)arg;
|
||||
struct scoutfs_ioctl_alloc_detail ad;
|
||||
struct copy_alloc_detail_args args;
|
||||
|
||||
if (copy_from_user(&ad, uad, sizeof(ad)))
|
||||
return -EFAULT;
|
||||
|
||||
args.uade = (struct scoutfs_ioctl_alloc_detail_entry __user *)
|
||||
(uintptr_t)ad.entries_ptr;
|
||||
args.nr = ad.entries_nr;
|
||||
args.copied = 0;
|
||||
|
||||
return scoutfs_alloc_foreach(sb, copy_alloc_detail_to_user, &args) ?:
|
||||
args.copied;
|
||||
}
|
||||
|
||||
long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case SCOUTFS_IOC_WALK_INODES:
|
||||
return scoutfs_ioc_walk_inodes(file, arg);
|
||||
case SCOUTFS_IOC_INO_PATH:
|
||||
return scoutfs_ioc_ino_path(file, arg);
|
||||
case SCOUTFS_IOC_RELEASE:
|
||||
return scoutfs_ioc_release(file, arg);
|
||||
case SCOUTFS_IOC_STAGE:
|
||||
return scoutfs_ioc_stage(file, arg);
|
||||
case SCOUTFS_IOC_STAT_MORE:
|
||||
return scoutfs_ioc_stat_more(file, arg);
|
||||
case SCOUTFS_IOC_DATA_WAITING:
|
||||
return scoutfs_ioc_data_waiting(file, arg);
|
||||
case SCOUTFS_IOC_SETATTR_MORE:
|
||||
return scoutfs_ioc_setattr_more(file, arg);
|
||||
case SCOUTFS_IOC_LISTXATTR_HIDDEN:
|
||||
return scoutfs_ioc_listxattr_hidden(file, arg);
|
||||
case SCOUTFS_IOC_SEARCH_XATTRS:
|
||||
return scoutfs_ioc_search_xattrs(file, arg);
|
||||
case SCOUTFS_IOC_STATFS_MORE:
|
||||
return scoutfs_ioc_statfs_more(file, arg);
|
||||
case SCOUTFS_IOC_DATA_WAIT_ERR:
|
||||
return scoutfs_ioc_data_wait_err(file, arg);
|
||||
case SCOUTFS_IOC_ALLOC_DETAIL:
|
||||
return scoutfs_ioc_alloc_detail(file, arg);
|
||||
}
|
||||
|
||||
return -ENOTTY;
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
#ifndef _SCOUTFS_IOCTL_H_
|
||||
#define _SCOUTFS_IOCTL_H_
|
||||
|
||||
/*
|
||||
* We naturally align explicit width fields in the ioctl structs so that
|
||||
* userspace doesn't need to deal with padding or unaligned packing and
|
||||
* we don't have to deal with 32/64 compat. It makes it a little
|
||||
* awkward to communicate persistent packed structs through the ioctls
|
||||
* but that happens very rarely. An interesting special case are
|
||||
* 0length arrays that follow the structs. We make those start at the
|
||||
* next aligned offset of the struct to be safe.
|
||||
*
|
||||
* This is enforced by pahole scripting in external build environments.
|
||||
*/
|
||||
|
||||
/* XXX I have no idea how these are chosen. */
|
||||
#define SCOUTFS_IOCTL_MAGIC 's'
|
||||
|
||||
/*
|
||||
* Packed scoutfs keys rarely cross the ioctl boundary so we have a
|
||||
* translation struct.
|
||||
*/
|
||||
struct scoutfs_ioctl_key {
|
||||
__le64 _sk_first;
|
||||
__le64 _sk_second;
|
||||
__le64 _sk_third;
|
||||
__u8 _sk_fourth;
|
||||
__u8 sk_type;
|
||||
__u8 sk_zone;
|
||||
__u8 _pad[5];
|
||||
};
|
||||
|
||||
struct scoutfs_ioctl_walk_inodes_entry {
|
||||
__u64 major;
|
||||
__u64 ino;
|
||||
__u32 minor;
|
||||
__u8 _pad[4];
|
||||
};
|
||||
|
||||
/*
|
||||
* Walk inodes in an index that is sorted by one of their fields.
|
||||
*
|
||||
* Each index is built from generic index items that have major and
|
||||
* minor values that are set to the field being indexed. In time
|
||||
* indices, for example, major is seconds and minor is nanoseconds.
|
||||
*
|
||||
* @first The first index entry that can be returned.
|
||||
* @last The last index entry that can be returned.
|
||||
* @entries_ptr Pointer to emory containing buffer for entry results.
|
||||
* @nr_entries The number of entries that can fit in the buffer.
|
||||
* @index Which index to walk, enumerated in _WALK_INODES_ constants.
|
||||
*
|
||||
* To start iterating first can be memset to 0 and last to 0xff. Then
|
||||
* after each set of results first can be set to the last entry returned
|
||||
* and then the fields can be incremented in reverse sort order (ino <
|
||||
* minor < major) as each increasingly significant value wraps around to
|
||||
* 0.
|
||||
*
|
||||
* These indexes are not strictly consistent. The items that back these
|
||||
* index entries aren't updated with cluster locks so they're not
|
||||
* guaranteed to be visible the moment you read after writing. They're
|
||||
* only visible when the transaction that updated them is synced.
|
||||
*
|
||||
* In addition, the seq indexes will only allow walking through sequence
|
||||
* space that has been consistent. This prevents old dirty entries from
|
||||
* becoming visible after newer stable entries are displayed.
|
||||
*
|
||||
* If first is greater than last then the walk will return 0 entries.
|
||||
*
|
||||
* XXX invalidate before reading.
|
||||
*/
|
||||
struct scoutfs_ioctl_walk_inodes {
|
||||
struct scoutfs_ioctl_walk_inodes_entry first;
|
||||
struct scoutfs_ioctl_walk_inodes_entry last;
|
||||
__u64 entries_ptr;
|
||||
__u32 nr_entries;
|
||||
__u8 index;
|
||||
__u8 _pad[11]; /* padded to align walk_inodes_entry total size */
|
||||
};
|
||||
|
||||
enum scoutfs_ino_walk_seq_type {
|
||||
SCOUTFS_IOC_WALK_INODES_META_SEQ = 0,
|
||||
SCOUTFS_IOC_WALK_INODES_DATA_SEQ,
|
||||
SCOUTFS_IOC_WALK_INODES_UNKNOWN,
|
||||
};
|
||||
|
||||
/*
|
||||
* Adds entries to the user's buffer for each inode that is found in the
|
||||
* given index between the first and last positions.
|
||||
*/
|
||||
#define SCOUTFS_IOC_WALK_INODES _IOR(SCOUTFS_IOCTL_MAGIC, 1, \
|
||||
struct scoutfs_ioctl_walk_inodes)
|
||||
|
||||
/*
|
||||
* Fill the result buffer with the next absolute path to the target
|
||||
* inode searching from a given position in a parent directory.
|
||||
*
|
||||
* @ino: The target ino that we're finding paths to. Constant across
|
||||
* all the calls that make up an iteration over all the inode's paths.
|
||||
*
|
||||
* @dir_ino: The inode number of the directory containing the entry to
|
||||
* our inode to search from. If this parent directory contains no more
|
||||
* entries to our inode then we'll search through other parent directory
|
||||
* inodes in inode order.
|
||||
*
|
||||
* @dir_pos: The position in the dir_ino parent directory of the entry
|
||||
* to our inode to search from. If there is no entry at this position
|
||||
* then we'll search through other entry positions in increasing order.
|
||||
* If we exhaust the parent directory then we'll search through
|
||||
* additional parent directories in inode order.
|
||||
*
|
||||
* @result_ptr: A pointer to the buffer where the result struct and
|
||||
* absolute path will be stored.
|
||||
*
|
||||
* @result_bytes: The size of the buffer that will contain the result
|
||||
* struct and the null terminated absolute path name.
|
||||
*
|
||||
* To start iterating set the desired target inode, dir_ino to 0,
|
||||
* dir_pos to 0, and set result_ptr and _bytes to a sufficiently large
|
||||
* buffeer (sizeof(result) + PATH_MAX is a solid choice).
|
||||
*
|
||||
* After each returned result set the next search dir_ino and dir_pos to
|
||||
* the returned dir_ino and dir_pos. Then increment the search dir_pos,
|
||||
* and if it wrapped to 0, increment dir_ino.
|
||||
*
|
||||
* This only walks back through full hard links. None of the returned
|
||||
* paths will reflect symlinks to components in the path.
|
||||
*
|
||||
* This doesn't ensure that the caller has permissions to traverse the
|
||||
* returned paths to the inode. It requires CAP_DAC_READ_SEARCH which
|
||||
* bypasses permissions checking.
|
||||
*
|
||||
* This call is not serialized with any modification (create, rename,
|
||||
* unlink) of the path components. It will return all the paths that
|
||||
* were stable both before and after the call. It may or may not return
|
||||
* paths which are created or unlinked during the call.
|
||||
*
|
||||
* On success 0 is returned and result struct is filled with the next
|
||||
* absolute path. The path_bytes length of the path includes a null
|
||||
* terminating byte. dir_ino and dir_pos refer to the position of the
|
||||
* final component in its parent directory and can be advanced to search
|
||||
* for the next terminal entry whose path is then built by walking up
|
||||
* parent directories.
|
||||
*
|
||||
* ENOENT is returned when no paths are found.
|
||||
*
|
||||
* ENAMETOOLONG is returned when the result struct and path found
|
||||
* doesn't fit in the result buffer.
|
||||
*
|
||||
* Many other errnos indicate hard failure to find the next path.
|
||||
*/
|
||||
struct scoutfs_ioctl_ino_path {
|
||||
__u64 ino;
|
||||
__u64 dir_ino;
|
||||
__u64 dir_pos;
|
||||
__u64 result_ptr;
|
||||
__u16 result_bytes;
|
||||
__u8 _pad[6];
|
||||
};
|
||||
|
||||
struct scoutfs_ioctl_ino_path_result {
|
||||
__u64 dir_ino;
|
||||
__u64 dir_pos;
|
||||
__u16 path_bytes;
|
||||
__u8 _pad[6];
|
||||
__u8 path[0];
|
||||
};
|
||||
|
||||
/* Get a single path from the root to the given inode number */
|
||||
#define SCOUTFS_IOC_INO_PATH _IOR(SCOUTFS_IOCTL_MAGIC, 2, \
|
||||
struct scoutfs_ioctl_ino_path)
|
||||
|
||||
/*
|
||||
* "Release" a contiguous range of logical blocks of file data.
|
||||
* Released blocks are removed from the file system like truncation, but
|
||||
* an offline record is left behind to trigger demand staging if the
|
||||
* file is read.
|
||||
*
|
||||
* The starting block offset and number of blocks to release are in
|
||||
* units 4KB blocks.
|
||||
*
|
||||
* The specified range can extend past i_size and can straddle sparse
|
||||
* regions or blocks that are already offline. The only change it makes
|
||||
* is to free and mark offline any existing blocks that intersect with
|
||||
* the region.
|
||||
*
|
||||
* Returns 0 if the operation succeeds. If an error is returned then
|
||||
* some partial region of the blocks in the region may have been marked
|
||||
* offline.
|
||||
*
|
||||
* If the operation succeeds then inode metadata that reflects file data
|
||||
* contents are not updated. This is intended to be transparent to the
|
||||
* presentation of the data in the file.
|
||||
*/
|
||||
struct scoutfs_ioctl_release {
|
||||
__u64 block;
|
||||
__u64 count;
|
||||
__u64 data_version;
|
||||
};
|
||||
|
||||
#define SCOUTFS_IOC_RELEASE _IOW(SCOUTFS_IOCTL_MAGIC, 3, \
|
||||
struct scoutfs_ioctl_release)
|
||||
|
||||
struct scoutfs_ioctl_stage {
|
||||
__u64 data_version;
|
||||
__u64 buf_ptr;
|
||||
__u64 offset;
|
||||
__s32 count;
|
||||
__u32 _pad;
|
||||
};
|
||||
|
||||
#define SCOUTFS_IOC_STAGE _IOW(SCOUTFS_IOCTL_MAGIC, 4, \
|
||||
struct scoutfs_ioctl_stage)
|
||||
|
||||
/*
|
||||
* Give the user inode fields that are not otherwise visible. statx()
|
||||
* isn't always available and xattrs are relatively expensive.
|
||||
*
|
||||
* @valid_bytes stores the number of bytes that are valid in the
|
||||
* structure. The caller sets this to the size of the struct that they
|
||||
* understand. The kernel then fills and copies back the min of the
|
||||
* size they and the user caller understand. The user can tell if a
|
||||
* field is set if all of its bytes are within the valid_bytes that the
|
||||
* kernel set on return.
|
||||
*
|
||||
* New fields are only added to the end of the struct.
|
||||
*/
|
||||
struct scoutfs_ioctl_stat_more {
|
||||
__u64 valid_bytes;
|
||||
__u64 meta_seq;
|
||||
__u64 data_seq;
|
||||
__u64 data_version;
|
||||
__u64 online_blocks;
|
||||
__u64 offline_blocks;
|
||||
};
|
||||
|
||||
#define SCOUTFS_IOC_STAT_MORE _IOR(SCOUTFS_IOCTL_MAGIC, 5, \
|
||||
struct scoutfs_ioctl_stat_more)
|
||||
|
||||
|
||||
struct scoutfs_ioctl_data_waiting_entry {
|
||||
__u64 ino;
|
||||
__u64 iblock;
|
||||
__u8 op;
|
||||
__u8 _pad[7];
|
||||
};
|
||||
|
||||
#define SCOUTFS_IOC_DWO_READ (1 << 0)
|
||||
#define SCOUTFS_IOC_DWO_WRITE (1 << 1)
|
||||
#define SCOUTFS_IOC_DWO_CHANGE_SIZE (1 << 2)
|
||||
#define SCOUTFS_IOC_DWO_UNKNOWN (U8_MAX << 3)
|
||||
|
||||
struct scoutfs_ioctl_data_waiting {
|
||||
__u64 flags;
|
||||
__u64 after_ino;
|
||||
__u64 after_iblock;
|
||||
__u64 ents_ptr;
|
||||
__u16 ents_nr;
|
||||
__u8 _pad[6];
|
||||
};
|
||||
|
||||
#define SCOUTFS_IOC_DATA_WAITING_FLAGS_UNKNOWN (U8_MAX << 0)
|
||||
|
||||
#define SCOUTFS_IOC_DATA_WAITING _IOR(SCOUTFS_IOCTL_MAGIC, 6, \
|
||||
struct scoutfs_ioctl_data_waiting)
|
||||
|
||||
/*
|
||||
* If i_size is set then data_version must be non-zero. If the offline
|
||||
* flag is set then i_size must be set and a offline extent will be
|
||||
* created from offset 0 to i_size.
|
||||
*/
|
||||
struct scoutfs_ioctl_setattr_more {
|
||||
__u64 data_version;
|
||||
__u64 i_size;
|
||||
__u64 flags;
|
||||
__u64 ctime_sec;
|
||||
__u32 ctime_nsec;
|
||||
__u8 _pad[4];
|
||||
};
|
||||
|
||||
#define SCOUTFS_IOC_SETATTR_MORE_OFFLINE (1 << 0)
|
||||
#define SCOUTFS_IOC_SETATTR_MORE_UNKNOWN (U8_MAX << 1)
|
||||
|
||||
#define SCOUTFS_IOC_SETATTR_MORE _IOW(SCOUTFS_IOCTL_MAGIC, 7, \
|
||||
struct scoutfs_ioctl_setattr_more)
|
||||
|
||||
struct scoutfs_ioctl_listxattr_hidden {
|
||||
__u64 id_pos;
|
||||
__u64 buf_ptr;
|
||||
__u32 buf_bytes;
|
||||
__u32 hash_pos;
|
||||
};
|
||||
|
||||
#define SCOUTFS_IOC_LISTXATTR_HIDDEN _IOR(SCOUTFS_IOCTL_MAGIC, 8, \
|
||||
struct scoutfs_ioctl_listxattr_hidden)
|
||||
|
||||
/*
|
||||
* Return the inode numbers of inodes which might contain the given
|
||||
* xattr. The inode may not have a set xattr with that name, the caller
|
||||
* must check the returned inodes to see if they match.
|
||||
*
|
||||
* @next_ino: The next inode number that could be returned. Initialized
|
||||
* to 0 when first searching and set to one past the last inode number
|
||||
* returned to continue searching.
|
||||
* @last_ino: The last inode number that could be returned. U64_MAX to
|
||||
* find all inodes.
|
||||
* @name_ptr: The address of the name of the xattr to search for. It is
|
||||
* not null terminated.
|
||||
* @inodes_ptr: The address of the array of uint64_t inode numbers in
|
||||
* which to store inode numbers that may contain the xattr. EFAULT may
|
||||
* be returned if this address is not naturally aligned.
|
||||
* @output_flags: Set as success is returned. If an error is returned
|
||||
* then this field is undefined and should not be read.
|
||||
* @nr_inodes: The number of elements in the array found at inodes_ptr.
|
||||
* @name_bytes: The number of non-null bytes found in the name at
|
||||
* name_ptr.
|
||||
*
|
||||
* This requires the CAP_SYS_ADMIN capability and will return -EPERM if
|
||||
* it's not granted.
|
||||
*
|
||||
* The number of inode numbers stored in the inodes_ptr array is
|
||||
* returned. If nr_inodes is 0 or last_ino is less than next_ino then 0
|
||||
* will be immediately returned.
|
||||
*
|
||||
* Partial progress can be returned if an error is hit or if nr_inodes
|
||||
* was larger than the internal limit on the number of inodes returned
|
||||
* in a search pass. The _END output flag is set if all the results
|
||||
* including last_ino were searched in this pass.
|
||||
*
|
||||
* It's valuable to provide a large inodes array so that all the results
|
||||
* can be found in one search pass and _END can be set. There are
|
||||
* significant constant costs for performing each search pass.
|
||||
*/
|
||||
struct scoutfs_ioctl_search_xattrs {
|
||||
__u64 next_ino;
|
||||
__u64 last_ino;
|
||||
__u64 name_ptr;
|
||||
__u64 inodes_ptr;
|
||||
__u64 output_flags;
|
||||
__u64 nr_inodes;
|
||||
__u16 name_bytes;
|
||||
__u8 _pad[6];
|
||||
};
|
||||
|
||||
/* set in output_flags if returned inodes reached last_ino */
|
||||
#define SCOUTFS_SEARCH_XATTRS_OFLAG_END (1ULL << 0)
|
||||
|
||||
#define SCOUTFS_IOC_SEARCH_XATTRS _IOR(SCOUTFS_IOCTL_MAGIC, 9, \
|
||||
struct scoutfs_ioctl_search_xattrs)
|
||||
|
||||
/*
|
||||
* Give the user information about the filesystem.
|
||||
*
|
||||
* @valid_bytes stores the number of bytes that are valid in the
|
||||
* structure. The caller sets this to the size of the struct that they
|
||||
* understand. The kernel then fills and copies back the min of the
|
||||
* size they and the user caller understand. The user can tell if a
|
||||
* field is set if all of its bytes are within the valid_bytes that the
|
||||
* kernel set on return.
|
||||
*
|
||||
* @committed_seq: All seqs up to and including this seq have been
|
||||
* committed. Can be compared with meta_seq and data_seq from inodes in
|
||||
* stat_more to discover if changes have been committed to disk.
|
||||
*
|
||||
* New fields are only added to the end of the struct.
|
||||
*/
|
||||
struct scoutfs_ioctl_statfs_more {
|
||||
__u64 valid_bytes;
|
||||
__u64 fsid;
|
||||
__u64 rid;
|
||||
__u64 committed_seq;
|
||||
__u64 total_meta_blocks;
|
||||
__u64 total_data_blocks;
|
||||
};
|
||||
|
||||
#define SCOUTFS_IOC_STATFS_MORE _IOR(SCOUTFS_IOCTL_MAGIC, 10, \
|
||||
struct scoutfs_ioctl_statfs_more)
|
||||
|
||||
/*
|
||||
* Cause matching waiters to return an error.
|
||||
*
|
||||
* Find current waiters that match the inode, op, and block range to wake
|
||||
* up and return an error.
|
||||
*/
|
||||
struct scoutfs_ioctl_data_wait_err {
|
||||
__u64 ino;
|
||||
__u64 data_version;
|
||||
__u64 offset;
|
||||
__u64 count;
|
||||
__u64 op;
|
||||
__s64 err;
|
||||
};
|
||||
|
||||
#define SCOUTFS_IOC_DATA_WAIT_ERR _IOR(SCOUTFS_IOCTL_MAGIC, 11, \
|
||||
struct scoutfs_ioctl_data_wait_err)
|
||||
|
||||
|
||||
#define SCOUTFS_IOC_ALLOC_DETAIL _IOR(SCOUTFS_IOCTL_MAGIC, 12, \
|
||||
struct scoutfs_ioctl_alloc_detail)
|
||||
|
||||
struct scoutfs_ioctl_alloc_detail {
|
||||
__u64 entries_ptr;
|
||||
__u64 entries_nr;
|
||||
};
|
||||
|
||||
struct scoutfs_ioctl_alloc_detail_entry {
|
||||
__u64 id;
|
||||
__u64 blocks;
|
||||
__u8 type;
|
||||
__u8 meta:1,
|
||||
avail:1;
|
||||
__u8 __bit_pad:6;
|
||||
__u8 __pad[6];
|
||||
};
|
||||
|
||||
#endif
|
||||
+2541
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
#ifndef _SCOUTFS_ITEM_H_
|
||||
#define _SCOUTFS_ITEM_H_
|
||||
|
||||
int scoutfs_item_lookup(struct super_block *sb, struct scoutfs_key *key,
|
||||
void *val, int val_len, struct scoutfs_lock *lock);
|
||||
int scoutfs_item_lookup_exact(struct super_block *sb, struct scoutfs_key *key,
|
||||
void *val, int val_len,
|
||||
struct scoutfs_lock *lock);
|
||||
int scoutfs_item_next(struct super_block *sb, struct scoutfs_key *key,
|
||||
struct scoutfs_key *last, void *val, int val_len,
|
||||
struct scoutfs_lock *lock);
|
||||
int scoutfs_item_dirty(struct super_block *sb, struct scoutfs_key *key,
|
||||
struct scoutfs_lock *lock);
|
||||
int scoutfs_item_create(struct super_block *sb, struct scoutfs_key *key,
|
||||
void *val, int val_len, struct scoutfs_lock *lock);
|
||||
int scoutfs_item_create_force(struct super_block *sb, struct scoutfs_key *key,
|
||||
void *val, int val_len,
|
||||
struct scoutfs_lock *lock);
|
||||
int scoutfs_item_update(struct super_block *sb, struct scoutfs_key *key,
|
||||
void *val, int val_len, struct scoutfs_lock *lock);
|
||||
int scoutfs_item_delete(struct super_block *sb, struct scoutfs_key *key,
|
||||
struct scoutfs_lock *lock);
|
||||
int scoutfs_item_delete_force(struct super_block *sb,
|
||||
struct scoutfs_key *key,
|
||||
struct scoutfs_lock *lock);
|
||||
|
||||
u64 scoutfs_item_dirty_pages(struct super_block *sb);
|
||||
int scoutfs_item_write_dirty(struct super_block *sb);
|
||||
int scoutfs_item_write_done(struct super_block *sb);
|
||||
bool scoutfs_item_range_cached(struct super_block *sb,
|
||||
struct scoutfs_key *start,
|
||||
struct scoutfs_key *end, bool *dirty);
|
||||
void scoutfs_item_invalidate(struct super_block *sb, struct scoutfs_key *start,
|
||||
struct scoutfs_key *end);
|
||||
|
||||
int scoutfs_item_setup(struct super_block *sb);
|
||||
void scoutfs_item_destroy(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef _SCOUTFS_KERNELCOMPAT_H_
|
||||
#define _SCOUTFS_KERNELCOMPAT_H_
|
||||
|
||||
#ifndef KC_ITERATE_DIR_CONTEXT
|
||||
#include <linux/fs.h>
|
||||
typedef filldir_t kc_readdir_ctx_t;
|
||||
#define KC_DECLARE_READDIR(name, file, dirent, ctx) name(file, dirent, ctx)
|
||||
#define KC_FOP_READDIR readdir
|
||||
#define kc_readdir_pos(filp, ctx) (filp)->f_pos
|
||||
#define kc_dir_emit_dots(file, dirent, ctx) dir_emit_dots(file, dirent, ctx)
|
||||
#define kc_dir_emit(ctx, dirent, name, name_len, pos, ino, dt) \
|
||||
(ctx(dirent, name, name_len, pos, ino, dt) == 0)
|
||||
#else
|
||||
typedef struct dir_context * kc_readdir_ctx_t;
|
||||
#define KC_DECLARE_READDIR(name, file, dirent, ctx) name(file, ctx)
|
||||
#define KC_FOP_READDIR iterate
|
||||
#define kc_readdir_pos(filp, ctx) (ctx)->pos
|
||||
#define kc_dir_emit_dots(file, dirent, ctx) dir_emit_dots(file, ctx)
|
||||
#define kc_dir_emit(ctx, dirent, name, name_len, pos, ino, dt) \
|
||||
dir_emit(ctx, name, name_len, ino, dt)
|
||||
#endif
|
||||
|
||||
#ifndef KC_DIR_EMIT_DOTS
|
||||
/*
|
||||
* Kernels before ->iterate and don't have dir_emit_dots so we give them
|
||||
* one that works with the ->readdir() filldir() method.
|
||||
*/
|
||||
static inline int dir_emit_dots(struct file *file, void *dirent,
|
||||
filldir_t filldir)
|
||||
{
|
||||
if (file->f_pos == 0) {
|
||||
if (filldir(dirent, ".", 1, 1,
|
||||
file->f_path.dentry->d_inode->i_ino, DT_DIR))
|
||||
return 0;
|
||||
file->f_pos = 1;
|
||||
}
|
||||
|
||||
if (file->f_pos == 1) {
|
||||
if (filldir(dirent, "..", 2, 1,
|
||||
parent_ino(file->f_path.dentry), DT_DIR))
|
||||
return 0;
|
||||
file->f_pos = 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
#ifndef _SCOUTFS_KEY_H_
|
||||
#define _SCOUTFS_KEY_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include "format.h"
|
||||
#include "cmp.h"
|
||||
#include "endian_swap.h"
|
||||
|
||||
#define SK_FMT "%u.%llu.%u.%llu.%llu.%u"
|
||||
|
||||
/* This does not support null keys */
|
||||
#define SK_ARG(key) (key)->sk_zone, \
|
||||
le64_to_cpu((key)->_sk_first), \
|
||||
(key)->sk_type, \
|
||||
le64_to_cpu((key)->_sk_second), \
|
||||
le64_to_cpu((key)->_sk_third), \
|
||||
(key)->_sk_fourth
|
||||
|
||||
/* userspace trace event printing doesn't like arguments with structure
|
||||
* field references. So we explode structures into their fields instead
|
||||
* of
|
||||
*/
|
||||
#define sk_trace_define(name) \
|
||||
__field(__u8, name##_zone) \
|
||||
__field(__u64, name##_first) \
|
||||
__field(__u8, name##_type) \
|
||||
__field(__u64, name##_second) \
|
||||
__field(__u64, name##_third) \
|
||||
__field(__u8, name##_fourth)
|
||||
|
||||
#define sk_trace_assign(name, key) \
|
||||
do { \
|
||||
__typeof__(key) _key = (key); \
|
||||
if (_key) { \
|
||||
__entry->name##_zone = _key->sk_zone; \
|
||||
__entry->name##_first = le64_to_cpu(_key->_sk_first); \
|
||||
__entry->name##_type = _key->sk_type; \
|
||||
__entry->name##_second = le64_to_cpu(_key->_sk_second);\
|
||||
__entry->name##_third = le64_to_cpu(_key->_sk_third); \
|
||||
__entry->name##_fourth = _key->_sk_fourth; \
|
||||
} else { \
|
||||
__entry->name##_zone = 0; \
|
||||
__entry->name##_first = 0; \
|
||||
__entry->name##_type = 0; \
|
||||
__entry->name##_second = 0; \
|
||||
__entry->name##_third = 0; \
|
||||
__entry->name##_fourth = 0; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define sk_trace_args(name) \
|
||||
__entry->name##_zone, __entry->name##_first, __entry->name##_type, \
|
||||
__entry->name##_second, __entry->name##_third, __entry->name##_fourth
|
||||
|
||||
/*
|
||||
* copy fields between keys with the same fields but different types.
|
||||
* The destination type might have internal padding so we zero it.
|
||||
*/
|
||||
#define scoutfs_key_copy_types(a, b) \
|
||||
do { \
|
||||
__typeof__(a) _to = (a); \
|
||||
__typeof__(b) _from = (b); \
|
||||
\
|
||||
memset(_to, 0, sizeof(*_to)); \
|
||||
_to->sk_zone = _from->sk_zone; \
|
||||
_to->_sk_first = _from->_sk_first; \
|
||||
_to->sk_type = _from->sk_type; \
|
||||
_to->_sk_second = _from->_sk_second; \
|
||||
_to->_sk_third = _from->_sk_third; \
|
||||
_to->_sk_fourth = _from->_sk_fourth; \
|
||||
} while (0)
|
||||
|
||||
static inline void scoutfs_key_set_zeros(struct scoutfs_key *key)
|
||||
{
|
||||
key->sk_zone = 0;
|
||||
key->_sk_first = 0;
|
||||
key->sk_type = 0;
|
||||
key->_sk_second = 0;
|
||||
key->_sk_third = 0;
|
||||
key->_sk_fourth = 0;
|
||||
memset(key->__pad, 0, sizeof(key->__pad));
|
||||
}
|
||||
|
||||
static inline bool scoutfs_key_is_zeros(struct scoutfs_key *key)
|
||||
{
|
||||
return key->sk_zone == 0 && key->_sk_first == 0 && key->sk_type == 0 &&
|
||||
key->_sk_second == 0 && key->_sk_third == 0 &&
|
||||
key->_sk_fourth == 0;
|
||||
}
|
||||
|
||||
static inline void scoutfs_key_copy_or_zeros(struct scoutfs_key *dst,
|
||||
struct scoutfs_key *src)
|
||||
{
|
||||
if (src)
|
||||
*dst = *src;
|
||||
else
|
||||
scoutfs_key_set_zeros(dst);
|
||||
}
|
||||
|
||||
static inline void scoutfs_key_set_ones(struct scoutfs_key *key)
|
||||
{
|
||||
key->sk_zone = U8_MAX;
|
||||
key->_sk_first = cpu_to_le64(U64_MAX);
|
||||
key->sk_type = U8_MAX;
|
||||
key->_sk_second = cpu_to_le64(U64_MAX);
|
||||
key->_sk_third = cpu_to_le64(U64_MAX);
|
||||
key->_sk_fourth = U8_MAX;
|
||||
memset(key->__pad, 0, sizeof(key->__pad));
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a -1/0/1 comparison of keys.
|
||||
*
|
||||
* It turns out that these ternary chains are consistently cheaper than
|
||||
* other alternatives across keys that first differ in any of the
|
||||
* values. Say maybe 20% faster than memcmp.
|
||||
*/
|
||||
static inline int scoutfs_key_compare(struct scoutfs_key *a,
|
||||
struct scoutfs_key *b)
|
||||
{
|
||||
return scoutfs_cmp(a->sk_zone, b->sk_zone) ?:
|
||||
scoutfs_cmp(le64_to_cpu(a->_sk_first), le64_to_cpu(b->_sk_first)) ?:
|
||||
scoutfs_cmp(a->sk_type, b->sk_type) ?:
|
||||
scoutfs_cmp(le64_to_cpu(a->_sk_second), le64_to_cpu(b->_sk_second)) ?:
|
||||
scoutfs_cmp(le64_to_cpu(a->_sk_third), le64_to_cpu(b->_sk_third)) ?:
|
||||
scoutfs_cmp(a->_sk_fourth, b->_sk_fourth);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare ranges of keys where overlapping is equality. Returns:
|
||||
* -1: a_end < b_start
|
||||
* 1: a_start > b_end
|
||||
* else 0: ranges overlap
|
||||
*/
|
||||
static inline int scoutfs_key_compare_ranges(struct scoutfs_key *a_start,
|
||||
struct scoutfs_key *a_end,
|
||||
struct scoutfs_key *b_start,
|
||||
struct scoutfs_key *b_end)
|
||||
{
|
||||
return scoutfs_key_compare(a_end, b_start) < 0 ? -1 :
|
||||
scoutfs_key_compare(a_start, b_end) > 0 ? 1 :
|
||||
0;
|
||||
}
|
||||
|
||||
static inline void scoutfs_key_inc(struct scoutfs_key *key)
|
||||
{
|
||||
if (++key->_sk_fourth != 0)
|
||||
return;
|
||||
|
||||
le64_add_cpu(&key->_sk_third, 1);
|
||||
if (key->_sk_third != 0)
|
||||
return;
|
||||
|
||||
le64_add_cpu(&key->_sk_second, 1);
|
||||
if (key->_sk_second != 0)
|
||||
return;
|
||||
|
||||
if (++key->sk_type != 0)
|
||||
return;
|
||||
|
||||
le64_add_cpu(&key->_sk_first, 1);
|
||||
if (key->_sk_first != 0)
|
||||
return;
|
||||
|
||||
key->sk_zone++;
|
||||
}
|
||||
|
||||
static inline void scoutfs_key_dec(struct scoutfs_key *key)
|
||||
{
|
||||
if (--key->_sk_fourth != U8_MAX)
|
||||
return;
|
||||
|
||||
le64_add_cpu(&key->_sk_third, -1);
|
||||
if (key->_sk_third != cpu_to_le64(U64_MAX))
|
||||
return;
|
||||
|
||||
le64_add_cpu(&key->_sk_second, -1);
|
||||
if (key->_sk_second != cpu_to_le64(U64_MAX))
|
||||
return;
|
||||
|
||||
if (--key->sk_type != U8_MAX)
|
||||
return;
|
||||
|
||||
le64_add_cpu(&key->_sk_first, -1);
|
||||
if (key->_sk_first != cpu_to_le64(U64_MAX))
|
||||
return;
|
||||
|
||||
key->sk_zone--;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some key types are used by multiple subsystems and shouldn't have
|
||||
* duplicate private key init functions.
|
||||
*/
|
||||
|
||||
static inline void scoutfs_key_init_log_trees(struct scoutfs_key *key,
|
||||
u64 rid, u64 nr)
|
||||
{
|
||||
*key = (struct scoutfs_key) {
|
||||
.sk_zone = SCOUTFS_LOG_TREES_ZONE,
|
||||
.sklt_rid = cpu_to_le64(rid),
|
||||
.sklt_nr = cpu_to_le64(nr),
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
+1709
File diff suppressed because it is too large
Load Diff
+105
@@ -0,0 +1,105 @@
|
||||
#ifndef _SCOUTFS_LOCK_H_
|
||||
#define _SCOUTFS_LOCK_H_
|
||||
|
||||
#include "key.h"
|
||||
#include "tseq.h"
|
||||
|
||||
#define SCOUTFS_LKF_REFRESH_INODE 0x01 /* update stale inode from item */
|
||||
#define SCOUTFS_LKF_NONBLOCK 0x02 /* only use already held locks */
|
||||
#define SCOUTFS_LKF_INVALID (~((SCOUTFS_LKF_NONBLOCK << 1) - 1))
|
||||
|
||||
#define SCOUTFS_LOCK_NR_MODES SCOUTFS_LOCK_INVALID
|
||||
|
||||
/*
|
||||
* A few fields (start, end, refresh_gen, write_version, granted_mode)
|
||||
* are referenced by code outside lock.c.
|
||||
*/
|
||||
struct scoutfs_lock {
|
||||
struct super_block *sb;
|
||||
struct scoutfs_key start;
|
||||
struct scoutfs_key end;
|
||||
struct rb_node node;
|
||||
struct rb_node range_node;
|
||||
u64 refresh_gen;
|
||||
u64 write_version;
|
||||
u64 dirty_trans_seq;
|
||||
struct scoutfs_net_roots roots;
|
||||
struct list_head lru_head;
|
||||
wait_queue_head_t waitq;
|
||||
ktime_t grace_deadline;
|
||||
unsigned long request_pending:1,
|
||||
invalidate_pending:1;
|
||||
|
||||
struct list_head grant_head;
|
||||
struct scoutfs_net_lock_grant_response grant_resp;
|
||||
struct list_head inv_head;
|
||||
struct scoutfs_net_lock inv_nl;
|
||||
u64 inv_net_id;
|
||||
struct list_head shrink_head;
|
||||
|
||||
spinlock_t cov_list_lock;
|
||||
struct list_head cov_list;
|
||||
|
||||
enum scoutfs_lock_mode mode;
|
||||
unsigned int waiters[SCOUTFS_LOCK_NR_MODES];
|
||||
unsigned int users[SCOUTFS_LOCK_NR_MODES];
|
||||
|
||||
struct scoutfs_tseq_entry tseq_entry;
|
||||
|
||||
/* the forest tracks which log tree last saw bloom bit updates */
|
||||
atomic64_t forest_bloom_nr;
|
||||
};
|
||||
|
||||
struct scoutfs_lock_coverage {
|
||||
spinlock_t cov_lock;
|
||||
struct scoutfs_lock *lock;
|
||||
struct list_head head;
|
||||
};
|
||||
|
||||
int scoutfs_lock_grant_response(struct super_block *sb,
|
||||
struct scoutfs_net_lock_grant_response *gr);
|
||||
int scoutfs_lock_invalidate_request(struct super_block *sb, u64 net_id,
|
||||
struct scoutfs_net_lock *nl);
|
||||
int scoutfs_lock_recover_request(struct super_block *sb, u64 net_id,
|
||||
struct scoutfs_key *key);
|
||||
|
||||
int scoutfs_lock_inode(struct super_block *sb, enum scoutfs_lock_mode mode, int flags,
|
||||
struct inode *inode, struct scoutfs_lock **ret_lock);
|
||||
int scoutfs_lock_ino(struct super_block *sb, enum scoutfs_lock_mode mode, int flags, u64 ino,
|
||||
struct scoutfs_lock **ret_lock);
|
||||
void scoutfs_lock_get_index_item_range(u8 type, u64 major, u64 ino,
|
||||
struct scoutfs_key *start,
|
||||
struct scoutfs_key *end);
|
||||
int scoutfs_lock_inode_index(struct super_block *sb, enum scoutfs_lock_mode mode,
|
||||
u8 type, u64 major, u64 ino,
|
||||
struct scoutfs_lock **ret_lock);
|
||||
int scoutfs_lock_inodes(struct super_block *sb, enum scoutfs_lock_mode mode, int flags,
|
||||
struct inode *a, struct scoutfs_lock **a_lock,
|
||||
struct inode *b, struct scoutfs_lock **b_lock,
|
||||
struct inode *c, struct scoutfs_lock **c_lock,
|
||||
struct inode *d, struct scoutfs_lock **D_lock);
|
||||
int scoutfs_lock_rename(struct super_block *sb, enum scoutfs_lock_mode mode, int flags,
|
||||
struct scoutfs_lock **lock);
|
||||
int scoutfs_lock_rid(struct super_block *sb, enum scoutfs_lock_mode mode, int flags,
|
||||
u64 rid, struct scoutfs_lock **lock);
|
||||
void scoutfs_unlock(struct super_block *sb, struct scoutfs_lock *lock,
|
||||
enum scoutfs_lock_mode mode);
|
||||
|
||||
void scoutfs_lock_init_coverage(struct scoutfs_lock_coverage *cov);
|
||||
void scoutfs_lock_add_coverage(struct super_block *sb,
|
||||
struct scoutfs_lock *lock,
|
||||
struct scoutfs_lock_coverage *cov);
|
||||
bool scoutfs_lock_is_covered(struct super_block *sb,
|
||||
struct scoutfs_lock_coverage *cov);
|
||||
void scoutfs_lock_del_coverage(struct super_block *sb,
|
||||
struct scoutfs_lock_coverage *cov);
|
||||
bool scoutfs_lock_protected(struct scoutfs_lock *lock, struct scoutfs_key *key,
|
||||
enum scoutfs_lock_mode mode);
|
||||
|
||||
void scoutfs_free_unused_locks(struct super_block *sb, unsigned long nr);
|
||||
|
||||
int scoutfs_lock_setup(struct super_block *sb);
|
||||
void scoutfs_lock_shutdown(struct super_block *sb);
|
||||
void scoutfs_lock_destroy(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
#ifndef _SCOUTFS_LOCK_SERVER_H_
|
||||
#define _SCOUTFS_LOCK_SERVER_H_
|
||||
|
||||
int scoutfs_lock_server_recover_response(struct super_block *sb, u64 rid,
|
||||
struct scoutfs_net_lock_recover *nlr);
|
||||
int scoutfs_lock_server_request(struct super_block *sb, u64 rid,
|
||||
u64 net_id, struct scoutfs_net_lock *nl);
|
||||
int scoutfs_lock_server_greeting(struct super_block *sb, u64 rid,
|
||||
bool should_exist);
|
||||
int scoutfs_lock_server_response(struct super_block *sb, u64 rid,
|
||||
struct scoutfs_net_lock *nl);
|
||||
int scoutfs_lock_server_farewell(struct super_block *sb, u64 rid);
|
||||
|
||||
int scoutfs_lock_server_setup(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri, u64 max_vers);
|
||||
void scoutfs_lock_server_destroy(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
#include "msg.h"
|
||||
|
||||
/*
|
||||
* This can be called with pre-emption disabled if the caller is printing
|
||||
* the contents of formated per-cpu key string buffers.
|
||||
*/
|
||||
void scoutfs_msg(struct super_block *sb, const char *prefix, const char *str,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
struct va_format vaf;
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
|
||||
vaf.fmt = fmt;
|
||||
vaf.va = &args;
|
||||
|
||||
printk("%sscoutfs "SCSBF"%s: %pV\n", prefix, SCSB_ARGS(sb), str, &vaf);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef _SCOUTFS_MSG_H_
|
||||
#define _SCOUTFS_MSG_H_
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include "key.h"
|
||||
#include "counters.h"
|
||||
|
||||
void __printf(4, 5) scoutfs_msg(struct super_block *sb, const char *prefix,
|
||||
const char *str, const char *fmt, ...);
|
||||
|
||||
#define scoutfs_msg_check(sb, pref, str, fmt, args...) \
|
||||
do { \
|
||||
BUILD_BUG_ON(fmt[sizeof(fmt) - 2] == '\n'); \
|
||||
scoutfs_msg(sb, pref, str, fmt, ##args); \
|
||||
} while (0)
|
||||
|
||||
#define scoutfs_err(sb, fmt, args...) \
|
||||
scoutfs_msg_check(sb, KERN_ERR, " error", fmt, ##args)
|
||||
|
||||
#define scoutfs_warn(sb, fmt, args...) \
|
||||
scoutfs_msg_check(sb, KERN_WARNING, " warning", fmt, ##args)
|
||||
|
||||
#define scoutfs_info(sb, fmt, args...) \
|
||||
scoutfs_msg_check(sb, KERN_INFO, "", fmt, ##args)
|
||||
|
||||
#define scoutfs_bug_on(sb, cond, fmt, args...) \
|
||||
do { \
|
||||
if (cond) { \
|
||||
scoutfs_err(sb, "(" __stringify(cond) "), " fmt, ##args); \
|
||||
BUG(); \
|
||||
} \
|
||||
} while (0) \
|
||||
|
||||
/*
|
||||
* Each message is only generated once per volume. Remounting resets
|
||||
* the messages.
|
||||
*/
|
||||
#define scoutfs_corruption(sb, which, counter, fmt, args...) \
|
||||
do { \
|
||||
__typeof__(sb) _sb = (sb); \
|
||||
struct scoutfs_sb_info *_sbi = SCOUTFS_SB(_sb); \
|
||||
unsigned int _bit = (which); \
|
||||
\
|
||||
if (WARN_ON_ONCE(_bit >= SC_NR_SOURCES)) \
|
||||
break; \
|
||||
\
|
||||
scoutfs_inc_counter(_sb, counter); \
|
||||
if (!test_and_set_bit(_bit, _sbi->corruption_messages_once)) { \
|
||||
scoutfs_err(_sb, "corruption (see scoutfs-corruption(5)): " \
|
||||
#which ": " fmt, ##args); \
|
||||
dump_stack(); \
|
||||
} \
|
||||
} while (0) \
|
||||
|
||||
#endif
|
||||
+1913
File diff suppressed because it is too large
Load Diff
+165
@@ -0,0 +1,165 @@
|
||||
#ifndef _SCOUTFS_NET_H_
|
||||
#define _SCOUTFS_NET_H_
|
||||
|
||||
#include <linux/in.h>
|
||||
#include "endian_swap.h"
|
||||
#include "tseq.h"
|
||||
|
||||
struct scoutfs_net_connection;
|
||||
|
||||
/* These are called in their own blocking context */
|
||||
typedef int (*scoutfs_net_request_t)(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
u8 cmd, u64 id, void *arg, u16 arg_len);
|
||||
|
||||
/* These are called in their own blocking context */
|
||||
typedef int (*scoutfs_net_response_t)(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
void *resp, unsigned int resp_len,
|
||||
int error, void *data);
|
||||
|
||||
typedef void (*scoutfs_net_notify_t)(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
void *info, u64 rid);
|
||||
|
||||
/*
|
||||
* The conn is only here so that tracing can get at its fields without
|
||||
* having trace functions with a trillion arguments. Tracing requires
|
||||
* duplicating the arguments for every event, no thanks.
|
||||
*/
|
||||
|
||||
struct scoutfs_net_connection {
|
||||
struct super_block *sb;
|
||||
scoutfs_net_notify_t notify_up;
|
||||
scoutfs_net_notify_t notify_down;
|
||||
size_t info_size;
|
||||
scoutfs_net_request_t *req_funcs;
|
||||
|
||||
spinlock_t lock;
|
||||
wait_queue_head_t waitq;
|
||||
|
||||
unsigned long flags; /* CONN_FL_* bitmask */
|
||||
unsigned long reconn_deadline;
|
||||
|
||||
struct sockaddr_in connect_sin;
|
||||
unsigned long connect_timeout_ms;
|
||||
|
||||
struct socket *sock;
|
||||
u64 rid;
|
||||
u64 greeting_id;
|
||||
struct sockaddr_in sockname;
|
||||
struct sockaddr_in peername;
|
||||
|
||||
struct list_head accepted_head;
|
||||
struct scoutfs_net_connection *listening_conn;
|
||||
struct list_head accepted_list;
|
||||
|
||||
u64 next_send_seq;
|
||||
u64 next_send_id;
|
||||
struct list_head send_queue;
|
||||
struct list_head resend_queue;
|
||||
|
||||
atomic64_t recv_seq;
|
||||
|
||||
struct workqueue_struct *workq;
|
||||
struct work_struct listen_work;
|
||||
struct work_struct connect_work;
|
||||
struct work_struct send_work;
|
||||
struct work_struct recv_work;
|
||||
struct work_struct shutdown_work;
|
||||
struct work_struct destroy_work;
|
||||
struct delayed_work reconn_free_dwork;
|
||||
/* message_recv proc_work also executes in the conn workq */
|
||||
|
||||
struct scoutfs_tseq_entry tseq_entry;
|
||||
|
||||
void *info;
|
||||
};
|
||||
|
||||
enum conn_flags {
|
||||
CONN_FL_valid_greeting = (1UL << 0), /* other commands can proceed */
|
||||
CONN_FL_established = (1UL << 1), /* added sends queue send work */
|
||||
CONN_FL_shutting_down = (1UL << 2), /* shutdown work was queued */
|
||||
CONN_FL_saw_greeting = (1UL << 3), /* saw greeting on this sock */
|
||||
CONN_FL_saw_farewell = (1UL << 4), /* saw farewell response */
|
||||
CONN_FL_reconn_wait = (1UL << 5), /* shutdown, waiting for reconn */
|
||||
CONN_FL_reconn_freeing = (1UL << 6), /* waiting done, setter frees */
|
||||
};
|
||||
|
||||
#define SIN_FMT "%pIS:%u"
|
||||
#define SIN_ARG(sin) sin, be16_to_cpu((sin)->sin_port)
|
||||
|
||||
static inline void scoutfs_addr_to_sin(struct sockaddr_in *sin,
|
||||
struct scoutfs_inet_addr *addr)
|
||||
{
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr.s_addr = cpu_to_be32(le32_to_cpu(addr->addr));
|
||||
sin->sin_port = cpu_to_be16(le16_to_cpu(addr->port));
|
||||
}
|
||||
|
||||
static inline void scoutfs_addr_from_sin(struct scoutfs_inet_addr *addr,
|
||||
struct sockaddr_in *sin)
|
||||
{
|
||||
addr->addr = be32_to_le32(sin->sin_addr.s_addr);
|
||||
addr->port = be16_to_le16(sin->sin_port);
|
||||
memset(addr->__pad, 0, sizeof(addr->__pad));
|
||||
}
|
||||
|
||||
struct scoutfs_net_connection *
|
||||
scoutfs_net_alloc_conn(struct super_block *sb,
|
||||
scoutfs_net_notify_t notify_up,
|
||||
scoutfs_net_notify_t notify_down, size_t info_size,
|
||||
scoutfs_net_request_t *req_funcs, char *name_suffix);
|
||||
u64 scoutfs_net_client_rid(struct scoutfs_net_connection *conn);
|
||||
int scoutfs_net_connect(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
struct sockaddr_in *sin, unsigned long timeout_ms);
|
||||
int scoutfs_net_bind(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
struct sockaddr_in *sin);
|
||||
void scoutfs_net_listen(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn);
|
||||
int scoutfs_net_submit_request(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
u8 cmd, void *arg, u16 arg_len,
|
||||
scoutfs_net_response_t resp_func,
|
||||
void *resp_data, u64 *id_ret);
|
||||
int scoutfs_net_submit_request_node(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
u64 rid, u8 cmd, void *arg, u16 arg_len,
|
||||
scoutfs_net_response_t resp_func,
|
||||
void *resp_data, u64 *id_ret);
|
||||
void scoutfs_net_cancel_request(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
u8 cmd, u64 id);
|
||||
int scoutfs_net_sync_request(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
u8 cmd, void *arg, unsigned arg_len,
|
||||
void *resp, size_t resp_len);
|
||||
int scoutfs_net_response(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
u8 cmd, u64 id, int error, void *resp, u16 resp_len);
|
||||
int scoutfs_net_response_node(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
u64 rid, u8 cmd, u64 id, int error,
|
||||
void *resp, u16 resp_len);
|
||||
void scoutfs_net_shutdown(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn);
|
||||
void scoutfs_net_free_conn(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn);
|
||||
|
||||
void scoutfs_net_client_greeting(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
bool new_server);
|
||||
void scoutfs_net_server_greeting(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn,
|
||||
u64 rid, u64 greeting_id,
|
||||
bool reconnecting, bool first_contact,
|
||||
bool farewell);
|
||||
void scoutfs_net_farewell(struct super_block *sb,
|
||||
struct scoutfs_net_connection *conn);
|
||||
|
||||
int scoutfs_net_setup(struct super_block *sb);
|
||||
void scoutfs_net_destroy(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
static const match_table_t tokens = {
|
||||
{Opt_server_addr, "server_addr=%s"},
|
||||
{Opt_metadev_path, "metadev_path=%s"},
|
||||
{Opt_err, NULL}
|
||||
};
|
||||
|
||||
struct options_sb_info {
|
||||
struct dentry *debugfs_dir;
|
||||
};
|
||||
|
||||
u32 scoutfs_option_u32(struct super_block *sb, int token)
|
||||
{
|
||||
WARN_ON_ONCE(1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The caller's string is null terminted and can be clobbered */
|
||||
static int parse_ipv4(struct super_block *sb, char *str,
|
||||
struct sockaddr_in *sin)
|
||||
{
|
||||
unsigned long port = 0;
|
||||
__be32 addr;
|
||||
char *c;
|
||||
int ret;
|
||||
|
||||
/* null term port, if specified */
|
||||
c = strchr(str, ':');
|
||||
if (c)
|
||||
*c = '\0';
|
||||
|
||||
/* parse addr */
|
||||
addr = in_aton(str);
|
||||
if (ipv4_is_multicast(addr) || ipv4_is_lbcast(addr) ||
|
||||
ipv4_is_zeronet(addr) ||
|
||||
ipv4_is_local_multicast(addr)) {
|
||||
scoutfs_err(sb, "invalid unicast ipv4 address: %s", str);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* parse port, if specified */
|
||||
if (c) {
|
||||
c++;
|
||||
ret = kstrtoul(c, 0, &port);
|
||||
if (ret != 0 || port == 0 || port >= U16_MAX) {
|
||||
scoutfs_err(sb, "invalid port in ipv4 address: %s", c);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr.s_addr = addr;
|
||||
sin->sin_port = cpu_to_be16(port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int scoutfs_parse_options(struct super_block *sb, char *options,
|
||||
struct mount_options *parsed)
|
||||
{
|
||||
char ipstr[INET_ADDRSTRLEN + 1];
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
int token;
|
||||
char *p;
|
||||
int ret;
|
||||
|
||||
/* Set defaults */
|
||||
memset(parsed, 0, sizeof(*parsed));
|
||||
|
||||
while ((p = strsep(&options, ",")) != NULL) {
|
||||
if (!*p)
|
||||
continue;
|
||||
|
||||
token = match_token(p, tokens, args);
|
||||
switch (token) {
|
||||
case Opt_server_addr:
|
||||
|
||||
match_strlcpy(ipstr, args, ARRAY_SIZE(ipstr));
|
||||
ret = parse_ipv4(sb, ipstr, &parsed->server_addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
case Opt_metadev_path:
|
||||
|
||||
ret = parse_bdev_path(sb, &args[0],
|
||||
&parsed->metadev_path);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
default:
|
||||
scoutfs_err(sb, "Unknown or malformed option, \"%s\"",
|
||||
p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsed->metadev_path) {
|
||||
scoutfs_err(sb, "Required mount option \"metadev_path\" not found");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int scoutfs_options_setup(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct options_sb_info *osi;
|
||||
int ret;
|
||||
|
||||
osi = kzalloc(sizeof(struct options_sb_info), GFP_KERNEL);
|
||||
if (!osi)
|
||||
return -ENOMEM;
|
||||
|
||||
sbi->options = osi;
|
||||
|
||||
osi->debugfs_dir = debugfs_create_dir("options", sbi->debug_root);
|
||||
if (!osi->debugfs_dir) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
if (ret)
|
||||
scoutfs_options_destroy(sb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void scoutfs_options_destroy(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct options_sb_info *osi = sbi->options;
|
||||
|
||||
if (osi) {
|
||||
if (osi->debugfs_dir)
|
||||
debugfs_remove_recursive(osi->debugfs_dir);
|
||||
kfree(osi);
|
||||
sbi->options = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef _SCOUTFS_OPTIONS_H_
|
||||
#define _SCOUTFS_OPTIONS_H_
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/in.h>
|
||||
#include "format.h"
|
||||
|
||||
enum scoutfs_mount_options {
|
||||
Opt_server_addr,
|
||||
Opt_metadev_path,
|
||||
Opt_err,
|
||||
};
|
||||
|
||||
struct mount_options {
|
||||
struct sockaddr_in server_addr;
|
||||
char *metadev_path;
|
||||
};
|
||||
|
||||
int scoutfs_parse_options(struct super_block *sb, char *options,
|
||||
struct mount_options *parsed);
|
||||
int scoutfs_options_setup(struct super_block *sb);
|
||||
void scoutfs_options_destroy(struct super_block *sb);
|
||||
|
||||
u32 scoutfs_option_u32(struct super_block *sb, int token);
|
||||
#define scoutfs_option_bool scoutfs_option_u32
|
||||
|
||||
#endif /* _SCOUTFS_OPTIONS_H_ */
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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/sched.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#include "per_task.h"
|
||||
|
||||
/*
|
||||
* There are times when we'd like to pass data from a caller to its
|
||||
* callee but we're bouncing through functions and callbacks that don't
|
||||
* provide per-task storage. We add a trivial little locked list that
|
||||
* lets a caller store a pointer for callees. The lists are put in the
|
||||
* scope of the sharing so the contention is rare and limited to real
|
||||
* concurrency -- imagine, for example, concurrent file reading on an
|
||||
* inode.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Return the pointer that our caller added for us on the given list.
|
||||
* The expected promise is that the pointer is valid until we return to
|
||||
* the caller who will remove it from the list.
|
||||
*/
|
||||
void *scoutfs_per_task_get(struct scoutfs_per_task *pt)
|
||||
{
|
||||
const struct task_struct *task = current;
|
||||
struct scoutfs_per_task_entry *ent;
|
||||
void *ret = NULL;
|
||||
|
||||
spin_lock(&pt->lock);
|
||||
|
||||
list_for_each_entry(ent, &pt->list, head) {
|
||||
if (ent->task == task){
|
||||
ret = ent->ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock(&pt->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void scoutfs_per_task_add(struct scoutfs_per_task *pt,
|
||||
struct scoutfs_per_task_entry *ent, void *ptr)
|
||||
{
|
||||
ent->task = current;
|
||||
ent->ptr = ptr;
|
||||
|
||||
spin_lock(&pt->lock);
|
||||
list_add(&ent->head, &pt->list);
|
||||
spin_unlock(&pt->lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the entry to the per-task list if the task didn't already have an
|
||||
* entry on the list. Returns true if the entry was added, false if it
|
||||
* wasn't.
|
||||
*/
|
||||
bool scoutfs_per_task_add_excl(struct scoutfs_per_task *pt,
|
||||
struct scoutfs_per_task_entry *ent, void *ptr)
|
||||
{
|
||||
if (!scoutfs_per_task_get(pt)) {
|
||||
scoutfs_per_task_add(pt, ent, ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the entry was found on the list and was deleted,
|
||||
* returns false if the entry wasn't present on a list.
|
||||
*/
|
||||
bool scoutfs_per_task_del(struct scoutfs_per_task *pt,
|
||||
struct scoutfs_per_task_entry *ent)
|
||||
{
|
||||
BUG_ON(!list_empty(&ent->head) && ent->task != current);
|
||||
|
||||
if (!list_empty(&ent->head)) {
|
||||
spin_lock(&pt->lock);
|
||||
list_del_init(&ent->head);
|
||||
spin_unlock(&pt->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void scoutfs_per_task_init(struct scoutfs_per_task *pt)
|
||||
{
|
||||
spin_lock_init(&pt->lock);
|
||||
INIT_LIST_HEAD(&pt->list);
|
||||
}
|
||||
|
||||
void scoutfs_per_task_init_entry(struct scoutfs_per_task_entry *ent)
|
||||
{
|
||||
INIT_LIST_HEAD(&ent->head);
|
||||
ent->task = NULL;
|
||||
ent->ptr = NULL;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef _SCOUTFS_PER_TASK_H_
|
||||
#define _SCOUTFS_PER_TASK_H_
|
||||
|
||||
struct scoutfs_per_task {
|
||||
spinlock_t lock;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct scoutfs_per_task_entry {
|
||||
struct list_head head;
|
||||
struct task_struct *task;
|
||||
void *ptr;
|
||||
};
|
||||
|
||||
#define SCOUTFS_DECLARE_PER_TASK_ENTRY(name) \
|
||||
struct scoutfs_per_task_entry name = { \
|
||||
.head = LIST_HEAD_INIT((name).head), \
|
||||
}
|
||||
|
||||
|
||||
void *scoutfs_per_task_get(struct scoutfs_per_task *pt);
|
||||
void scoutfs_per_task_add(struct scoutfs_per_task *pt,
|
||||
struct scoutfs_per_task_entry *ent, void *ptr);
|
||||
bool scoutfs_per_task_add_excl(struct scoutfs_per_task *pt,
|
||||
struct scoutfs_per_task_entry *ent, void *ptr);
|
||||
bool scoutfs_per_task_del(struct scoutfs_per_task *pt,
|
||||
struct scoutfs_per_task_entry *ent);
|
||||
void scoutfs_per_task_init(struct scoutfs_per_task *pt);
|
||||
void scoutfs_per_task_init_entry(struct scoutfs_per_task_entry *ent);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,779 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/crc32c.h>
|
||||
#include <linux/sort.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/blkdev.h>
|
||||
|
||||
#include "format.h"
|
||||
#include "msg.h"
|
||||
#include "counters.h"
|
||||
#include "quorum.h"
|
||||
#include "server.h"
|
||||
#include "net.h"
|
||||
#include "sysfs.h"
|
||||
#include "scoutfs_trace.h"
|
||||
|
||||
/*
|
||||
* scoutfs mounts communicate through a region of preallocated blocks to
|
||||
* elect a leader who starts the server. Mounts which have been
|
||||
* configured with a server address and which can't connect to a server
|
||||
* attempt to form a quorum to elect a new leader who starts a new
|
||||
* server.
|
||||
*
|
||||
* The mounts participating in the election use a variant of the raft
|
||||
* election protocol to establish quorum and elect a leader. We use
|
||||
* block reads and writes instead of network messages. Mounts read all
|
||||
* the blocks looking for messages to receive. Mounts write their vote
|
||||
* to a random block in the region to send a message to all other
|
||||
* mounts. Unlikely collisions are analogous to lossy networks losing
|
||||
* messages and are handled by the protocol.
|
||||
*
|
||||
* We allow a "majority" of 1 voter when there are less than three
|
||||
* possible voters. This lets a simple network establish quorum. If
|
||||
* the raft quorum timeouts align to leaders could both elect themselves
|
||||
* and race to fence each other. In the worst case they could continue
|
||||
* to do this indefinitely but it's unlikely as it would require a
|
||||
* sequence of identical random raft timeouts.
|
||||
*
|
||||
* One of the reasons we use block reads and writes as the quorum
|
||||
* communication medium is that it lets us leave behind a shared
|
||||
* persistent log of previous election results. This then lets a newly
|
||||
* elected leader fence all previously elected leaders that haven't
|
||||
* shutdown so that they can safely assume exclusive access to the
|
||||
* shared device. Every written block includes a log of election
|
||||
* results. Every voter merges the log from every block it reads the
|
||||
* block it writes. A leader doesn't attempt to fence until it's spent
|
||||
* a few cycles writing blocks with itself as the log entry. This gives
|
||||
* other voters time to migrate the log entry through the blocks.
|
||||
*
|
||||
* Once a leader is elected it fences any previously elected leaders
|
||||
* still present in the log it merged while reading all the voting
|
||||
* blocks. Once they've fenced they update the super block record of
|
||||
* the latest term that has been fenced. This trims the log over time
|
||||
* and keeps from attempting to fence the same mounts multiple times.
|
||||
* As the server later shuts down it writes its term into the super to
|
||||
* stop it from being fenced.
|
||||
*
|
||||
* The final complication comes during unmount. Clients exit after the
|
||||
* server responds to their farewell request. But a majority of clients
|
||||
* need to be present to elect a server to process farewell requests.
|
||||
* The server knows which clients will attempt to vote for quorum and
|
||||
* only responds to their farewell requests once they're no longer
|
||||
* needed to elect a server -- either there's still quorum remaining of
|
||||
* other mounts or the only mounts remaining are all quorum voters that
|
||||
* have sent farewell requests. Before sending these final responses
|
||||
* the server updates an unmount_barrier field in the super. If clients
|
||||
* that are waiting for a farewell response see the unmount barrier
|
||||
* increment they know that their farewell has been processed and they
|
||||
* can assume a successful farewell response and exit cleanly.
|
||||
*
|
||||
* XXX: - actually fence
|
||||
*/
|
||||
|
||||
struct quorum_info {
|
||||
struct scoutfs_sysfs_attrs ssa;
|
||||
|
||||
bool is_leader;
|
||||
};
|
||||
|
||||
#define DECLARE_QUORUM_INFO(sb, name) \
|
||||
struct quorum_info *name = SCOUTFS_SB(sb)->quorum_info
|
||||
#define DECLARE_QUORUM_INFO_KOBJ(kobj, name) \
|
||||
DECLARE_QUORUM_INFO(SCOUTFS_SYSFS_ATTRS_SB(kobj), name)
|
||||
|
||||
/*
|
||||
* Return an absolute ktime timeout expires value in the future after a
|
||||
* random duration between hi and lo where both limits are possible.
|
||||
*/
|
||||
static ktime_t random_to(u32 lo, u32 hi)
|
||||
{
|
||||
return ktime_add_ms(ktime_get(), lo + prandom_u32_max((hi + 1) - lo));
|
||||
}
|
||||
|
||||
/*
|
||||
* The caller is about to read all the quorum blocks. We invalidate any
|
||||
* cached blocks and issue one large contiguous read to repopulate the
|
||||
* cache. The caller then uses normal __bread to read each block. I'm
|
||||
* not a huge fan of the plug but I couldn't get the individual
|
||||
* readahead requests merged without it.
|
||||
*/
|
||||
static void readahead_quorum_blocks(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct buffer_head *bh;
|
||||
struct blk_plug plug;
|
||||
int i;
|
||||
|
||||
blk_start_plug(&plug);
|
||||
|
||||
for (i = 0; i < SCOUTFS_QUORUM_BLOCKS; i++) {
|
||||
bh = __getblk(sbi->meta_bdev, SCOUTFS_QUORUM_BLKNO + i,
|
||||
SCOUTFS_BLOCK_SM_SIZE);
|
||||
if (!bh)
|
||||
continue;
|
||||
|
||||
lock_buffer(bh);
|
||||
clear_buffer_uptodate(bh);
|
||||
unlock_buffer(bh);
|
||||
|
||||
ll_rw_block(READA | REQ_META | REQ_PRIO, 1, &bh);
|
||||
brelse(bh);
|
||||
}
|
||||
|
||||
blk_finish_plug(&plug);
|
||||
}
|
||||
|
||||
struct quorum_block_head {
|
||||
struct list_head head;
|
||||
union {
|
||||
struct scoutfs_quorum_block blk;
|
||||
u8 bytes[SCOUTFS_BLOCK_SM_SIZE];
|
||||
};
|
||||
};
|
||||
|
||||
static void free_quorum_blocks(struct list_head *blocks)
|
||||
{
|
||||
struct quorum_block_head *qbh;
|
||||
struct quorum_block_head *tmp;
|
||||
|
||||
list_for_each_entry_safe(qbh, tmp, blocks, head) {
|
||||
list_del_init(&qbh->head);
|
||||
kfree(qbh);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Callers don't mind us clobbering the crc temporarily.
|
||||
*/
|
||||
static __le32 quorum_block_crc(struct scoutfs_quorum_block *blk)
|
||||
{
|
||||
__le32 calc_crc;
|
||||
__le32 blk_crc;
|
||||
|
||||
blk_crc = blk->crc;
|
||||
blk->crc = 0;
|
||||
calc_crc = cpu_to_le32(crc32c(~0, blk, sizeof(*blk)));
|
||||
blk->crc = blk_crc;
|
||||
|
||||
return calc_crc;
|
||||
}
|
||||
|
||||
static size_t quorum_block_bytes(struct scoutfs_quorum_block *blk)
|
||||
{
|
||||
return offsetof(struct scoutfs_quorum_block,
|
||||
log[blk->log_nr]);
|
||||
}
|
||||
|
||||
static bool invalid_quorum_block(struct buffer_head *bh,
|
||||
struct scoutfs_quorum_block *blk)
|
||||
{
|
||||
return bh->b_size != SCOUTFS_BLOCK_SM_SIZE ||
|
||||
sizeof(struct scoutfs_quorum_block) > SCOUTFS_BLOCK_SM_SIZE ||
|
||||
quorum_block_crc(blk) != blk->crc ||
|
||||
le64_to_cpu(blk->blkno) != bh->b_blocknr ||
|
||||
blk->term == 0 ||
|
||||
blk->log_nr > SCOUTFS_QUORUM_LOG_MAX ||
|
||||
quorum_block_bytes(blk) > SCOUTFS_BLOCK_SM_SIZE;
|
||||
}
|
||||
|
||||
/* true if a is stale and should be ignored */
|
||||
static bool stale_quorum_block(struct scoutfs_quorum_block *a,
|
||||
struct scoutfs_quorum_block *b)
|
||||
{
|
||||
if (le64_to_cpu(a->term) < le64_to_cpu(b->term))
|
||||
return true;
|
||||
|
||||
if (le64_to_cpu(a->voter_rid) == le64_to_cpu(b->voter_rid) &&
|
||||
le64_to_cpu(a->write_nr) <= le64_to_cpu(b->write_nr))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the most recent blocks from all the voters for the most recent term.
|
||||
* We ignore any corrupt blocks, blocks not for our fsid, previous terms,
|
||||
* and previous writes from a rid in the current term.
|
||||
*/
|
||||
static int read_quorum_blocks(struct super_block *sb, struct list_head *blocks)
|
||||
{
|
||||
struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super;
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_quorum_block *blk;
|
||||
struct quorum_block_head *qbh;
|
||||
struct quorum_block_head *tmp;
|
||||
struct buffer_head *bh = NULL;
|
||||
LIST_HEAD(stale);
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
readahead_quorum_blocks(sb);
|
||||
|
||||
for (i = 0; i < SCOUTFS_QUORUM_BLOCKS; i++) {
|
||||
brelse(bh);
|
||||
bh = __bread(sbi->meta_bdev, SCOUTFS_QUORUM_BLKNO + i,
|
||||
SCOUTFS_BLOCK_SM_SIZE);
|
||||
if (!bh) {
|
||||
scoutfs_inc_counter(sb, quorum_read_block_error);
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
blk = (void *)(bh->b_data);
|
||||
|
||||
/* ignore unwritten blocks or blocks for other filesystems */
|
||||
if (blk->voter_rid == 0 || blk->fsid != super->hdr.fsid)
|
||||
continue;
|
||||
|
||||
if (invalid_quorum_block(bh, blk)) {
|
||||
scoutfs_inc_counter(sb, quorum_read_invalid_block);
|
||||
continue;
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(qbh, tmp, blocks, head) {
|
||||
if (stale_quorum_block(blk, &qbh->blk)) {
|
||||
blk = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (stale_quorum_block(&qbh->blk, blk))
|
||||
list_move(&qbh->head, &stale);
|
||||
}
|
||||
free_quorum_blocks(&stale);
|
||||
|
||||
if (!blk)
|
||||
continue;
|
||||
|
||||
qbh = kmalloc(sizeof(struct quorum_block_head),
|
||||
GFP_NOFS);
|
||||
if (!qbh) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(&qbh->blk, blk, quorum_block_bytes(blk));
|
||||
list_add_tail(&qbh->head, blocks);
|
||||
}
|
||||
|
||||
list_for_each_entry(qbh, blocks, head) {
|
||||
trace_scoutfs_quorum_read_block(sb, &qbh->blk);
|
||||
scoutfs_inc_counter(sb, quorum_read_block);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
brelse(bh);
|
||||
if (ret < 0)
|
||||
free_quorum_blocks(blocks);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Synchronously write a single quorum block. The caller has provided
|
||||
* the meaningful fields for the write. We fill in the fsid, blkno, and
|
||||
* crc for every write and zero the rest of the block.
|
||||
*/
|
||||
static int write_quorum_block(struct super_block *sb,
|
||||
struct scoutfs_quorum_block *our_blk)
|
||||
{
|
||||
struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super;
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_quorum_block *blk;
|
||||
struct buffer_head *bh = NULL;
|
||||
size_t size;
|
||||
int ret;
|
||||
|
||||
BUILD_BUG_ON(sizeof(struct scoutfs_quorum_block) >
|
||||
SCOUTFS_BLOCK_SM_SIZE);
|
||||
|
||||
bh = __getblk(sbi->meta_bdev, SCOUTFS_QUORUM_BLKNO +
|
||||
prandom_u32_max(SCOUTFS_QUORUM_BLOCKS),
|
||||
SCOUTFS_BLOCK_SM_SIZE);
|
||||
if (bh == NULL) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
size = quorum_block_bytes(our_blk);
|
||||
if (WARN_ON_ONCE(size > SCOUTFS_BLOCK_SM_SIZE || size > bh->b_size)) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
blk = (void *)bh->b_data;
|
||||
memset(blk, 0, bh->b_size);
|
||||
memcpy(blk, our_blk, size);
|
||||
|
||||
blk->fsid = super->hdr.fsid;
|
||||
blk->blkno = cpu_to_le64(bh->b_blocknr);
|
||||
blk->crc = quorum_block_crc(blk);
|
||||
|
||||
lock_buffer(bh);
|
||||
set_buffer_mapped(bh);
|
||||
bh->b_end_io = end_buffer_write_sync;
|
||||
get_bh(bh);
|
||||
submit_bh(WRITE_SYNC | REQ_META | REQ_PRIO, bh);
|
||||
|
||||
wait_on_buffer(bh);
|
||||
if (!buffer_uptodate(bh))
|
||||
ret = -EIO;
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
if (ret == 0) {
|
||||
trace_scoutfs_quorum_write_block(sb, blk);
|
||||
scoutfs_inc_counter(sb, quorum_write_block);
|
||||
}
|
||||
out:
|
||||
if (ret)
|
||||
scoutfs_inc_counter(sb, quorum_write_block_error);
|
||||
brelse(bh);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if there's an entry for the given election.
|
||||
*/
|
||||
static bool log_contains(struct scoutfs_quorum_block *blk, u64 term, u64 rid)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < blk->log_nr; i++) {
|
||||
if (le64_to_cpu(blk->log[i].term) == term &&
|
||||
le64_to_cpu(blk->log[i].rid) == rid)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* add an entry to the log, returning error if it's full */
|
||||
static int log_add(struct scoutfs_quorum_block *blk, u64 term, u64 rid,
|
||||
struct scoutfs_inet_addr *addr)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (log_contains(blk, term, rid))
|
||||
return 0;
|
||||
|
||||
if (blk->log_nr == SCOUTFS_QUORUM_LOG_MAX)
|
||||
return -ENOSPC;
|
||||
|
||||
i = blk->log_nr++;
|
||||
blk->log[i].term = cpu_to_le64(term);
|
||||
blk->log[i].rid = cpu_to_le64(rid);
|
||||
blk->log[i].addr = *addr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* migrate live log entries between blocks, returning err if full */
|
||||
static int log_merge(struct scoutfs_quorum_block *our_blk,
|
||||
struct scoutfs_quorum_block *blk,
|
||||
u64 fenced_term)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < blk->log_nr; i++) {
|
||||
if (le64_to_cpu(blk->log[i].term) > fenced_term) {
|
||||
ret = log_add(our_blk, le64_to_cpu(blk->log[i].term),
|
||||
le64_to_cpu(blk->log[i].rid),
|
||||
&blk->log[i].addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remove old log entries for a voter before a given term. */
|
||||
static void log_purge(struct scoutfs_quorum_block *blk, u64 term, u64 rid)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < blk->log_nr; i++) {
|
||||
if (le64_to_cpu(blk->log[i].term) < term &&
|
||||
le64_to_cpu(blk->log[i].rid) == rid) {
|
||||
if (i != blk->log_nr - 1)
|
||||
swap(blk->log[i], blk->log[blk->log_nr - 1]);
|
||||
blk->log_nr--;
|
||||
i--; /* continue from swapped in entry */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The caller received a majority of votes and has been elected. Before
|
||||
* assuming exclusive write access to the device we fence the winners of
|
||||
* any previous elections still present in the log. Once they're fenced
|
||||
* we re-read the super and update the fenced_term to indicate that
|
||||
* those previous elections can be ignored and purged from the log.
|
||||
*
|
||||
* We can be attempting this concurrently with both previous and future
|
||||
* elected leaders. The leader with the greatest elected term will win
|
||||
* and fence all previous elected leaders.
|
||||
*
|
||||
* We clobber the caller's block as we go to not fence rids multiple times.
|
||||
*/
|
||||
static int fence_previous(struct super_block *sb,
|
||||
struct scoutfs_quorum_block *blk,
|
||||
u64 our_rid, u64 fenced_term, u64 term)
|
||||
{
|
||||
struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super;
|
||||
struct sockaddr_in their_sin;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < blk->log_nr; i++) {
|
||||
if (le64_to_cpu(blk->log[i].rid) != our_rid &&
|
||||
le64_to_cpu(blk->log[i].term) > fenced_term &&
|
||||
le64_to_cpu(blk->log[i].term) < term) {
|
||||
|
||||
scoutfs_inc_counter(sb, quorum_fenced);
|
||||
scoutfs_addr_to_sin(&their_sin, &blk->log[i].addr);
|
||||
scoutfs_err(sb, "fencing "SCSBF" at "SIN_FMT,
|
||||
SCSB_LEFR_ARGS(super->hdr.fsid,
|
||||
blk->log[i].rid),
|
||||
SIN_ARG(&their_sin));
|
||||
|
||||
log_purge(blk, term, le64_to_cpu(blk->log[i].rid));
|
||||
i = -1; /* start over */
|
||||
}
|
||||
}
|
||||
|
||||
/* update fenced term now that we have exclusive access */
|
||||
ret = 0;
|
||||
super = kmalloc(sizeof(struct scoutfs_super_block), GFP_NOFS);
|
||||
if (super) {
|
||||
ret = scoutfs_read_super(sb, super);
|
||||
if (ret == 0) {
|
||||
super->quorum_fenced_term = cpu_to_le64(term - 1);
|
||||
ret = scoutfs_write_super(sb, super);
|
||||
|
||||
}
|
||||
kfree(super);
|
||||
} else {
|
||||
ret = -ENOMEM;
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
scoutfs_err(sb, "failed to update fenced_term in super, this mount will probably be fenced");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* The calling voting mount couldn't connect to a server. Participate
|
||||
* in a raft election to chose a mount to start a new server. If a
|
||||
* majority of other mounts join us then one of us will be elected and
|
||||
* our caller will start the server.
|
||||
*
|
||||
* Voting members read the blocks at regular intervals. If they see a
|
||||
* new election they vote for that candidate for the remainder of the
|
||||
* election. If the election timeout expires they will start a new
|
||||
* election and vote for themselves. Eventually a sufficient majority
|
||||
* sees a new election and all vote in the majority for that candidate.
|
||||
*
|
||||
* The calling client may have just failed to connect to an elected
|
||||
* address in the super block. We assume that server is dead and ignore
|
||||
* it when trying to elect a new leader. But we eventually return with
|
||||
* a timeout because the server could actually be fine and the client
|
||||
* could have had communication to the server restored.
|
||||
*
|
||||
* We return success if we see a new server elected. If we are elected
|
||||
* we set the caller's elected_term so they know to start the server.
|
||||
*/
|
||||
int scoutfs_quorum_election(struct super_block *sb, ktime_t timeout_abs,
|
||||
u64 prev_term, u64 *elected_term)
|
||||
{
|
||||
DECLARE_QUORUM_INFO(sb, qinf);
|
||||
struct mount_options *opts = &SCOUTFS_SB(sb)->opts;
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_super_block *super = NULL;
|
||||
struct scoutfs_quorum_block *our_blk = NULL;
|
||||
struct scoutfs_quorum_block *blk;
|
||||
struct quorum_block_head *qbh;
|
||||
struct scoutfs_inet_addr addr;
|
||||
enum { VOTER, CANDIDATE };
|
||||
ktime_t cycle_to;
|
||||
ktime_t term_to;
|
||||
LIST_HEAD(blocks);
|
||||
u64 vote_for_write_nr;
|
||||
u64 vote_for_rid;
|
||||
u64 write_nr;
|
||||
u64 term;
|
||||
int log_cycles = 0;
|
||||
int votes;
|
||||
int role;
|
||||
int ret;
|
||||
|
||||
*elected_term = 0;
|
||||
|
||||
trace_scoutfs_quorum_election(sb, prev_term);
|
||||
|
||||
super = kmalloc(sizeof(struct scoutfs_super_block), GFP_NOFS);
|
||||
our_blk = kmalloc(SCOUTFS_BLOCK_SM_SIZE, GFP_NOFS);
|
||||
if (!super || !our_blk) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* start out as a passive voter */
|
||||
role = VOTER;
|
||||
term = 0;
|
||||
write_nr = 0;
|
||||
vote_for_rid = 0;
|
||||
vote_for_write_nr = 0;
|
||||
|
||||
/* we'll become a candidate if we don't see another candidate */
|
||||
term_to = random_to(SCOUTFS_QUORUM_TERM_LO_MS,
|
||||
SCOUTFS_QUORUM_TERM_HI_MS);
|
||||
|
||||
for (;;) {
|
||||
memset(our_blk, 0, SCOUTFS_BLOCK_SM_SIZE);
|
||||
|
||||
scoutfs_inc_counter(sb, quorum_cycle);
|
||||
|
||||
ret = scoutfs_read_super(sb, super);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* done if we see evidence of a new server */
|
||||
if (le64_to_cpu(super->quorum_server_term) > prev_term) {
|
||||
scoutfs_inc_counter(sb, quorum_saw_super_leader);
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* done if we couldn't elect anyone */
|
||||
if (ktime_after(ktime_get(), timeout_abs)) {
|
||||
scoutfs_inc_counter(sb, quorum_timedout);
|
||||
ret = -ETIMEDOUT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* become a candidate if the election times out */
|
||||
if (ktime_after(ktime_get(), term_to)) {
|
||||
scoutfs_inc_counter(sb, quorum_election_timeout);
|
||||
term_to = random_to(SCOUTFS_QUORUM_TERM_LO_MS,
|
||||
SCOUTFS_QUORUM_TERM_HI_MS);
|
||||
role = CANDIDATE;
|
||||
term++;
|
||||
vote_for_rid = sbi->rid;
|
||||
log_cycles = 0;
|
||||
}
|
||||
|
||||
free_quorum_blocks(&blocks);
|
||||
ret = read_quorum_blocks(sb, &blocks);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
votes = 0;
|
||||
|
||||
list_for_each_entry(qbh, &blocks, head) {
|
||||
blk = &qbh->blk;
|
||||
|
||||
/*
|
||||
* Become a voter for a candidate the first time
|
||||
* we see a new term.
|
||||
*
|
||||
* And also if we're a candidate and see a
|
||||
* higher rid candidate in our term. This
|
||||
* minimizes instability when two quorums are
|
||||
* possible and race to elect two leaders. This
|
||||
* is only barely reasonable when accepting the
|
||||
* risk of instability in two mount
|
||||
* configurations.
|
||||
*/
|
||||
if ((le64_to_cpu(blk->term) > term) ||
|
||||
(role == CANDIDATE &&
|
||||
le64_to_cpu(blk->term) == term &&
|
||||
blk->voter_rid == blk->vote_for_rid &&
|
||||
le64_to_cpu(blk->voter_rid) > sbi->rid)) {
|
||||
role = VOTER;
|
||||
term = le64_to_cpu(blk->term);
|
||||
vote_for_rid = le64_to_cpu(blk->vote_for_rid);
|
||||
vote_for_write_nr = 0;
|
||||
votes = 0;
|
||||
log_cycles = 0;
|
||||
}
|
||||
|
||||
/* candidate writes suppress voter election timers */
|
||||
if (role == VOTER &&
|
||||
blk->voter_rid == blk->vote_for_rid &&
|
||||
le64_to_cpu(blk->write_nr) > vote_for_write_nr) {
|
||||
term_to = random_to(SCOUTFS_QUORUM_TERM_LO_MS,
|
||||
SCOUTFS_QUORUM_TERM_HI_MS);
|
||||
vote_for_write_nr = le64_to_cpu(blk->write_nr);
|
||||
}
|
||||
|
||||
/* count our votes */
|
||||
if (role == CANDIDATE &&
|
||||
le64_to_cpu(blk->vote_for_rid) == sbi->rid) {
|
||||
votes++;
|
||||
}
|
||||
|
||||
/* try to write greater write_nr */
|
||||
write_nr = max(write_nr, le64_to_cpu(blk->write_nr));
|
||||
}
|
||||
|
||||
trace_scoutfs_quorum_election_vote(sb, role, term,
|
||||
vote_for_rid, votes,
|
||||
log_cycles,
|
||||
super->quorum_count);
|
||||
|
||||
/* first merge logs from all votes this term */
|
||||
list_for_each_entry(qbh, &blocks, head) {
|
||||
blk = &qbh->blk;
|
||||
|
||||
ret = log_merge(our_blk, blk,
|
||||
le64_to_cpu(super->quorum_fenced_term));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* remove logs for voters that can't be servers */
|
||||
list_for_each_entry(qbh, &blocks, head) {
|
||||
blk = &qbh->blk;
|
||||
|
||||
if (blk->voter_rid != blk->vote_for_rid)
|
||||
log_purge(our_blk, le64_to_cpu(blk->term),
|
||||
le64_to_cpu(blk->voter_rid));
|
||||
}
|
||||
|
||||
/* add ourselves to the log when we see vote quorum */
|
||||
if (role == CANDIDATE && votes >= super->quorum_count) {
|
||||
scoutfs_addr_from_sin(&addr, &opts->server_addr);
|
||||
ret = log_add(our_blk, term, vote_for_rid, &addr);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
log_cycles++; /* will be written *this* cycle */
|
||||
}
|
||||
|
||||
/* elected candidates can proceed after their log cycles */
|
||||
if (role == CANDIDATE &&
|
||||
log_cycles > SCOUTFS_QUORUM_ELECTED_LOG_CYCLES) {
|
||||
/* our_blk is clobbered */
|
||||
ret = fence_previous(sb, our_blk, sbi->rid,
|
||||
le64_to_cpu(super->quorum_fenced_term),
|
||||
term);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
scoutfs_inc_counter(sb, quorum_elected_leader);
|
||||
qinf->is_leader = true;
|
||||
*elected_term = term;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* write our block every cycle */
|
||||
if (term > 0) {
|
||||
our_blk->term = cpu_to_le64(term);
|
||||
write_nr++;
|
||||
our_blk->write_nr = cpu_to_le64(write_nr);
|
||||
our_blk->voter_rid = cpu_to_le64(sbi->rid);
|
||||
our_blk->vote_for_rid = cpu_to_le64(vote_for_rid);
|
||||
|
||||
ret = write_quorum_block(sb, our_blk);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* add a small random delay to each cycle */
|
||||
cycle_to = random_to(SCOUTFS_QUORUM_CYCLE_LO_MS,
|
||||
SCOUTFS_QUORUM_CYCLE_HI_MS);
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
schedule_hrtimeout(&cycle_to, HRTIMER_MODE_ABS);
|
||||
}
|
||||
|
||||
out:
|
||||
free_quorum_blocks(&blocks);
|
||||
kfree(super);
|
||||
kfree(our_blk);
|
||||
|
||||
trace_scoutfs_quorum_election_ret(sb, ret, *elected_term);
|
||||
if (ret)
|
||||
scoutfs_inc_counter(sb, quorum_failure);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void scoutfs_quorum_clear_leader(struct super_block *sb)
|
||||
{
|
||||
DECLARE_QUORUM_INFO(sb, qinf);
|
||||
|
||||
qinf->is_leader = false;
|
||||
}
|
||||
|
||||
static ssize_t is_leader_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
DECLARE_QUORUM_INFO_KOBJ(kobj, qinf);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u", !!qinf->is_leader);
|
||||
}
|
||||
SCOUTFS_ATTR_RO(is_leader);
|
||||
|
||||
static struct attribute *quorum_attrs[] = {
|
||||
SCOUTFS_ATTR_PTR(is_leader),
|
||||
NULL,
|
||||
};
|
||||
|
||||
int scoutfs_quorum_setup(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct quorum_info *qinf;
|
||||
int ret;
|
||||
|
||||
qinf = kzalloc(sizeof(struct quorum_info), GFP_KERNEL);
|
||||
if (!qinf) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
scoutfs_sysfs_init_attrs(sb, &qinf->ssa);
|
||||
|
||||
sbi->quorum_info = qinf;
|
||||
|
||||
ret = scoutfs_sysfs_create_attrs(sb, &qinf->ssa, quorum_attrs,
|
||||
"quorum");
|
||||
out:
|
||||
if (ret)
|
||||
scoutfs_quorum_destroy(sb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void scoutfs_quorum_destroy(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct quorum_info *qinf = SCOUTFS_SB(sb)->quorum_info;
|
||||
|
||||
if (qinf) {
|
||||
scoutfs_sysfs_destroy_attrs(sb, &qinf->ssa);
|
||||
kfree(qinf);
|
||||
sbi->quorum_info = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef _SCOUTFS_QUORUM_H_
|
||||
#define _SCOUTFS_QUORUM_H_
|
||||
|
||||
int scoutfs_quorum_election(struct super_block *sb, ktime_t timeout_abs,
|
||||
u64 prev_term, u64 *elected_term);
|
||||
void scoutfs_quorum_clear_leader(struct super_block *sb);
|
||||
|
||||
int scoutfs_quorum_setup(struct super_block *sb);
|
||||
void scoutfs_quorum_destroy(struct super_block *sb);
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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/slab.h>
|
||||
#include <linux/magic.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/random.h>
|
||||
|
||||
#include "super.h"
|
||||
#include "format.h"
|
||||
#include "inode.h"
|
||||
#include "dir.h"
|
||||
#include "msg.h"
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "scoutfs_trace.h"
|
||||
File diff suppressed because it is too large
Load Diff
+1768
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,80 @@
|
||||
#ifndef _SCOUTFS_SERVER_H_
|
||||
#define _SCOUTFS_SERVER_H_
|
||||
|
||||
#define SI4_FMT "%u.%u.%u.%u:%u"
|
||||
|
||||
#define si4_trace_define(name) \
|
||||
__field(__u32, name##_addr) \
|
||||
__field(__u16, name##_port)
|
||||
|
||||
#define si4_trace_assign(name, sin) \
|
||||
do { \
|
||||
__typeof__(sin) _sin = (sin); \
|
||||
\
|
||||
__entry->name##_addr = be32_to_cpu(_sin->sin_addr.s_addr); \
|
||||
__entry->name##_port = be16_to_cpu(_sin->sin_port); \
|
||||
} while(0)
|
||||
|
||||
#define si4_trace_args(name) \
|
||||
(__entry->name##_addr >> 24), \
|
||||
(__entry->name##_addr >> 16) & 255, \
|
||||
(__entry->name##_addr >> 8) & 255, \
|
||||
__entry->name##_addr & 255, \
|
||||
__entry->name##_port
|
||||
|
||||
#define SNH_FMT \
|
||||
"seq %llu recv_seq %llu id %llu data_len %u cmd %u flags 0x%x error %u"
|
||||
#define SNH_ARG(nh) \
|
||||
le64_to_cpu((nh)->seq), le64_to_cpu((nh)->recv_seq), \
|
||||
le64_to_cpu((nh)->id), le16_to_cpu((nh)->data_len), (nh)->cmd, \
|
||||
(nh)->flags, (nh)->error
|
||||
|
||||
#define snh_trace_define(name) \
|
||||
__field(__u64, name##_seq) \
|
||||
__field(__u64, name##_recv_seq) \
|
||||
__field(__u64, name##_id) \
|
||||
__field(__u16, name##_data_len) \
|
||||
__field(__u8, name##_cmd) \
|
||||
__field(__u8, name##_flags) \
|
||||
__field(__u8, name##_error)
|
||||
|
||||
#define snh_trace_assign(name, nh) \
|
||||
do { \
|
||||
__typeof__(nh) _nh = (nh); \
|
||||
\
|
||||
__entry->name##_seq = le64_to_cpu(_nh->seq); \
|
||||
__entry->name##_recv_seq = le64_to_cpu(_nh->recv_seq); \
|
||||
__entry->name##_id = le64_to_cpu(_nh->id); \
|
||||
__entry->name##_data_len = le16_to_cpu(_nh->data_len); \
|
||||
__entry->name##_cmd = _nh->cmd; \
|
||||
__entry->name##_flags = _nh->flags; \
|
||||
__entry->name##_error = _nh->error; \
|
||||
} while (0)
|
||||
|
||||
#define snh_trace_args(name) \
|
||||
__entry->name##_seq, __entry->name##_recv_seq, __entry->name##_id, \
|
||||
__entry->name##_data_len, __entry->name##_cmd, __entry->name##_flags, \
|
||||
__entry->name##_error
|
||||
|
||||
int scoutfs_server_lock_request(struct super_block *sb, u64 rid,
|
||||
struct scoutfs_net_lock *nl);
|
||||
int scoutfs_server_lock_response(struct super_block *sb, u64 rid, u64 id,
|
||||
struct scoutfs_net_lock_grant_response *gr);
|
||||
int scoutfs_server_lock_recover_request(struct super_block *sb, u64 rid,
|
||||
struct scoutfs_key *key);
|
||||
void scoutfs_server_get_roots(struct super_block *sb,
|
||||
struct scoutfs_net_roots *roots);
|
||||
int scoutfs_server_hold_commit(struct super_block *sb);
|
||||
int scoutfs_server_apply_commit(struct super_block *sb, int err);
|
||||
|
||||
struct sockaddr_in;
|
||||
struct scoutfs_quorum_elected_info;
|
||||
int scoutfs_server_start(struct super_block *sb, struct sockaddr_in *sin,
|
||||
u64 term);
|
||||
void scoutfs_server_abort(struct super_block *sb);
|
||||
void scoutfs_server_stop(struct super_block *sb);
|
||||
|
||||
int scoutfs_server_setup(struct super_block *sb);
|
||||
void scoutfs_server_destroy(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* A copy of sort() from upstream with a priv argument that's passed
|
||||
* to comparison, like list_sort().
|
||||
*/
|
||||
|
||||
/* ------------------------ */
|
||||
|
||||
/*
|
||||
* A fast, small, non-recursive O(nlog n) sort for the Linux kernel
|
||||
*
|
||||
* Jan 23 2005 Matt Mackall <mpm@selenic.com>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/sort.h>
|
||||
#include <linux/slab.h>
|
||||
#include "sort_priv.h"
|
||||
|
||||
/**
|
||||
* sort - sort an array of elements
|
||||
* @priv: caller's pointer to pass to comparison and swap functions
|
||||
* @base: pointer to data to sort
|
||||
* @num: number of elements
|
||||
* @size: size of each element
|
||||
* @cmp_func: pointer to comparison function
|
||||
* @swap_func: pointer to swap function or NULL
|
||||
*
|
||||
* This function does a heapsort on the given array. You may provide a
|
||||
* swap_func function optimized to your element type.
|
||||
*
|
||||
* Sorting time is O(n log n) both on average and worst-case. While
|
||||
* qsort is about 20% faster on average, it suffers from exploitable
|
||||
* O(n*n) worst-case behavior and extra memory requirements that make
|
||||
* it less suitable for kernel use.
|
||||
*/
|
||||
|
||||
void sort_priv(void *priv, void *base, size_t num, size_t size,
|
||||
int (*cmp_func)(void *priv, const void *, const void *),
|
||||
void (*swap_func)(void *priv, void *, void *, int size))
|
||||
{
|
||||
/* pre-scale counters for performance */
|
||||
int i = (num/2 - 1) * size, n = num * size, c, r;
|
||||
|
||||
/* heapify */
|
||||
for ( ; i >= 0; i -= size) {
|
||||
for (r = i; r * 2 + size < n; r = c) {
|
||||
c = r * 2 + size;
|
||||
if (c < n - size &&
|
||||
cmp_func(priv, base + c, base + c + size) < 0)
|
||||
c += size;
|
||||
if (cmp_func(priv, base + r, base + c) >= 0)
|
||||
break;
|
||||
swap_func(priv, base + r, base + c, size);
|
||||
}
|
||||
}
|
||||
|
||||
/* sort */
|
||||
for (i = n - size; i > 0; i -= size) {
|
||||
swap_func(priv, base, base + i, size);
|
||||
for (r = 0; r * 2 + size < i; r = c) {
|
||||
c = r * 2 + size;
|
||||
if (c < i - size &&
|
||||
cmp_func(priv, base + c, base + c + size) < 0)
|
||||
c += size;
|
||||
if (cmp_func(priv, base + r, base + c) >= 0)
|
||||
break;
|
||||
swap_func(priv, base + r, base + c, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef _SCOUTFS_SORT_PRIV_H_
|
||||
#define _SCOUTFS_SORT_PRIV_H_
|
||||
|
||||
void sort_priv(void *priv, void *base, size_t num, size_t size,
|
||||
int (*cmp_func)(void *priv, const void *, const void *),
|
||||
void (*swap_func)(void *priv, void *, void *, int size));
|
||||
|
||||
#endif
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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/slab.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/rbtree.h>
|
||||
|
||||
#include "spbm.h"
|
||||
|
||||
#define SPBM_BITS 128
|
||||
#define SPBM_SHIFT ilog2(SPBM_BITS)
|
||||
#define SPBM_MASK ((u64)SPBM_BITS - 1)
|
||||
#define SPBM_LONGS (SPBM_BITS / BITS_PER_LONG)
|
||||
|
||||
/*
|
||||
* Maintain a sparse bitmap in an rbtree. Setting bits can allocate and
|
||||
* fail but clearing will always succeed. Locking is left up to the
|
||||
* caller.
|
||||
*/
|
||||
|
||||
struct spbm_node {
|
||||
struct rb_node node;
|
||||
u64 index;
|
||||
unsigned long bits[SPBM_LONGS];
|
||||
};
|
||||
|
||||
void scoutfs_spbm_init(struct scoutfs_spbm *spbm)
|
||||
{
|
||||
BUILD_BUG_ON(!is_power_of_2(SPBM_BITS));
|
||||
|
||||
spbm->root = RB_ROOT;
|
||||
}
|
||||
|
||||
bool scoutfs_spbm_empty(struct scoutfs_spbm *spbm)
|
||||
{
|
||||
return RB_EMPTY_ROOT(&spbm->root);
|
||||
}
|
||||
|
||||
enum spbm_flags {
|
||||
/* if a node isn't found then return an allocated new node */
|
||||
SPBM_FIND_ALLOC = (1 << 0),
|
||||
};
|
||||
static struct spbm_node *find_node(struct scoutfs_spbm *spbm, u64 index,
|
||||
int flags)
|
||||
{
|
||||
struct rb_node *parent;
|
||||
struct rb_node **node;
|
||||
struct spbm_node *sn;
|
||||
|
||||
node = &spbm->root.rb_node;
|
||||
parent = NULL;
|
||||
sn = NULL;
|
||||
while (*node) {
|
||||
parent = *node;
|
||||
sn = container_of(*node, struct spbm_node, node);
|
||||
|
||||
if (index < sn->index) {
|
||||
node = &(*node)->rb_left;
|
||||
} else if (index > sn->index) {
|
||||
node = &(*node)->rb_right;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
sn = NULL;
|
||||
}
|
||||
|
||||
if (!sn && (flags & SPBM_FIND_ALLOC)) {
|
||||
sn = kzalloc(sizeof(struct spbm_node), GFP_NOFS);
|
||||
if (sn) {
|
||||
sn->index = index;
|
||||
rb_link_node(&sn->node, parent, node);
|
||||
rb_insert_color(&sn->node, &spbm->root);
|
||||
}
|
||||
}
|
||||
|
||||
return sn;
|
||||
}
|
||||
|
||||
static void calc_index_nr(u64 *index, int *nr, u64 bit)
|
||||
{
|
||||
*index = bit >> SPBM_SHIFT;
|
||||
*nr = bit & SPBM_MASK;
|
||||
}
|
||||
|
||||
int scoutfs_spbm_set(struct scoutfs_spbm *spbm, u64 bit)
|
||||
{
|
||||
struct spbm_node *sn;
|
||||
u64 index;
|
||||
int nr;
|
||||
|
||||
calc_index_nr(&index, &nr, bit);
|
||||
|
||||
sn = find_node(spbm, index, SPBM_FIND_ALLOC);
|
||||
if (!sn)
|
||||
return -ENOMEM;
|
||||
|
||||
set_bit(nr, sn->bits);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int scoutfs_spbm_test(struct scoutfs_spbm *spbm, u64 bit)
|
||||
{
|
||||
struct spbm_node *sn;
|
||||
u64 index;
|
||||
int nr;
|
||||
|
||||
calc_index_nr(&index, &nr, bit);
|
||||
|
||||
sn = find_node(spbm, index, 0);
|
||||
if (sn)
|
||||
return !!test_bit(nr, sn->bits);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void free_node(struct scoutfs_spbm *spbm, struct spbm_node *sn)
|
||||
{
|
||||
rb_erase(&sn->node, &spbm->root);
|
||||
kfree(sn);
|
||||
}
|
||||
|
||||
void scoutfs_spbm_clear(struct scoutfs_spbm *spbm, u64 bit)
|
||||
{
|
||||
struct spbm_node *sn;
|
||||
u64 index;
|
||||
int nr;
|
||||
|
||||
calc_index_nr(&index, &nr, bit);
|
||||
|
||||
sn = find_node(spbm, index, 0);
|
||||
if (sn) {
|
||||
clear_bit(nr, sn->bits);
|
||||
if (bitmap_empty(sn->bits, SPBM_BITS))
|
||||
free_node(spbm, sn);
|
||||
}
|
||||
}
|
||||
|
||||
void scoutfs_spbm_destroy(struct scoutfs_spbm *spbm)
|
||||
{
|
||||
struct spbm_node *sn;
|
||||
struct spbm_node *pos;
|
||||
|
||||
rbtree_postorder_for_each_entry_safe(sn, pos, &spbm->root, node)
|
||||
free_node(spbm, sn);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef _SCOUTFS_SPBM_H_
|
||||
#define _SCOUTFS_SPBM_H_
|
||||
|
||||
struct scoutfs_spbm {
|
||||
struct rb_root root;
|
||||
};
|
||||
|
||||
void scoutfs_spbm_init(struct scoutfs_spbm *spbm);
|
||||
bool scoutfs_spbm_empty(struct scoutfs_spbm *spbm);
|
||||
void scoutfs_spbm_destroy(struct scoutfs_spbm *spbm);
|
||||
|
||||
int scoutfs_spbm_set(struct scoutfs_spbm *spbm, u64 bit);
|
||||
int scoutfs_spbm_test(struct scoutfs_spbm *spbm, u64 bit);
|
||||
void scoutfs_spbm_clear(struct scoutfs_spbm *spbm, u64 bit);
|
||||
|
||||
#endif
|
||||
+2331
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
#ifndef _SCOUTFS_SRCH_H_
|
||||
#define _SCOUTFS_SRCH_H_
|
||||
|
||||
struct scoutfs_block;
|
||||
|
||||
struct scoutfs_srch_rb_root {
|
||||
struct rb_root root;
|
||||
struct rb_node *last;
|
||||
unsigned long nr;
|
||||
};
|
||||
|
||||
struct scoutfs_srch_rb_node {
|
||||
struct rb_node node;
|
||||
u64 ino;
|
||||
u64 id;
|
||||
};
|
||||
|
||||
#define scoutfs_srch_foreach_rb_node(snode, node, sroot) \
|
||||
for (node = rb_first(&(sroot)->root); \
|
||||
node && (snode = container_of(node, struct scoutfs_srch_rb_node, \
|
||||
node), 1); \
|
||||
node = rb_next(node))
|
||||
|
||||
int scoutfs_srch_add(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_srch_file *sfl,
|
||||
struct scoutfs_block **bl_ret,
|
||||
u64 hash, u64 ino, u64 id);
|
||||
|
||||
void scoutfs_srch_destroy_rb_root(struct scoutfs_srch_rb_root *sroot);
|
||||
int scoutfs_srch_search_xattrs(struct super_block *sb,
|
||||
struct scoutfs_srch_rb_root *sroot,
|
||||
u64 hash, u64 ino, u64 last_ino, bool *done);
|
||||
|
||||
int scoutfs_srch_rotate_log(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root,
|
||||
struct scoutfs_srch_file *sfl);
|
||||
int scoutfs_srch_get_compact(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root,
|
||||
u64 rid, struct scoutfs_srch_compact *sc);
|
||||
int scoutfs_srch_update_compact(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root, u64 rid,
|
||||
struct scoutfs_srch_compact *sc);
|
||||
int scoutfs_srch_commit_compact(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root, u64 rid,
|
||||
struct scoutfs_srch_compact *res,
|
||||
struct scoutfs_alloc_list_head *av,
|
||||
struct scoutfs_alloc_list_head *fr);
|
||||
int scoutfs_srch_cancel_compact(struct super_block *sb,
|
||||
struct scoutfs_alloc *alloc,
|
||||
struct scoutfs_block_writer *wri,
|
||||
struct scoutfs_btree_root *root, u64 rid,
|
||||
struct scoutfs_alloc_list_head *av,
|
||||
struct scoutfs_alloc_list_head *fr);
|
||||
|
||||
void scoutfs_srch_destroy(struct super_block *sb);
|
||||
int scoutfs_srch_setup(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,716 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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/slab.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/magic.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/statfs.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/percpu.h>
|
||||
|
||||
#include "super.h"
|
||||
#include "block.h"
|
||||
#include "export.h"
|
||||
#include "format.h"
|
||||
#include "inode.h"
|
||||
#include "dir.h"
|
||||
#include "msg.h"
|
||||
#include "counters.h"
|
||||
#include "triggers.h"
|
||||
#include "trans.h"
|
||||
#include "data.h"
|
||||
#include "lock.h"
|
||||
#include "net.h"
|
||||
#include "client.h"
|
||||
#include "server.h"
|
||||
#include "options.h"
|
||||
#include "sysfs.h"
|
||||
#include "quorum.h"
|
||||
#include "forest.h"
|
||||
#include "srch.h"
|
||||
#include "item.h"
|
||||
#include "alloc.h"
|
||||
#include "scoutfs_trace.h"
|
||||
|
||||
static struct dentry *scoutfs_debugfs_root;
|
||||
|
||||
static DEFINE_PER_CPU(u64, clock_sync_ids) = 0;
|
||||
|
||||
/*
|
||||
* Give the caller a unique clock sync id for a message they're about to
|
||||
* send. We make the ids reasonably globally unique by using randomly
|
||||
* initialized per-cpu 64bit counters.
|
||||
*/
|
||||
__le64 scoutfs_clock_sync_id(void)
|
||||
{
|
||||
u64 rnd = 0;
|
||||
u64 ret;
|
||||
u64 *id;
|
||||
|
||||
retry:
|
||||
preempt_disable();
|
||||
id = this_cpu_ptr(&clock_sync_ids);
|
||||
if (*id == 0) {
|
||||
if (rnd == 0) {
|
||||
preempt_enable();
|
||||
get_random_bytes(&rnd, sizeof(rnd));
|
||||
goto retry;
|
||||
}
|
||||
*id = rnd;
|
||||
}
|
||||
|
||||
ret = ++(*id);
|
||||
preempt_enable();
|
||||
|
||||
return cpu_to_le64(ret);
|
||||
}
|
||||
|
||||
struct statfs_free_blocks {
|
||||
u64 meta;
|
||||
u64 data;
|
||||
};
|
||||
|
||||
static int count_free_blocks(struct super_block *sb, void *arg, int owner,
|
||||
u64 id, bool meta, bool avail, u64 blocks)
|
||||
{
|
||||
struct statfs_free_blocks *sfb = arg;
|
||||
|
||||
if (meta)
|
||||
sfb->meta += blocks;
|
||||
else
|
||||
sfb->data += blocks;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build the free block counts by having alloc read all the persistent
|
||||
* blocks which contain allocators and calling us for each of them.
|
||||
* Only the super block reads aren't cached so repeatedly calling statfs
|
||||
* is like repeated O_DIRECT IO. We can add a cache and stale results
|
||||
* if that IO becomes a problem.
|
||||
*
|
||||
* We fake the number of free inodes value by assuming that we can fill
|
||||
* free blocks with a certain number of inodes. We then the number of
|
||||
* current inodes to that free count to determine the total possible
|
||||
* inodes.
|
||||
*
|
||||
* The fsid that we report is constructed from the xor of the first two
|
||||
* and second two little endian u32s that make up the uuid bytes.
|
||||
*/
|
||||
static int scoutfs_statfs(struct dentry *dentry, struct kstatfs *kst)
|
||||
{
|
||||
struct super_block *sb = dentry->d_inode->i_sb;
|
||||
struct scoutfs_super_block *super = NULL;
|
||||
struct statfs_free_blocks sfb = {0,};
|
||||
__le32 uuid[4];
|
||||
int ret;
|
||||
|
||||
scoutfs_inc_counter(sb, statfs);
|
||||
|
||||
super = kzalloc(sizeof(struct scoutfs_super_block), GFP_NOFS);
|
||||
if (!super) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = scoutfs_read_super(sb, super);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = scoutfs_alloc_foreach(sb, count_free_blocks, &sfb);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
kst->f_bfree = (sfb.meta << SCOUTFS_BLOCK_SM_LG_SHIFT) + sfb.data;
|
||||
kst->f_type = SCOUTFS_SUPER_MAGIC;
|
||||
kst->f_bsize = SCOUTFS_BLOCK_SM_SIZE;
|
||||
kst->f_blocks = (le64_to_cpu(super->total_meta_blocks) <<
|
||||
SCOUTFS_BLOCK_SM_LG_SHIFT) +
|
||||
le64_to_cpu(super->total_data_blocks);
|
||||
kst->f_bavail = kst->f_bfree;
|
||||
|
||||
/* arbitrarily assume ~1K / empty file */
|
||||
kst->f_ffree = sfb.meta * (SCOUTFS_BLOCK_LG_SIZE / 1024);
|
||||
kst->f_files = kst->f_ffree + le64_to_cpu(super->next_ino);
|
||||
|
||||
BUILD_BUG_ON(sizeof(uuid) != sizeof(super->uuid));
|
||||
memcpy(uuid, super->uuid, sizeof(uuid));
|
||||
kst->f_fsid.val[0] = le32_to_cpu(uuid[0]) ^ le32_to_cpu(uuid[1]);
|
||||
kst->f_fsid.val[1] = le32_to_cpu(uuid[2]) ^ le32_to_cpu(uuid[3]);
|
||||
kst->f_namelen = SCOUTFS_NAME_LEN;
|
||||
kst->f_frsize = SCOUTFS_BLOCK_SM_SIZE;
|
||||
|
||||
/* the vfs fills f_flags */
|
||||
ret = 0;
|
||||
out:
|
||||
kfree(super);
|
||||
|
||||
/*
|
||||
* We don't take cluster locks in statfs which makes it a very
|
||||
* convenient place to trigger lock reclaim for debugging. We
|
||||
* try to free as many locks as possible.
|
||||
*/
|
||||
if (scoutfs_trigger(sb, STATFS_LOCK_PURGE))
|
||||
scoutfs_free_unused_locks(sb, -1UL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scoutfs_show_options(struct seq_file *seq, struct dentry *root)
|
||||
{
|
||||
struct super_block *sb = root->d_sb;
|
||||
struct mount_options *opts = &SCOUTFS_SB(sb)->opts;
|
||||
|
||||
seq_printf(seq, ",server_addr="SIN_FMT, SIN_ARG(&opts->server_addr));
|
||||
seq_printf(seq, ",metadev_path=%s", opts->metadev_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 mount_options *opts = &SCOUTFS_SB(sb)->opts;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%s", opts->metadev_path);
|
||||
}
|
||||
SCOUTFS_ATTR_RO(metadev_path);
|
||||
|
||||
static ssize_t server_addr_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
struct super_block *sb = SCOUTFS_SYSFS_ATTRS_SB(kobj);
|
||||
struct mount_options *opts = &SCOUTFS_SB(sb)->opts;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, SIN_FMT"\n",
|
||||
SIN_ARG(&opts->server_addr));
|
||||
}
|
||||
SCOUTFS_ATTR_RO(server_addr);
|
||||
|
||||
static struct attribute *mount_options_attrs[] = {
|
||||
SCOUTFS_ATTR_PTR(metadev_path),
|
||||
SCOUTFS_ATTR_PTR(server_addr),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int scoutfs_sync_fs(struct super_block *sb, int wait)
|
||||
{
|
||||
trace_scoutfs_sync_fs(sb, wait);
|
||||
scoutfs_inc_counter(sb, trans_commit_sync_fs);
|
||||
|
||||
return scoutfs_trans_sync(sb, wait);
|
||||
}
|
||||
|
||||
/*
|
||||
* Data dev is closed by generic code, but we have to explicitly close the meta
|
||||
* dev.
|
||||
*/
|
||||
static void scoutfs_metadev_close(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
|
||||
if (sbi->meta_bdev) {
|
||||
blkdev_put(sbi->meta_bdev, SCOUTFS_META_BDEV_MODE);
|
||||
sbi->meta_bdev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This destroys all the state that's built up in the sb info during
|
||||
* mount. It's called by us on errors during mount if we haven't set
|
||||
* s_root, by mount after returning errors if we have set s_root, and by
|
||||
* unmount after having synced the super.
|
||||
*/
|
||||
static void scoutfs_put_super(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
|
||||
trace_scoutfs_put_super(sb);
|
||||
|
||||
sbi->shutdown = true;
|
||||
|
||||
scoutfs_data_destroy(sb);
|
||||
scoutfs_srch_destroy(sb);
|
||||
|
||||
scoutfs_unlock(sb, sbi->rid_lock, SCOUTFS_LOCK_WRITE);
|
||||
sbi->rid_lock = NULL;
|
||||
|
||||
scoutfs_shutdown_trans(sb);
|
||||
scoutfs_client_destroy(sb);
|
||||
scoutfs_inode_destroy(sb);
|
||||
scoutfs_item_destroy(sb);
|
||||
scoutfs_forest_destroy(sb);
|
||||
|
||||
/* the server locks the listen address and compacts */
|
||||
scoutfs_lock_shutdown(sb);
|
||||
scoutfs_server_destroy(sb);
|
||||
scoutfs_net_destroy(sb);
|
||||
scoutfs_lock_destroy(sb);
|
||||
|
||||
/* server clears quorum leader flag during shutdown */
|
||||
scoutfs_quorum_destroy(sb);
|
||||
|
||||
scoutfs_block_destroy(sb);
|
||||
scoutfs_destroy_triggers(sb);
|
||||
scoutfs_options_destroy(sb);
|
||||
scoutfs_sysfs_destroy_attrs(sb, &sbi->mopts_ssa);
|
||||
debugfs_remove(sbi->debug_root);
|
||||
scoutfs_destroy_counters(sb);
|
||||
scoutfs_destroy_sysfs(sb);
|
||||
scoutfs_metadev_close(sb);
|
||||
|
||||
kfree(sbi->opts.metadev_path);
|
||||
kfree(sbi);
|
||||
|
||||
sb->s_fs_info = NULL;
|
||||
}
|
||||
|
||||
static const struct super_operations scoutfs_super_ops = {
|
||||
.alloc_inode = scoutfs_alloc_inode,
|
||||
.drop_inode = scoutfs_drop_inode,
|
||||
.evict_inode = scoutfs_evict_inode,
|
||||
.destroy_inode = scoutfs_destroy_inode,
|
||||
.sync_fs = scoutfs_sync_fs,
|
||||
.statfs = scoutfs_statfs,
|
||||
.show_options = scoutfs_show_options,
|
||||
.put_super = scoutfs_put_super,
|
||||
};
|
||||
|
||||
/*
|
||||
* Write the caller's super. The caller has always read a valid super
|
||||
* before modifying and writing it. The caller's super is modified
|
||||
* to reflect the write.
|
||||
*/
|
||||
int scoutfs_write_super(struct super_block *sb,
|
||||
struct scoutfs_super_block *super)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
|
||||
le64_add_cpu(&super->hdr.seq, 1);
|
||||
|
||||
return scoutfs_block_write_sm(sb, sbi->meta_bdev, SCOUTFS_SUPER_BLKNO,
|
||||
&super->hdr,
|
||||
sizeof(struct scoutfs_super_block));
|
||||
}
|
||||
|
||||
/*
|
||||
* Read super, specifying bdev.
|
||||
*/
|
||||
static int scoutfs_read_super_from_bdev(struct super_block *sb,
|
||||
struct block_device *bdev,
|
||||
struct scoutfs_super_block *super_res)
|
||||
{
|
||||
struct scoutfs_super_block *super;
|
||||
__le32 calc;
|
||||
u64 blkno;
|
||||
int ret;
|
||||
|
||||
super = kmalloc(sizeof(struct scoutfs_super_block), GFP_NOFS);
|
||||
if (!super)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = scoutfs_block_read_sm(sb, bdev, SCOUTFS_SUPER_BLKNO, &super->hdr,
|
||||
sizeof(struct scoutfs_super_block), &calc);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (super->hdr.magic != cpu_to_le32(SCOUTFS_BLOCK_MAGIC_SUPER)) {
|
||||
scoutfs_err(sb, "super block has invalid magic value 0x%08x",
|
||||
le32_to_cpu(super->hdr.magic));
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (calc != super->hdr.crc) {
|
||||
scoutfs_err(sb, "super block has invalid crc 0x%08x, calculated 0x%08x",
|
||||
le32_to_cpu(super->hdr.crc), le32_to_cpu(calc));
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (le64_to_cpu(super->hdr.blkno) != SCOUTFS_SUPER_BLKNO) {
|
||||
scoutfs_err(sb, "super block has invalid block number %llu, data read from %llu",
|
||||
le64_to_cpu(super->hdr.blkno), SCOUTFS_SUPER_BLKNO);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
if (super->format_hash != cpu_to_le64(SCOUTFS_FORMAT_HASH)) {
|
||||
scoutfs_err(sb, "super block has invalid format hash 0x%llx, expected 0x%llx",
|
||||
le64_to_cpu(super->format_hash),
|
||||
SCOUTFS_FORMAT_HASH);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* XXX do we want more rigorous invalid super checking? */
|
||||
|
||||
if (super->quorum_count == 0 ||
|
||||
super->quorum_count > SCOUTFS_QUORUM_MAX_COUNT) {
|
||||
scoutfs_err(sb, "super block has invalid quorum count %u, must be > 0 and <= %u",
|
||||
super->quorum_count, SCOUTFS_QUORUM_MAX_COUNT);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
blkno = (SCOUTFS_QUORUM_BLKNO + SCOUTFS_QUORUM_BLOCKS) >>
|
||||
SCOUTFS_BLOCK_SM_LG_SHIFT;
|
||||
if (le64_to_cpu(super->first_meta_blkno) < blkno) {
|
||||
scoutfs_err(sb, "super block first meta blkno %llu is within quorum blocks",
|
||||
le64_to_cpu(super->first_meta_blkno));
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (le64_to_cpu(super->first_meta_blkno) >
|
||||
le64_to_cpu(super->last_meta_blkno)) {
|
||||
scoutfs_err(sb, "super block first meta blkno %llu is greater than last meta blkno %llu",
|
||||
le64_to_cpu(super->first_meta_blkno),
|
||||
le64_to_cpu(super->last_meta_blkno));
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (le64_to_cpu(super->first_data_blkno) >
|
||||
le64_to_cpu(super->last_data_blkno)) {
|
||||
scoutfs_err(sb, "super block first data blkno %llu is greater than last data blkno %llu",
|
||||
le64_to_cpu(super->first_data_blkno),
|
||||
le64_to_cpu(super->last_data_blkno));
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
blkno = (i_size_read(sb->s_bdev->bd_inode) >>
|
||||
SCOUTFS_BLOCK_SM_SHIFT) - 1;
|
||||
if (le64_to_cpu(super->last_data_blkno) > blkno) {
|
||||
scoutfs_err(sb, "super block last data blkno %llu is outsite device size last blkno %llu",
|
||||
le64_to_cpu(super->last_data_blkno), blkno);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
if (ret == 0)
|
||||
*super_res = *super;
|
||||
kfree(super);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the super block from meta dev.
|
||||
*/
|
||||
int scoutfs_read_super(struct super_block *sb,
|
||||
struct scoutfs_super_block *super_res)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
|
||||
return scoutfs_read_super_from_bdev(sb, sbi->meta_bdev, super_res);
|
||||
}
|
||||
|
||||
/*
|
||||
* This needs to be setup after reading the super because it uses the
|
||||
* fsid found in the super block.
|
||||
*/
|
||||
static int scoutfs_debugfs_setup(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
char name[32];
|
||||
|
||||
snprintf(name, ARRAY_SIZE(name), SCSBF, SCSB_ARGS(sb));
|
||||
|
||||
sbi->debug_root = debugfs_create_dir(name, scoutfs_debugfs_root);
|
||||
if (!sbi->debug_root)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate a random id for the mount very early, it's used in tracing
|
||||
* and message output. The system assumes that a rid of 0 can't exist. We're
|
||||
* also paranoid and avoid rids that are likely the result of bad rng.
|
||||
*/
|
||||
static int assign_random_id(struct scoutfs_sb_info *sbi)
|
||||
{
|
||||
unsigned int attempts = 0;
|
||||
|
||||
do {
|
||||
if (++attempts == 100)
|
||||
return -EIO;
|
||||
get_random_bytes(&sbi->rid, sizeof(sbi->rid));
|
||||
} while (sbi->rid == 0 || sbi->rid == ~0ULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure superblock copies in metadata and data block devices are valid, and
|
||||
* fill in in-memory superblock if so.
|
||||
*/
|
||||
static int scoutfs_read_supers(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_super_block *meta_super = NULL;
|
||||
struct scoutfs_super_block *data_super = NULL;
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
int ret = 0;
|
||||
|
||||
meta_super = kmalloc(sizeof(struct scoutfs_super_block), GFP_NOFS);
|
||||
data_super = kmalloc(sizeof(struct scoutfs_super_block), GFP_NOFS);
|
||||
if (!meta_super || !data_super) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = scoutfs_read_super_from_bdev(sb, sbi->meta_bdev, meta_super);
|
||||
if (ret < 0) {
|
||||
scoutfs_err(sb, "could not get meta_super: error %d", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = scoutfs_read_super_from_bdev(sb, sb->s_bdev, data_super);
|
||||
if (ret < 0) {
|
||||
scoutfs_err(sb, "could not get data_super: error %d", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!SCOUTFS_IS_META_BDEV(meta_super)) {
|
||||
scoutfs_err(sb, "meta_super META flag not set");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (SCOUTFS_IS_META_BDEV(data_super)) {
|
||||
scoutfs_err(sb, "data_super META flag set");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (memcmp(meta_super->uuid, data_super->uuid, SCOUTFS_UUID_BYTES)) {
|
||||
scoutfs_err(sb, "superblock UUID mismatch");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
sbi->super = *meta_super;
|
||||
out:
|
||||
kfree(meta_super);
|
||||
kfree(data_super);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scoutfs_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi;
|
||||
struct mount_options opts;
|
||||
struct block_device *meta_bdev;
|
||||
struct inode *inode;
|
||||
int ret;
|
||||
|
||||
trace_scoutfs_fill_super(sb);
|
||||
|
||||
sb->s_magic = SCOUTFS_SUPER_MAGIC;
|
||||
sb->s_maxbytes = MAX_LFS_FILESIZE;
|
||||
sb->s_op = &scoutfs_super_ops;
|
||||
sb->s_export_op = &scoutfs_export_ops;
|
||||
|
||||
/* btree blocks use long lived bh->b_data refs */
|
||||
mapping_set_gfp_mask(sb->s_bdev->bd_inode->i_mapping, GFP_NOFS);
|
||||
|
||||
sbi = kzalloc(sizeof(struct scoutfs_sb_info), GFP_KERNEL);
|
||||
sb->s_fs_info = sbi;
|
||||
sbi->sb = sb;
|
||||
if (!sbi)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = assign_random_id(sbi);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
spin_lock_init(&sbi->next_ino_lock);
|
||||
init_waitqueue_head(&sbi->trans_hold_wq);
|
||||
spin_lock_init(&sbi->data_wait_root.lock);
|
||||
sbi->data_wait_root.root = RB_ROOT;
|
||||
spin_lock_init(&sbi->trans_write_lock);
|
||||
INIT_DELAYED_WORK(&sbi->trans_write_work, scoutfs_trans_write_func);
|
||||
init_waitqueue_head(&sbi->trans_write_wq);
|
||||
scoutfs_sysfs_init_attrs(sb, &sbi->mopts_ssa);
|
||||
|
||||
ret = scoutfs_parse_options(sb, data, &opts);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
sbi->opts = opts;
|
||||
|
||||
ret = sb_set_blocksize(sb, SCOUTFS_BLOCK_SM_SIZE);
|
||||
if (ret != SCOUTFS_BLOCK_SM_SIZE) {
|
||||
scoutfs_err(sb, "failed to set blocksize, returned %d", ret);
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
meta_bdev =
|
||||
blkdev_get_by_path(sbi->opts.metadev_path,
|
||||
SCOUTFS_META_BDEV_MODE, sb);
|
||||
if (IS_ERR(meta_bdev)) {
|
||||
scoutfs_err(sb, "could not open metadev: error %ld",
|
||||
PTR_ERR(meta_bdev));
|
||||
ret = PTR_ERR(meta_bdev);
|
||||
goto out;
|
||||
}
|
||||
sbi->meta_bdev = meta_bdev;
|
||||
ret = set_blocksize(sbi->meta_bdev, SCOUTFS_BLOCK_SM_SIZE);
|
||||
if (ret != 0) {
|
||||
scoutfs_err(sb, "failed to set metadev blocksize, returned %d",
|
||||
ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = scoutfs_read_supers(sb) ?:
|
||||
scoutfs_debugfs_setup(sb) ?:
|
||||
scoutfs_setup_sysfs(sb) ?:
|
||||
scoutfs_setup_counters(sb) ?:
|
||||
scoutfs_options_setup(sb) ?:
|
||||
scoutfs_sysfs_create_attrs(sb, &sbi->mopts_ssa,
|
||||
mount_options_attrs, "mount_options") ?:
|
||||
scoutfs_setup_triggers(sb) ?:
|
||||
scoutfs_block_setup(sb) ?:
|
||||
scoutfs_forest_setup(sb) ?:
|
||||
scoutfs_item_setup(sb) ?:
|
||||
scoutfs_inode_setup(sb) ?:
|
||||
scoutfs_data_setup(sb) ?:
|
||||
scoutfs_setup_trans(sb) ?:
|
||||
scoutfs_lock_setup(sb) ?:
|
||||
scoutfs_net_setup(sb) ?:
|
||||
scoutfs_quorum_setup(sb) ?:
|
||||
scoutfs_server_setup(sb) ?:
|
||||
scoutfs_client_setup(sb) ?:
|
||||
scoutfs_lock_rid(sb, SCOUTFS_LOCK_WRITE, 0, sbi->rid,
|
||||
&sbi->rid_lock) ?:
|
||||
scoutfs_trans_get_log_trees(sb) ?:
|
||||
scoutfs_srch_setup(sb);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
inode = scoutfs_iget(sb, SCOUTFS_ROOT_INO);
|
||||
if (IS_ERR(inode)) {
|
||||
ret = PTR_ERR(inode);
|
||||
goto out;
|
||||
}
|
||||
|
||||
sb->s_root = d_make_root(inode);
|
||||
if (!sb->s_root) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = scoutfs_client_advance_seq(sb, &sbi->trans_seq);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
scoutfs_trans_restart_sync_deadline(sb);
|
||||
// scoutfs_scan_orphans(sb);
|
||||
ret = 0;
|
||||
out:
|
||||
/* on error, generic_shutdown_super calls put_super if s_root */
|
||||
if (ret && !sb->s_root)
|
||||
scoutfs_put_super(sb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct dentry *scoutfs_mount(struct file_system_type *fs_type, int flags,
|
||||
const char *dev_name, void *data)
|
||||
{
|
||||
return mount_bdev(fs_type, flags, dev_name, data, scoutfs_fill_super);
|
||||
}
|
||||
|
||||
/*
|
||||
* kill_block_super eventually calls ->put_super if s_root is set
|
||||
*/
|
||||
static void scoutfs_kill_sb(struct super_block *sb)
|
||||
{
|
||||
trace_scoutfs_kill_sb(sb);
|
||||
|
||||
kill_block_super(sb);
|
||||
}
|
||||
|
||||
static struct file_system_type scoutfs_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "scoutfs",
|
||||
.mount = scoutfs_mount,
|
||||
.kill_sb = scoutfs_kill_sb,
|
||||
.fs_flags = FS_REQUIRES_DEV,
|
||||
};
|
||||
MODULE_ALIAS_FS("scoutfs");
|
||||
|
||||
/* safe to call at any failure point in _init */
|
||||
static void teardown_module(void)
|
||||
{
|
||||
debugfs_remove(scoutfs_debugfs_root);
|
||||
scoutfs_dir_exit();
|
||||
scoutfs_inode_exit();
|
||||
scoutfs_sysfs_exit();
|
||||
}
|
||||
|
||||
static int __init scoutfs_module_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* gcc only recently learned to let __attribute__(section) add
|
||||
* SHT_NOTE notes. But the assembler always could.
|
||||
*/
|
||||
__asm__ __volatile__ (
|
||||
".section .note.git_describe,\"a\"\n"
|
||||
".string \""SCOUTFS_GIT_DESCRIBE"\\n\"\n"
|
||||
".previous\n");
|
||||
|
||||
scoutfs_init_counters();
|
||||
|
||||
ret = scoutfs_sysfs_init();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoutfs_debugfs_root = debugfs_create_dir("scoutfs", NULL);
|
||||
if (!scoutfs_debugfs_root) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
ret = scoutfs_inode_init() ?:
|
||||
scoutfs_dir_init() ?:
|
||||
register_filesystem(&scoutfs_fs_type);
|
||||
out:
|
||||
if (ret)
|
||||
teardown_module();
|
||||
return ret;
|
||||
}
|
||||
module_init(scoutfs_module_init)
|
||||
|
||||
static void __exit scoutfs_module_exit(void)
|
||||
{
|
||||
unregister_filesystem(&scoutfs_fs_type);
|
||||
teardown_module();
|
||||
}
|
||||
module_exit(scoutfs_module_exit)
|
||||
|
||||
MODULE_AUTHOR("Zach Brown <zab@versity.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_INFO(git_describe, SCOUTFS_GIT_DESCRIBE);
|
||||
@@ -0,0 +1,145 @@
|
||||
#ifndef _SCOUTFS_SUPER_H_
|
||||
#define _SCOUTFS_SUPER_H_
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/rbtree.h>
|
||||
|
||||
#include "format.h"
|
||||
#include "options.h"
|
||||
#include "data.h"
|
||||
#include "sysfs.h"
|
||||
|
||||
struct scoutfs_counters;
|
||||
struct scoutfs_triggers;
|
||||
struct manifest;
|
||||
struct data_info;
|
||||
struct trans_info;
|
||||
struct lock_info;
|
||||
struct lock_server_info;
|
||||
struct client_info;
|
||||
struct server_info;
|
||||
struct inode_sb_info;
|
||||
struct btree_info;
|
||||
struct sysfs_info;
|
||||
struct options_sb_info;
|
||||
struct net_info;
|
||||
struct block_info;
|
||||
struct forest_info;
|
||||
struct srch_info;
|
||||
|
||||
struct scoutfs_sb_info {
|
||||
struct super_block *sb;
|
||||
|
||||
/* assigned once at the start of each mount, read-only */
|
||||
u64 rid;
|
||||
struct scoutfs_lock *rid_lock;
|
||||
|
||||
struct scoutfs_super_block super;
|
||||
|
||||
struct block_device *meta_bdev;
|
||||
|
||||
spinlock_t next_ino_lock;
|
||||
|
||||
struct data_info *data_info;
|
||||
struct inode_sb_info *inode_sb_info;
|
||||
struct btree_info *btree_info;
|
||||
struct net_info *net_info;
|
||||
struct quorum_info *quorum_info;
|
||||
struct block_info *block_info;
|
||||
struct forest_info *forest_info;
|
||||
struct srch_info *srch_info;
|
||||
struct item_cache_info *item_cache_info;
|
||||
|
||||
wait_queue_head_t trans_hold_wq;
|
||||
struct task_struct *trans_task;
|
||||
|
||||
/* tracks tasks waiting for data extents */
|
||||
struct scoutfs_data_wait_root data_wait_root;
|
||||
|
||||
spinlock_t trans_write_lock;
|
||||
u64 trans_write_count;
|
||||
u64 trans_seq;
|
||||
int trans_write_ret;
|
||||
struct delayed_work trans_write_work;
|
||||
wait_queue_head_t trans_write_wq;
|
||||
struct workqueue_struct *trans_write_workq;
|
||||
bool trans_deadline_expired;
|
||||
|
||||
struct trans_info *trans_info;
|
||||
struct lock_info *lock_info;
|
||||
struct lock_server_info *lock_server_info;
|
||||
struct client_info *client_info;
|
||||
struct server_info *server_info;
|
||||
struct sysfs_info *sfsinfo;
|
||||
|
||||
struct scoutfs_counters *counters;
|
||||
struct scoutfs_triggers *triggers;
|
||||
|
||||
struct mount_options opts;
|
||||
struct options_sb_info *options;
|
||||
struct scoutfs_sysfs_attrs mopts_ssa;
|
||||
|
||||
struct dentry *debug_root;
|
||||
|
||||
bool shutdown;
|
||||
|
||||
unsigned long corruption_messages_once[SC_NR_LONGS];
|
||||
};
|
||||
|
||||
static inline struct scoutfs_sb_info *SCOUTFS_SB(struct super_block *sb)
|
||||
{
|
||||
return sb->s_fs_info;
|
||||
}
|
||||
|
||||
static inline bool SCOUTFS_HAS_SBI(struct super_block *sb)
|
||||
{
|
||||
return (sb != NULL) && (SCOUTFS_SB(sb) != NULL);
|
||||
}
|
||||
|
||||
static inline bool SCOUTFS_IS_META_BDEV(struct scoutfs_super_block *super_block)
|
||||
{
|
||||
return !!(le64_to_cpu(super_block->flags) & SCOUTFS_FLAG_IS_META_BDEV);
|
||||
}
|
||||
|
||||
#define SCOUTFS_META_BDEV_MODE (FMODE_READ | FMODE_WRITE | FMODE_EXCL)
|
||||
|
||||
/*
|
||||
* A small string embedded in messages that's used to identify a
|
||||
* specific mount. It's the three most significant bytes of the fsid
|
||||
* and the rid. That gives us a strong chance of avoiding collisions
|
||||
* with typical numbers of mounts. We give it a bit of structure to
|
||||
* make it searchable and to be able to identify format changes, should
|
||||
* we need to. The fsid will be 0 until the super has been read and the
|
||||
* fsid discovered.
|
||||
*/
|
||||
#define SCSBF "f.%.06x.r.%.06x"
|
||||
#define SCSB_SHIFT (64 - (8 * 3))
|
||||
#define SCSB_LEFR_ARGS(fsid, rid) \
|
||||
(int)(le64_to_cpu(fsid) >> SCSB_SHIFT), \
|
||||
(int)(le64_to_cpu(rid) >> SCSB_SHIFT)
|
||||
#define SCSB_ARGS(sb) \
|
||||
(int)(le64_to_cpu(SCOUTFS_SB(sb)->super.hdr.fsid) >> SCSB_SHIFT), \
|
||||
(int)(SCOUTFS_SB(sb)->rid >> SCSB_SHIFT)
|
||||
#define SCSB_TRACE_FIELDS \
|
||||
__field(__u64, fsid) \
|
||||
__field(__u64, rid)
|
||||
#define SCSB_TRACE_ASSIGN(sb) \
|
||||
__entry->fsid = SCOUTFS_HAS_SBI(sb) ? \
|
||||
le64_to_cpu(SCOUTFS_SB(sb)->super.hdr.fsid) : 0;\
|
||||
__entry->rid = SCOUTFS_HAS_SBI(sb) ? \
|
||||
SCOUTFS_SB(sb)->rid : 0;
|
||||
#define SCSB_TRACE_ARGS \
|
||||
(int)(__entry->fsid >> SCSB_SHIFT), \
|
||||
(int)(__entry->rid >> SCSB_SHIFT)
|
||||
|
||||
int scoutfs_read_super(struct super_block *sb,
|
||||
struct scoutfs_super_block *super_res);
|
||||
int scoutfs_write_super(struct super_block *sb,
|
||||
struct scoutfs_super_block *super);
|
||||
|
||||
/* to keep this out of the ioctl.h public interface definition */
|
||||
long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
|
||||
|
||||
__le64 scoutfs_clock_sync_id(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* 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/slab.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
#include "super.h"
|
||||
#include "sysfs.h"
|
||||
|
||||
static struct kset *scoutfs_kset;
|
||||
|
||||
struct sysfs_info {
|
||||
struct super_block *sb;
|
||||
struct kobject sb_id_kobj;
|
||||
struct completion sb_id_comp;
|
||||
};
|
||||
|
||||
#define KOBJ_TO_SB(kobj, which) \
|
||||
container_of(kobj, struct sysfs_info, which)->sb
|
||||
|
||||
struct attr_funcs {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf);
|
||||
};
|
||||
|
||||
#define ATTR_FUNCS_RO(_name) \
|
||||
static struct attr_funcs _name##_attr_funcs = __ATTR_RO(_name)
|
||||
|
||||
static ssize_t fsid_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct super_block *sb = KOBJ_TO_SB(kobj, sb_id_kobj);
|
||||
struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%016llx\n",
|
||||
le64_to_cpu(super->hdr.fsid));
|
||||
}
|
||||
ATTR_FUNCS_RO(fsid);
|
||||
|
||||
static ssize_t rid_show(struct kobject *kobj, struct attribute *attr, char *buf)
|
||||
{
|
||||
struct super_block *sb = KOBJ_TO_SB(kobj, sb_id_kobj);
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%016llx\n", sbi->rid);
|
||||
}
|
||||
ATTR_FUNCS_RO(rid);
|
||||
|
||||
/*
|
||||
* ops are defined per type, not per attribute. To have attributes with
|
||||
* different types that want different funcs we wrap them with a struct
|
||||
* that has per-type funcs.
|
||||
*/
|
||||
static ssize_t attr_funcs_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct attr_funcs *af = container_of(attr, struct attr_funcs, attr);
|
||||
|
||||
return af->show(kobj, attr, buf);
|
||||
}
|
||||
|
||||
#define KTYPE(_name) \
|
||||
static void _name##_release(struct kobject *kobj) \
|
||||
{ \
|
||||
struct sysfs_info *sfsinfo; \
|
||||
\
|
||||
sfsinfo = container_of(kobj, struct sysfs_info, _name##_kobj);\
|
||||
\
|
||||
complete(&sfsinfo->_name##_comp); \
|
||||
} \
|
||||
static const struct sysfs_ops _name##_sysfs_ops = { \
|
||||
.show = attr_funcs_show, \
|
||||
}; \
|
||||
\
|
||||
static struct kobj_type _name##_ktype = { \
|
||||
.default_attrs = _name##_attrs, \
|
||||
.sysfs_ops = &_name##_sysfs_ops, \
|
||||
.release = _name##_release, \
|
||||
};
|
||||
|
||||
|
||||
static struct attribute *sb_id_attrs[] = {
|
||||
&fsid_attr_funcs.attr,
|
||||
&rid_attr_funcs.attr,
|
||||
NULL,
|
||||
};
|
||||
KTYPE(sb_id);
|
||||
|
||||
struct kobject *scoutfs_sysfs_sb_dir(struct super_block *sb)
|
||||
{
|
||||
struct sysfs_info *sfsinfo = SCOUTFS_SB(sb)->sfsinfo;
|
||||
|
||||
return &sfsinfo->sb_id_kobj;
|
||||
}
|
||||
|
||||
static void kobj_del_put_wait(struct kobject *kobj, struct completion *comp)
|
||||
{
|
||||
kobject_del(kobj);
|
||||
kobject_put(kobj);
|
||||
wait_for_completion(comp);
|
||||
}
|
||||
|
||||
#define shutdown_kobj(sfinfo, _name) \
|
||||
kobj_del_put_wait(&sfsinfo->_name##_kobj, &sfsinfo->_name##_comp)
|
||||
|
||||
static void scoutfs_sysfs_release(struct kobject *kobj)
|
||||
{
|
||||
DECLARE_SCOUTFS_SYSFS_ATTRS(ssa, kobj);
|
||||
|
||||
complete(&ssa->comp);
|
||||
}
|
||||
|
||||
void scoutfs_sysfs_init_attrs(struct super_block *sb,
|
||||
struct scoutfs_sysfs_attrs *ssa)
|
||||
{
|
||||
ssa->name = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this returns success then the file will be visible and show can
|
||||
* be called until unmount.
|
||||
*/
|
||||
int scoutfs_sysfs_create_attrs(struct super_block *sb,
|
||||
struct scoutfs_sysfs_attrs *ssa,
|
||||
struct attribute **attrs, char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
size_t name_len;
|
||||
size_t size;
|
||||
int ret;
|
||||
|
||||
/* ssa should have seen init or destroy */
|
||||
if (WARN_ON_ONCE(ssa->name != NULL))
|
||||
return -EINVAL;
|
||||
|
||||
ssa->sb = sb;
|
||||
init_completion(&ssa->comp);
|
||||
ssa->ktype.default_attrs = attrs;
|
||||
ssa->ktype.sysfs_ops = &kobj_sysfs_ops;
|
||||
ssa->ktype.release = scoutfs_sysfs_release;
|
||||
|
||||
va_start(args, fmt);
|
||||
name_len = vsnprintf(NULL, 0, fmt, args);
|
||||
va_end(args);
|
||||
if (WARN_ON_ONCE(name_len < 1 || name_len > NAME_MAX)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
size = name_len + 1; /* with null */
|
||||
|
||||
ssa->name = kmalloc(size, GFP_KERNEL);
|
||||
if (!ssa->name) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
va_start(args, fmt);
|
||||
ret = vsnprintf(ssa->name, size, fmt, args);
|
||||
va_end(args);
|
||||
if (ret != name_len) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = kobject_init_and_add(&ssa->kobj, &ssa->ktype,
|
||||
scoutfs_sysfs_sb_dir(sb), "%s", ssa->name);
|
||||
out:
|
||||
if (ret) {
|
||||
kfree(ssa->name);
|
||||
ssa->name = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void scoutfs_sysfs_destroy_attrs(struct super_block *sb,
|
||||
struct scoutfs_sysfs_attrs *ssa)
|
||||
{
|
||||
if (ssa->name) {
|
||||
kobject_del(&ssa->kobj);
|
||||
kobject_put(&ssa->kobj);
|
||||
wait_for_completion(&ssa->comp);
|
||||
kfree(ssa->name);
|
||||
ssa->name = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Only the return from kobj_init_and_add() tells us if the kobj needs
|
||||
* to be cleaned up or not. This must manually clean up the kobjs and
|
||||
* only leave full cleanup to _destroy_.
|
||||
*/
|
||||
int scoutfs_setup_sysfs(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct sysfs_info *sfsinfo;
|
||||
int ret;
|
||||
|
||||
sfsinfo = kzalloc(sizeof(struct sysfs_info), GFP_KERNEL);
|
||||
if (!sfsinfo)
|
||||
return -ENOMEM;
|
||||
|
||||
sfsinfo->sb = sb;
|
||||
sbi->sfsinfo = sfsinfo;
|
||||
|
||||
init_completion(&sfsinfo->sb_id_comp);
|
||||
ret = kobject_init_and_add(&sfsinfo->sb_id_kobj, &sb_id_ktype,
|
||||
&scoutfs_kset->kobj, SCSBF, SCSB_ARGS(sb));
|
||||
if (ret)
|
||||
kfree(sfsinfo);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void scoutfs_destroy_sysfs(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct sysfs_info *sfsinfo = sbi->sfsinfo;
|
||||
|
||||
if (sfsinfo) {
|
||||
shutdown_kobj(sfsinfo, sb_id);
|
||||
|
||||
kfree(sfsinfo);
|
||||
sbi->sfsinfo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int __init scoutfs_sysfs_init(void)
|
||||
{
|
||||
scoutfs_kset = kset_create_and_add("scoutfs", NULL, fs_kobj);
|
||||
if (!scoutfs_kset)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __exit scoutfs_sysfs_exit(void)
|
||||
{
|
||||
if (scoutfs_kset)
|
||||
kset_unregister(scoutfs_kset);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef _SCOUTFS_SYSFS_H_
|
||||
#define _SCOUTFS_SYSFS_H_
|
||||
|
||||
#include <linux/kobject.h>
|
||||
|
||||
/*
|
||||
* We have some light wrappers around sysfs attributes to make it safe
|
||||
* to tear down the attributes before freeing the data they describe.
|
||||
*/
|
||||
|
||||
#define SCOUTFS_ATTR_RO(_name) \
|
||||
static struct kobj_attribute scoutfs_attr_##_name = __ATTR_RO(_name)
|
||||
|
||||
#define SCOUTFS_ATTR_PTR(_name) \
|
||||
&scoutfs_attr_##_name.attr
|
||||
|
||||
struct scoutfs_sysfs_attrs {
|
||||
struct super_block *sb;
|
||||
char *name;
|
||||
struct completion comp;
|
||||
|
||||
struct kobject kobj;
|
||||
struct kobj_type ktype;
|
||||
};
|
||||
|
||||
#define SCOUTFS_SYSFS_ATTRS(kobj) \
|
||||
container_of(kobj, struct scoutfs_sysfs_attrs, kobj)
|
||||
|
||||
#define SCOUTFS_SYSFS_ATTRS_SB(kobj) \
|
||||
(SCOUTFS_SYSFS_ATTRS(kobj)->sb)
|
||||
|
||||
#define DECLARE_SCOUTFS_SYSFS_ATTRS(name, kobj) \
|
||||
struct scoutfs_sysfs_attrs *ssa = SCOUTFS_SYSFS_ATTRS(kobj)
|
||||
|
||||
void scoutfs_sysfs_init_attrs(struct super_block *sb,
|
||||
struct scoutfs_sysfs_attrs *ssa);
|
||||
int scoutfs_sysfs_create_attrs(struct super_block *sb,
|
||||
struct scoutfs_sysfs_attrs *ssa,
|
||||
struct attribute **attrs, char *fmt, ...);
|
||||
void scoutfs_sysfs_destroy_attrs(struct super_block *sb,
|
||||
struct scoutfs_sysfs_attrs *ssa);
|
||||
|
||||
struct kobject *scoutfs_sysfs_sb_dir(struct super_block *sb);
|
||||
|
||||
int scoutfs_setup_sysfs(struct super_block *sb);
|
||||
void scoutfs_destroy_sysfs(struct super_block *sb);
|
||||
|
||||
int __init scoutfs_sysfs_init(void);
|
||||
void __exit scoutfs_sysfs_exit(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,614 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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/fs.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/writeback.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "super.h"
|
||||
#include "trans.h"
|
||||
#include "data.h"
|
||||
#include "forest.h"
|
||||
#include "counters.h"
|
||||
#include "client.h"
|
||||
#include "inode.h"
|
||||
#include "alloc.h"
|
||||
#include "block.h"
|
||||
#include "msg.h"
|
||||
#include "item.h"
|
||||
#include "scoutfs_trace.h"
|
||||
|
||||
/*
|
||||
* scoutfs blocks are written in atomic transactions.
|
||||
*
|
||||
* Writers hold transactions to dirty blocks. The transaction can't be
|
||||
* written until these active writers release the transaction. We don't
|
||||
* track the relationships between dirty blocks so there's only ever one
|
||||
* transaction being built.
|
||||
*
|
||||
* The copy of the on-disk super block in the fs sb info has its header
|
||||
* sequence advanced so that new dirty blocks inherit this dirty
|
||||
* sequence number. It's only advanced once all those dirty blocks are
|
||||
* reachable after having first written them all out and then the new
|
||||
* super with that seq. It's first incremented at mount.
|
||||
*
|
||||
* Unfortunately writers can nest. We don't bother trying to special
|
||||
* case holding a transaction that you're already holding because that
|
||||
* requires per-task storage. We just let anyone hold transactions
|
||||
* regardless of waiters waiting to write, which risks waiters waiting a
|
||||
* very long time.
|
||||
*/
|
||||
|
||||
/* sync dirty data at least this often */
|
||||
#define TRANS_SYNC_DELAY (HZ * 10)
|
||||
|
||||
/*
|
||||
* XXX move the rest of the super trans_ fields here.
|
||||
*/
|
||||
struct trans_info {
|
||||
spinlock_t lock;
|
||||
unsigned reserved_items;
|
||||
unsigned reserved_vals;
|
||||
unsigned holders;
|
||||
bool writing;
|
||||
|
||||
struct scoutfs_log_trees lt;
|
||||
struct scoutfs_alloc alloc;
|
||||
struct scoutfs_block_writer wri;
|
||||
};
|
||||
|
||||
#define DECLARE_TRANS_INFO(sb, name) \
|
||||
struct trans_info *name = SCOUTFS_SB(sb)->trans_info
|
||||
|
||||
static bool drained_holders(struct trans_info *tri)
|
||||
{
|
||||
bool drained;
|
||||
|
||||
spin_lock(&tri->lock);
|
||||
tri->writing = true;
|
||||
drained = tri->holders == 0;
|
||||
spin_unlock(&tri->lock);
|
||||
|
||||
return drained;
|
||||
}
|
||||
|
||||
static int commit_btrees(struct super_block *sb)
|
||||
{
|
||||
DECLARE_TRANS_INFO(sb, tri);
|
||||
struct scoutfs_log_trees lt;
|
||||
|
||||
lt = tri->lt;
|
||||
lt.meta_avail = tri->alloc.avail;
|
||||
lt.meta_freed = tri->alloc.freed;
|
||||
scoutfs_forest_get_btrees(sb, <);
|
||||
scoutfs_data_get_btrees(sb, <);
|
||||
|
||||
return scoutfs_client_commit_log_trees(sb, <);
|
||||
}
|
||||
|
||||
/*
|
||||
* This gets all the resources from the server that the client will
|
||||
* need during the transaction.
|
||||
*/
|
||||
int scoutfs_trans_get_log_trees(struct super_block *sb)
|
||||
{
|
||||
DECLARE_TRANS_INFO(sb, tri);
|
||||
struct scoutfs_log_trees lt;
|
||||
int ret = 0;
|
||||
|
||||
ret = scoutfs_client_get_log_trees(sb, <);
|
||||
if (ret == 0) {
|
||||
tri->lt = lt;
|
||||
scoutfs_alloc_init(&tri->alloc, <.meta_avail, <.meta_freed);
|
||||
scoutfs_block_writer_init(sb, &tri->wri);
|
||||
|
||||
scoutfs_forest_init_btrees(sb, &tri->alloc, &tri->wri, <);
|
||||
scoutfs_data_init_btrees(sb, &tri->alloc, &tri->wri, <);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool scoutfs_trans_has_dirty(struct super_block *sb)
|
||||
{
|
||||
DECLARE_TRANS_INFO(sb, tri);
|
||||
|
||||
return scoutfs_block_writer_has_dirty(sb, &tri->wri);
|
||||
}
|
||||
|
||||
/*
|
||||
* This work func is responsible for writing out all the dirty blocks
|
||||
* that make up the current dirty transaction. It prevents writers from
|
||||
* holding a transaction so it doesn't have to worry about blocks being
|
||||
* dirtied while it is working.
|
||||
*
|
||||
* In the course of doing its work this task might need to use write
|
||||
* functions that would try to hold the transaction. We record the task
|
||||
* whose committing the transaction so that holding won't deadlock.
|
||||
*
|
||||
* Any dirty block had to have allocated a new blkno which would have
|
||||
* created dirty allocator metadata blocks. We can avoid writing
|
||||
* entirely if we don't have any dirty metadata blocks. This is
|
||||
* important because we don't try to serialize this work during
|
||||
* unmount.. we can execute as the vfs is shutting down.. we need to
|
||||
* decide that nothing is dirty without calling the vfs at all.
|
||||
*
|
||||
* We first try to sync the dirty inodes and write their dirty data blocks,
|
||||
* then we write all our dirty metadata blocks, and only when those succeed
|
||||
* do we write the new super that references all of these newly written blocks.
|
||||
*
|
||||
* If there are write errors then blocks are kept dirty in memory and will
|
||||
* be written again at the next sync.
|
||||
*/
|
||||
void scoutfs_trans_write_func(struct work_struct *work)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = container_of(work, struct scoutfs_sb_info,
|
||||
trans_write_work.work);
|
||||
struct super_block *sb = sbi->sb;
|
||||
DECLARE_TRANS_INFO(sb, tri);
|
||||
u64 trans_seq = sbi->trans_seq;
|
||||
char *s = NULL;
|
||||
int ret = 0;
|
||||
|
||||
sbi->trans_task = current;
|
||||
|
||||
wait_event(sbi->trans_hold_wq, drained_holders(tri));
|
||||
|
||||
trace_scoutfs_trans_write_func(sb,
|
||||
scoutfs_block_writer_dirty_bytes(sb, &tri->wri));
|
||||
|
||||
if (!scoutfs_block_writer_has_dirty(sb, &tri->wri) &&
|
||||
!scoutfs_item_dirty_pages(sb)) {
|
||||
if (sbi->trans_deadline_expired) {
|
||||
/*
|
||||
* If we're not writing data then we only advance the
|
||||
* seq at the sync deadline interval. This keeps idle
|
||||
* mounts from pinning a seq and stopping readers of the
|
||||
* seq indices but doesn't send a message for every sync
|
||||
* syscall.
|
||||
*/
|
||||
ret = scoutfs_client_advance_seq(sb, &trans_seq);
|
||||
if (ret < 0)
|
||||
s = "clean advance seq";
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (sbi->trans_deadline_expired)
|
||||
scoutfs_inc_counter(sb, trans_commit_timer);
|
||||
|
||||
scoutfs_inc_counter(sb, trans_commit_written);
|
||||
|
||||
/* XXX this all needs serious work for dealing with errors */
|
||||
ret = (s = "data submit", scoutfs_inode_walk_writeback(sb, true)) ?:
|
||||
(s = "item dirty", scoutfs_item_write_dirty(sb)) ?:
|
||||
(s = "data prepare", scoutfs_data_prepare_commit(sb)) ?:
|
||||
(s = "alloc prepare", scoutfs_alloc_prepare_commit(sb,
|
||||
&tri->alloc, &tri->wri)) ?:
|
||||
(s = "meta write", scoutfs_block_writer_write(sb, &tri->wri)) ?:
|
||||
(s = "data wait", scoutfs_inode_walk_writeback(sb, false)) ?:
|
||||
(s = "commit log trees", commit_btrees(sb)) ?:
|
||||
scoutfs_item_write_done(sb) ?:
|
||||
(s = "advance seq", scoutfs_client_advance_seq(sb, &trans_seq)) ?:
|
||||
(s = "get log trees", scoutfs_trans_get_log_trees(sb));
|
||||
out:
|
||||
if (ret < 0)
|
||||
scoutfs_err(sb, "critical transaction commit failure: %s, %d",
|
||||
s, ret);
|
||||
|
||||
spin_lock(&sbi->trans_write_lock);
|
||||
sbi->trans_write_count++;
|
||||
sbi->trans_write_ret = ret;
|
||||
sbi->trans_seq = trans_seq;
|
||||
spin_unlock(&sbi->trans_write_lock);
|
||||
wake_up(&sbi->trans_write_wq);
|
||||
|
||||
spin_lock(&tri->lock);
|
||||
tri->writing = false;
|
||||
spin_unlock(&tri->lock);
|
||||
|
||||
wake_up(&sbi->trans_hold_wq);
|
||||
|
||||
sbi->trans_task = NULL;
|
||||
|
||||
scoutfs_trans_restart_sync_deadline(sb);
|
||||
}
|
||||
|
||||
struct write_attempt {
|
||||
u64 count;
|
||||
int ret;
|
||||
};
|
||||
|
||||
/* this is called as a wait_event() condition so it can't change task state */
|
||||
static int write_attempted(struct scoutfs_sb_info *sbi,
|
||||
struct write_attempt *attempt)
|
||||
{
|
||||
int done = 1;
|
||||
|
||||
spin_lock(&sbi->trans_write_lock);
|
||||
if (sbi->trans_write_count > attempt->count)
|
||||
attempt->ret = sbi->trans_write_ret;
|
||||
else
|
||||
done = 0;
|
||||
spin_unlock(&sbi->trans_write_lock);
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* We always have delayed sync work pending but the caller wants it
|
||||
* to execute immediately.
|
||||
*/
|
||||
static void queue_trans_work(struct scoutfs_sb_info *sbi)
|
||||
{
|
||||
sbi->trans_deadline_expired = false;
|
||||
mod_delayed_work(sbi->trans_write_workq, &sbi->trans_write_work, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait for a trans commit to finish and return its error code. There
|
||||
* can already be one in flight that we end up waiting for the
|
||||
* completion of. This is safe because dirtying and trans commits are
|
||||
* serialized. There's no way that there could have been dirty data
|
||||
* before the caller got here that wouldn't be covered by a commit
|
||||
* that's in flight.
|
||||
*/
|
||||
int scoutfs_trans_sync(struct super_block *sb, int wait)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct write_attempt attempt;
|
||||
int ret;
|
||||
|
||||
|
||||
if (!wait) {
|
||||
queue_trans_work(sbi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
spin_lock(&sbi->trans_write_lock);
|
||||
attempt.count = sbi->trans_write_count;
|
||||
spin_unlock(&sbi->trans_write_lock);
|
||||
|
||||
queue_trans_work(sbi);
|
||||
|
||||
ret = wait_event_interruptible(sbi->trans_write_wq,
|
||||
write_attempted(sbi, &attempt));
|
||||
if (ret == 0)
|
||||
ret = attempt.ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scoutfs_file_fsync(struct file *file, loff_t start, loff_t end,
|
||||
int datasync)
|
||||
{
|
||||
struct super_block *sb = file_inode(file)->i_sb;
|
||||
|
||||
scoutfs_inc_counter(sb, trans_commit_fsync);
|
||||
return scoutfs_trans_sync(sb, 1);
|
||||
}
|
||||
|
||||
void scoutfs_trans_restart_sync_deadline(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
|
||||
sbi->trans_deadline_expired = true;
|
||||
mod_delayed_work(sbi->trans_write_workq, &sbi->trans_write_work,
|
||||
TRANS_SYNC_DELAY);
|
||||
}
|
||||
|
||||
/*
|
||||
* Each thread reserves space in the segment for their dirty items while
|
||||
* they hold the transaction. This is calculated before the first
|
||||
* transaction hold is acquired. It includes all the potential nested
|
||||
* item manipulation that could happen with the transaction held.
|
||||
* Including nested holds avoids having to deal with writing out partial
|
||||
* transactions while a caller still holds the transaction.
|
||||
*/
|
||||
#define SCOUTFS_RESERVATION_MAGIC 0xd57cd13b
|
||||
struct scoutfs_reservation {
|
||||
unsigned magic;
|
||||
unsigned holders;
|
||||
struct scoutfs_item_count reserved;
|
||||
struct scoutfs_item_count actual;
|
||||
};
|
||||
|
||||
/*
|
||||
* Try to hold the transaction. If a caller already holds the trans then
|
||||
* we piggy back on their hold. We wait if the writer is trying to
|
||||
* write out the transation. And if our items won't fit then we kick off
|
||||
* a write.
|
||||
*
|
||||
* This is called as a condition for wait_event. It is very limited in
|
||||
* the locking (blocking) it can do because the caller has set the task
|
||||
* state before testing the condition safely race with waking after
|
||||
* setting the condition. Our checking the amount of dirty metadata
|
||||
* blocks and free data blocks is racy, but we don't mind the risk of
|
||||
* delaying or prematurely forcing commits.
|
||||
*/
|
||||
static bool acquired_hold(struct super_block *sb,
|
||||
struct scoutfs_reservation *rsv,
|
||||
const struct scoutfs_item_count *cnt)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
DECLARE_TRANS_INFO(sb, tri);
|
||||
bool acquired = false;
|
||||
unsigned items;
|
||||
unsigned vals;
|
||||
|
||||
spin_lock(&tri->lock);
|
||||
|
||||
trace_scoutfs_trans_acquired_hold(sb, cnt, rsv, rsv->holders,
|
||||
&rsv->reserved, &rsv->actual,
|
||||
tri->holders, tri->writing,
|
||||
tri->reserved_items,
|
||||
tri->reserved_vals);
|
||||
|
||||
/* use a caller's existing reservation */
|
||||
if (rsv->holders)
|
||||
goto hold;
|
||||
|
||||
/* wait until the writing thread is finished */
|
||||
if (tri->writing)
|
||||
goto out;
|
||||
|
||||
/* see if we can reserve space for our item count */
|
||||
items = tri->reserved_items + cnt->items;
|
||||
vals = tri->reserved_vals + cnt->vals;
|
||||
|
||||
/*
|
||||
* In theory each dirty item page could be straddling two full
|
||||
* blocks, requiring 4 allocations for each item cache page.
|
||||
* That's much too conservative, typically many dirty item cache
|
||||
* pages that are near each other all land in one block. This
|
||||
* rough estimate is still so far beyond what typically happens
|
||||
* that it accounts for having to dirty parent blocks and
|
||||
* whatever dirtying is done during the transaction hold.
|
||||
*/
|
||||
if (scoutfs_alloc_meta_low(sb, &tri->alloc,
|
||||
scoutfs_item_dirty_pages(sb) * 2)) {
|
||||
scoutfs_inc_counter(sb, trans_commit_dirty_meta_full);
|
||||
queue_trans_work(sbi);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extent modifications can use meta allocators without creating
|
||||
* dirty items so we have to check the meta alloc specifically.
|
||||
* The size of the client's avail and freed roots are bound so
|
||||
* we're unlikely to need very many block allocations per
|
||||
* transaction hold. XXX This should be more precisely tuned.
|
||||
*/
|
||||
if (scoutfs_alloc_meta_low(sb, &tri->alloc, 16)) {
|
||||
scoutfs_inc_counter(sb, trans_commit_meta_alloc_low);
|
||||
queue_trans_work(sbi);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Try to refill data allocator before premature enospc */
|
||||
if (scoutfs_data_alloc_free_bytes(sb) <= SCOUTFS_TRANS_DATA_ALLOC_LWM) {
|
||||
scoutfs_inc_counter(sb, trans_commit_data_alloc_low);
|
||||
queue_trans_work(sbi);
|
||||
goto out;
|
||||
}
|
||||
|
||||
tri->reserved_items = items;
|
||||
tri->reserved_vals = vals;
|
||||
|
||||
rsv->reserved.items = cnt->items;
|
||||
rsv->reserved.vals = cnt->vals;
|
||||
|
||||
hold:
|
||||
rsv->holders++;
|
||||
tri->holders++;
|
||||
acquired = true;
|
||||
|
||||
out:
|
||||
|
||||
spin_unlock(&tri->lock);
|
||||
|
||||
return acquired;
|
||||
}
|
||||
|
||||
int scoutfs_hold_trans(struct super_block *sb,
|
||||
const struct scoutfs_item_count cnt)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_reservation *rsv;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Caller shouldn't provide garbage counts, nor counts that
|
||||
* can't fit in segments by themselves.
|
||||
*/
|
||||
if (WARN_ON_ONCE(cnt.items <= 0 || cnt.vals < 0))
|
||||
return -EINVAL;
|
||||
|
||||
if (current == sbi->trans_task)
|
||||
return 0;
|
||||
|
||||
rsv = current->journal_info;
|
||||
if (rsv == NULL) {
|
||||
rsv = kzalloc(sizeof(struct scoutfs_reservation), GFP_NOFS);
|
||||
if (!rsv)
|
||||
return -ENOMEM;
|
||||
|
||||
rsv->magic = SCOUTFS_RESERVATION_MAGIC;
|
||||
current->journal_info = rsv;
|
||||
}
|
||||
|
||||
BUG_ON(rsv->magic != SCOUTFS_RESERVATION_MAGIC);
|
||||
|
||||
ret = wait_event_interruptible(sbi->trans_hold_wq,
|
||||
acquired_hold(sb, rsv, &cnt));
|
||||
if (ret && rsv->holders == 0) {
|
||||
current->journal_info = NULL;
|
||||
kfree(rsv);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the current task has a transaction held. That is,
|
||||
* true if the current transaction can't finish and be written out if
|
||||
* the current task blocks.
|
||||
*/
|
||||
bool scoutfs_trans_held(void)
|
||||
{
|
||||
struct scoutfs_reservation *rsv = current->journal_info;
|
||||
|
||||
return rsv && rsv->magic == SCOUTFS_RESERVATION_MAGIC;
|
||||
}
|
||||
|
||||
/*
|
||||
* Record a transaction holder's individual contribution to the dirty
|
||||
* items in the current transaction. We're making sure that the
|
||||
* reservation matches the possible item manipulations while they hold
|
||||
* the reservation.
|
||||
*
|
||||
* It is possible and legitimate for an individual contribution to be
|
||||
* negative if they delete dirty items. The item cache makes sure that
|
||||
* the total dirty item count doesn't fall below zero.
|
||||
*/
|
||||
void scoutfs_trans_track_item(struct super_block *sb, signed items,
|
||||
signed vals)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_reservation *rsv = current->journal_info;
|
||||
|
||||
if (current == sbi->trans_task)
|
||||
return;
|
||||
|
||||
BUG_ON(!rsv || rsv->magic != SCOUTFS_RESERVATION_MAGIC);
|
||||
|
||||
rsv->actual.items += items;
|
||||
rsv->actual.vals += vals;
|
||||
|
||||
trace_scoutfs_trans_track_item(sb, items, vals, rsv->actual.items,
|
||||
rsv->actual.vals, rsv->reserved.items,
|
||||
rsv->reserved.vals);
|
||||
|
||||
WARN_ON_ONCE(rsv->actual.items > rsv->reserved.items);
|
||||
WARN_ON_ONCE(rsv->actual.vals > rsv->reserved.vals);
|
||||
}
|
||||
|
||||
/*
|
||||
* As we drop the last hold in the reservation we try and wake other
|
||||
* hold attempts that were waiting for space. As we drop the last trans
|
||||
* holder we try to wake a writing thread that was waiting for us to
|
||||
* finish.
|
||||
*/
|
||||
void scoutfs_release_trans(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_reservation *rsv;
|
||||
DECLARE_TRANS_INFO(sb, tri);
|
||||
bool wake = false;
|
||||
|
||||
if (current == sbi->trans_task)
|
||||
return;
|
||||
|
||||
rsv = current->journal_info;
|
||||
BUG_ON(!rsv || rsv->magic != SCOUTFS_RESERVATION_MAGIC);
|
||||
|
||||
spin_lock(&tri->lock);
|
||||
|
||||
trace_scoutfs_release_trans(sb, rsv, rsv->holders, &rsv->reserved,
|
||||
&rsv->actual, tri->holders, tri->writing,
|
||||
tri->reserved_items, tri->reserved_vals);
|
||||
|
||||
BUG_ON(rsv->holders <= 0);
|
||||
BUG_ON(tri->holders <= 0);
|
||||
|
||||
if (--rsv->holders == 0) {
|
||||
tri->reserved_items -= rsv->reserved.items;
|
||||
tri->reserved_vals -= rsv->reserved.vals;
|
||||
current->journal_info = NULL;
|
||||
kfree(rsv);
|
||||
wake = true;
|
||||
}
|
||||
|
||||
if (--tri->holders == 0)
|
||||
wake = true;
|
||||
|
||||
spin_unlock(&tri->lock);
|
||||
|
||||
if (wake)
|
||||
wake_up(&sbi->trans_hold_wq);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the current transaction sequence. Whether this is racing with
|
||||
* the transaction write thread is entirely dependent on the caller's
|
||||
* context.
|
||||
*/
|
||||
u64 scoutfs_trans_sample_seq(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
u64 ret;
|
||||
|
||||
spin_lock(&sbi->trans_write_lock);
|
||||
ret = sbi->trans_seq;
|
||||
spin_unlock(&sbi->trans_write_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scoutfs_setup_trans(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct trans_info *tri;
|
||||
|
||||
tri = kzalloc(sizeof(struct trans_info), GFP_KERNEL);
|
||||
if (!tri)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&tri->lock);
|
||||
scoutfs_block_writer_init(sb, &tri->wri);
|
||||
|
||||
sbi->trans_write_workq = alloc_workqueue("scoutfs_trans",
|
||||
WQ_UNBOUND, 1);
|
||||
if (!sbi->trans_write_workq) {
|
||||
kfree(tri);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
sbi->trans_info = tri;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* kill_sb calls sync before getting here so we know that dirty data
|
||||
* should be in flight. We just have to wait for it to quiesce.
|
||||
*/
|
||||
void scoutfs_shutdown_trans(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
DECLARE_TRANS_INFO(sb, tri);
|
||||
|
||||
if (tri) {
|
||||
scoutfs_block_writer_forget_all(sb, &tri->wri);
|
||||
if (sbi->trans_write_workq) {
|
||||
cancel_delayed_work_sync(&sbi->trans_write_work);
|
||||
destroy_workqueue(sbi->trans_write_workq);
|
||||
/* trans work schedules after shutdown see null */
|
||||
sbi->trans_write_workq = NULL;
|
||||
}
|
||||
kfree(tri);
|
||||
sbi->trans_info = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef _SCOUTFS_TRANS_H_
|
||||
#define _SCOUTFS_TRANS_H_
|
||||
|
||||
/* the server will attempt to fill data allocs for each trans */
|
||||
#define SCOUTFS_TRANS_DATA_ALLOC_HWM (2ULL * 1024 * 1024 * 1024)
|
||||
/* the client will force commits if data allocators get too low */
|
||||
#define SCOUTFS_TRANS_DATA_ALLOC_LWM (256ULL * 1024 * 1024)
|
||||
|
||||
#include "count.h"
|
||||
|
||||
void scoutfs_trans_write_func(struct work_struct *work);
|
||||
int scoutfs_trans_sync(struct super_block *sb, int wait);
|
||||
int scoutfs_file_fsync(struct file *file, loff_t start, loff_t end,
|
||||
int datasync);
|
||||
void scoutfs_trans_restart_sync_deadline(struct super_block *sb);
|
||||
|
||||
int scoutfs_hold_trans(struct super_block *sb,
|
||||
const struct scoutfs_item_count cnt);
|
||||
bool scoutfs_trans_held(void);
|
||||
void scoutfs_release_trans(struct super_block *sb);
|
||||
u64 scoutfs_trans_sample_seq(struct super_block *sb);
|
||||
void scoutfs_trans_track_item(struct super_block *sb, signed items,
|
||||
signed vals);
|
||||
|
||||
int scoutfs_trans_get_log_trees(struct super_block *sb);
|
||||
bool scoutfs_trans_has_dirty(struct super_block *sb);
|
||||
|
||||
int scoutfs_setup_trans(struct super_block *sb);
|
||||
void scoutfs_shutdown_trans(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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/slab.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
#include "super.h"
|
||||
#include "triggers.h"
|
||||
|
||||
/*
|
||||
* We have debugfs files we can write to which arm triggers which
|
||||
* atomically fire once for testing or debugging.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The atomic cachelines are kept hot and shared by being read by fast
|
||||
* paths. They're very rarely modified by debugfs writes which arm them
|
||||
* and then the next read will atomically clear and return true.
|
||||
*/
|
||||
struct scoutfs_triggers {
|
||||
struct dentry *dir;
|
||||
atomic_t atomics[SCOUTFS_TRIGGER_NR];
|
||||
};
|
||||
|
||||
#define DECLARE_TRIGGERS(sb, name) \
|
||||
struct scoutfs_triggers *name = SCOUTFS_SB(sb)->triggers
|
||||
|
||||
static char *names[] = {
|
||||
[SCOUTFS_TRIGGER_BTREE_STALE_READ] = "btree_stale_read",
|
||||
[SCOUTFS_TRIGGER_BTREE_ADVANCE_RING_HALF] = "btree_advance_ring_half",
|
||||
[SCOUTFS_TRIGGER_HARD_STALE_ERROR] = "hard_stale_error",
|
||||
[SCOUTFS_TRIGGER_SEG_STALE_READ] = "seg_stale_read",
|
||||
[SCOUTFS_TRIGGER_STATFS_LOCK_PURGE] = "statfs_lock_purge",
|
||||
};
|
||||
|
||||
bool scoutfs_trigger_test_and_clear(struct super_block *sb, unsigned int t)
|
||||
{
|
||||
DECLARE_TRIGGERS(sb, triggers);
|
||||
atomic_t *atom;
|
||||
int old;
|
||||
int mem;
|
||||
|
||||
BUG_ON(t >= SCOUTFS_TRIGGER_NR);
|
||||
atom = &triggers->atomics[t];
|
||||
|
||||
mem = atomic_read(atom);
|
||||
if (likely(!mem))
|
||||
return 0;
|
||||
|
||||
do {
|
||||
old = mem;
|
||||
mem = atomic_cmpxchg(atom, old, 0);
|
||||
} while (mem && mem != old);
|
||||
|
||||
return !!mem;
|
||||
}
|
||||
|
||||
int scoutfs_setup_triggers(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_triggers *triggers;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
BUILD_BUG_ON(ARRAY_SIZE(names) != SCOUTFS_TRIGGER_NR);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(names); i++) {
|
||||
if (WARN_ON(!names[i]))
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
triggers = kzalloc(sizeof(struct scoutfs_triggers), GFP_KERNEL);
|
||||
if (!triggers)
|
||||
return -ENOMEM;
|
||||
|
||||
sbi->triggers = triggers;
|
||||
|
||||
triggers->dir = debugfs_create_dir("trigger", sbi->debug_root);
|
||||
if (!triggers->dir) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(triggers->atomics); i++) {
|
||||
if (!debugfs_create_atomic_t(names[i], 0644, triggers->dir,
|
||||
&triggers->atomics[i])) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
if (ret)
|
||||
scoutfs_destroy_triggers(sb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void scoutfs_destroy_triggers(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
struct scoutfs_triggers *triggers = sbi->triggers;
|
||||
|
||||
if (triggers) {
|
||||
if (triggers->dir)
|
||||
debugfs_remove_recursive(triggers->dir);
|
||||
kfree(triggers);
|
||||
sbi->triggers = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef _SCOUTFS_TRIGGERS_H_
|
||||
#define _SCOUTFS_TRIGGERS_H_
|
||||
|
||||
enum scoutfs_trigger {
|
||||
SCOUTFS_TRIGGER_BTREE_STALE_READ,
|
||||
SCOUTFS_TRIGGER_BTREE_ADVANCE_RING_HALF,
|
||||
SCOUTFS_TRIGGER_HARD_STALE_ERROR,
|
||||
SCOUTFS_TRIGGER_SEG_STALE_READ,
|
||||
SCOUTFS_TRIGGER_STATFS_LOCK_PURGE,
|
||||
SCOUTFS_TRIGGER_NR,
|
||||
};
|
||||
|
||||
bool scoutfs_trigger_test_and_clear(struct super_block *sb, unsigned int t);
|
||||
|
||||
#define scoutfs_trigger(sb, which) \
|
||||
scoutfs_trigger_test_and_clear(sb, SCOUTFS_TRIGGER_##which)
|
||||
|
||||
int scoutfs_setup_triggers(struct super_block *sb);
|
||||
void scoutfs_destroy_triggers(struct super_block *sb);
|
||||
|
||||
#endif
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/rbtree_augmented.h>
|
||||
|
||||
#include "tseq.h"
|
||||
|
||||
/*
|
||||
* This trivial seq file wrapper takes care of the details of displaying
|
||||
* a set of objects in seq file output. We use an augmented rbtree to
|
||||
* add new objects at the next free file position. The caller takes
|
||||
* care of the object life times, only debugfs file creation can fail.
|
||||
*/
|
||||
|
||||
static loff_t tseq_node_total(struct rb_node *node)
|
||||
{
|
||||
struct scoutfs_tseq_entry *ent;
|
||||
|
||||
if (node == NULL)
|
||||
return 0;
|
||||
|
||||
ent = rb_entry(node, struct scoutfs_tseq_entry, node);
|
||||
return ent->total;
|
||||
}
|
||||
|
||||
static struct scoutfs_tseq_entry *tseq_rb_next(struct scoutfs_tseq_entry *ent)
|
||||
{
|
||||
struct rb_node *node = rb_next(&ent->node);
|
||||
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
return rb_entry(node, struct scoutfs_tseq_entry, node);
|
||||
}
|
||||
|
||||
static loff_t tseq_compute_total(struct scoutfs_tseq_entry *ent)
|
||||
{
|
||||
return 1 + tseq_node_total(ent->node.rb_left) +
|
||||
tseq_node_total(ent->node.rb_right);
|
||||
}
|
||||
|
||||
RB_DECLARE_CALLBACKS(static, tseq_rb_callbacks, struct scoutfs_tseq_entry,
|
||||
node, loff_t, total, tseq_compute_total)
|
||||
|
||||
void scoutfs_tseq_tree_init(struct scoutfs_tseq_tree *tree,
|
||||
scoutfs_tseq_show_t show)
|
||||
{
|
||||
spin_lock_init(&tree->lock);
|
||||
tree->root = RB_ROOT;
|
||||
tree->show = show;
|
||||
}
|
||||
|
||||
/*
|
||||
* Descend towards the leaf node that should be the parent for inserting
|
||||
* a new entry.
|
||||
*
|
||||
* We use the augmented subtree totals to see when a left subtree has
|
||||
* fewer entries than the current entry's pos which tells us that there
|
||||
* is a lesser free pos.
|
||||
*
|
||||
* If there isn't a lesser free pos then we descend to the right and set
|
||||
* the minimum possible pos to the pos after the entry we're traversing.
|
||||
*/
|
||||
void scoutfs_tseq_add(struct scoutfs_tseq_tree *tree,
|
||||
struct scoutfs_tseq_entry *ins)
|
||||
{
|
||||
struct scoutfs_tseq_entry *ent;
|
||||
struct rb_node *parent;
|
||||
struct rb_node **node;
|
||||
loff_t min_pos;
|
||||
|
||||
spin_lock(&tree->lock);
|
||||
|
||||
node = &tree->root.rb_node;
|
||||
parent = NULL;
|
||||
min_pos = 0;
|
||||
|
||||
while (*node) {
|
||||
parent = *node;
|
||||
ent = rb_entry(*node, struct scoutfs_tseq_entry, node);
|
||||
|
||||
ent->total++;
|
||||
|
||||
if (min_pos + tseq_node_total(ent->node.rb_left) < ent->pos) {
|
||||
node = &ent->node.rb_left;
|
||||
} else {
|
||||
min_pos = ent->pos + 1;
|
||||
node = &ent->node.rb_right;
|
||||
}
|
||||
}
|
||||
|
||||
ins->pos = min_pos;
|
||||
ins->total = 1;
|
||||
rb_link_node(&ins->node, parent, node);
|
||||
rb_insert_augmented(&ins->node, &tree->root, &tseq_rb_callbacks);
|
||||
|
||||
spin_unlock(&tree->lock);
|
||||
}
|
||||
|
||||
static struct scoutfs_tseq_entry *tseq_pos_next(struct scoutfs_tseq_tree *tree,
|
||||
loff_t pos)
|
||||
{
|
||||
struct scoutfs_tseq_entry *next;
|
||||
struct scoutfs_tseq_entry *ent;
|
||||
struct rb_node *node;
|
||||
|
||||
assert_spin_locked(&tree->lock);
|
||||
|
||||
node = tree->root.rb_node;
|
||||
next = NULL;
|
||||
|
||||
while (node) {
|
||||
ent = rb_entry(node, struct scoutfs_tseq_entry, node);
|
||||
|
||||
if (pos < ent->pos) {
|
||||
next = ent;
|
||||
node = ent->node.rb_left;
|
||||
} else if (pos > ent->pos) {
|
||||
node = ent->node.rb_right;
|
||||
} else {
|
||||
return ent;
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
void scoutfs_tseq_del(struct scoutfs_tseq_tree *tree,
|
||||
struct scoutfs_tseq_entry *ent)
|
||||
{
|
||||
spin_lock(&tree->lock);
|
||||
rb_erase_augmented(&ent->node, &tree->root, &tseq_rb_callbacks);
|
||||
RB_CLEAR_NODE(&ent->node);
|
||||
spin_unlock(&tree->lock);
|
||||
}
|
||||
|
||||
/* _stop is always called no matter what start returns */
|
||||
static void *scoutfs_tseq_seq_start(struct seq_file *m, loff_t *pos)
|
||||
__acquires(tree->lock)
|
||||
{
|
||||
struct scoutfs_tseq_tree *tree = m->private;
|
||||
|
||||
spin_lock(&tree->lock);
|
||||
|
||||
return tseq_pos_next(tree, *pos);
|
||||
}
|
||||
|
||||
static void *scoutfs_tseq_seq_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
{
|
||||
struct scoutfs_tseq_entry *ent = v;
|
||||
|
||||
ent = tseq_rb_next(ent);
|
||||
if (ent)
|
||||
*pos = ent->pos;
|
||||
return ent;
|
||||
}
|
||||
|
||||
static void scoutfs_tseq_seq_stop(struct seq_file *m, void *v)
|
||||
__releases(tree->lock)
|
||||
{
|
||||
struct scoutfs_tseq_tree *tree = m->private;
|
||||
|
||||
spin_unlock(&tree->lock);
|
||||
}
|
||||
|
||||
static int scoutfs_tseq_seq_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct scoutfs_tseq_tree *tree = m->private;
|
||||
struct scoutfs_tseq_entry *ent = v;
|
||||
|
||||
tree->show(m, ent);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations scoutfs_tseq_seq_ops = {
|
||||
.start = scoutfs_tseq_seq_start,
|
||||
.next = scoutfs_tseq_seq_next,
|
||||
.stop = scoutfs_tseq_seq_stop,
|
||||
.show = scoutfs_tseq_seq_show,
|
||||
};
|
||||
|
||||
static int scoutfs_tseq_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct seq_file *m;
|
||||
int ret;
|
||||
|
||||
ret = seq_open(file, &scoutfs_tseq_seq_ops);
|
||||
if (ret == 0) {
|
||||
m = file->private_data;
|
||||
m->private = inode->i_private;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations scoutfs_tseq_fops = {
|
||||
.open = scoutfs_tseq_open,
|
||||
.release = seq_release,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
};
|
||||
|
||||
/*
|
||||
* This doesn't create any additional state so the returned dentry
|
||||
* can be destroyed with the usual debugfs file calls.
|
||||
*/
|
||||
struct dentry *scoutfs_tseq_create(const char *name, struct dentry *parent,
|
||||
struct scoutfs_tseq_tree *tree)
|
||||
{
|
||||
return debugfs_create_file(name, S_IFREG|S_IRUSR, parent, tree,
|
||||
&scoutfs_tseq_fops);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef _SCOUTFS_TSEQ_H_
|
||||
#define _SCOUTFS_TSEQ_H_
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
struct scoutfs_tseq_entry;
|
||||
typedef void (*scoutfs_tseq_show_t)(struct seq_file *m,
|
||||
struct scoutfs_tseq_entry *ent);
|
||||
|
||||
struct scoutfs_tseq_tree {
|
||||
spinlock_t lock;
|
||||
struct rb_root root;
|
||||
scoutfs_tseq_show_t show;
|
||||
};
|
||||
|
||||
struct scoutfs_tseq_entry {
|
||||
struct rb_node node;
|
||||
loff_t pos;
|
||||
loff_t total;
|
||||
};
|
||||
|
||||
void scoutfs_tseq_tree_init(struct scoutfs_tseq_tree *tree,
|
||||
scoutfs_tseq_show_t show);
|
||||
void scoutfs_tseq_add(struct scoutfs_tseq_tree *tree,
|
||||
struct scoutfs_tseq_entry *ent);
|
||||
void scoutfs_tseq_del(struct scoutfs_tseq_tree *tree,
|
||||
struct scoutfs_tseq_entry *ent);
|
||||
|
||||
struct dentry *scoutfs_tseq_create(const char *name, struct dentry *parent,
|
||||
struct scoutfs_tseq_tree *tree);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,812 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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/fs.h>
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/xattr.h>
|
||||
#include <linux/crc32c.h>
|
||||
|
||||
#include "format.h"
|
||||
#include "inode.h"
|
||||
#include "key.h"
|
||||
#include "super.h"
|
||||
#include "item.h"
|
||||
#include "forest.h"
|
||||
#include "trans.h"
|
||||
#include "xattr.h"
|
||||
#include "lock.h"
|
||||
#include "hash.h"
|
||||
#include "scoutfs_trace.h"
|
||||
|
||||
/*
|
||||
* Extended attributes are packed into multiple smaller file system
|
||||
* items. The common case only uses one item.
|
||||
*
|
||||
* The xattr keys contain the hash of the xattr name and a unique
|
||||
* identifier used to differentiate xattrs whose names hash to the same
|
||||
* value. xattr lookup has to walk all the xattrs with the matching
|
||||
* name hash to compare the names.
|
||||
*
|
||||
* We use a rwsem in the inode to serialize modification of multiple
|
||||
* items to make sure that we don't let readers race and see an
|
||||
* inconsistent mix of the items that make up xattrs.
|
||||
*
|
||||
* XXX
|
||||
* - add acl support and call generic xattr->handlers for SYSTEM
|
||||
*/
|
||||
|
||||
static u32 xattr_name_hash(const char *name, unsigned int name_len)
|
||||
{
|
||||
return crc32c(U32_MAX, name, name_len);
|
||||
}
|
||||
|
||||
/* only compare names if the lens match, callers might not have both names */
|
||||
static u32 xattr_names_equal(const char *a_name, unsigned int a_len,
|
||||
const char *b_name, unsigned int b_len)
|
||||
{
|
||||
return a_len == b_len && memcmp(a_name, b_name, a_len) == 0;
|
||||
}
|
||||
|
||||
static unsigned int xattr_full_bytes(struct scoutfs_xattr *xat)
|
||||
{
|
||||
return offsetof(struct scoutfs_xattr,
|
||||
name[xat->name_len + le16_to_cpu(xat->val_len)]);
|
||||
}
|
||||
|
||||
static unsigned int xattr_nr_parts(struct scoutfs_xattr *xat)
|
||||
{
|
||||
return SCOUTFS_XATTR_NR_PARTS(xat->name_len,
|
||||
le16_to_cpu(xat->val_len));
|
||||
}
|
||||
|
||||
static void init_xattr_key(struct scoutfs_key *key, u64 ino, u32 name_hash,
|
||||
u64 id)
|
||||
{
|
||||
*key = (struct scoutfs_key) {
|
||||
.sk_zone = SCOUTFS_FS_ZONE,
|
||||
.skx_ino = cpu_to_le64(ino),
|
||||
.sk_type = SCOUTFS_XATTR_TYPE,
|
||||
.skx_name_hash = cpu_to_le64(name_hash),
|
||||
.skx_id = cpu_to_le64(id),
|
||||
.skx_part = 0,
|
||||
};
|
||||
}
|
||||
|
||||
#define SCOUTFS_XATTR_PREFIX "scoutfs."
|
||||
#define SCOUTFS_XATTR_PREFIX_LEN (sizeof(SCOUTFS_XATTR_PREFIX) - 1)
|
||||
|
||||
static int unknown_prefix(const char *name)
|
||||
{
|
||||
return strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) &&
|
||||
strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN) &&
|
||||
strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN) &&
|
||||
strncmp(name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN)&&
|
||||
strncmp(name, SCOUTFS_XATTR_PREFIX, SCOUTFS_XATTR_PREFIX_LEN);
|
||||
}
|
||||
|
||||
|
||||
#define HIDE_TAG "hide."
|
||||
#define SRCH_TAG "srch."
|
||||
#define TAG_LEN (sizeof(HIDE_TAG) - 1)
|
||||
|
||||
int scoutfs_xattr_parse_tags(const char *name, unsigned int name_len,
|
||||
struct scoutfs_xattr_prefix_tags *tgs)
|
||||
{
|
||||
bool found;
|
||||
|
||||
memset(tgs, 0, sizeof(struct scoutfs_xattr_prefix_tags));
|
||||
|
||||
if ((name_len < (SCOUTFS_XATTR_PREFIX_LEN + TAG_LEN + 1)) ||
|
||||
strncmp(name, SCOUTFS_XATTR_PREFIX, SCOUTFS_XATTR_PREFIX_LEN))
|
||||
return 0;
|
||||
name += SCOUTFS_XATTR_PREFIX_LEN;
|
||||
|
||||
found = false;
|
||||
for (;;) {
|
||||
if (!strncmp(name, HIDE_TAG, TAG_LEN)) {
|
||||
if (++tgs->hide == 0)
|
||||
return -EINVAL;
|
||||
} else if (!strncmp(name, SRCH_TAG, TAG_LEN)) {
|
||||
if (++tgs->srch == 0)
|
||||
return -EINVAL;
|
||||
} else {
|
||||
/* only reason to use scoutfs. is tags */
|
||||
if (!found)
|
||||
return -EINVAL;
|
||||
break;
|
||||
}
|
||||
name += TAG_LEN;
|
||||
found = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the next xattr and copy the key, xattr header, and as much of
|
||||
* the name and value into the callers buffer as we can. Returns the
|
||||
* number of bytes copied which include the header, name, and value and
|
||||
* can be limited by the xattr length or the callers buffer. The caller
|
||||
* is responsible for comparing their lengths, the header, and the
|
||||
* returned length before safely using the xattr.
|
||||
*
|
||||
* If a name is provided then we'll iterate over items with a matching
|
||||
* name_hash until we find a matching name. If we don't find a matching
|
||||
* name then we return -ENOENT.
|
||||
*
|
||||
* If a name isn't provided then we'll return the next xattr from the
|
||||
* given name_hash and id position.
|
||||
*
|
||||
* Returns -ENOENT if it didn't find a next item.
|
||||
*/
|
||||
static int get_next_xattr(struct inode *inode, struct scoutfs_key *key,
|
||||
struct scoutfs_xattr *xat, unsigned int bytes,
|
||||
const char *name, unsigned int name_len,
|
||||
u64 name_hash, u64 id, struct scoutfs_lock *lock)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_key last;
|
||||
u8 last_part;
|
||||
int total;
|
||||
u8 part;
|
||||
int ret;
|
||||
|
||||
/* need to be able to see the name we're looking for */
|
||||
if (WARN_ON_ONCE(name_len > 0 && bytes < offsetof(struct scoutfs_xattr,
|
||||
name[name_len])))
|
||||
return -EINVAL;
|
||||
|
||||
if (name_len)
|
||||
name_hash = xattr_name_hash(name, name_len);
|
||||
|
||||
init_xattr_key(key, scoutfs_ino(inode), name_hash, id);
|
||||
init_xattr_key(&last, scoutfs_ino(inode), U32_MAX, U64_MAX);
|
||||
|
||||
last_part = 0;
|
||||
part = 0;
|
||||
total = 0;
|
||||
|
||||
for (;;) {
|
||||
key->skx_part = part;
|
||||
ret = scoutfs_item_next(sb, key, &last,
|
||||
(void *)xat + total, bytes - total,
|
||||
lock);
|
||||
if (ret < 0) {
|
||||
/* XXX corruption, ran out of parts */
|
||||
if (ret == -ENOENT && part > 0)
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
trace_scoutfs_xattr_get_next_key(sb, key);
|
||||
|
||||
/* XXX corruption */
|
||||
if (key->skx_part != part) {
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX corruption: We should have seen a valid header in
|
||||
* the first part and if the next xattr name fits in our
|
||||
* buffer then the item must have included it.
|
||||
*/
|
||||
if (part == 0 &&
|
||||
(ret < sizeof(struct scoutfs_xattr) ||
|
||||
(xat->name_len <= name_len &&
|
||||
ret < offsetof(struct scoutfs_xattr,
|
||||
name[xat->name_len])) ||
|
||||
xat->name_len > SCOUTFS_XATTR_MAX_NAME_LEN ||
|
||||
le16_to_cpu(xat->val_len) > SCOUTFS_XATTR_MAX_VAL_LEN)) {
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (part == 0 && name_len) {
|
||||
/* ran out of names that could match */
|
||||
if (le64_to_cpu(key->skx_name_hash) != name_hash) {
|
||||
ret = -ENOENT;
|
||||
break;
|
||||
}
|
||||
|
||||
/* keep looking for our name */
|
||||
if (!xattr_names_equal(name, name_len,
|
||||
xat->name, xat->name_len)) {
|
||||
part = 0;
|
||||
le64_add_cpu(&key->skx_id, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* use the matching name we found */
|
||||
last_part = xattr_nr_parts(xat) - 1;
|
||||
}
|
||||
|
||||
total += ret;
|
||||
if (total == bytes || part == last_part) {
|
||||
/* copied as much as we could */
|
||||
ret = total;
|
||||
break;
|
||||
}
|
||||
part++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create all the items associated with the given xattr. If this
|
||||
* returns an error it will have already cleaned up any items it created
|
||||
* before seeing the error.
|
||||
*/
|
||||
static int create_xattr_items(struct inode *inode, u64 id,
|
||||
struct scoutfs_xattr *xat, unsigned int bytes,
|
||||
struct scoutfs_lock *lock)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_key key;
|
||||
unsigned int part_bytes;
|
||||
unsigned int total;
|
||||
int ret;
|
||||
|
||||
init_xattr_key(&key, scoutfs_ino(inode),
|
||||
xattr_name_hash(xat->name, xat->name_len), id);
|
||||
|
||||
total = 0;
|
||||
ret = 0;
|
||||
while (total < bytes) {
|
||||
part_bytes = min_t(unsigned int, bytes - total,
|
||||
SCOUTFS_XATTR_MAX_PART_SIZE);
|
||||
|
||||
ret = scoutfs_item_create(sb, &key,
|
||||
(void *)xat + total, part_bytes,
|
||||
lock);
|
||||
if (ret) {
|
||||
while (key.skx_part-- > 0)
|
||||
scoutfs_item_delete(sb, &key, lock);
|
||||
break;
|
||||
}
|
||||
|
||||
total += part_bytes;
|
||||
key.skx_part++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete the items that make up the given xattr. If this returns an
|
||||
* error then no items have been deleted.
|
||||
*/
|
||||
static int delete_xattr_items(struct inode *inode, u32 name_hash, u64 id,
|
||||
u8 nr_parts, struct scoutfs_lock *lock)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_key key;
|
||||
int ret = 0;
|
||||
int i;
|
||||
|
||||
init_xattr_key(&key, scoutfs_ino(inode), name_hash, id);
|
||||
|
||||
/* dirty additional existing old items */
|
||||
for (i = 1; i < nr_parts; i++) {
|
||||
key.skx_part = i;
|
||||
ret = scoutfs_item_dirty(sb, &key, lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < nr_parts; i++) {
|
||||
key.skx_part = i;
|
||||
ret = scoutfs_item_delete(sb, &key, lock);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The caller needs to overwrite existing old xattr items with new
|
||||
* items. We carefully stage the changes so that we can always unwind
|
||||
* to the original items if we return an error. Both items have at
|
||||
* least one part. Either the old or new can have more parts. We dirty
|
||||
* and create first because we can always unwind those. We delete last
|
||||
* after dirtying so that it can't fail and we don't have to restore the
|
||||
* deleted items.
|
||||
*/
|
||||
static int change_xattr_items(struct inode *inode, u64 id,
|
||||
struct scoutfs_xattr *new_xat,
|
||||
unsigned int new_bytes, u8 new_parts,
|
||||
u8 old_parts, struct scoutfs_lock *lock)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_key key;
|
||||
int last_created = -1;
|
||||
int bytes;
|
||||
int off;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
init_xattr_key(&key, scoutfs_ino(inode),
|
||||
xattr_name_hash(new_xat->name, new_xat->name_len), id);
|
||||
|
||||
/* dirty existing old items */
|
||||
for (i = 0; i < old_parts; i++) {
|
||||
key.skx_part = i;
|
||||
ret = scoutfs_item_dirty(sb, &key, lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* create any new items past the old */
|
||||
for (i = old_parts; i < new_parts; i++) {
|
||||
off = i * SCOUTFS_XATTR_MAX_PART_SIZE;
|
||||
bytes = min_t(unsigned int, new_bytes - off,
|
||||
SCOUTFS_XATTR_MAX_PART_SIZE);
|
||||
|
||||
key.skx_part = i;
|
||||
ret = scoutfs_item_create(sb, &key, (void *)new_xat + off,
|
||||
bytes, lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
last_created = i;
|
||||
}
|
||||
|
||||
/* update dirtied overlapping existing items, last partial first */
|
||||
for (i = old_parts - 1; i >= 0; i--) {
|
||||
off = i * SCOUTFS_XATTR_MAX_PART_SIZE;
|
||||
bytes = min_t(unsigned int, new_bytes - off,
|
||||
SCOUTFS_XATTR_MAX_PART_SIZE);
|
||||
|
||||
key.skx_part = i;
|
||||
ret = scoutfs_item_update(sb, &key, (void *)new_xat + off,
|
||||
bytes, lock);
|
||||
/* only last partial can fail, then we unwind created */
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* delete any dirtied old items past new */
|
||||
for (i = new_parts; i < old_parts; i++) {
|
||||
key.skx_part = i;
|
||||
scoutfs_item_delete(sb, &key, lock);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
if (ret < 0) {
|
||||
/* delete any newly created items */
|
||||
for (i = old_parts; i <= last_created; i++) {
|
||||
key.skx_part = i;
|
||||
scoutfs_item_delete(sb, &key, lock);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy the value for the given xattr name into the caller's buffer, if it
|
||||
* fits. Return the bytes copied or -ERANGE if it doesn't fit.
|
||||
*/
|
||||
ssize_t scoutfs_getxattr(struct dentry *dentry, const char *name, void *buffer,
|
||||
size_t size)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_xattr *xat = NULL;
|
||||
struct scoutfs_lock *lck = NULL;
|
||||
struct scoutfs_key key;
|
||||
unsigned int bytes;
|
||||
size_t name_len;
|
||||
int ret;
|
||||
|
||||
if (unknown_prefix(name))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
name_len = strlen(name);
|
||||
if (name_len > SCOUTFS_XATTR_MAX_NAME_LEN)
|
||||
return -ENODATA;
|
||||
|
||||
/* only need enough for caller's name and value sizes */
|
||||
bytes = sizeof(struct scoutfs_xattr) + name_len + size;
|
||||
xat = __vmalloc(bytes, GFP_NOFS, PAGE_KERNEL);
|
||||
if (!xat)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, 0, inode, &lck);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
down_read(&si->xattr_rwsem);
|
||||
|
||||
ret = get_next_xattr(inode, &key, xat, bytes,
|
||||
name, name_len, 0, 0, lck);
|
||||
|
||||
up_read(&si->xattr_rwsem);
|
||||
scoutfs_unlock(sb, lck, SCOUTFS_LOCK_READ);
|
||||
|
||||
if (ret < 0) {
|
||||
if (ret == -ENOENT)
|
||||
ret = -ENODATA;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* the caller just wants to know the size */
|
||||
if (size == 0) {
|
||||
ret = le16_to_cpu(xat->val_len);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* the caller's buffer wasn't big enough */
|
||||
if (size < le16_to_cpu(xat->val_len)) {
|
||||
ret = -ERANGE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* XXX corruption, the items didn't match the header */
|
||||
if (ret < xattr_full_bytes(xat)) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = le16_to_cpu(xat->val_len);
|
||||
memcpy(buffer, &xat->name[xat->name_len], ret);
|
||||
out:
|
||||
vfree(xat);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The confusing swiss army knife of creating, modifying, and deleting
|
||||
* xattrs.
|
||||
*
|
||||
* This always removes the old existing xattr items.
|
||||
*
|
||||
* If the value pointer is set then we're adding a new xattr. The flags
|
||||
* cause creation to fail if the xattr already exists (_CREATE) or
|
||||
* doesn't already exist (_REPLACE). xattrs can have a zero length
|
||||
* value.
|
||||
*/
|
||||
static int scoutfs_xattr_set(struct dentry *dentry, const char *name,
|
||||
const void *value, size_t size, int flags)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
const u64 ino = scoutfs_ino(inode);
|
||||
struct scoutfs_xattr_prefix_tags tgs;
|
||||
struct scoutfs_xattr *xat = NULL;
|
||||
struct scoutfs_lock *lck = NULL;
|
||||
size_t name_len = strlen(name);
|
||||
struct scoutfs_key key;
|
||||
bool undo_srch = false;
|
||||
LIST_HEAD(ind_locks);
|
||||
u8 found_parts;
|
||||
unsigned int bytes;
|
||||
u64 ind_seq;
|
||||
u64 hash = 0;
|
||||
u64 id = 0;
|
||||
int ret;
|
||||
int err;
|
||||
|
||||
trace_scoutfs_xattr_set(sb, name_len, value, size, flags);
|
||||
|
||||
/* mirror the syscall's errors for large names and values */
|
||||
if (name_len > SCOUTFS_XATTR_MAX_NAME_LEN)
|
||||
return -ERANGE;
|
||||
if (value && size > SCOUTFS_XATTR_MAX_VAL_LEN)
|
||||
return -E2BIG;
|
||||
|
||||
if (((flags & XATTR_CREATE) && (flags & XATTR_REPLACE)) ||
|
||||
(flags & ~(XATTR_CREATE | XATTR_REPLACE)))
|
||||
return -EINVAL;
|
||||
|
||||
if (unknown_prefix(name))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (scoutfs_xattr_parse_tags(name, name_len, &tgs) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
if ((tgs.hide || tgs.srch) && !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
bytes = sizeof(struct scoutfs_xattr) + name_len + size;
|
||||
xat = __vmalloc(bytes, GFP_NOFS, PAGE_KERNEL);
|
||||
if (!xat) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_WRITE,
|
||||
SCOUTFS_LKF_REFRESH_INODE, inode, &lck);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
down_write(&si->xattr_rwsem);
|
||||
|
||||
/* find an existing xattr to delete */
|
||||
ret = get_next_xattr(inode, &key, xat,
|
||||
sizeof(struct scoutfs_xattr) + name_len,
|
||||
name, name_len, 0, 0, lck);
|
||||
if (ret < 0 && ret != -ENOENT)
|
||||
goto unlock;
|
||||
|
||||
/* check existence constraint flags */
|
||||
if (ret == -ENOENT && (flags & XATTR_REPLACE)) {
|
||||
ret = -ENODATA;
|
||||
goto unlock;
|
||||
} else if (ret >= 0 && (flags & XATTR_CREATE)) {
|
||||
ret = -EEXIST;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* not an error to delete something that doesn't exist */
|
||||
if (ret == -ENOENT && !value) {
|
||||
ret = 0;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* found fields in key will also be used */
|
||||
found_parts = ret >= 0 ? xattr_nr_parts(xat) : 0;
|
||||
|
||||
/* prepare our xattr */
|
||||
if (value) {
|
||||
if (found_parts)
|
||||
id = le64_to_cpu(key.skx_id);
|
||||
else
|
||||
id = si->next_xattr_id++;
|
||||
xat->name_len = name_len;
|
||||
xat->val_len = cpu_to_le16(size);
|
||||
memset(xat->__pad, 0, sizeof(xat->__pad));
|
||||
memcpy(xat->name, name, name_len);
|
||||
memcpy(&xat->name[xat->name_len], value, size);
|
||||
}
|
||||
|
||||
retry:
|
||||
ret = scoutfs_inode_index_start(sb, &ind_seq) ?:
|
||||
scoutfs_inode_index_prepare(sb, &ind_locks, inode, false) ?:
|
||||
scoutfs_inode_index_try_lock_hold(sb, &ind_locks, ind_seq,
|
||||
SIC_XATTR_SET(found_parts,
|
||||
value != NULL,
|
||||
name_len, size));
|
||||
if (ret > 0)
|
||||
goto retry;
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
ret = scoutfs_dirty_inode_item(inode, lck);
|
||||
if (ret < 0)
|
||||
goto release;
|
||||
|
||||
if (tgs.srch && !(found_parts && value)) {
|
||||
if (found_parts)
|
||||
id = le64_to_cpu(key.skx_id);
|
||||
hash = scoutfs_hash64(name, name_len);
|
||||
ret = scoutfs_forest_srch_add(sb, hash, ino, id);
|
||||
if (ret < 0)
|
||||
goto release;
|
||||
undo_srch = true;
|
||||
}
|
||||
|
||||
if (found_parts && value)
|
||||
ret = change_xattr_items(inode, id, xat, bytes,
|
||||
xattr_nr_parts(xat), found_parts, lck);
|
||||
else if (found_parts)
|
||||
ret = delete_xattr_items(inode, le64_to_cpu(key.skx_name_hash),
|
||||
le64_to_cpu(key.skx_id), found_parts,
|
||||
lck);
|
||||
else
|
||||
ret = create_xattr_items(inode, id, xat, bytes, lck);
|
||||
if (ret < 0)
|
||||
goto release;
|
||||
|
||||
/* XXX do these want i_mutex or anything? */
|
||||
inode_inc_iversion(inode);
|
||||
inode->i_ctime = CURRENT_TIME;
|
||||
scoutfs_update_inode_item(inode, lck, &ind_locks);
|
||||
ret = 0;
|
||||
|
||||
release:
|
||||
if (ret < 0 && undo_srch) {
|
||||
err = scoutfs_forest_srch_add(sb, hash, ino, id);
|
||||
BUG_ON(err);
|
||||
}
|
||||
|
||||
scoutfs_release_trans(sb);
|
||||
scoutfs_inode_index_unlock(sb, &ind_locks);
|
||||
unlock:
|
||||
up_write(&si->xattr_rwsem);
|
||||
scoutfs_unlock(sb, lck, SCOUTFS_LOCK_WRITE);
|
||||
out:
|
||||
vfree(xat);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scoutfs_setxattr(struct dentry *dentry, const char *name,
|
||||
const void *value, size_t size, int flags)
|
||||
{
|
||||
if (size == 0)
|
||||
value = ""; /* set empty value */
|
||||
|
||||
return scoutfs_xattr_set(dentry, name, value, size, flags);
|
||||
}
|
||||
|
||||
int scoutfs_removexattr(struct dentry *dentry, const char *name)
|
||||
{
|
||||
return scoutfs_xattr_set(dentry, name, NULL, 0, XATTR_REPLACE);
|
||||
}
|
||||
|
||||
ssize_t scoutfs_list_xattrs(struct inode *inode, char *buffer,
|
||||
size_t size, __u32 *hash_pos, __u64 *id_pos,
|
||||
bool e_range, bool show_hidden)
|
||||
{
|
||||
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct scoutfs_xattr_prefix_tags tgs;
|
||||
struct scoutfs_xattr *xat = NULL;
|
||||
struct scoutfs_lock *lck = NULL;
|
||||
struct scoutfs_key key;
|
||||
unsigned int bytes;
|
||||
ssize_t total = 0;
|
||||
u32 name_hash = 0;
|
||||
bool is_hidden;
|
||||
u64 id = 0;
|
||||
int ret;
|
||||
|
||||
if (hash_pos)
|
||||
name_hash = *hash_pos;
|
||||
if (id_pos)
|
||||
id = *id_pos;
|
||||
|
||||
/* need a buffer large enough for all possible names */
|
||||
bytes = sizeof(struct scoutfs_xattr) + SCOUTFS_XATTR_MAX_NAME_LEN;
|
||||
xat = kmalloc(bytes, GFP_NOFS);
|
||||
if (!xat) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, 0, inode, &lck);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
down_read(&si->xattr_rwsem);
|
||||
|
||||
for (;;) {
|
||||
ret = get_next_xattr(inode, &key, xat, bytes,
|
||||
NULL, 0, name_hash, id, lck);
|
||||
if (ret < 0) {
|
||||
if (ret == -ENOENT)
|
||||
ret = total;
|
||||
break;
|
||||
}
|
||||
|
||||
is_hidden = scoutfs_xattr_parse_tags(xat->name, xat->name_len,
|
||||
&tgs) == 0 && tgs.hide;
|
||||
|
||||
if (show_hidden == is_hidden) {
|
||||
if (size) {
|
||||
if ((total + xat->name_len + 1) > size) {
|
||||
if (e_range)
|
||||
ret = -ERANGE;
|
||||
else
|
||||
ret = total;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(buffer, xat->name, xat->name_len);
|
||||
buffer += xat->name_len;
|
||||
*(buffer++) = '\0';
|
||||
}
|
||||
|
||||
total += xat->name_len + 1;
|
||||
}
|
||||
|
||||
name_hash = le64_to_cpu(key.skx_name_hash);
|
||||
id = le64_to_cpu(key.skx_id) + 1;
|
||||
}
|
||||
|
||||
up_read(&si->xattr_rwsem);
|
||||
scoutfs_unlock(sb, lck, SCOUTFS_LOCK_READ);
|
||||
out:
|
||||
kfree(xat);
|
||||
|
||||
if (hash_pos)
|
||||
*hash_pos = name_hash;
|
||||
if (id_pos)
|
||||
*id_pos = id;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
|
||||
return scoutfs_list_xattrs(inode, buffer, size,
|
||||
NULL, NULL, true, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete all the xattr items associated with this inode. The inode is
|
||||
* dead so we don't need the xattr rwsem.
|
||||
*/
|
||||
int scoutfs_xattr_drop(struct super_block *sb, u64 ino,
|
||||
struct scoutfs_lock *lock)
|
||||
{
|
||||
struct scoutfs_xattr_prefix_tags tgs;
|
||||
struct scoutfs_xattr *xat = NULL;
|
||||
struct scoutfs_key last;
|
||||
struct scoutfs_key key;
|
||||
bool release = false;
|
||||
unsigned int bytes;
|
||||
u64 hash;
|
||||
int ret;
|
||||
|
||||
/* need a buffer large enough for all possible names */
|
||||
bytes = sizeof(struct scoutfs_xattr) + SCOUTFS_XATTR_MAX_NAME_LEN;
|
||||
xat = kmalloc(bytes, GFP_NOFS);
|
||||
if (!xat) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
init_xattr_key(&key, ino, 0, 0);
|
||||
init_xattr_key(&last, ino, U32_MAX, U64_MAX);
|
||||
|
||||
for (;;) {
|
||||
ret = scoutfs_item_next(sb, &key, &last, (void *)xat, bytes,
|
||||
lock);
|
||||
if (ret < 0) {
|
||||
if (ret == -ENOENT)
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (key.skx_part != 0 ||
|
||||
scoutfs_xattr_parse_tags(xat->name, xat->name_len,
|
||||
&tgs) != 0)
|
||||
memset(&tgs, 0, sizeof(tgs));
|
||||
|
||||
ret = scoutfs_hold_trans(sb, SIC_EXACT(2, 0));
|
||||
if (ret < 0)
|
||||
break;
|
||||
release = true;
|
||||
|
||||
ret = scoutfs_item_delete(sb, &key, lock);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
if (tgs.srch) {
|
||||
hash = scoutfs_hash64(xat->name, xat->name_len);
|
||||
ret = scoutfs_forest_srch_add(sb, hash, ino,
|
||||
le64_to_cpu(key.skx_id));
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
scoutfs_release_trans(sb);
|
||||
release = false;
|
||||
|
||||
/* don't need to inc, next won't see deleted item */
|
||||
}
|
||||
|
||||
if (release)
|
||||
scoutfs_release_trans(sb);
|
||||
kfree(xat);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef _SCOUTFS_XATTR_H_
|
||||
#define _SCOUTFS_XATTR_H_
|
||||
|
||||
ssize_t scoutfs_getxattr(struct dentry *dentry, const char *name, void *buffer,
|
||||
size_t size);
|
||||
int scoutfs_setxattr(struct dentry *dentry, const char *name,
|
||||
const void *value, size_t size, int flags);
|
||||
int scoutfs_removexattr(struct dentry *dentry, const char *name);
|
||||
ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size);
|
||||
ssize_t scoutfs_list_xattrs(struct inode *inode, char *buffer,
|
||||
size_t size, __u32 *hash_pos, __u64 *id_pos,
|
||||
bool e_range, bool show_hidden);
|
||||
|
||||
int scoutfs_xattr_drop(struct super_block *sb, u64 ino,
|
||||
struct scoutfs_lock *lock);
|
||||
|
||||
struct scoutfs_xattr_prefix_tags {
|
||||
unsigned long hide:1,
|
||||
srch:1;
|
||||
};
|
||||
|
||||
int scoutfs_xattr_parse_tags(const char *name, unsigned int name_len,
|
||||
struct scoutfs_xattr_prefix_tags *tgs);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user