ANDROID: Incremental fs: inotify support

Bug: 175323815
Test: incfs_test passes
Signed-off-by: Paul Lawrence <paullawrence@google.com>
Change-Id: Ife372fa2f10dd51f61def9feb461e965d276c6bf
This commit is contained in:
Paul Lawrence
2020-12-22 12:25:51 -08:00
parent c3bf09a68b
commit a699d0a2ab
4 changed files with 304 additions and 29 deletions

View File

@@ -5,6 +5,7 @@
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/fsnotify.h>
#include <linux/namei.h>
#include <linux/poll.h>
#include <linux/syscalls.h>
@@ -230,13 +231,16 @@ static int validate_name(char *file_name)
static int dir_relative_path_resolve(
struct mount_info *mi,
const char __user *relative_path,
struct path *result_path)
struct path *result_path,
struct path *base_path)
{
struct path *base_path = &mi->mi_backing_dir_path;
int dir_fd = get_unused_fd_flags(0);
struct file *dir_f = NULL;
int error = 0;
if (!base_path)
base_path = &mi->mi_backing_dir_path;
if (dir_fd < 0)
return dir_fd;
@@ -261,7 +265,7 @@ static int dir_relative_path_resolve(
out:
ksys_close(dir_fd);
if (error)
pr_debug("incfs: %s %d\n", __func__, error);
pr_debug("Error: %d\n", error);
return error;
}
@@ -363,6 +367,7 @@ static int init_new_file(struct mount_info *mi, struct dentry *dentry,
if (error)
goto out;
out:
if (bfc) {
mutex_unlock(&bfc->bc_mutex);
@@ -376,9 +381,84 @@ out:
return error;
}
static long ioctl_create_file(struct mount_info *mi,
static void notify_create(struct file *pending_reads_file,
const char __user *dir_name, const char *file_name,
const char *file_id_str, bool incomplete_file)
{
struct mount_info *mi =
get_mount_info(file_superblock(pending_reads_file));
struct path base_path = {
.mnt = pending_reads_file->f_path.mnt,
.dentry = pending_reads_file->f_path.dentry->d_parent,
};
struct path dir_path = {};
struct dentry *file = NULL;
struct dentry *dir = NULL;
int error;
error = dir_relative_path_resolve(mi, dir_name, &dir_path, &base_path);
if (error)
goto out;
file = incfs_lookup_dentry(dir_path.dentry, file_name);
if (IS_ERR(file)) {
error = PTR_ERR(file);
file = NULL;
goto out;
}
fsnotify_create(d_inode(dir_path.dentry), file);
dir = incfs_lookup_dentry(base_path.dentry, INCFS_INDEX_NAME);
if (IS_ERR(dir)) {
error = PTR_ERR(dir);
dir = NULL;
goto out;
}
dput(file);
file = incfs_lookup_dentry(dir, file_id_str);
if (IS_ERR(file)) {
error = PTR_ERR(file);
file = NULL;
goto out;
}
fsnotify_create(d_inode(dir), file);
if (incomplete_file) {
dput(dir);
dir = incfs_lookup_dentry(base_path.dentry,
INCFS_INCOMPLETE_NAME);
if (IS_ERR(dir)) {
error = PTR_ERR(dir);
dir = NULL;
goto out;
}
dput(file);
file = incfs_lookup_dentry(dir, file_id_str);
if (IS_ERR(file)) {
error = PTR_ERR(file);
file = NULL;
goto out;
}
fsnotify_create(d_inode(dir), file);
}
out:
if (error)
pr_warn("%s failed with error %d\n", __func__, error);
dput(dir);
dput(file);
path_put(&dir_path);
}
static long ioctl_create_file(struct file *file,
struct incfs_new_file_args __user *usr_args)
{
struct mount_info *mi = get_mount_info(file_superblock(file));
struct incfs_new_file_args args;
char *file_id_str = NULL;
struct dentry *index_file_dentry = NULL;
@@ -430,7 +510,7 @@ static long ioctl_create_file(struct mount_info *mi,
/* Find a directory to put the file into. */
error = dir_relative_path_resolve(mi,
u64_to_user_ptr(args.directory_path),
&parent_dir_path);
&parent_dir_path, NULL);
if (error)
goto out;
@@ -589,6 +669,9 @@ static long ioctl_create_file(struct mount_info *mi,
incomplete_linked = true;
}
notify_create(file, u64_to_user_ptr(args.directory_path), file_name,
file_id_str, args.size != 0);
out:
if (error) {
pr_debug("incfs: %s err:%d\n", __func__, error);
@@ -609,6 +692,7 @@ out:
path_put(&parent_dir_path);
if (locked)
mutex_unlock(&mi->mi_dir_struct_mutex);
return error;
}
@@ -741,7 +825,7 @@ static long ioctl_create_mapped_file(struct mount_info *mi, void __user *arg)
/* Find a directory to put the file into. */
error = dir_relative_path_resolve(mi,
u64_to_user_ptr(args.directory_path),
&parent_dir_path);
&parent_dir_path, NULL);
if (error)
goto out;
@@ -901,7 +985,7 @@ static long pending_reads_dispatch_ioctl(struct file *f, unsigned int req,
switch (req) {
case INCFS_IOC_CREATE_FILE:
return ioctl_create_file(mi, (void __user *)arg);
return ioctl_create_file(f, (void __user *)arg);
case INCFS_IOC_PERMIT_FILL:
return ioctl_permit_fill(f, (void __user *)arg);
case INCFS_IOC_CREATE_MAPPED_FILE:
@@ -984,15 +1068,15 @@ static ssize_t log_read(struct file *f, char __user *buf, size_t len,
min_t(ssize_t, reads_to_collect, reads_per_page));
if (reads_collected <= 0) {
result = total_reads_collected ?
total_reads_collected * record_size :
reads_collected;
total_reads_collected * record_size :
reads_collected;
goto out;
}
if (copy_to_user(buf, (void *)page,
reads_collected * record_size)) {
result = total_reads_collected ?
total_reads_collected * record_size :
-EFAULT;
total_reads_collected * record_size :
-EFAULT;
goto out;
}

View File

@@ -7,6 +7,7 @@
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/fs_stack.h>
#include <linux/fsnotify.h>
#include <linux/namei.h>
#include <linux/parser.h>
#include <linux/seq_file.h>
@@ -545,12 +546,62 @@ static int incfs_rmdir(struct dentry *dentry)
return error;
}
static void maybe_delete_incomplete_file(struct data_file *df)
static void notify_unlink(struct dentry *dentry, const char *file_id_str,
const char *special_directory)
{
struct dentry *root = dentry;
struct dentry *file = NULL;
struct dentry *dir = NULL;
int error = 0;
bool take_lock = root->d_parent != root->d_parent->d_parent;
while (root != root->d_parent)
root = root->d_parent;
if (take_lock)
dir = incfs_lookup_dentry(root, special_directory);
else
dir = lookup_one_len(special_directory, root,
strlen(special_directory));
if (IS_ERR(dir)) {
error = PTR_ERR(dir);
goto out;
}
if (d_is_negative(dir)) {
error = -ENOENT;
goto out;
}
file = incfs_lookup_dentry(dir, file_id_str);
if (IS_ERR(file)) {
error = PTR_ERR(file);
goto out;
}
if (d_is_negative(file)) {
error = -ENOENT;
goto out;
}
fsnotify_unlink(d_inode(dir), file);
d_delete(file);
out:
if (error)
pr_warn("%s failed with error %d\n", __func__, error);
dput(dir);
dput(file);
}
static void maybe_delete_incomplete_file(struct file *f,
struct data_file *df)
{
struct mount_info *mi = df->df_mount_info;
char *file_id_str = NULL;
struct dentry *incomplete_file_dentry = NULL;
const struct cred *old_cred = override_creds(mi->mi_owner);
int error;
if (atomic_read(&df->df_data_blocks_written) < df->df_data_block_count)
goto out;
@@ -572,9 +623,16 @@ static void maybe_delete_incomplete_file(struct data_file *df)
goto out;
vfs_fsync(df->df_backing_file_context->bc_file, 0);
incfs_unlink(incomplete_file_dentry);
error = incfs_unlink(incomplete_file_dentry);
if (error)
goto out;
notify_unlink(f->f_path.dentry, file_id_str, INCFS_INCOMPLETE_NAME);
out:
if (error)
pr_warn("incfs: Deleting incomplete file failed: %d\n", error);
dput(incomplete_file_dentry);
kfree(file_id_str);
revert_creds(old_cred);
@@ -641,7 +699,7 @@ static long ioctl_fill_blocks(struct file *f, void __user *arg)
if (data_buf)
free_pages((unsigned long)data_buf, get_order(data_buf_size));
maybe_delete_incomplete_file(df);
maybe_delete_incomplete_file(f, df);
/*
* Only report the error if no records were processed, otherwise
@@ -918,9 +976,8 @@ path_err:
* Delete file referenced by backing_dentry and if appropriate its hardlink
* from .index and .incomplete
*/
static int file_delete(struct mount_info *mi,
struct dentry *backing_dentry,
int nlink)
static int file_delete(struct mount_info *mi, struct dentry *dentry,
struct dentry *backing_dentry, int nlink)
{
struct dentry *index_file_dentry = NULL;
struct dentry *incomplete_file_dentry = NULL;
@@ -973,15 +1030,19 @@ static int file_delete(struct mount_info *mi,
if (nlink > 1)
goto just_unlink;
if (d_really_is_positive(index_file_dentry))
if (d_really_is_positive(index_file_dentry)) {
error = incfs_unlink(index_file_dentry);
if (error)
goto out;
if (error)
goto out;
notify_unlink(dentry, file_id_str, INCFS_INDEX_NAME);
}
if (d_really_is_positive(incomplete_file_dentry))
if (d_really_is_positive(incomplete_file_dentry)) {
error = incfs_unlink(incomplete_file_dentry);
if (error)
goto out;
if (error)
goto out;
notify_unlink(dentry, file_id_str, INCFS_INCOMPLETE_NAME);
}
just_unlink:
error = incfs_unlink(backing_dentry);
@@ -1031,7 +1092,7 @@ static int dir_unlink(struct inode *dir, struct dentry *dentry)
if (err)
goto out;
err = file_delete(mi, backing_path.dentry, stat.nlink);
err = file_delete(mi, dentry, backing_path.dentry, stat.nlink);
d_drop(dentry);
out:
@@ -1517,8 +1578,6 @@ static ssize_t incfs_listxattr(struct dentry *d, char *list, size_t size)
struct dentry *incfs_mount_fs(struct file_system_type *type, int flags,
const char *dev_name, void *data)
{
static const char index_name[] = ".index";
static const char incomplete_name[] = ".incomplete";
struct mount_options options = {};
struct mount_info *mi = NULL;
struct path backing_dir_path = {};
@@ -1577,7 +1636,7 @@ struct dentry *incfs_mount_fs(struct file_system_type *type, int flags,
}
index_dir = open_or_create_special_dir(backing_dir_path.dentry,
index_name);
INCFS_INDEX_NAME);
if (IS_ERR_OR_NULL(index_dir)) {
error = PTR_ERR(index_dir);
pr_err("incfs: Can't find or create .index dir in %s\n",
@@ -1588,7 +1647,7 @@ struct dentry *incfs_mount_fs(struct file_system_type *type, int flags,
mi->mi_index_dir = index_dir;
incomplete_dir = open_or_create_special_dir(backing_dir_path.dentry,
incomplete_name);
INCFS_INCOMPLETE_NAME);
if (IS_ERR_OR_NULL(incomplete_dir)) {
error = PTR_ERR(incomplete_dir);
pr_err("incfs: Can't find or create .incomplete dir in %s\n",

View File

@@ -28,6 +28,8 @@
#define INCFS_MAX_HASH_SIZE 32
#define INCFS_MAX_FILE_ATTR_SIZE 512
#define INCFS_INDEX_NAME ".index"
#define INCFS_INCOMPLETE_NAME ".incomplete"
#define INCFS_PENDING_READS_FILENAME ".pending_reads"
#define INCFS_LOG_FILENAME ".log"
#define INCFS_BLOCKS_WRITTEN_FILENAME ".blocks_written"

View File

@@ -17,6 +17,7 @@
#include <unistd.h>
#include <zstd.h>
#include <sys/inotify.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -1632,7 +1633,7 @@ static int work_after_remount_test(const char *mount_dir)
goto failure;
}
if (access(filename_in_index, F_OK) != 0) {
if (access(filename_in_index, F_OK) != -1) {
ksft_print_msg("File %s is still present.\n",
filename_in_index);
goto failure;
@@ -3472,6 +3473,134 @@ out:
return result;
}
#define DIRS 3
static int inotify_test(const char *mount_dir)
{
struct test_file file = {
.name = "file",
.size = 16 * INCFS_DATA_FILE_BLOCK_SIZE
};
int result = TEST_FAILURE;
char *backing_dir = NULL, *index_dir = NULL, *incomplete_dir = NULL;
char *file_name = NULL;
int cmd_fd = -1;
int notify_fd = -1;
int wds[DIRS];
char buffer[DIRS * (sizeof(struct inotify_event) + NAME_MAX + 1)];
char *ptr = buffer;
struct inotify_event *event;
struct inotify_event *events[DIRS] = {};
const char *names[DIRS] = {};
int res;
char id[sizeof(incfs_uuid_t) * 2 + 1];
/* File creation triggers inotify events in .index and .incomplete? */
TEST(backing_dir = create_backing_dir(mount_dir), backing_dir);
TEST(index_dir = concat_file_name(mount_dir, ".index"), index_dir);
TEST(incomplete_dir = concat_file_name(mount_dir, ".incomplete"),
incomplete_dir);
TESTEQUAL(mount_fs(mount_dir, backing_dir, 50), 0);
TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1);
TEST(notify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC),
notify_fd != -1);
TEST(wds[0] = inotify_add_watch(notify_fd, mount_dir,
IN_CREATE | IN_DELETE),
wds[0] != -1);
TEST(wds[1] = inotify_add_watch(notify_fd, index_dir,
IN_CREATE | IN_DELETE),
wds[1] != -1);
TEST(wds[2] = inotify_add_watch(notify_fd, incomplete_dir,
IN_CREATE | IN_DELETE),
wds[2] != -1);
TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size,
NULL), 0);
TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1);
while (ptr < buffer + res) {
int i;
event = (struct inotify_event *) ptr;
TESTCOND(ptr + sizeof(*event) <= buffer + res);
for (i = 0; i < DIRS; ++i)
if (event->wd == wds[i]) {
TESTEQUAL(events[i], NULL);
events[i] = event;
ptr += sizeof(*event);
names[i] = ptr;
ptr += events[i]->len;
TESTCOND(ptr <= buffer + res);
break;
}
TESTCOND(i < DIRS);
}
TESTNE(events[0], NULL);
TESTNE(events[1], NULL);
TESTNE(events[2], NULL);
bin2hex(id, file.id.bytes, sizeof(incfs_uuid_t));
TESTEQUAL(events[0]->mask, IN_CREATE);
TESTEQUAL(events[1]->mask, IN_CREATE);
TESTEQUAL(events[2]->mask, IN_CREATE);
TESTEQUAL(strcmp(names[0], file.name), 0);
TESTEQUAL(strcmp(names[1], id), 0);
TESTEQUAL(strcmp(names[2], id), 0);
/* File completion triggers inotify event in .incomplete? */
TESTEQUAL(emit_test_file_data(mount_dir, &file), 0);
TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1);
event = (struct inotify_event *) buffer;
TESTEQUAL(event->wd, wds[2]);
TESTEQUAL(event->mask, IN_DELETE);
TESTEQUAL(strcmp(event->name, id), 0);
/* File unlinking triggers inotify event in .index? */
TEST(file_name = concat_file_name(mount_dir, file.name), file_name);
TESTEQUAL(unlink(file_name), 0);
TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1);
memset(events, 0, sizeof(events));
memset(names, 0, sizeof(names));
for (ptr = buffer; ptr < buffer + res;) {
event = (struct inotify_event *) ptr;
int i;
TESTCOND(ptr + sizeof(*event) <= buffer + res);
for (i = 0; i < DIRS; ++i)
if (event->wd == wds[i]) {
TESTEQUAL(events[i], NULL);
events[i] = event;
ptr += sizeof(*event);
names[i] = ptr;
ptr += events[i]->len;
TESTCOND(ptr <= buffer + res);
break;
}
TESTCOND(i < DIRS);
}
TESTNE(events[0], NULL);
TESTNE(events[1], NULL);
TESTEQUAL(events[2], NULL);
TESTEQUAL(events[0]->mask, IN_DELETE);
TESTEQUAL(events[1]->mask, IN_DELETE);
TESTEQUAL(strcmp(names[0], file.name), 0);
TESTEQUAL(strcmp(names[1], id), 0);
result = TEST_SUCCESS;
out:
free(file_name);
close(notify_fd);
close(cmd_fd);
umount(mount_dir);
free(backing_dir);
free(index_dir);
free(incomplete_dir);
return result;
}
static char *setup_mount_dir()
{
struct stat st;
@@ -3585,6 +3714,7 @@ int main(int argc, char *argv[])
MAKE_TEST(data_block_count_test),
MAKE_TEST(hash_block_count_test),
MAKE_TEST(per_uid_read_timeouts_test),
MAKE_TEST(inotify_test),
};
#undef MAKE_TEST