blob: 5bf26eadd421b67ac716211b780fc07e378ce777 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2021 Google LLC
*/
#include "test_fuse.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <gelf.h>
#include <libelf.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/xattr.h>
#include <linux/unistd.h>
#include <include/uapi/linux/fuse.h>
#include <include/uapi/linux/bpf.h>
struct _test_options test_options;
struct s s(const char *s1)
{
struct s s = {0};
if (!s1)
return s;
s.s = malloc(strlen(s1) + 1);
if (!s.s)
return s;
strcpy(s.s, s1);
return s;
}
struct s sn(const char *s1, const char *s2)
{
struct s s = {0};
if (!s1)
return s;
s.s = malloc(s2 - s1 + 1);
if (!s.s)
return s;
strncpy(s.s, s1, s2 - s1);
s.s[s2 - s1] = 0;
return s;
}
int s_cmp(struct s s1, struct s s2)
{
int result = -1;
if (!s1.s || !s2.s)
goto out;
result = strcmp(s1.s, s2.s);
out:
free(s1.s);
free(s2.s);
return result;
}
struct s s_cat(struct s s1, struct s s2)
{
struct s s = {0};
if (!s1.s || !s2.s)
goto out;
s.s = malloc(strlen(s1.s) + strlen(s2.s) + 1);
if (!s.s)
goto out;
strcpy(s.s, s1.s);
strcat(s.s, s2.s);
out:
free(s1.s);
free(s2.s);
return s;
}
struct s s_splitleft(struct s s1, char c)
{
struct s s = {0};
char *split;
if (!s1.s)
return s;
split = strchr(s1.s, c);
if (split)
s = sn(s1.s, split);
free(s1.s);
return s;
}
struct s s_splitright(struct s s1, char c)
{
struct s s2 = {0};
char *split;
if (!s1.s)
return s2;
split = strchr(s1.s, c);
if (split)
s2 = s(split + 1);
free(s1.s);
return s2;
}
struct s s_word(struct s s1, char c, size_t n)
{
while (n--)
s1 = s_splitright(s1, c);
return s_splitleft(s1, c);
}
struct s s_path(struct s s1, struct s s2)
{
return s_cat(s_cat(s1, s("/")), s2);
}
struct s s_pathn(size_t n, struct s s1, ...)
{
va_list argp;
va_start(argp, s1);
while (--n)
s1 = s_path(s1, va_arg(argp, struct s));
va_end(argp);
return s1;
}
int s_link(struct s src_pathname, struct s dst_pathname)
{
int res;
if (src_pathname.s && dst_pathname.s) {
res = link(src_pathname.s, dst_pathname.s);
} else {
res = -1;
errno = ENOMEM;
}
free(src_pathname.s);
free(dst_pathname.s);
return res;
}
int s_symlink(struct s src_pathname, struct s dst_pathname)
{
int res;
if (src_pathname.s && dst_pathname.s) {
res = symlink(src_pathname.s, dst_pathname.s);
} else {
res = -1;
errno = ENOMEM;
}
free(src_pathname.s);
free(dst_pathname.s);
return res;
}
int s_mkdir(struct s pathname, mode_t mode)
{
int res;
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
res = mkdir(pathname.s, mode);
free(pathname.s);
return res;
}
int s_rmdir(struct s pathname)
{
int res;
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
res = rmdir(pathname.s);
free(pathname.s);
return res;
}
int s_unlink(struct s pathname)
{
int res;
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
res = unlink(pathname.s);
free(pathname.s);
return res;
}
int s_open(struct s pathname, int flags, ...)
{
va_list ap;
int res;
va_start(ap, flags);
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
if (flags & (O_CREAT | O_TMPFILE))
res = open(pathname.s, flags, va_arg(ap, mode_t));
else
res = open(pathname.s, flags);
free(pathname.s);
va_end(ap);
return res;
}
int s_openat(int dirfd, struct s pathname, int flags, ...)
{
va_list ap;
int res;
va_start(ap, flags);
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
if (flags & (O_CREAT | O_TMPFILE))
res = openat(dirfd, pathname.s, flags, va_arg(ap, mode_t));
else
res = openat(dirfd, pathname.s, flags);
free(pathname.s);
va_end(ap);
return res;
}
int s_creat(struct s pathname, mode_t mode)
{
int res;
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
res = open(pathname.s, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
free(pathname.s);
return res;
}
int s_mkfifo(struct s pathname, mode_t mode)
{
int res;
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
res = mknod(pathname.s, S_IFIFO | mode, 0);
free(pathname.s);
return res;
}
int s_stat(struct s pathname, struct stat *st)
{
int res;
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
res = stat(pathname.s, st);
free(pathname.s);
return res;
}
int s_statfs(struct s pathname, struct statfs *st)
{
int res;
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
res = statfs(pathname.s, st);
free(pathname.s);
return res;
}
DIR *s_opendir(struct s pathname)
{
DIR *res;
res = opendir(pathname.s);
free(pathname.s);
return res;
}
int s_getxattr(struct s pathname, const char name[], void *value, size_t size,
ssize_t *ret_size)
{
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
*ret_size = getxattr(pathname.s, name, value, size);
free(pathname.s);
return *ret_size >= 0 ? 0 : -1;
}
int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_size)
{
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
*ret_size = listxattr(pathname.s, list, size);
free(pathname.s);
return *ret_size >= 0 ? 0 : -1;
}
int s_setxattr(struct s pathname, const char name[], const void *value, size_t size, int flags)
{
int res;
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
res = setxattr(pathname.s, name, value, size, flags);
free(pathname.s);
return res;
}
int s_removexattr(struct s pathname, const char name[])
{
int res;
if (!pathname.s) {
errno = ENOMEM;
return -1;
}
res = removexattr(pathname.s, name);
free(pathname.s);
return res;
}
int s_rename(struct s oldpathname, struct s newpathname)
{
int res;
if (!oldpathname.s || !newpathname.s) {
errno = ENOMEM;
return -1;
}
res = rename(oldpathname.s, newpathname.s);
free(oldpathname.s);
free(newpathname.s);
return res;
}
int s_fuse_attr(struct s pathname, struct fuse_attr *fuse_attr_out)
{
struct stat st;
int result = TEST_FAILURE;
TESTSYSCALL(s_stat(pathname, &st));
fuse_attr_out->ino = st.st_ino;
fuse_attr_out->mode = st.st_mode;
fuse_attr_out->nlink = st.st_nlink;
fuse_attr_out->uid = st.st_uid;
fuse_attr_out->gid = st.st_gid;
fuse_attr_out->rdev = st.st_rdev;
fuse_attr_out->size = st.st_size;
fuse_attr_out->blksize = st.st_blksize;
fuse_attr_out->blocks = st.st_blocks;
fuse_attr_out->atime = st.st_atime;
fuse_attr_out->mtime = st.st_mtime;
fuse_attr_out->ctime = st.st_ctime;
fuse_attr_out->atimensec = UINT32_MAX;
fuse_attr_out->mtimensec = UINT32_MAX;
fuse_attr_out->ctimensec = UINT32_MAX;
result = TEST_SUCCESS;
out:
return result;
}
struct s tracing_folder(void)
{
struct s trace = {0};
FILE *mounts = NULL;
char *line = NULL;
size_t size = 0;
TEST(mounts = fopen("/proc/mounts", "re"), mounts);
while (getline(&line, &size, mounts) != -1) {
if (!s_cmp(s_word(sn(line, line + size), ' ', 2),
s("tracefs"))) {
trace = s_word(sn(line, line + size), ' ', 1);
break;
}
if (!s_cmp(s_word(sn(line, line + size), ' ', 2), s("debugfs")))
trace = s_path(s_word(sn(line, line + size), ' ', 1),
s("tracing"));
}
out:
free(line);
fclose(mounts);
return trace;
}
int tracing_on(void)
{
int result = TEST_FAILURE;
int tracing_on = -1;
TEST(tracing_on = s_open(s_path(tracing_folder(), s("tracing_on")),
O_WRONLY | O_CLOEXEC),
tracing_on != -1);
TESTEQUAL(write(tracing_on, "1", 1), 1);
result = TEST_SUCCESS;
out:
close(tracing_on);
return result;
}
char *concat_file_name(const char *dir, const char *file)
{
char full_name[FILENAME_MAX] = "";
if (snprintf(full_name, ARRAY_SIZE(full_name), "%s/%s", dir, file) < 0)
return NULL;
return strdup(full_name);
}
char *setup_mount_dir(const char *name)
{
struct stat st;
char *current_dir = getcwd(NULL, 0);
char *mount_dir = concat_file_name(current_dir, name);
free(current_dir);
if (stat(mount_dir, &st) == 0) {
if (S_ISDIR(st.st_mode))
return mount_dir;
ksft_print_msg("%s is a file, not a dir.\n", mount_dir);
return NULL;
}
if (mkdir(mount_dir, 0777)) {
ksft_print_msg("Can't create mount dir.");
return NULL;
}
return mount_dir;
}
int delete_dir_tree(const char *dir_path, bool remove_root)
{
DIR *dir = NULL;
struct dirent *dp;
int result = 0;
dir = opendir(dir_path);
if (!dir) {
result = -errno;
goto out;
}
while ((dp = readdir(dir))) {
char *full_path;
if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
continue;
full_path = concat_file_name(dir_path, dp->d_name);
if (dp->d_type == DT_DIR)
result = delete_dir_tree(full_path, true);
else
result = unlink(full_path);
free(full_path);
if (result)
goto out;
}
out:
if (dir)
closedir(dir);
if (!result && remove_root)
rmdir(dir_path);
return result;
}
static int mount_fuse_maybe_init(const char *mount_dir, int bpf_fd, int dir_fd,
int *fuse_dev_ptr, bool init)
{
int result = TEST_FAILURE;
int fuse_dev = -1;
char options[FILENAME_MAX];
uint8_t bytes_in[FUSE_MIN_READ_BUFFER];
uint8_t bytes_out[FUSE_MIN_READ_BUFFER];
DECL_FUSE_IN(init);
TEST(fuse_dev = open("/dev/fuse", O_RDWR | O_CLOEXEC), fuse_dev != -1);
snprintf(options, FILENAME_MAX, "fd=%d,user_id=0,group_id=0,rootmode=0040000",
fuse_dev);
if (bpf_fd != -1)
snprintf(options + strlen(options),
sizeof(options) - strlen(options),
",root_bpf=%d", bpf_fd);
if (dir_fd != -1)
snprintf(options + strlen(options),
sizeof(options) - strlen(options),
",root_dir=%d", dir_fd);
TESTSYSCALL(mount("ABC", mount_dir, "fuse", 0, options));
if (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 = 0,
.max_background = 0,
.congestion_threshold = 0,
.max_write = 4096,
.time_gran = 1000,
.max_pages = 12,
.map_alignment = 4096,
}));
}
*fuse_dev_ptr = fuse_dev;
fuse_dev = -1;
result = TEST_SUCCESS;
out:
close(fuse_dev);
return result;
}
int mount_fuse(const char *mount_dir, int bpf_fd, int dir_fd, int *fuse_dev_ptr)
{
return mount_fuse_maybe_init(mount_dir, bpf_fd, dir_fd, fuse_dev_ptr,
true);
}
int mount_fuse_no_init(const char *mount_dir, int bpf_fd, int dir_fd,
int *fuse_dev_ptr)
{
return mount_fuse_maybe_init(mount_dir, bpf_fd, dir_fd, fuse_dev_ptr,
false);
}
struct fuse_bpf_map {
unsigned int map_type;
size_t key_size;
size_t value_size;
unsigned int max_entries;
};
static int install_maps(Elf_Data *maps, int maps_index, Elf *elf,
Elf_Data *symbols, int symbol_index,
struct map_relocation **mr, size_t *map_count)
{
int result = TEST_FAILURE;
int i;
GElf_Sym symbol;
TESTNE((void *)symbols, NULL);
for (i = 0; i < symbols->d_size / sizeof(symbol); ++i) {
TESTNE((void *)gelf_getsym(symbols, i, &symbol), 0);
if (symbol.st_shndx == maps_index) {
struct fuse_bpf_map *map;
union bpf_attr attr;
int map_fd;
map = (struct fuse_bpf_map *)
((char *)maps->d_buf + symbol.st_value);
attr = (union bpf_attr) {
.map_type = map->map_type,
.key_size = map->key_size,
.value_size = map->value_size,
.max_entries = map->max_entries,
};
TEST(*mr = realloc(*mr, ++*map_count *
sizeof(struct fuse_bpf_map)),
*mr);
TEST(map_fd = syscall(__NR_bpf, BPF_MAP_CREATE,
&attr, sizeof(attr)),
map_fd != -1);
(*mr)[*map_count - 1] = (struct map_relocation) {
.name = strdup(elf_strptr(elf, symbol_index,
symbol.st_name)),
.fd = map_fd,
.value = symbol.st_value,
};
}
}
result = TEST_SUCCESS;
out:
return result;
}
static inline int relocate_maps(GElf_Shdr *rel_header, Elf_Data *rel_data,
Elf_Data *prog_data, Elf_Data *symbol_data,
struct map_relocation *map_relocations,
size_t map_count)
{
int result = TEST_FAILURE;
int i;
struct bpf_insn *insns = (struct bpf_insn *) prog_data->d_buf;
for (i = 0; i < rel_header->sh_size / rel_header->sh_entsize; ++i) {
GElf_Sym sym;
GElf_Rel rel;
unsigned int insn_idx;
int map_idx;
gelf_getrel(rel_data, i, &rel);
insn_idx = rel.r_offset / sizeof(struct bpf_insn);
insns[insn_idx].src_reg = BPF_PSEUDO_MAP_FD;
gelf_getsym(symbol_data, GELF_R_SYM(rel.r_info), &sym);
for (map_idx = 0; map_idx < map_count; map_idx++) {
if (map_relocations[map_idx].value == sym.st_value) {
insns[insn_idx].imm =
map_relocations[map_idx].fd;
break;
}
}
TESTNE(map_idx, map_count);
}
result = TEST_SUCCESS;
out:
return result;
}
int install_elf_bpf(const char *file, const char *section, int *fd,
struct map_relocation **map_relocations, size_t *map_count)
{
int result = TEST_FAILURE;
char path[PATH_MAX] = {};
char *last_slash;
int filter_fd = -1;
union bpf_attr bpf_attr;
static char log[1 << 20];
Elf *elf = NULL;
GElf_Ehdr ehdr;
Elf_Data *data_prog = NULL, *data_maps = NULL, *data_symbols = NULL;
int maps_index, symbol_index, prog_index;
int i;
int bpf_prog_type_fuse_fd = -1;
char buffer[10] = {0};
int bpf_prog_type_fuse;
TESTNE(readlink("/proc/self/exe", path, PATH_MAX), -1);
TEST(last_slash = strrchr(path, '/'), last_slash);
strcpy(last_slash + 1, file);
TEST(filter_fd = open(path, O_RDONLY | O_CLOEXEC), filter_fd != -1);
TESTNE(elf_version(EV_CURRENT), EV_NONE);
TEST(elf = elf_begin(filter_fd, ELF_C_READ, NULL), elf);
TESTEQUAL((void *) gelf_getehdr(elf, &ehdr), &ehdr);
for (i = 1; i < ehdr.e_shnum; i++) {
char *shname;
GElf_Shdr shdr;
Elf_Scn *scn;
TEST(scn = elf_getscn(elf, i), scn);
TESTEQUAL((void *)gelf_getshdr(scn, &shdr), &shdr);
TEST(shname = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name),
shname);
if (!strcmp(shname, "maps")) {
TEST(data_maps = elf_getdata(scn, 0), data_maps);
maps_index = i;
} else if (shdr.sh_type == SHT_SYMTAB) {
TEST(data_symbols = elf_getdata(scn, 0), data_symbols);
symbol_index = shdr.sh_link;
} else if (!strcmp(shname, section)) {
TEST(data_prog = elf_getdata(scn, 0), data_prog);
prog_index = i;
}
}
TESTNE((void *) data_prog, NULL);
if (data_maps)
TESTEQUAL(install_maps(data_maps, maps_index, elf,
data_symbols, symbol_index,
map_relocations, map_count), 0);
/* Now relocate maps */
for (i = 1; i < ehdr.e_shnum; i++) {
GElf_Shdr rel_header;
Elf_Scn *scn;
Elf_Data *rel_data;
TEST(scn = elf_getscn(elf, i), scn);
TESTEQUAL((void *)gelf_getshdr(scn, &rel_header),
&rel_header);
if (rel_header.sh_type != SHT_REL)
continue;
TEST(rel_data = elf_getdata(scn, 0), rel_data);
if (rel_header.sh_info != prog_index)
continue;
TESTEQUAL(relocate_maps(&rel_header, rel_data,
data_prog, data_symbols,
*map_relocations, *map_count),
0);
}
TEST(bpf_prog_type_fuse_fd = open("/sys/fs/fuse/bpf_prog_type_fuse",
O_RDONLY | O_CLOEXEC),
bpf_prog_type_fuse_fd != -1);
TESTGE(read(bpf_prog_type_fuse_fd, buffer, sizeof(buffer)), 1);
TEST(bpf_prog_type_fuse = strtol(buffer, NULL, 10),
bpf_prog_type_fuse != 0);
bpf_attr = (union bpf_attr) {
.prog_type = bpf_prog_type_fuse,
.insn_cnt = data_prog->d_size / 8,
.insns = ptr_to_u64(data_prog->d_buf),
.license = ptr_to_u64("GPL"),
.log_buf = test_options.verbose ? ptr_to_u64(log) : 0,
.log_size = test_options.verbose ? sizeof(log) : 0,
.log_level = test_options.verbose ? 2 : 0,
};
*fd = syscall(__NR_bpf, BPF_PROG_LOAD, &bpf_attr, sizeof(bpf_attr));
if (test_options.verbose)
ksft_print_msg("%s\n", log);
if (*fd == -1 && errno == ENOSPC)
ksft_print_msg("bpf log size too small!\n");
TESTNE(*fd, -1);
result = TEST_SUCCESS;
out:
close(filter_fd);
close(bpf_prog_type_fuse_fd);
return result;
}