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>
This commit is contained in:
parent
3d471f0108
commit
44ffa65110
11 changed files with 700 additions and 122 deletions
64
Documentation/ABI/testing/sysfs-fs-incfs
Normal file
64
Documentation/ABI/testing/sysfs-fs-incfs
Normal file
|
|
@ -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.
|
||||
82
Documentation/filesystems/incfs.rst
Normal file
82
Documentation/filesystems/incfs.rst
Normal file
|
|
@ -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.
|
||||
|
|
@ -7,6 +7,7 @@ incrementalfs-y := \
|
|||
integrity.o \
|
||||
main.o \
|
||||
pseudo_files.o \
|
||||
sysfs.o \
|
||||
vfs.o
|
||||
|
||||
incrementalfs-$(CONFIG_FS_VERITY) += verity.o
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
ssize_t incfs_read_merkle_tree_blocks(struct mem_range dst,
|
||||
struct data_file *df, size_t offset);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
195
fs/incfs/sysfs.c
Normal file
195
fs/incfs/sysfs.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
48
fs/incfs/sysfs.h
Normal file
48
fs/incfs/sysfs.h
Normal file
|
|
@ -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
|
||||
|
|
@ -323,7 +323,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];
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ enum parse_parameter {
|
|||
Opt_rlog_pages,
|
||||
Opt_rlog_wakeup_cnt,
|
||||
Opt_report_uid,
|
||||
Opt_sysfs_name,
|
||||
Opt_err
|
||||
};
|
||||
|
||||
|
|
@ -207,9 +208,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];
|
||||
|
|
@ -263,7 +271,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -462,9 +474,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;
|
||||
|
||||
|
|
@ -475,24 +487,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)
|
||||
|
|
@ -1827,6 +1838,7 @@ err:
|
|||
path_put(&backing_dir_path);
|
||||
incfs_free_mount_info(mi);
|
||||
deactivate_locked_super(sb);
|
||||
free_options(&options);
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
|
||||
|
|
@ -1843,15 +1855,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)
|
||||
|
|
|
|||
|
|
@ -85,6 +85,16 @@ struct {
|
|||
#define TESTNE(statement, res) \
|
||||
TESTCOND((statement) != (res))
|
||||
|
||||
#define TESTSYSCALL(statement) \
|
||||
do { \
|
||||
int res = statement; \
|
||||
\
|
||||
if (res) \
|
||||
ksft_print_msg("Failed: %s (%d)\n", \
|
||||
strerror(errno), errno); \
|
||||
TESTEQUAL(res, 0); \
|
||||
} while (false)
|
||||
|
||||
void print_bytes(const void *data, size_t size)
|
||||
{
|
||||
const uint8_t *bytes = data;
|
||||
|
|
@ -3545,7 +3555,7 @@ out:
|
|||
|
||||
free(filename);
|
||||
close(cmd_fd);
|
||||
umount(backing_dir);
|
||||
umount(mount_dir);
|
||||
free(backing_dir);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -4320,7 +4330,177 @@ static int stat_test(const char *mount_dir)
|
|||
|
||||
result = TEST_SUCCESS;
|
||||
out:
|
||||
close(cmd_fd);
|
||||
umount(mount_dir);
|
||||
free(backing_dir);
|
||||
return result;
|
||||
}
|
||||
|
||||
#define SYSFS_DIR "/sys/fs/incremental-fs/instances/test_node/"
|
||||
|
||||
static int sysfs_test_value(const char *name, uint64_t value)
|
||||
{
|
||||
int result = TEST_FAILURE;
|
||||
char *filename = NULL;
|
||||
FILE *file = NULL;
|
||||
uint64_t res;
|
||||
|
||||
TEST(filename = concat_file_name(SYSFS_DIR, name), filename);
|
||||
TEST(file = fopen(filename, "re"), file);
|
||||
TESTEQUAL(fscanf(file, "%lu", &res), 1);
|
||||
TESTEQUAL(res, value);
|
||||
|
||||
result = TEST_SUCCESS;
|
||||
out:
|
||||
if (file)
|
||||
fclose(file);
|
||||
free(filename);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int sysfs_test_value_range(const char *name, uint64_t low, uint64_t high)
|
||||
{
|
||||
int result = TEST_FAILURE;
|
||||
char *filename = NULL;
|
||||
FILE *file = NULL;
|
||||
uint64_t res;
|
||||
|
||||
TEST(filename = concat_file_name(SYSFS_DIR, name), filename);
|
||||
TEST(file = fopen(filename, "re"), file);
|
||||
TESTEQUAL(fscanf(file, "%lu", &res), 1);
|
||||
TESTCOND(res >= low && res <= high);
|
||||
|
||||
result = TEST_SUCCESS;
|
||||
out:
|
||||
if (file)
|
||||
fclose(file);
|
||||
free(filename);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int sysfs_test(const char *mount_dir)
|
||||
{
|
||||
int result = TEST_FAILURE;
|
||||
char *backing_dir = NULL;
|
||||
int cmd_fd = -1;
|
||||
struct test_file file = {
|
||||
.name = "file",
|
||||
.size = INCFS_DATA_FILE_BLOCK_SIZE,
|
||||
};
|
||||
char *filename = NULL;
|
||||
int fd = -1;
|
||||
int pid = -1;
|
||||
char buffer[32];
|
||||
int status;
|
||||
struct incfs_per_uid_read_timeouts purt_set[] = {
|
||||
{
|
||||
.uid = 0,
|
||||
.min_time_us = 1000000,
|
||||
.min_pending_time_us = 1000000,
|
||||
.max_pending_time_us = 2000000,
|
||||
},
|
||||
};
|
||||
struct incfs_set_read_timeouts_args srt = {
|
||||
ptr_to_u64(purt_set),
|
||||
sizeof(purt_set)
|
||||
};
|
||||
|
||||
TEST(backing_dir = create_backing_dir(mount_dir), backing_dir);
|
||||
TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "sysfs_name=test_node",
|
||||
false),
|
||||
0);
|
||||
TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1);
|
||||
TESTEQUAL(build_mtree(&file), 0);
|
||||
file.root_hash[0] ^= 0xff;
|
||||
TESTEQUAL(crypto_emit_file(cmd_fd, NULL, file.name, &file.id, file.size,
|
||||
file.root_hash, file.sig.add_data),
|
||||
0);
|
||||
TEST(filename = concat_file_name(mount_dir, file.name), filename);
|
||||
TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
|
||||
TESTEQUAL(sysfs_test_value("reads_failed_timed_out", 0), 0);
|
||||
TEST(read(fd, NULL, 1), -1);
|
||||
TESTEQUAL(sysfs_test_value("reads_failed_timed_out", 2), 0);
|
||||
|
||||
TESTEQUAL(emit_test_file_data(mount_dir, &file), 0);
|
||||
TESTEQUAL(sysfs_test_value("reads_failed_hash_verification", 0), 0);
|
||||
TESTEQUAL(read(fd, NULL, 1), -1);
|
||||
TESTEQUAL(sysfs_test_value("reads_failed_hash_verification", 1), 0);
|
||||
TESTSYSCALL(close(fd));
|
||||
fd = -1;
|
||||
|
||||
TESTSYSCALL(unlink(filename));
|
||||
TESTEQUAL(mount_fs_opt(mount_dir, backing_dir,
|
||||
"read_timeout_ms=10000,sysfs_name=test_node",
|
||||
true),
|
||||
0);
|
||||
TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size, NULL),
|
||||
0);
|
||||
TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
|
||||
TESTSYSCALL(fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC));
|
||||
TEST(pid = fork(), pid != -1);
|
||||
if (pid == 0) {
|
||||
TESTEQUAL(read(fd, buffer, sizeof(buffer)), sizeof(buffer));
|
||||
exit(0);
|
||||
}
|
||||
sleep(1);
|
||||
TESTEQUAL(sysfs_test_value("reads_delayed_pending", 0), 0);
|
||||
TESTEQUAL(emit_test_file_data(mount_dir, &file), 0);
|
||||
TESTNE(wait(&status), -1);
|
||||
TESTEQUAL(status, 0);
|
||||
TESTEQUAL(sysfs_test_value("reads_delayed_pending", 1), 0);
|
||||
/* Allow +/- 10% */
|
||||
TESTEQUAL(sysfs_test_value_range("reads_delayed_pending_us", 900000, 1100000),
|
||||
0);
|
||||
|
||||
TESTSYSCALL(close(fd));
|
||||
fd = -1;
|
||||
|
||||
TESTSYSCALL(unlink(filename));
|
||||
TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_SET_READ_TIMEOUTS, &srt), 0);
|
||||
TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size, NULL),
|
||||
0);
|
||||
TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
|
||||
TESTEQUAL(sysfs_test_value("reads_delayed_min", 0), 0);
|
||||
TESTEQUAL(emit_test_file_data(mount_dir, &file), 0);
|
||||
TESTEQUAL(read(fd, buffer, sizeof(buffer)), sizeof(buffer));
|
||||
TESTEQUAL(sysfs_test_value("reads_delayed_min", 1), 0);
|
||||
/* This should be exact */
|
||||
TESTEQUAL(sysfs_test_value("reads_delayed_min_us", 1000000), 0);
|
||||
|
||||
TESTSYSCALL(close(fd));
|
||||
fd = -1;
|
||||
|
||||
TESTSYSCALL(unlink(filename));
|
||||
TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size, NULL),
|
||||
0);
|
||||
TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
|
||||
TESTSYSCALL(fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC));
|
||||
TEST(pid = fork(), pid != -1);
|
||||
if (pid == 0) {
|
||||
TESTEQUAL(read(fd, buffer, sizeof(buffer)), sizeof(buffer));
|
||||
exit(0);
|
||||
}
|
||||
usleep(500000);
|
||||
TESTEQUAL(sysfs_test_value("reads_delayed_pending", 1), 0);
|
||||
TESTEQUAL(sysfs_test_value("reads_delayed_min", 1), 0);
|
||||
TESTEQUAL(emit_test_file_data(mount_dir, &file), 0);
|
||||
TESTNE(wait(&status), -1);
|
||||
TESTEQUAL(status, 0);
|
||||
TESTEQUAL(sysfs_test_value("reads_delayed_pending", 2), 0);
|
||||
TESTEQUAL(sysfs_test_value("reads_delayed_min", 2), 0);
|
||||
/* Exact 1000000 plus 500000 +/- 10% */
|
||||
TESTEQUAL(sysfs_test_value_range("reads_delayed_min_us", 1450000, 1550000), 0);
|
||||
/* Allow +/- 10% */
|
||||
TESTEQUAL(sysfs_test_value_range("reads_delayed_pending_us", 1350000, 1650000),
|
||||
0);
|
||||
|
||||
result = TEST_SUCCESS;
|
||||
out:
|
||||
if (pid == 0)
|
||||
exit(result);
|
||||
free(file.mtree);
|
||||
free(filename);
|
||||
close(fd);
|
||||
close(cmd_fd);
|
||||
umount(mount_dir);
|
||||
free(backing_dir);
|
||||
|
|
@ -4446,6 +4626,7 @@ int main(int argc, char *argv[])
|
|||
MAKE_TEST(mmap_test),
|
||||
MAKE_TEST(truncate_test),
|
||||
MAKE_TEST(stat_test),
|
||||
MAKE_TEST(sysfs_test),
|
||||
};
|
||||
#undef MAKE_TEST
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue