/* 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 #include #include #include #include #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 __attribute__((unused)); \ struct fuse_##name##_out *name##_out __attribute__((unused)) #define FUSE_ACTION TEST(pid = fork(), pid != -1); \ if (pid) { #define FUSE_DAEMON } else { \ uint8_t bytes_in[FUSE_MIN_READ_BUFFER] \ __attribute__((unused)); \ uint8_t bytes_out[FUSE_MIN_READ_BUFFER] \ __attribute__((unused)); #define FUSE_DONE exit(TEST_SUCCESS); \ } \ TESTEQUAL(waitpid(pid, &status, 0), pid); \ TESTEQUAL(status, TEST_SUCCESS); 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