blob: 69dadc9c7e456a7eec71c053091a00ad07c7f4d3 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2021 Google LLC
*/
#ifndef TEST_FUSE__H
#define TEST_FUSE__H
#define _GNU_SOURCE
#include "test_framework.h"
#include <dirent.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/types.h>
#include <include/uapi/linux/android_fuse.h>
#include <include/uapi/linux/fuse.h>
#define PAGE_SIZE 4096
#define FUSE_POSTFILTER 0x20000
extern struct _test_options test_options;
/* Slow but semantically easy string functions */
/*
* struct s just wraps a char pointer
* It is a pointer to a malloc'd string, or null
* All consumers handle null input correctly
* All consumers free the string
*/
struct s {
char *s;
};
struct s s(const char *s1);
struct s sn(const char *s1, const char *s2);
int s_cmp(struct s s1, struct s s2);
struct s s_cat(struct s s1, struct s s2);
struct s s_splitleft(struct s s1, char c);
struct s s_splitright(struct s s1, char c);
struct s s_word(struct s s1, char c, size_t n);
struct s s_path(struct s s1, struct s s2);
struct s s_pathn(size_t n, struct s s1, ...);
int s_link(struct s src_pathname, struct s dst_pathname);
int s_symlink(struct s src_pathname, struct s dst_pathname);
int s_mkdir(struct s pathname, mode_t mode);
int s_rmdir(struct s pathname);
int s_unlink(struct s pathname);
int s_open(struct s pathname, int flags, ...);
int s_openat(int dirfd, struct s pathname, int flags, ...);
int s_creat(struct s pathname, mode_t mode);
int s_mkfifo(struct s pathname, mode_t mode);
int s_stat(struct s pathname, struct stat *st);
int s_statfs(struct s pathname, struct statfs *st);
int s_fuse_attr(struct s pathname, struct fuse_attr *fuse_attr_out);
DIR *s_opendir(struct s pathname);
int s_getxattr(struct s pathname, const char name[], void *value, size_t size,
ssize_t *ret_size);
int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_size);
int s_setxattr(struct s pathname, const char name[], const void *value,
size_t size, int flags);
int s_removexattr(struct s pathname, const char name[]);
int s_rename(struct s oldpathname, struct s newpathname);
struct s tracing_folder(void);
int tracing_on(void);
char *concat_file_name(const char *dir, const char *file);
char *setup_mount_dir(const char *name);
int delete_dir_tree(const char *dir_path, bool remove_root);
#define TESTFUSEINNULL(_opcode) \
do { \
struct fuse_in_header *in_header = \
(struct fuse_in_header *)bytes_in; \
ssize_t res = read(fuse_dev, &bytes_in, \
sizeof(bytes_in)); \
\
TESTEQUAL(in_header->opcode, _opcode); \
TESTEQUAL(res, sizeof(*in_header)); \
} while (false)
#define TESTFUSEIN(_opcode, in_struct) \
do { \
struct fuse_in_header *in_header = \
(struct fuse_in_header *)bytes_in; \
ssize_t res = read(fuse_dev, &bytes_in, \
sizeof(bytes_in)); \
\
TESTEQUAL(in_header->opcode, _opcode); \
TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct));\
} while (false)
#define TESTFUSEIN2(_opcode, in_struct1, in_struct2) \
do { \
struct fuse_in_header *in_header = \
(struct fuse_in_header *)bytes_in; \
ssize_t res = read(fuse_dev, &bytes_in, \
sizeof(bytes_in)); \
\
TESTEQUAL(in_header->opcode, _opcode); \
TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct1) \
+ sizeof(*in_struct2)); \
in_struct1 = (void *)(bytes_in + sizeof(*in_header)); \
in_struct2 = (void *)(bytes_in + sizeof(*in_header) \
+ sizeof(*in_struct1)); \
} while (false)
#define TESTFUSEINEXT(_opcode, in_struct, extra) \
do { \
struct fuse_in_header *in_header = \
(struct fuse_in_header *)bytes_in; \
ssize_t res = read(fuse_dev, &bytes_in, \
sizeof(bytes_in)); \
\
TESTEQUAL(in_header->opcode, _opcode); \
TESTEQUAL(res, \
sizeof(*in_header) + sizeof(*in_struct) + extra);\
} while (false)
#define TESTFUSEINUNKNOWN() \
do { \
struct fuse_in_header *in_header = \
(struct fuse_in_header *)bytes_in; \
ssize_t res = read(fuse_dev, &bytes_in, \
sizeof(bytes_in)); \
\
TESTGE(res, sizeof(*in_header)); \
TESTEQUAL(in_header->opcode, -1); \
} while (false)
/* Special case lookup since it is asymmetric */
#define TESTFUSELOOKUP(expected, filter) \
do { \
struct fuse_in_header *in_header = \
(struct fuse_in_header *)bytes_in; \
char *name = (char *) (bytes_in + sizeof(*in_header)); \
ssize_t res; \
\
TEST(res = read(fuse_dev, &bytes_in, sizeof(bytes_in)), \
res != -1); \
/* TODO once we handle forgets properly, remove */ \
if (in_header->opcode == FUSE_FORGET) \
continue; \
if (in_header->opcode == FUSE_BATCH_FORGET) \
continue; \
TESTGE(res, sizeof(*in_header)); \
TESTEQUAL(in_header->opcode, \
FUSE_LOOKUP | filter); \
TESTEQUAL(res, \
sizeof(*in_header) + strlen(expected) + 1 + \
(filter == FUSE_POSTFILTER ? \
sizeof(struct fuse_entry_out) + \
sizeof(struct fuse_entry_bpf_out) : 0));\
TESTCOND(!strcmp(name, expected)); \
break; \
} while (true)
#define TESTFUSEOUTEMPTY() \
do { \
struct fuse_in_header *in_header = \
(struct fuse_in_header *)bytes_in; \
struct fuse_out_header *out_header = \
(struct fuse_out_header *)bytes_out; \
\
*out_header = (struct fuse_out_header) { \
.len = sizeof(*out_header), \
.unique = in_header->unique, \
}; \
TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \
out_header->len); \
} while (false)
#define TESTFUSEOUTERROR(errno) \
do { \
struct fuse_in_header *in_header = \
(struct fuse_in_header *)bytes_in; \
struct fuse_out_header *out_header = \
(struct fuse_out_header *)bytes_out; \
\
*out_header = (struct fuse_out_header) { \
.len = sizeof(*out_header), \
.error = errno, \
.unique = in_header->unique, \
}; \
TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \
out_header->len); \
} while (false)
#define TESTFUSEOUTREAD(data, length) \
do { \
struct fuse_in_header *in_header = \
(struct fuse_in_header *)bytes_in; \
struct fuse_out_header *out_header = \
(struct fuse_out_header *)bytes_out; \
\
*out_header = (struct fuse_out_header) { \
.len = sizeof(*out_header) + length, \
.unique = in_header->unique, \
}; \
memcpy(bytes_out + sizeof(*out_header), data, length); \
TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \
out_header->len); \
} while (false)
#define TESTFUSEDIROUTREAD(read_out, data, length) \
do { \
struct fuse_in_header *in_header = \
(struct fuse_in_header *)bytes_in; \
struct fuse_out_header *out_header = \
(struct fuse_out_header *)bytes_out; \
\
*out_header = (struct fuse_out_header) { \
.len = sizeof(*out_header) + \
sizeof(*read_out) + length, \
.unique = in_header->unique, \
}; \
memcpy(bytes_out + sizeof(*out_header) + \
sizeof(*read_out), data, length); \
memcpy(bytes_out + sizeof(*out_header), \
read_out, sizeof(*read_out)); \
TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \
out_header->len); \
} while (false)
#define TESTFUSEOUT1(type1, obj1) \
do { \
*(struct fuse_out_header *) bytes_out \
= (struct fuse_out_header) { \
.len = sizeof(struct fuse_out_header) \
+ sizeof(struct type1), \
.unique = ((struct fuse_in_header *) \
bytes_in)->unique, \
}; \
*(struct type1 *) (bytes_out \
+ sizeof(struct fuse_out_header)) \
= obj1; \
TESTEQUAL(write(fuse_dev, bytes_out, \
((struct fuse_out_header *)bytes_out)->len), \
((struct fuse_out_header *)bytes_out)->len); \
} while (false)
#define TESTFUSEOUT2(type1, obj1, type2, obj2) \
do { \
*(struct fuse_out_header *) bytes_out \
= (struct fuse_out_header) { \
.len = sizeof(struct fuse_out_header) \
+ sizeof(struct type1) \
+ sizeof(struct type2), \
.unique = ((struct fuse_in_header *) \
bytes_in)->unique, \
}; \
*(struct type1 *) (bytes_out \
+ sizeof(struct fuse_out_header)) \
= obj1; \
*(struct type2 *) (bytes_out \
+ sizeof(struct fuse_out_header) \
+ sizeof(struct type1)) \
= obj2; \
TESTEQUAL(write(fuse_dev, bytes_out, \
((struct fuse_out_header *)bytes_out)->len), \
((struct fuse_out_header *)bytes_out)->len); \
} while (false)
#define TESTFUSEINITFLAGS(fuse_connection_flags) \
do { \
DECL_FUSE_IN(init); \
\
TESTFUSEIN(FUSE_INIT, init_in); \
TESTEQUAL(init_in->major, FUSE_KERNEL_VERSION); \
TESTEQUAL(init_in->minor, FUSE_KERNEL_MINOR_VERSION); \
TESTFUSEOUT1(fuse_init_out, ((struct fuse_init_out) { \
.major = FUSE_KERNEL_VERSION, \
.minor = FUSE_KERNEL_MINOR_VERSION, \
.max_readahead = 4096, \
.flags = fuse_connection_flags, \
.max_background = 0, \
.congestion_threshold = 0, \
.max_write = 4096, \
.time_gran = 1000, \
.max_pages = 12, \
.map_alignment = 4096, \
})); \
} while (false)
#define TESTFUSEINIT() \
TESTFUSEINITFLAGS(0)
#define DECL_FUSE_IN(name) \
struct fuse_##name##_in *name##_in = \
(struct fuse_##name##_in *) \
(bytes_in + sizeof(struct fuse_in_header))
#define DECL_FUSE(name) \
struct fuse_##name##_in *name##_in __maybe_unused; \
struct fuse_##name##_out *name##_out __maybe_unused
#define FUSE_DECLARE_DAEMON \
int daemon = -1; \
int status; \
bool action; \
uint8_t bytes_in[FUSE_MIN_READ_BUFFER] __maybe_unused; \
uint8_t bytes_out[FUSE_MIN_READ_BUFFER] __maybe_unused
#define FUSE_START_DAEMON() \
do { \
TEST(daemon = fork(), daemon != -1); \
action = daemon != 0; \
} while (false)
#define FUSE_END_DAEMON() \
do { \
TESTEQUAL(waitpid(daemon, &status, 0), daemon); \
TESTEQUAL(status, TEST_SUCCESS); \
result = TEST_SUCCESS; \
out: \
if (!daemon) \
exit(TEST_FAILURE); \
} while (false)
struct map_relocation {
char *name;
int fd;
int value;
};
int mount_fuse(const char *mount_dir, int bpf_fd, int dir_fd,
int *fuse_dev_ptr);
int mount_fuse_no_init(const char *mount_dir, int bpf_fd, int dir_fd,
int *fuse_dev_ptr);
int install_elf_bpf(const char *file, const char *section, int *fd,
struct map_relocation **map_relocations, size_t *map_count);
#endif