986 lines
28 KiB
Diff
986 lines
28 KiB
Diff
From 69023079aedd93721f3ef4abcae080998830703c Mon Sep 17 00:00:00 2001
|
|
From: Paul Lawrence <paullawrence@google.com>
|
|
Date: Tue, 23 Mar 2021 13:45:30 -0700
|
|
Subject: [PATCH 25/31] ANDROID: Incremental fs: Add status to sysfs
|
|
|
|
Adding seven sysfs entries per mount:
|
|
|
|
reads_failed_timed_out
|
|
reads_failed_hash_verification
|
|
reads_failed_other
|
|
reads_delayed_pending
|
|
reads_delayed_pending_us
|
|
reads_delayed_min
|
|
reads_delayed_min_us
|
|
|
|
to allow for status monitoring from userland
|
|
|
|
Change-Id: I50677511c2af4778ba0c574bb80323f31425b4d0
|
|
Test: incfs_test passes
|
|
Bug: 160634343
|
|
Bug: 184291759
|
|
Signed-off-by: Paul Lawrence <paullawrence@google.com>
|
|
---
|
|
Documentation/ABI/testing/sysfs-fs-incfs | 64 ++++++++
|
|
Documentation/filesystems/incfs.rst | 82 ++++++++++
|
|
fs/incfs/Makefile | 1 +
|
|
fs/incfs/data_mgmt.c | 110 +++++++++----
|
|
fs/incfs/data_mgmt.h | 15 +-
|
|
fs/incfs/main.c | 76 +--------
|
|
fs/incfs/sysfs.c | 195 +++++++++++++++++++++++
|
|
fs/incfs/sysfs.h | 48 ++++++
|
|
fs/incfs/verity.c | 2 +-
|
|
fs/incfs/vfs.c | 46 ++++--
|
|
10 files changed, 518 insertions(+), 121 deletions(-)
|
|
create mode 100644 Documentation/ABI/testing/sysfs-fs-incfs
|
|
create mode 100644 Documentation/filesystems/incfs.rst
|
|
create mode 100644 fs/incfs/sysfs.c
|
|
create mode 100644 fs/incfs/sysfs.h
|
|
|
|
diff --git a/Documentation/ABI/testing/sysfs-fs-incfs b/Documentation/ABI/testing/sysfs-fs-incfs
|
|
new file mode 100644
|
|
index 000000000000..690c687c67ab
|
|
--- /dev/null
|
|
+++ b/Documentation/ABI/testing/sysfs-fs-incfs
|
|
@@ -0,0 +1,64 @@
|
|
+What: /sys/fs/incremental-fs/features/corefs
|
|
+Date: 2019
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Reads 'supported'. Always present.
|
|
+
|
|
+What: /sys/fs/incremental-fs/features/v2
|
|
+Date: April 2021
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Reads 'supported'. Present if all v2 features of incfs are
|
|
+ supported.
|
|
+
|
|
+What: /sys/fs/incremental-fs/features/zstd
|
|
+Date: April 2021
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Reads 'supported'. Present if zstd compression is supported
|
|
+ for data blocks.
|
|
+
|
|
+What: /sys/fs/incremental-fs/instances/[name]
|
|
+Date: April 2021
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Folder created when incfs is mounted with the sysfs_name=[name]
|
|
+ option. If this option is used, the following values are created
|
|
+ in this folder.
|
|
+
|
|
+What: /sys/fs/incremental-fs/instances/[name]/reads_delayed_min
|
|
+Date: April 2021
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Returns a count of the number of reads that were delayed as a
|
|
+ result of the per UID read timeouts min time setting.
|
|
+
|
|
+What: /sys/fs/incremental-fs/instances/[name]/reads_delayed_min_us
|
|
+Date: April 2021
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Returns total delay time for all files since first mount as a
|
|
+ result of the per UID read timeouts min time setting.
|
|
+
|
|
+What: /sys/fs/incremental-fs/instances/[name]/reads_delayed_pending
|
|
+Date: April 2021
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Returns a count of the number of reads that were delayed as a
|
|
+ result of waiting for a pending read.
|
|
+
|
|
+What: /sys/fs/incremental-fs/instances/[name]/reads_delayed_pending_us
|
|
+Date: April 2021
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Returns total delay time for all files since first mount as a
|
|
+ result of waiting for a pending read.
|
|
+
|
|
+What: /sys/fs/incremental-fs/instances/[name]/reads_failed_hash_verification
|
|
+Date: April 2021
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Returns number of reads that failed because of hash verification
|
|
+ failures.
|
|
+
|
|
+What: /sys/fs/incremental-fs/instances/[name]/reads_failed_other
|
|
+Date: April 2021
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Returns number of reads that failed for reasons other than
|
|
+ timing out or hash failures.
|
|
+
|
|
+What: /sys/fs/incremental-fs/instances/[name]/reads_failed_timed_out
|
|
+Date: April 2021
|
|
+Contact: Paul Lawrence <paullawrence@google.com>
|
|
+Description: Returns number of reads that timed out.
|
|
diff --git a/Documentation/filesystems/incfs.rst b/Documentation/filesystems/incfs.rst
|
|
new file mode 100644
|
|
index 000000000000..03ae39ec72dc
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/incfs.rst
|
|
@@ -0,0 +1,82 @@
|
|
+.. SPDX-License-Identifier: GPL-2.0
|
|
+
|
|
+=================================================
|
|
+incfs: A stacked incremental filesystem for Linux
|
|
+=================================================
|
|
+
|
|
+/sys/fs interface
|
|
+=================
|
|
+
|
|
+Please update Documentation/ABI/testing/sys-fs-incfs if you update this
|
|
+section.
|
|
+
|
|
+incfs creates the following files in /sys/fs.
|
|
+
|
|
+Features
|
|
+--------
|
|
+
|
|
+/sys/fs/incremental-fs/features/corefs
|
|
+ Reads 'supported'. Always present.
|
|
+
|
|
+/sys/fs/incremental-fs/features/v2
|
|
+ Reads 'supported'. Present if all v2 features of incfs are supported. These
|
|
+ are:
|
|
+ fs-verity support
|
|
+ inotify support
|
|
+ ioclts:
|
|
+ INCFS_IOC_SET_READ_TIMEOUTS
|
|
+ INCFS_IOC_GET_READ_TIMEOUTS
|
|
+ INCFS_IOC_GET_BLOCK_COUNT
|
|
+ INCFS_IOC_CREATE_MAPPED_FILE
|
|
+ .incomplete folder
|
|
+ .blocks_written pseudo file
|
|
+ report_uid mount option
|
|
+
|
|
+/sys/fs/incremental-fs/features/zstd
|
|
+ Reads 'supported'. Present if zstd compression is supported for data blocks.
|
|
+
|
|
+Optional per mount
|
|
+------------------
|
|
+
|
|
+For each incfs mount, the mount option sysfs_name=[name] creates a /sys/fs
|
|
+node called:
|
|
+
|
|
+/sys/fs/incremental-fs/instances/[name]
|
|
+
|
|
+This will contain the following files:
|
|
+
|
|
+/sys/fs/incremental-fs/instances/[name]/reads_delayed_min
|
|
+ Returns a count of the number of reads that were delayed as a result of the
|
|
+ per UID read timeouts min time setting.
|
|
+
|
|
+/sys/fs/incremental-fs/instances/[name]/reads_delayed_min_us
|
|
+ Returns total delay time for all files since first mount as a result of the
|
|
+ per UID read timeouts min time setting.
|
|
+
|
|
+/sys/fs/incremental-fs/instances/[name]/reads_delayed_pending
|
|
+ Returns a count of the number of reads that were delayed as a result of
|
|
+ waiting for a pending read.
|
|
+
|
|
+/sys/fs/incremental-fs/instances/[name]/reads_delayed_pending_us
|
|
+ Returns total delay time for all files since first mount as a result of
|
|
+ waiting for a pending read.
|
|
+
|
|
+/sys/fs/incremental-fs/instances/[name]/reads_failed_hash_verification
|
|
+ Returns number of reads that failed because of hash verification failures.
|
|
+
|
|
+/sys/fs/incremental-fs/instances/[name]/reads_failed_other
|
|
+ Returns number of reads that failed for reasons other than timing out or
|
|
+ hash failures.
|
|
+
|
|
+/sys/fs/incremental-fs/instances/[name]/reads_failed_timed_out
|
|
+ Returns number of reads that timed out.
|
|
+
|
|
+For reads_delayed_*** settings, note that a file can count for both
|
|
+reads_delayed_min and reads_delayed_pending if incfs first waits for a pending
|
|
+read then has to wait further for the min time. In that case, the time spent
|
|
+waiting is split between reads_delayed_pending_us, which is increased by the
|
|
+time spent waiting for the pending read, and reads_delayed_min_us, which is
|
|
+increased by the remainder of the time spent waiting.
|
|
+
|
|
+Reads that timed out are not added to the reads_delayed_pending or the
|
|
+reads_delayed_pending_us counters.
|
|
diff --git a/fs/incfs/Makefile b/fs/incfs/Makefile
|
|
index 3503eda7a6e6..05795d12c874 100644
|
|
--- a/fs/incfs/Makefile
|
|
+++ b/fs/incfs/Makefile
|
|
@@ -7,6 +7,7 @@ incrementalfs-y := \
|
|
integrity.o \
|
|
main.o \
|
|
pseudo_files.o \
|
|
+ sysfs.o \
|
|
vfs.o
|
|
|
|
incrementalfs-$(CONFIG_FS_VERITY) += verity.o
|
|
diff --git a/fs/incfs/data_mgmt.c b/fs/incfs/data_mgmt.c
|
|
index b7727e3a8e43..8a4d87c79d56 100644
|
|
--- a/fs/incfs/data_mgmt.c
|
|
+++ b/fs/incfs/data_mgmt.c
|
|
@@ -7,6 +7,7 @@
|
|
#include <linux/file.h>
|
|
#include <linux/fsverity.h>
|
|
#include <linux/gfp.h>
|
|
+#include <linux/kobject.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/lz4.h>
|
|
#include <linux/mm.h>
|
|
@@ -19,6 +20,7 @@
|
|
#include "data_mgmt.h"
|
|
#include "format.h"
|
|
#include "integrity.h"
|
|
+#include "sysfs.h"
|
|
#include "verity.h"
|
|
|
|
static int incfs_scan_metadata_chain(struct data_file *df);
|
|
@@ -49,6 +51,7 @@ struct mount_info *incfs_alloc_mount_info(struct super_block *sb,
|
|
{
|
|
struct mount_info *mi = NULL;
|
|
int error = 0;
|
|
+ struct incfs_sysfs_node *node;
|
|
|
|
mi = kzalloc(sizeof(*mi), GFP_NOFS);
|
|
if (!mi)
|
|
@@ -71,6 +74,13 @@ struct mount_info *incfs_alloc_mount_info(struct super_block *sb,
|
|
mutex_init(&mi->mi_zstd_workspace_mutex);
|
|
INIT_DELAYED_WORK(&mi->mi_zstd_cleanup_work, zstd_free_workspace);
|
|
|
|
+ node = incfs_add_sysfs_node(options->sysfs_name);
|
|
+ if (IS_ERR(node)) {
|
|
+ error = PTR_ERR(node);
|
|
+ goto err;
|
|
+ }
|
|
+ mi->mi_sysfs_node = node;
|
|
+
|
|
error = incfs_realloc_mount_info(mi, options);
|
|
if (error)
|
|
goto err;
|
|
@@ -119,6 +129,15 @@ int incfs_realloc_mount_info(struct mount_info *mi,
|
|
kfree(old_buffer);
|
|
}
|
|
|
|
+ if ((options->sysfs_name && !mi->mi_sysfs_node) ||
|
|
+ (!options->sysfs_name && mi->mi_sysfs_node) ||
|
|
+ (options->sysfs_name &&
|
|
+ strcmp(options->sysfs_name,
|
|
+ kobject_name(&mi->mi_sysfs_node->isn_sysfs_node)))) {
|
|
+ pr_err("incfs: Can't change sysfs_name mount option on remount\n");
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+
|
|
mi->mi_options = *options;
|
|
return 0;
|
|
}
|
|
@@ -142,6 +161,7 @@ void incfs_free_mount_info(struct mount_info *mi)
|
|
for (i = 0; i < ARRAY_SIZE(mi->pseudo_file_xattr); ++i)
|
|
kfree(mi->pseudo_file_xattr[i].data);
|
|
kfree(mi->mi_per_uid_read_timeouts);
|
|
+ incfs_free_sysfs_node(mi->mi_sysfs_node);
|
|
kfree(mi);
|
|
}
|
|
|
|
@@ -1088,17 +1108,16 @@ static int usleep_interruptible(u32 us)
|
|
}
|
|
|
|
static int wait_for_data_block(struct data_file *df, int block_index,
|
|
- u32 min_time_us, u32 min_pending_time_us,
|
|
- u32 max_pending_time_us,
|
|
- struct data_file_block *res_block)
|
|
+ struct data_file_block *res_block,
|
|
+ struct incfs_read_data_file_timeouts *timeouts)
|
|
{
|
|
struct data_file_block block = {};
|
|
struct data_file_segment *segment = NULL;
|
|
struct pending_read *read = NULL;
|
|
struct mount_info *mi = NULL;
|
|
- int error = 0;
|
|
+ int error;
|
|
int wait_res = 0;
|
|
- u64 time;
|
|
+ unsigned int delayed_pending_us = 0, delayed_min_us = 0;
|
|
|
|
if (!df || !res_block)
|
|
return -EFAULT;
|
|
@@ -1126,13 +1145,16 @@ static int wait_for_data_block(struct data_file *df, int block_index,
|
|
|
|
/* If the block was found, just return it. No need to wait. */
|
|
if (is_data_block_present(&block)) {
|
|
- if (min_time_us)
|
|
- error = usleep_interruptible(min_time_us);
|
|
*res_block = block;
|
|
- return error;
|
|
+ if (timeouts && timeouts->min_time_us) {
|
|
+ delayed_min_us = timeouts->min_time_us;
|
|
+ error = usleep_interruptible(delayed_min_us);
|
|
+ goto out;
|
|
+ }
|
|
+ return 0;
|
|
} else {
|
|
/* If it's not found, create a pending read */
|
|
- if (max_pending_time_us != 0) {
|
|
+ if (timeouts && timeouts->max_pending_time_us) {
|
|
read = add_pending_read(df, block_index);
|
|
if (!read)
|
|
return -ENOMEM;
|
|
@@ -1142,14 +1164,17 @@ static int wait_for_data_block(struct data_file *df, int block_index,
|
|
}
|
|
}
|
|
|
|
- if (min_pending_time_us)
|
|
- time = ktime_get_ns();
|
|
+ /* Rest of function only applies if timeouts != NULL */
|
|
+ if (!timeouts) {
|
|
+ pr_warn("incfs: timeouts unexpectedly NULL\n");
|
|
+ return -EFSCORRUPTED;
|
|
+ }
|
|
|
|
/* Wait for notifications about block's arrival */
|
|
wait_res =
|
|
wait_event_interruptible_timeout(segment->new_data_arrival_wq,
|
|
- (is_read_done(read)),
|
|
- usecs_to_jiffies(max_pending_time_us));
|
|
+ (is_read_done(read)),
|
|
+ usecs_to_jiffies(timeouts->max_pending_time_us));
|
|
|
|
/* Woke up, the pending read is no longer needed. */
|
|
remove_pending_read(df, read);
|
|
@@ -1167,14 +1192,14 @@ static int wait_for_data_block(struct data_file *df, int block_index,
|
|
return wait_res;
|
|
}
|
|
|
|
- if (min_pending_time_us) {
|
|
- time = div_u64(ktime_get_ns() - time, 1000);
|
|
- if (min_pending_time_us > time) {
|
|
- error = usleep_interruptible(
|
|
- min_pending_time_us - time);
|
|
- if (error)
|
|
- return error;
|
|
- }
|
|
+ delayed_pending_us = timeouts->max_pending_time_us -
|
|
+ jiffies_to_usecs(wait_res);
|
|
+ if (timeouts->min_pending_time_us > delayed_pending_us) {
|
|
+ delayed_min_us = timeouts->min_pending_time_us -
|
|
+ delayed_pending_us;
|
|
+ error = usleep_interruptible(delayed_min_us);
|
|
+ if (error)
|
|
+ return error;
|
|
}
|
|
|
|
error = down_read_killable(&segment->rwsem);
|
|
@@ -1182,7 +1207,7 @@ static int wait_for_data_block(struct data_file *df, int block_index,
|
|
return error;
|
|
|
|
/*
|
|
- * Re-read block's info now, it has just arrived and
|
|
+ * Re-read blocks info now, it has just arrived and
|
|
* should be available.
|
|
*/
|
|
error = get_data_file_block(df, block_index, &block);
|
|
@@ -1191,22 +1216,39 @@ static int wait_for_data_block(struct data_file *df, int block_index,
|
|
*res_block = block;
|
|
else {
|
|
/*
|
|
- * Somehow wait finished successfully bug block still
|
|
+ * Somehow wait finished successfully but block still
|
|
* can't be found. It's not normal.
|
|
*/
|
|
pr_warn("incfs: Wait succeeded but block not found.\n");
|
|
error = -ENODATA;
|
|
}
|
|
}
|
|
-
|
|
up_read(&segment->rwsem);
|
|
- return error;
|
|
+
|
|
+out:
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ if (!mi->mi_sysfs_node)
|
|
+ return 0;
|
|
+
|
|
+ if (delayed_pending_us) {
|
|
+ mi->mi_sysfs_node->isn_reads_delayed_pending++;
|
|
+ mi->mi_sysfs_node->isn_reads_delayed_pending_us +=
|
|
+ delayed_pending_us;
|
|
+ }
|
|
+
|
|
+ if (delayed_min_us) {
|
|
+ mi->mi_sysfs_node->isn_reads_delayed_min++;
|
|
+ mi->mi_sysfs_node->isn_reads_delayed_min_us += delayed_min_us;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
ssize_t incfs_read_data_file_block(struct mem_range dst, struct file *f,
|
|
- int index, u32 min_time_us,
|
|
- u32 min_pending_time_us, u32 max_pending_time_us,
|
|
- struct mem_range tmp)
|
|
+ int index, struct mem_range tmp,
|
|
+ struct incfs_read_data_file_timeouts *timeouts)
|
|
{
|
|
loff_t pos;
|
|
ssize_t result;
|
|
@@ -1225,8 +1267,7 @@ ssize_t incfs_read_data_file_block(struct mem_range dst, struct file *f,
|
|
mi = df->df_mount_info;
|
|
bfc = df->df_backing_file_context;
|
|
|
|
- result = wait_for_data_block(df, index, min_time_us,
|
|
- min_pending_time_us, max_pending_time_us, &block);
|
|
+ result = wait_for_data_block(df, index, &block, timeouts);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
@@ -1269,6 +1310,15 @@ ssize_t incfs_read_data_file_block(struct mem_range dst, struct file *f,
|
|
log_block_read(mi, &df->df_id, index);
|
|
|
|
out:
|
|
+ if (mi->mi_sysfs_node) {
|
|
+ if (result == -ETIME)
|
|
+ mi->mi_sysfs_node->isn_reads_failed_timed_out++;
|
|
+ else if (result == -EBADMSG)
|
|
+ mi->mi_sysfs_node->isn_reads_failed_hash_verification++;
|
|
+ else if (result < 0)
|
|
+ mi->mi_sysfs_node->isn_reads_failed_other++;
|
|
+ }
|
|
+
|
|
return result;
|
|
}
|
|
|
|
diff --git a/fs/incfs/data_mgmt.h b/fs/incfs/data_mgmt.h
|
|
index 516e2e0dd5da..010bf4ec90da 100644
|
|
--- a/fs/incfs/data_mgmt.h
|
|
+++ b/fs/incfs/data_mgmt.h
|
|
@@ -122,6 +122,7 @@ struct mount_options {
|
|
unsigned int read_log_pages;
|
|
unsigned int read_log_wakeup_count;
|
|
bool report_uid;
|
|
+ char *sysfs_name;
|
|
};
|
|
|
|
struct mount_info {
|
|
@@ -188,6 +189,9 @@ struct mount_info {
|
|
void *mi_zstd_workspace;
|
|
ZSTD_DStream *mi_zstd_stream;
|
|
struct delayed_work mi_zstd_cleanup_work;
|
|
+
|
|
+ /* sysfs node */
|
|
+ struct incfs_sysfs_node *mi_sysfs_node;
|
|
};
|
|
|
|
struct data_file_block {
|
|
@@ -372,10 +376,15 @@ void incfs_free_data_file(struct data_file *df);
|
|
struct dir_file *incfs_open_dir_file(struct mount_info *mi, struct file *bf);
|
|
void incfs_free_dir_file(struct dir_file *dir);
|
|
|
|
+struct incfs_read_data_file_timeouts {
|
|
+ u32 min_time_us;
|
|
+ u32 min_pending_time_us;
|
|
+ u32 max_pending_time_us;
|
|
+};
|
|
+
|
|
ssize_t incfs_read_data_file_block(struct mem_range dst, struct file *f,
|
|
- int index, u32 min_time_us,
|
|
- u32 min_pending_time_us, u32 max_pending_time_us,
|
|
- struct mem_range tmp);
|
|
+ int index, struct mem_range tmp,
|
|
+ struct incfs_read_data_file_timeouts *timeouts);
|
|
|
|
int incfs_get_filled_blocks(struct data_file *df,
|
|
struct incfs_file_data *fd,
|
|
diff --git a/fs/incfs/main.c b/fs/incfs/main.c
|
|
index 23cf3fefac97..23347acac8bf 100644
|
|
--- a/fs/incfs/main.c
|
|
+++ b/fs/incfs/main.c
|
|
@@ -8,10 +8,9 @@
|
|
|
|
#include <uapi/linux/incrementalfs.h>
|
|
|
|
+#include "sysfs.h"
|
|
#include "vfs.h"
|
|
|
|
-#define INCFS_NODE_FEATURES "features"
|
|
-
|
|
static struct file_system_type incfs_fs_type = {
|
|
.owner = THIS_MODULE,
|
|
.name = INCFS_NAME,
|
|
@@ -20,91 +19,24 @@ static struct file_system_type incfs_fs_type = {
|
|
.fs_flags = 0
|
|
};
|
|
|
|
-static struct kobject *sysfs_root, *featurefs_root;
|
|
-
|
|
-static ssize_t supported(struct kobject *kobj,
|
|
- struct kobj_attribute *attr, char *buff)
|
|
-{
|
|
- return snprintf(buff, PAGE_SIZE, "supported\n");
|
|
-}
|
|
-
|
|
-typedef ssize_t (*const attr_show)(struct kobject *kobj,
|
|
- struct kobj_attribute *attr, char *buff);
|
|
-
|
|
-#define _DECLARE_FEATURE_FLAG(name) \
|
|
- static attr_show name##_show = supported; \
|
|
- static struct kobj_attribute name##_attr = __ATTR_RO(name)
|
|
-
|
|
-#define DECLARE_FEATURE_FLAG(name) _DECLARE_FEATURE_FLAG(name)
|
|
-
|
|
-DECLARE_FEATURE_FLAG(corefs);
|
|
-DECLARE_FEATURE_FLAG(zstd);
|
|
-DECLARE_FEATURE_FLAG(v2);
|
|
-
|
|
-static struct attribute *attributes[] = {
|
|
- &corefs_attr.attr,
|
|
- &zstd_attr.attr,
|
|
- &v2_attr.attr,
|
|
- NULL,
|
|
-};
|
|
-
|
|
-static const struct attribute_group attr_group = {
|
|
- .attrs = attributes,
|
|
-};
|
|
-
|
|
-static int __init init_sysfs(void)
|
|
-{
|
|
- int res = 0;
|
|
-
|
|
- sysfs_root = kobject_create_and_add(INCFS_NAME, fs_kobj);
|
|
- if (!sysfs_root)
|
|
- return -ENOMEM;
|
|
-
|
|
- featurefs_root = kobject_create_and_add(INCFS_NODE_FEATURES,
|
|
- sysfs_root);
|
|
- if (!featurefs_root)
|
|
- return -ENOMEM;
|
|
-
|
|
- res = sysfs_create_group(featurefs_root, &attr_group);
|
|
- if (res) {
|
|
- kobject_put(sysfs_root);
|
|
- sysfs_root = NULL;
|
|
- }
|
|
- return res;
|
|
-}
|
|
-
|
|
-static void cleanup_sysfs(void)
|
|
-{
|
|
- if (featurefs_root) {
|
|
- sysfs_remove_group(featurefs_root, &attr_group);
|
|
- kobject_put(featurefs_root);
|
|
- featurefs_root = NULL;
|
|
- }
|
|
-
|
|
- if (sysfs_root) {
|
|
- kobject_put(sysfs_root);
|
|
- sysfs_root = NULL;
|
|
- }
|
|
-}
|
|
-
|
|
static int __init init_incfs_module(void)
|
|
{
|
|
int err = 0;
|
|
|
|
- err = init_sysfs();
|
|
+ err = incfs_init_sysfs();
|
|
if (err)
|
|
return err;
|
|
|
|
err = register_filesystem(&incfs_fs_type);
|
|
if (err)
|
|
- cleanup_sysfs();
|
|
+ incfs_cleanup_sysfs();
|
|
|
|
return err;
|
|
}
|
|
|
|
static void __exit cleanup_incfs_module(void)
|
|
{
|
|
- cleanup_sysfs();
|
|
+ incfs_cleanup_sysfs();
|
|
unregister_filesystem(&incfs_fs_type);
|
|
}
|
|
|
|
diff --git a/fs/incfs/sysfs.c b/fs/incfs/sysfs.c
|
|
new file mode 100644
|
|
index 000000000000..27aee9ed4341
|
|
--- /dev/null
|
|
+++ b/fs/incfs/sysfs.c
|
|
@@ -0,0 +1,195 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright 2021 Google LLC
|
|
+ */
|
|
+#include <linux/fs.h>
|
|
+#include <linux/kobject.h>
|
|
+
|
|
+#include <uapi/linux/incrementalfs.h>
|
|
+
|
|
+#include "sysfs.h"
|
|
+#include "data_mgmt.h"
|
|
+#include "vfs.h"
|
|
+
|
|
+/******************************************************************************
|
|
+ * Define sys/fs/incrementalfs & sys/fs/incrementalfs/features
|
|
+ *****************************************************************************/
|
|
+#define INCFS_NODE_FEATURES "features"
|
|
+#define INCFS_NODE_INSTANCES "instances"
|
|
+
|
|
+static struct kobject *sysfs_root;
|
|
+static struct kobject *features_node;
|
|
+static struct kobject *instances_node;
|
|
+
|
|
+#define DECLARE_FEATURE_FLAG(name) \
|
|
+ static ssize_t name##_show(struct kobject *kobj, \
|
|
+ struct kobj_attribute *attr, char *buff) \
|
|
+{ \
|
|
+ return sysfs_emit(buff, "supported\n"); \
|
|
+} \
|
|
+ \
|
|
+static struct kobj_attribute name##_attr = __ATTR_RO(name)
|
|
+
|
|
+DECLARE_FEATURE_FLAG(corefs);
|
|
+DECLARE_FEATURE_FLAG(zstd);
|
|
+DECLARE_FEATURE_FLAG(v2);
|
|
+
|
|
+static struct attribute *attributes[] = {
|
|
+ &corefs_attr.attr,
|
|
+ &zstd_attr.attr,
|
|
+ &v2_attr.attr,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+static const struct attribute_group attr_group = {
|
|
+ .attrs = attributes,
|
|
+};
|
|
+
|
|
+int __init incfs_init_sysfs(void)
|
|
+{
|
|
+ int res = -ENOMEM;
|
|
+
|
|
+ sysfs_root = kobject_create_and_add(INCFS_NAME, fs_kobj);
|
|
+ if (!sysfs_root)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ instances_node = kobject_create_and_add(INCFS_NODE_INSTANCES,
|
|
+ sysfs_root);
|
|
+ if (!instances_node)
|
|
+ goto err_put_root;
|
|
+
|
|
+ features_node = kobject_create_and_add(INCFS_NODE_FEATURES,
|
|
+ sysfs_root);
|
|
+ if (!features_node)
|
|
+ goto err_put_instances;
|
|
+
|
|
+ res = sysfs_create_group(features_node, &attr_group);
|
|
+ if (res)
|
|
+ goto err_put_features;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_put_features:
|
|
+ kobject_put(features_node);
|
|
+err_put_instances:
|
|
+ kobject_put(instances_node);
|
|
+err_put_root:
|
|
+ kobject_put(sysfs_root);
|
|
+
|
|
+ return res;
|
|
+}
|
|
+
|
|
+void incfs_cleanup_sysfs(void)
|
|
+{
|
|
+ if (features_node) {
|
|
+ sysfs_remove_group(features_node, &attr_group);
|
|
+ kobject_put(features_node);
|
|
+ }
|
|
+
|
|
+ kobject_put(instances_node);
|
|
+ kobject_put(sysfs_root);
|
|
+}
|
|
+
|
|
+/******************************************************************************
|
|
+ * Define sys/fs/incrementalfs/instances/<name>/
|
|
+ *****************************************************************************/
|
|
+#define __DECLARE_STATUS_FLAG(name) \
|
|
+static ssize_t name##_show(struct kobject *kobj, \
|
|
+ struct kobj_attribute *attr, char *buff) \
|
|
+{ \
|
|
+ struct incfs_sysfs_node *node = container_of(kobj, \
|
|
+ struct incfs_sysfs_node, isn_sysfs_node); \
|
|
+ \
|
|
+ return sysfs_emit(buff, "%d\n", node->isn_##name); \
|
|
+} \
|
|
+ \
|
|
+static struct kobj_attribute name##_attr = __ATTR_RO(name)
|
|
+
|
|
+#define __DECLARE_STATUS_FLAG64(name) \
|
|
+static ssize_t name##_show(struct kobject *kobj, \
|
|
+ struct kobj_attribute *attr, char *buff) \
|
|
+{ \
|
|
+ struct incfs_sysfs_node *node = container_of(kobj, \
|
|
+ struct incfs_sysfs_node, isn_sysfs_node); \
|
|
+ \
|
|
+ return sysfs_emit(buff, "%lld\n", node->isn_##name); \
|
|
+} \
|
|
+ \
|
|
+static struct kobj_attribute name##_attr = __ATTR_RO(name)
|
|
+
|
|
+__DECLARE_STATUS_FLAG(reads_failed_timed_out);
|
|
+__DECLARE_STATUS_FLAG(reads_failed_hash_verification);
|
|
+__DECLARE_STATUS_FLAG(reads_failed_other);
|
|
+__DECLARE_STATUS_FLAG(reads_delayed_pending);
|
|
+__DECLARE_STATUS_FLAG64(reads_delayed_pending_us);
|
|
+__DECLARE_STATUS_FLAG(reads_delayed_min);
|
|
+__DECLARE_STATUS_FLAG64(reads_delayed_min_us);
|
|
+
|
|
+static struct attribute *mount_attributes[] = {
|
|
+ &reads_failed_timed_out_attr.attr,
|
|
+ &reads_failed_hash_verification_attr.attr,
|
|
+ &reads_failed_other_attr.attr,
|
|
+ &reads_delayed_pending_attr.attr,
|
|
+ &reads_delayed_pending_us_attr.attr,
|
|
+ &reads_delayed_min_attr.attr,
|
|
+ &reads_delayed_min_us_attr.attr,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+static void incfs_sysfs_release(struct kobject *kobj)
|
|
+{
|
|
+ struct incfs_sysfs_node *node = container_of(kobj,
|
|
+ struct incfs_sysfs_node, isn_sysfs_node);
|
|
+
|
|
+ kfree(node);
|
|
+}
|
|
+
|
|
+static const struct attribute_group mount_attr_group = {
|
|
+ .attrs = mount_attributes,
|
|
+};
|
|
+
|
|
+static struct kobj_type incfs_kobj_node_ktype = {
|
|
+ .sysfs_ops = &kobj_sysfs_ops,
|
|
+ .release = &incfs_sysfs_release,
|
|
+};
|
|
+
|
|
+struct incfs_sysfs_node *incfs_add_sysfs_node(const char *name)
|
|
+{
|
|
+ struct incfs_sysfs_node *node = NULL;
|
|
+ int error;
|
|
+
|
|
+ if (!name)
|
|
+ return NULL;
|
|
+
|
|
+ node = kzalloc(sizeof(*node), GFP_NOFS);
|
|
+ if (!node)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ kobject_init(&node->isn_sysfs_node, &incfs_kobj_node_ktype);
|
|
+ error = kobject_add(&node->isn_sysfs_node, instances_node, "%s", name);
|
|
+ if (error)
|
|
+ goto err;
|
|
+
|
|
+ error = sysfs_create_group(&node->isn_sysfs_node, &mount_attr_group);
|
|
+ if (error)
|
|
+ goto err;
|
|
+
|
|
+ return node;
|
|
+
|
|
+err:
|
|
+ /*
|
|
+ * Note kobject_put always calls release, so incfs_sysfs_release will
|
|
+ * free node
|
|
+ */
|
|
+ kobject_put(&node->isn_sysfs_node);
|
|
+ return ERR_PTR(error);
|
|
+}
|
|
+
|
|
+void incfs_free_sysfs_node(struct incfs_sysfs_node *node)
|
|
+{
|
|
+ if (!node)
|
|
+ return;
|
|
+
|
|
+ sysfs_remove_group(&node->isn_sysfs_node, &mount_attr_group);
|
|
+ kobject_put(&node->isn_sysfs_node);
|
|
+}
|
|
diff --git a/fs/incfs/sysfs.h b/fs/incfs/sysfs.h
|
|
new file mode 100644
|
|
index 000000000000..446813fac205
|
|
--- /dev/null
|
|
+++ b/fs/incfs/sysfs.h
|
|
@@ -0,0 +1,48 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright 2021 Google LLC
|
|
+ */
|
|
+#ifndef _INCFS_SYSFS_H
|
|
+#define _INCFS_SYSFS_H
|
|
+
|
|
+struct incfs_sysfs_node {
|
|
+ struct kobject isn_sysfs_node;
|
|
+
|
|
+ /* Number of reads timed out */
|
|
+ u32 isn_reads_failed_timed_out;
|
|
+
|
|
+ /* Number of reads failed because hash verification failed */
|
|
+ u32 isn_reads_failed_hash_verification;
|
|
+
|
|
+ /* Number of reads failed for another reason */
|
|
+ u32 isn_reads_failed_other;
|
|
+
|
|
+ /* Number of reads delayed because page had to be fetched */
|
|
+ u32 isn_reads_delayed_pending;
|
|
+
|
|
+ /* Total time waiting for pages to be fetched */
|
|
+ u64 isn_reads_delayed_pending_us;
|
|
+
|
|
+ /*
|
|
+ * Number of reads delayed because of per-uid min_time_us or
|
|
+ * min_pending_time_us settings
|
|
+ */
|
|
+ u32 isn_reads_delayed_min;
|
|
+
|
|
+ /* Total time waiting because of per-uid min_time_us or
|
|
+ * min_pending_time_us settings.
|
|
+ *
|
|
+ * Note that if a read is initially delayed because we have to wait for
|
|
+ * the page, then further delayed because of min_pending_time_us
|
|
+ * setting, this counter gets incremented by only the further delay
|
|
+ * time.
|
|
+ */
|
|
+ u64 isn_reads_delayed_min_us;
|
|
+};
|
|
+
|
|
+int incfs_init_sysfs(void);
|
|
+void incfs_cleanup_sysfs(void);
|
|
+struct incfs_sysfs_node *incfs_add_sysfs_node(const char *name);
|
|
+void incfs_free_sysfs_node(struct incfs_sysfs_node *node);
|
|
+
|
|
+#endif
|
|
diff --git a/fs/incfs/verity.c b/fs/incfs/verity.c
|
|
index 1131aa8f2373..313440f787d9 100644
|
|
--- a/fs/incfs/verity.c
|
|
+++ b/fs/incfs/verity.c
|
|
@@ -308,7 +308,7 @@ static int incfs_build_merkle_tree(struct file *f, struct data_file *df,
|
|
|
|
if (lvl == 0)
|
|
result = incfs_read_data_file_block(partial_buf,
|
|
- f, i, 0, 0, 0, tmp);
|
|
+ f, i, tmp, NULL);
|
|
else {
|
|
hash_level_offset = hash_offset +
|
|
hash_tree->hash_level_suboffset[lvl - 1];
|
|
diff --git a/fs/incfs/vfs.c b/fs/incfs/vfs.c
|
|
index 46dfabfebd52..9531f20b300f 100644
|
|
--- a/fs/incfs/vfs.c
|
|
+++ b/fs/incfs/vfs.c
|
|
@@ -196,6 +196,7 @@ enum parse_parameter {
|
|
Opt_rlog_pages,
|
|
Opt_rlog_wakeup_cnt,
|
|
Opt_report_uid,
|
|
+ Opt_sysfs_name,
|
|
Opt_err
|
|
};
|
|
|
|
@@ -205,9 +206,16 @@ static const match_table_t option_tokens = {
|
|
{ Opt_rlog_pages, "rlog_pages=%u" },
|
|
{ Opt_rlog_wakeup_cnt, "rlog_wakeup_cnt=%u" },
|
|
{ Opt_report_uid, "report_uid" },
|
|
+ { Opt_sysfs_name, "sysfs_name=%s" },
|
|
{ Opt_err, NULL }
|
|
};
|
|
|
|
+static void free_options(struct mount_options *opts)
|
|
+{
|
|
+ kfree(opts->sysfs_name);
|
|
+ opts->sysfs_name = NULL;
|
|
+}
|
|
+
|
|
static int parse_options(struct mount_options *opts, char *str)
|
|
{
|
|
substring_t args[MAX_OPT_ARGS];
|
|
@@ -261,7 +269,11 @@ static int parse_options(struct mount_options *opts, char *str)
|
|
case Opt_report_uid:
|
|
opts->report_uid = true;
|
|
break;
|
|
+ case Opt_sysfs_name:
|
|
+ opts->sysfs_name = match_strdup(&args[0]);
|
|
+ break;
|
|
default:
|
|
+ free_options(opts);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
@@ -460,9 +472,9 @@ static int read_single_page_timeouts(struct data_file *df, struct file *f,
|
|
struct mem_range tmp)
|
|
{
|
|
struct mount_info *mi = df->df_mount_info;
|
|
- u32 min_time_us = 0;
|
|
- u32 min_pending_time_us = 0;
|
|
- u32 max_pending_time_us = U32_MAX;
|
|
+ struct incfs_read_data_file_timeouts timeouts = {
|
|
+ .max_pending_time_us = U32_MAX,
|
|
+ };
|
|
int uid = current_uid().val;
|
|
int i;
|
|
|
|
@@ -473,24 +485,23 @@ static int read_single_page_timeouts(struct data_file *df, struct file *f,
|
|
&mi->mi_per_uid_read_timeouts[i];
|
|
|
|
if(t->uid == uid) {
|
|
- min_time_us = t->min_time_us;
|
|
- min_pending_time_us = t->min_pending_time_us;
|
|
- max_pending_time_us = t->max_pending_time_us;
|
|
+ timeouts.min_time_us = t->min_time_us;
|
|
+ timeouts.min_pending_time_us = t->min_pending_time_us;
|
|
+ timeouts.max_pending_time_us = t->max_pending_time_us;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock(&mi->mi_per_uid_read_timeouts_lock);
|
|
- if (max_pending_time_us == U32_MAX) {
|
|
+ if (timeouts.max_pending_time_us == U32_MAX) {
|
|
u64 read_timeout_us = (u64)mi->mi_options.read_timeout_ms *
|
|
1000;
|
|
|
|
- max_pending_time_us = read_timeout_us <= U32_MAX ?
|
|
- read_timeout_us : U32_MAX;
|
|
+ timeouts.max_pending_time_us = read_timeout_us <= U32_MAX ?
|
|
+ read_timeout_us : U32_MAX;
|
|
}
|
|
|
|
- return incfs_read_data_file_block(range, f, block_index,
|
|
- min_time_us, min_pending_time_us, max_pending_time_us,
|
|
- tmp);
|
|
+ return incfs_read_data_file_block(range, f, block_index, tmp,
|
|
+ &timeouts);
|
|
}
|
|
|
|
static int read_single_page(struct file *f, struct page *page)
|
|
@@ -1822,6 +1833,7 @@ struct dentry *incfs_mount_fs(struct file_system_type *type, int flags,
|
|
path_put(&backing_dir_path);
|
|
incfs_free_mount_info(mi);
|
|
deactivate_locked_super(sb);
|
|
+ free_options(&options);
|
|
return ERR_PTR(error);
|
|
}
|
|
|
|
@@ -1838,15 +1850,19 @@ static int incfs_remount_fs(struct super_block *sb, int *flags, char *data)
|
|
|
|
if (options.report_uid != mi->mi_options.report_uid) {
|
|
pr_err("incfs: Can't change report_uid mount option on remount\n");
|
|
- return -EOPNOTSUPP;
|
|
+ err = -EOPNOTSUPP;
|
|
+ goto out;
|
|
}
|
|
|
|
err = incfs_realloc_mount_info(mi, &options);
|
|
if (err)
|
|
- return err;
|
|
+ goto out;
|
|
|
|
pr_debug("incfs: remount\n");
|
|
- return 0;
|
|
+
|
|
+out:
|
|
+ free_options(&options);
|
|
+ return err;
|
|
}
|
|
|
|
void incfs_kill_sb(struct super_block *sb)
|
|
--
|
|
2.17.1
|
|
|