From 3f472567bdd9bc3fbfd99b342ee29b25d5b553be Mon Sep 17 00:00:00 2001 From: Felipe Barriga Richards Date: Sun, 23 Oct 2016 22:42:10 -0300 Subject: [PATCH] second commit :) --- .gitignore | 40 +++ .idea/codeStyleSettings.xml | 35 +++ .idea/dictionaries/fbarriga.xml | 9 + .idea/misc.xml | 11 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .travis.yml | 20 ++ AUTHORS | 11 + CMakeLists.txt | 44 +++ ChangeLog | 4 + README.md | 41 ++- TODO.md | 36 +++ binary_storage.c | 468 ++++++++++++++++++++++++++++++++ binary_storage.h | 19 ++ compat/fuse_opt.c | 364 +++++++++++++++++++++++++ compat/fuse_opt.h | 258 ++++++++++++++++++ const.h | 21 ++ fuse_xattrs.1.in | 18 ++ fuse_xattrs.c | 134 +++++++++ passthrough.c | 305 +++++++++++++++++++++ passthrough.h | 36 +++ run_tests.sh | 18 ++ test/tests.py | 215 +++++++++++++++ utils.c | 59 ++++ utils.h | 42 +++ 25 files changed, 2220 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .idea/codeStyleSettings.xml create mode 100644 .idea/dictionaries/fbarriga.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .travis.yml create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 ChangeLog create mode 100644 TODO.md create mode 100644 binary_storage.c create mode 100644 binary_storage.h create mode 100644 compat/fuse_opt.c create mode 100644 compat/fuse_opt.h create mode 100644 const.h create mode 100644 fuse_xattrs.1.in create mode 100644 fuse_xattrs.c create mode 100644 passthrough.c create mode 100644 passthrough.h create mode 100755 run_tests.sh create mode 100755 test/tests.py create mode 100644 utils.c create mode 100644 utils.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5de50e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# +# NOTE! Don't add files that are generated in specific +# subdirectories here. Add them in the ".gitignore" file +# in that subdirectory instead. +# +# NOTE! Please use 'git ls-files -i --exclude-standard' +# command after changing this file, to see if there are +# any tracked files which get ignored after the change. +!.gitignore +*.o +*.lo +*.la +*.gz +\#*# +*.orig +*~ +Makefile.in +Makefile +*.m4 +stamp-h* +config.* +sshfs.1 +/sshfs +/ltmain.sh +/configure +/install-sh +/mkinstalldirs +/missing +/*.cache +/depcomp +/compile +/libtool +/INSTALL +/.pc +/patches +/m4 +/build +/test/__pycache__ +/.idea/workspace.xml +/.idea/*.iml diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 0000000..5e6dd34 --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,35 @@ + + + + + + \ No newline at end of file diff --git a/.idea/dictionaries/fbarriga.xml b/.idea/dictionaries/fbarriga.xml new file mode 100644 index 0000000..d0996d6 --- /dev/null +++ b/.idea/dictionaries/fbarriga.xml @@ -0,0 +1,9 @@ + + + + fbarriga + xattr + xattrs + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..44332e5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e2953b6 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8feeddb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: + - cpp + +before_install: + - sudo apt-get update -qq + - sudo apt-get install libfuse-dev valgrind python3 + - sudo pip install xattr + +compiler: + - clang + +script: + - mkdir -p build && cd build + - cmake .. + - make + - make test + - cd .. + +after_success: + - true diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d764e5e --- /dev/null +++ b/AUTHORS @@ -0,0 +1,11 @@ +Current Maintainer +------------------ + +Felipe Barriga Richards + + +Contributors +------------ + +- All the project structure was 'stolen' from https://github.com/libfuse/sshfs +- The code is highly based on https://github.com/libfuse/libfuse/blob/master/example/passthrough.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e4254d6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.6) +project(fuse_xattrs) + +include (CheckIncludeFileCXX) +check_include_file_cxx (attr/xattr.h HAVE_ATTR_XATTR_H) +check_include_file_cxx (sys/xattr.h HAVE_SYS_XATTR_H) + +# Check if xattr functions take extra argument. +include (CheckCXXSourceCompiles) +CHECK_CXX_SOURCE_COMPILES ("#include + #include + int main() { getxattr(0,0,0,0,0,0); return 1; } " XATTR_ADD_OPT) + +add_definitions (-D_FILE_OFFSET_BITS=64) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(SOURCE_FILES + compat/fuse_opt.c + compat/fuse_opt.h + fuse_xattrs.c + passthrough.c + passthrough.h + binary_storage.c + binary_storage.h + utils.c + utils.h + const.h +) + +add_executable(fuse_xattrs ${SOURCE_FILES}) + +target_link_libraries ( + fuse_xattrs + fuse +) + +install (TARGETS fuse_xattrs DESTINATION bin) + +enable_testing() +configure_file(run_tests.sh run_tests.sh COPYONLY) +configure_file(test/tests.py test/tests.py COPYONLY) +add_test(NAME integration + COMMAND run_tests.sh) diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..b81b8a8 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,4 @@ +Release 0.1 (2016-10-23) +------------------------ + +* Initial release diff --git a/README.md b/README.md index d57d794..62e8399 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ -# fuse_xattrs -add xattrs support using sidecar files. +## Abstract + +This filesystem provides xattr support using sidecar files. + +## Latest version + +The latest version and more information can be found on +http://github.com/fbarriga/fuse_xattrs + + +## How to mount a filesystem + +Once fuse_xattrs is installed (see next section) running it is very simple: + + fuse_xattrs mountpoint + +To unmount the filesystem: + + fusermount -u mountpoint + + +## Installing + +First you need to download FUSE 2.9 or later from +http://github.com/libfuse/libfuse. + + mkdir build + cd build + cmake .. + make + + +## Links + +- http://man7.org/linux/man-pages/man2/setxattr.2.html +- http://man7.org/linux/man-pages/man2/listxattr.2.html +- http://man7.org/linux/man-pages/man2/getxattr.2.html +- http://man7.org/linux/man-pages/man3/errno.3.html +- https://www.freedesktop.org/wiki/CommonExtendedAttributes/ diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..32503d0 --- /dev/null +++ b/TODO.md @@ -0,0 +1,36 @@ +TODO +---- + +- Add mutex to avoid issues when two processes are modifying the same file. +- Handle permission issues with .xattr files +- Add options to: + - Change extension name of sidecar files + - Hide sidecar files +- CI: travis ? + - Valgrind support + - Code coverage + - C unit tests + +BUGS +---- + +- Fix error code when value size > 64KiB (libfuse bug) + +FEATURES +-------- + +- binary_storage: + - crc32 + - make it endian-independent: + - http://www.ibm.com/developerworks/aix/library/au-endianc/index.html + - add header to file to determine the format / version of the sidecar +- Be able to use a database instead of sidecar files ? +- Support multiple namespaces +- Make DEBUG a runtime option +- Test it on macOS + +OPTIMIZATIONS +------------- + +- Use passthrough_ll code from libfuse +- Add option to present all files as symbolic links to original files diff --git a/binary_storage.c b/binary_storage.c new file mode 100644 index 0000000..139f3a0 --- /dev/null +++ b/binary_storage.c @@ -0,0 +1,468 @@ +/* + fuse_xattrs - Add xattrs support using sidecar files + + Copyright (C) 2016 Felipe Barriga Richards + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include +#include +#include +#include +#include + +#include "binary_storage.h" +#include "utils.h" +#include "const.h" + +#include + +struct on_memory_attr { + u_int16_t name_size; + size_t value_size; + + char *name; + char *value; +}; + +void __print_on_memory_attr(struct on_memory_attr *attr) +{ +#ifdef DEBUG + char *sanitized_value = sanitize_value(attr->value, attr->value_size); + debug_print("--------------\n"); + debug_print("name size: %hu\n", attr->name_size); + debug_print("name: '%s'\n", attr->name); + debug_print("value size: %zu\n", attr->value_size); + debug_print("sanitized_value: '%s'\n", sanitized_value); + debug_print("--------------\n"); + free(sanitized_value); +#endif +} + +void __free_on_memory_attr(struct on_memory_attr *attr) +{ + free(attr->name); + free(attr->value); + free(attr); +} + +char *__read_file(const char *path, int *buffer_size) +{ + FILE *file = fopen(path, "r"); + char *buffer = NULL; + + if (file == NULL) { + debug_print("file not found: %s\n", path); + *buffer_size = -ENOENT; + return NULL; + } + + debug_print("file found, reading it: %s\n", path); + + fseek(file, 0, SEEK_END); + *buffer_size = (int)ftell(file); + + if (*buffer_size == -1) { + error_print("error: path: %s, size: %d, errno=%d\n", path, *buffer_size, errno); + *buffer_size = errno; + fclose(file); + return NULL; + } + + if (*buffer_size > MAX_METADATA_SIZE) { + error_print("metadata file too big. path: %s, size: %d\n", path, *buffer_size); + *buffer_size = -ENOSPC; + fclose(file); + return NULL; + } + + if (*buffer_size == 0) { + debug_print("empty file.\n"); + *buffer_size = -ENOENT; + fclose(file); + return NULL; + } + assert(*buffer_size > 0); + size_t _buffer_size = (size_t) *buffer_size; + + fseek(file, 0, SEEK_SET); + buffer = malloc(_buffer_size); + if (buffer == NULL) { + *buffer_size = -ENOMEM; + error_print("cannot allocate memory.\n"); + fclose(file); + return NULL; + } + + memset(buffer, '\0', _buffer_size); + fread(buffer, 1, _buffer_size, file); + fclose(file); + + return buffer; +} + +char *__read_file_sidecar(const char *path, int *buffer_size) +{ + char *sidecar_path = get_sidecar_path(path); + debug_print("path=%s sidecar_path=%s\n", path, sidecar_path); + + char *buffer = __read_file(sidecar_path, buffer_size); + free (sidecar_path); + return buffer; +} + +int __cmp_name(const char *name, size_t name_length, struct on_memory_attr *attr) +{ + if (attr->name_size == name_length && memcmp(attr->name, name, name_length) == 0) { + debug_print("match: name=%s, name_length=%zu\n", name, name_length); + __print_on_memory_attr(attr); + return 1; + } + + debug_print("doesn't match: name=%s, name_length=%zu\n", name, name_length); + __print_on_memory_attr(attr); + + return 0; +} + +struct on_memory_attr *__read_on_memory_attr(size_t *offset, char *buffer, size_t buffer_size) +{ + debug_print("offset=%zu\n", *offset); + struct on_memory_attr *attr = malloc(sizeof(struct on_memory_attr)); + + //////////////////////////////// + // Read name size + size_t data_size = sizeof(u_int16_t); + if (*offset + data_size > buffer_size) { + error_print("Error, sizes doesn't match.\n"); + __free_on_memory_attr(attr); + return NULL; + } + memcpy(&attr->name_size, buffer + *offset, data_size); + *offset += data_size; + debug_print("attr->name_size=%hu\n", attr->name_size); + + //////////////////////////////// + // Read name data + data_size = attr->name_size; + if (*offset + data_size > buffer_size) { + error_print("Error, sizes doesn't match.\n"); + __free_on_memory_attr(attr); + return NULL; + } + attr->name = malloc(data_size); + memcpy(attr->name, buffer + *offset, data_size); + *offset += data_size; + + //////////////////////////////// + // Read value size + data_size = sizeof(size_t); + if (*offset + data_size > buffer_size) { + error_print("Error, sizes doesn't match.\n"); + __free_on_memory_attr(attr); + return NULL; + } + memcpy(&attr->value_size, buffer + *offset, data_size); + *offset += data_size; + debug_print("attr->value_size=%zu\n", attr->value_size); + + //////////////////////////////// + // Read value data + data_size = attr->value_size; + if (*offset + data_size > buffer_size) { + error_print("Error, sizes doesn't match. data_size=%zu buffer_size=%zu\n", + data_size, buffer_size); + + __free_on_memory_attr(attr); + return NULL; + } + attr->value = malloc(data_size); + memcpy(attr->value, buffer + *offset, data_size); + *offset += data_size; + + return attr; +} + +int __write_to_file(FILE *file, const char *name, const char *value, const size_t value_size) +{ + const u_int16_t name_size = (int) strlen(name) + 1; + +#ifdef DEBUG + char *sanitized_value = sanitize_value(value, value_size); + debug_print("name=%s sanitized_value=%s value_size=%zu\n", name, sanitized_value, value_size); + free(sanitized_value); +#endif + + // write name + if (fwrite(&name_size, sizeof(u_int16_t), 1, file) != 1) { + return -1; + } + if (fwrite(name, name_size, 1, file) != 1) { + return -1; + } + + // write value + if (fwrite(&value_size, sizeof(size_t), 1, file) != 1) { + return -1; + } + if (fwrite(value, value_size, 1, file) != 1) { + return -1; + } + + return 0; +} + +/** + * + * @param path - path to file. + * @param name - attribute name. string null terminated. size <= XATTR_NAME_MAX bytes + * @param value - attribute value. size < XATTR_SIZE_MAX + * @param size + * @param flags - XATTR_CREATE and/or XATTR_REPLACE + * @return On success, zero is returned. On failure, -errno is returnted. + */ +int binary_storage_write_key(const char *path, const char *name, const char *value, size_t size, int flags) +{ +#ifdef DEBUG + char *sanitized_value = sanitize_value(value, size); + debug_print("path=%s name=%s sanitized_value=%s size=%zu flags=%d\n", path, name, sanitized_value, size, flags); + free(sanitized_value); +#endif + + int buffer_size; + char *buffer = __read_file_sidecar(path, &buffer_size); + + if (buffer == NULL && buffer_size == -ENOENT && flags & XATTR_REPLACE) { + error_print("No xattr. (flag XATTR_REPLACE)"); + return -ENODATA; + } + + if (buffer == NULL && buffer_size != -ENOENT) { + return buffer_size; + } + + int status; + char *sidecar_path = get_sidecar_path(path); + FILE *file = fopen(sidecar_path, "w"); + free(sidecar_path); + + if (buffer == NULL) { + debug_print("new file, writing directly...\n"); + status = __write_to_file(file, name, value, size); + assert(status == 0); + fclose(file); + free(buffer); + return 0; + } + assert(buffer_size >= 0); + size_t _buffer_size = (size_t)buffer_size; + + int res = 0; + size_t offset = 0; + size_t name_len = strlen(name) + 1; // null byte + int replaced = 0; + while(offset < _buffer_size) + { + debug_print("replaced=%d offset=%zu buffer_size=%zu\n", replaced, offset, _buffer_size); + struct on_memory_attr *attr = __read_on_memory_attr(&offset, buffer, _buffer_size); + + // FIXME: handle attr == NULL + assert(attr != NULL); + + if (memcmp(attr->name, name, name_len) == 0) { + assert(replaced == 0); + if (flags & XATTR_CREATE) { + error_print("Key already exists. (flag XATTR_CREATE)"); + status = __write_to_file(file, attr->name, attr->value, attr->value_size); + assert(status == 0); + res = -EEXIST; + } else { + status = __write_to_file(file, name, value, size); + assert(status == 0); + replaced = 1; + } + } else { + status = __write_to_file(file, attr->name, attr->value, attr->value_size); + assert(status == 0); + } + __free_on_memory_attr(attr); + } + + if (replaced == 0 && res == 0) { + if (flags & XATTR_REPLACE) { + error_print("Key doesn't exists. (flag XATTR_REPLACE)"); + res = -ENODATA; + } else { + status = __write_to_file(file, name, value, size); + assert(status == 0); + } + } + + fclose(file); + free(buffer); + return res; +} + +int binary_storage_read_key(const char *path, const char *name, char *value, size_t size) +{ + int buffer_size; + char *buffer = __read_file_sidecar(path, &buffer_size); + + if (buffer == NULL) { + if (buffer_size == -ENOENT) { + return -ENOATTR; + } + return buffer_size; + } + assert(buffer_size >= 0); + size_t _buffer_size = (size_t)buffer_size; + + if (size > 0) { + memset(value, '\0', size); + } + + size_t name_len = strlen(name) + 1; // null byte \0 + size_t offset = 0; + while(offset < _buffer_size) + { + struct on_memory_attr *attr = __read_on_memory_attr(&offset, buffer, _buffer_size); + if (attr == NULL) { + free(buffer); + return -EILSEQ; + } + + if (__cmp_name(name, name_len, attr) == 1) { + int value_size = (int)attr->value_size; + int res; + if (size == 0) { + res = value_size; + } else if (attr->value_size <= size) { + memcpy(value, attr->value, attr->value_size); + res = value_size; + } else { + error_print("error, attr->value_size=%zu > size=%zu\n", attr->value_size, size); + res = -ERANGE; + } + free(buffer); + __free_on_memory_attr(attr); + return res; + } + __free_on_memory_attr(attr); + } + free(buffer); + + return -ENOATTR; +} + +int binary_storage_list_keys(const char *path, char *list, size_t size) +{ + int buffer_size; + char *buffer = __read_file_sidecar(path, &buffer_size); + + if (buffer == NULL) { + debug_print("buffer == NULL buffer_size=%d\n", buffer_size); + if (buffer_size == -ENOENT) { + return 0; + } + return buffer_size; + } + + assert(buffer_size > 0); + size_t _buffer_size = (size_t)buffer_size; + + if (size > 0) { + memset(list, '\0', size); + } + + size_t res = 0; + size_t offset = 0; + while(offset < _buffer_size) + { + struct on_memory_attr *attr = __read_on_memory_attr(&offset, buffer, _buffer_size); + if (attr == NULL) { + free(buffer); + return -EILSEQ; + } + + if (size > 0) { + if (attr->name_size + res > size) { + error_print("Not enough memory allocated. allocated=%zu required=%ld\n", + size, attr->name_size + res); + __free_on_memory_attr(attr); + free(buffer); + return -ERANGE; + } else { + memcpy(list + res, attr->name, attr->name_size); + res += attr->name_size; + } + } else { + res += attr->name_size; + } + __free_on_memory_attr(attr); + } + free(buffer); + + if (size == 0 && res > XATTR_LIST_MAX) { + // FIXME: we should return the size or an error ? + return -E2BIG; + } + + return (int)res; +} + +int binary_storage_remove_key(const char *path, const char *name) +{ + debug_print("path=%s name=%s\n", path, name); + int buffer_size; + char *buffer = __read_file_sidecar(path, &buffer_size); + + if (buffer == NULL) { + return buffer_size; + } + assert(buffer_size > 0); + size_t _buffer_size = (size_t) buffer_size; + + char *sidecar_path = get_sidecar_path(path); + FILE *file = fopen(sidecar_path, "w"); + free(sidecar_path); + + size_t offset = 0; + size_t name_len = strlen(name) + 1; // null byte \0 + + int removed = 0; + while(offset < _buffer_size) + { + debug_print("removed=%d offset=%zu buffer_size=%zu\n", removed, offset, _buffer_size); + + struct on_memory_attr *attr = __read_on_memory_attr(&offset, buffer, _buffer_size); + if (attr == NULL) { + error_print("error reading file. corrupted ?"); + break; + } + + if (memcmp(attr->name, name, name_len) == 0) { + removed++; + } else { + int status = __write_to_file(file, attr->name, attr->value, attr->value_size); + assert(status == 0); + } + __free_on_memory_attr(attr); + } + + int res = 0; + if (removed == 1) { + debug_print("key removed successfully.\n"); + } else if (removed == 0) { + error_print("key not found.\n"); + res = -ENOATTR; + } else { + debug_print("removed %d keys (was duplicated)\n", removed); + res = -EILSEQ; + } + + fclose(file); + free(buffer); + return res; +} \ No newline at end of file diff --git a/binary_storage.h b/binary_storage.h new file mode 100644 index 0000000..0b44d5a --- /dev/null +++ b/binary_storage.h @@ -0,0 +1,19 @@ +/* + fuse_xattrs - Add xattrs support using sidecar files + + Copyright (C) 2016 Felipe Barriga Richards + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ +#include + +#ifndef FUSE_XATTRS_BINARY_STORAGE_STRUCT_H +#define FUSE_XATTRS_BINARY_STORAGE_STRUCT_H + +int binary_storage_write_key(const char *path, const char *name, const char *value, size_t size, int flags); +int binary_storage_read_key(const char *path, const char *name, char *value, size_t size); +int binary_storage_list_keys(const char *path, char *list, size_t size); +int binary_storage_remove_key(const char *path, const char *name); + +#endif //FUSE_XATTRS_BINARY_STORAGE_STRUCT_H diff --git a/compat/fuse_opt.c b/compat/fuse_opt.c new file mode 100644 index 0000000..582c6ad --- /dev/null +++ b/compat/fuse_opt.c @@ -0,0 +1,364 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2006 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPL. + See the file COPYING.LIB +*/ + +#include "fuse_opt.h" + +#include +#include +#include +#include + +struct fuse_opt_context { + void *data; + const struct fuse_opt *opt; + fuse_opt_proc_t proc; + int argctr; + int argc; + char **argv; + struct fuse_args outargs; + char *opts; + int nonopt; +}; + +void fuse_opt_free_args(struct fuse_args *args) +{ + if (args && args->argv && args->allocated) { + int i; + for (i = 0; i < args->argc; i++) + free(args->argv[i]); + free(args->argv); + args->argv = NULL; + args->allocated = 0; + } +} + +static int alloc_failed(void) +{ + fprintf(stderr, "fuse: memory allocation failed\n"); + return -1; +} + +int fuse_opt_add_arg(struct fuse_args *args, const char *arg) +{ + char **newargv; + char *newarg; + + assert(!args->argv || args->allocated); + + newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *)); + newarg = newargv ? strdup(arg) : NULL; + if (!newargv || !newarg) + return alloc_failed(); + + args->argv = newargv; + args->allocated = 1; + args->argv[args->argc++] = newarg; + args->argv[args->argc] = NULL; + return 0; +} + +int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg) +{ + assert(pos <= args->argc); + if (fuse_opt_add_arg(args, arg) == -1) + return -1; + + if (pos != args->argc - 1) { + char *newarg = args->argv[args->argc - 1]; + memmove(&args->argv[pos + 1], &args->argv[pos], + sizeof(char *) * (args->argc - pos - 1)); + args->argv[pos] = newarg; + } + return 0; +} + +static int next_arg(struct fuse_opt_context *ctx, const char *opt) +{ + if (ctx->argctr + 1 >= ctx->argc) { + fprintf(stderr, "fuse: missing argument after `%s'\n", opt); + return -1; + } + ctx->argctr++; + return 0; +} + +static int add_arg(struct fuse_opt_context *ctx, const char *arg) +{ + return fuse_opt_add_arg(&ctx->outargs, arg); +} + +int fuse_opt_add_opt(char **opts, const char *opt) +{ + char *newopts; + if (!*opts) + newopts = strdup(opt); + else { + unsigned oldlen = strlen(*opts); + newopts = realloc(*opts, oldlen + 1 + strlen(opt) + 1); + if (newopts) { + newopts[oldlen] = ','; + strcpy(newopts + oldlen + 1, opt); + } + } + if (!newopts) + return alloc_failed(); + + *opts = newopts; + return 0; +} + +static int add_opt(struct fuse_opt_context *ctx, const char *opt) +{ + return fuse_opt_add_opt(&ctx->opts, opt); +} + +static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key, + int iso) +{ + if (key == FUSE_OPT_KEY_DISCARD) + return 0; + + if (key != FUSE_OPT_KEY_KEEP && ctx->proc) { + int res = ctx->proc(ctx->data, arg, key, &ctx->outargs); + if (res == -1 || !res) + return res; + } + if (iso) + return add_opt(ctx, arg); + else + return add_arg(ctx, arg); +} + +static int match_template(const char *t, const char *arg, unsigned *sepp) +{ + int arglen = strlen(arg); + const char *sep = strchr(t, '='); + sep = sep ? sep : strchr(t, ' '); + if (sep && (!sep[1] || sep[1] == '%')) { + int tlen = sep - t; + if (sep[0] == '=') + tlen ++; + if (arglen >= tlen && strncmp(arg, t, tlen) == 0) { + *sepp = sep - t; + return 1; + } + } + if (strcmp(t, arg) == 0) { + *sepp = 0; + return 1; + } + return 0; +} + +static const struct fuse_opt *find_opt(const struct fuse_opt *opt, + const char *arg, unsigned *sepp) +{ + for (; opt && opt->templ; opt++) + if (match_template(opt->templ, arg, sepp)) + return opt; + return NULL; +} + +int fuse_opt_match(const struct fuse_opt *opts, const char *opt) +{ + unsigned dummy; + return find_opt(opts, opt, &dummy) ? 1 : 0; +} + +static int process_opt_param(void *var, const char *format, const char *param, + const char *arg) +{ + assert(format[0] == '%'); + if (format[1] == 's') { + char *copy = strdup(param); + if (!copy) + return alloc_failed(); + + *(char **) var = copy; + } else { + if (sscanf(param, format, var) != 1) { + fprintf(stderr, "fuse: invalid parameter in option `%s'\n", arg); + return -1; + } + } + return 0; +} + +static int process_opt(struct fuse_opt_context *ctx, + const struct fuse_opt *opt, unsigned sep, + const char *arg, int iso) +{ + if (opt->offset == -1U) { + if (call_proc(ctx, arg, opt->value, iso) == -1) + return -1; + } else { + void *var = ctx->data + opt->offset; + if (sep && opt->templ[sep + 1]) { + const char *param = arg + sep; + if (opt->templ[sep] == '=') + param ++; + if (process_opt_param(var, opt->templ + sep + 1, + param, arg) == -1) + return -1; + } else + *(int *)var = opt->value; + } + return 0; +} + +static int process_opt_sep_arg(struct fuse_opt_context *ctx, + const struct fuse_opt *opt, unsigned sep, + const char *arg, int iso) +{ + int res; + char *newarg; + char *param; + + if (next_arg(ctx, arg) == -1) + return -1; + + param = ctx->argv[ctx->argctr]; + newarg = malloc(sep + strlen(param) + 1); + if (!newarg) + return alloc_failed(); + + memcpy(newarg, arg, sep); + strcpy(newarg + sep, param); + res = process_opt(ctx, opt, sep, newarg, iso); + free(newarg); + + return res; +} + +static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso) +{ + unsigned sep; + const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep); + if (opt) { + for (; opt; opt = find_opt(opt + 1, arg, &sep)) { + int res; + if (sep && opt->templ[sep] == ' ' && !arg[sep]) + res = process_opt_sep_arg(ctx, opt, sep, arg, iso); + else + res = process_opt(ctx, opt, sep, arg, iso); + if (res == -1) + return -1; + } + return 0; + } else + return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso); +} + +static int process_real_option_group(struct fuse_opt_context *ctx, char *opts) +{ + char *sep; + + do { + int res; + sep = strchr(opts, ','); + if (sep) + *sep = '\0'; + res = process_gopt(ctx, opts, 1); + if (res == -1) + return -1; + opts = sep + 1; + } while (sep); + + return 0; +} + +static int process_option_group(struct fuse_opt_context *ctx, const char *opts) +{ + int res; + char *copy; + const char *sep = strchr(opts, ','); + if (!sep) + return process_gopt(ctx, opts, 1); + + copy = strdup(opts); + if (!copy) { + fprintf(stderr, "fuse: memory allocation failed\n"); + return -1; + } + res = process_real_option_group(ctx, copy); + free(copy); + return res; +} + +static int process_one(struct fuse_opt_context *ctx, const char *arg) +{ + if (ctx->nonopt || arg[0] != '-') + return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0); + else if (arg[1] == 'o') { + if (arg[2]) + return process_option_group(ctx, arg + 2); + else { + if (next_arg(ctx, arg) == -1) + return -1; + + return process_option_group(ctx, ctx->argv[ctx->argctr]); + } + } else if (arg[1] == '-' && !arg[2]) { + if (add_arg(ctx, arg) == -1) + return -1; + ctx->nonopt = ctx->outargs.argc; + return 0; + } else + return process_gopt(ctx, arg, 0); +} + +static int opt_parse(struct fuse_opt_context *ctx) +{ + if (ctx->argc) { + if (add_arg(ctx, ctx->argv[0]) == -1) + return -1; + } + + for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++) + if (process_one(ctx, ctx->argv[ctx->argctr]) == -1) + return -1; + + if (ctx->opts) { + if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 || + fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1) + return -1; + } + if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc) { + free(ctx->outargs.argv[ctx->outargs.argc - 1]); + ctx->outargs.argv[--ctx->outargs.argc] = NULL; + } + + return 0; +} + +int fuse_opt_parse(struct fuse_args *args, void *data, + const struct fuse_opt opts[], fuse_opt_proc_t proc) +{ + int res; + struct fuse_opt_context ctx = { + .data = data, + .opt = opts, + .proc = proc, + }; + + if (!args || !args->argv || !args->argc) + return 0; + + ctx.argc = args->argc; + ctx.argv = args->argv; + + res = opt_parse(&ctx); + if (res != -1) { + struct fuse_args tmp = *args; + *args = ctx.outargs; + ctx.outargs = tmp; + } + free(ctx.opts); + fuse_opt_free_args(&ctx.outargs); + return res; +} diff --git a/compat/fuse_opt.h b/compat/fuse_opt.h new file mode 100644 index 0000000..9e159d5 --- /dev/null +++ b/compat/fuse_opt.h @@ -0,0 +1,258 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2006 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#ifndef _FUSE_OPT_H_ +#define _FUSE_OPT_H_ + +/* This file defines the option parsing interface of FUSE */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Option description + * + * This structure describes a single option, and and action associated + * with it, in case it matches. + * + * More than one such match may occur, in which case the action for + * each match is executed. + * + * There are three possible actions in case of a match: + * + * i) An integer (int or unsigned) variable determined by 'offset' is + * set to 'value' + * + * ii) The processing function is called, with 'value' as the key + * + * iii) An integer (any) or string (char *) variable determined by + * 'offset' is set to the value of an option parameter + * + * 'offset' should normally be either set to + * + * - 'offsetof(struct foo, member)' actions i) and iii) + * + * - -1 action ii) + * + * The 'offsetof()' macro is defined in the header. + * + * The template determines which options match, and also have an + * effect on the action. Normally the action is either i) or ii), but + * if a format is present in the template, then action iii) is + * performed. + * + * The types of templates are: + * + * 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only + * themselves. Invalid values are "--" and anything beginning + * with "-o" + * + * 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or + * the relevant option in a comma separated option list + * + * 3) "bar=", "--foo=", etc. These are variations of 1) and 2) + * which have a parameter + * + * 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform + * action iii). + * + * 5) "-x ", etc. Matches either "-xparam" or "-x param" as + * two separate arguments + * + * 6) "-x %s", etc. Combination of 4) and 5) + * + * If the format is "%s", memory is allocated for the string unlike + * with scanf(). + */ +struct fuse_opt { + /** Matching template and optional parameter formatting */ + const char *templ; + + /** + * Offset of variable within 'data' parameter of fuse_opt_parse() + * or -1 + */ + unsigned long offset; + + /** + * Value to set the variable to, or to be passed as 'key' to the + * processing function. Ignored if template has a format + */ + int value; +}; + +/** + * Key option. In case of a match, the processing function will be + * called with the specified key. + */ +#define FUSE_OPT_KEY(templ, key) { templ, -1U, key } + +/** + * Last option. An array of 'struct fuse_opt' must end with a NULL + * template value + */ +#define FUSE_OPT_END { .templ = NULL } + +/** + * Argument list + */ +struct fuse_args { + /** Argument count */ + int argc; + + /** Argument vector. NULL terminated */ + char **argv; + + /** Is 'argv' allocated? */ + int allocated; +}; + +/** + * Initializer for 'struct fuse_args' + */ +#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 } + +/** + * Key value passed to the processing function if an option did not + * match any template + */ +#define FUSE_OPT_KEY_OPT -1 + +/** + * Key value passed to the processing function for all non-options + * + * Non-options are the arguments beginning with a charater other than + * '-' or all arguments after the special '--' option + */ +#define FUSE_OPT_KEY_NONOPT -2 + +/** + * Special key value for options to keep + * + * Argument is not passed to processing function, but behave as if the + * processing function returned 1 + */ +#define FUSE_OPT_KEY_KEEP -3 + +/** + * Special key value for options to discard + * + * Argument is not passed to processing function, but behave as if the + * processing function returned zero + */ +#define FUSE_OPT_KEY_DISCARD -4 + +/** + * Processing function + * + * This function is called if + * - option did not match any 'struct fuse_opt' + * - argument is a non-option + * - option did match and offset was set to -1 + * + * The 'arg' parameter will always contain the whole argument or + * option including the parameter if exists. A two-argument option + * ("-x foo") is always converted to single arguemnt option of the + * form "-xfoo" before this function is called. + * + * Options of the form '-ofoo' are passed to this function without the + * '-o' prefix. + * + * The return value of this function determines whether this argument + * is to be inserted into the output argument vector, or discarded. + * + * @param data is the user data passed to the fuse_opt_parse() function + * @param arg is the whole argument or option + * @param key determines why the processing function was called + * @param outargs the current output argument list + * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept + */ +typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, + struct fuse_args *outargs); + +/** + * Option parsing function + * + * If 'args' was returned from a previous call to fuse_opt_parse() or + * it was constructed from + * + * A NULL 'args' is equivalent to an empty argument vector + * + * A NULL 'opts' is equivalent to an 'opts' array containing a single + * end marker + * + * A NULL 'proc' is equivalent to a processing function always + * returning '1' + * + * @param args is the input and output argument list + * @param data is the user data + * @param opts is the option description array + * @param proc is the processing function + * @return -1 on error, 0 on success + */ +int fuse_opt_parse(struct fuse_args *args, void *data, + const struct fuse_opt opts[], fuse_opt_proc_t proc); + +/** + * Add an option to a comma separated option list + * + * @param opts is a pointer to an option list, may point to a NULL value + * @param opt is the option to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_opt(char **opts, const char *opt); + +/** + * Add an argument to a NULL terminated argument vector + * + * @param args is the structure containing the current argument list + * @param arg is the new argument to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_arg(struct fuse_args *args, const char *arg); + +/** + * Add an argument at the specified position in a NULL terminated + * argument vector + * + * Adds the argument to the N-th position. This is useful for adding + * options at the beggining of the array which must not come after the + * special '--' option. + * + * @param args is the structure containing the current argument list + * @param pos is the position at which to add the argument + * @param arg is the new argument to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg); + +/** + * Free the contents of argument list + * + * The structure itself is not freed + * + * @param args is the structure containing the argument list + */ +void fuse_opt_free_args(struct fuse_args *args); + + +/** + * Check if an option matches + * + * @param opts is the option description array + * @param opt is the option to match + * @return 1 if a match is found, 0 if not + */ +int fuse_opt_match(const struct fuse_opt opts[], const char *opt); + +#ifdef __cplusplus +} +#endif + +#endif /* _FUSE_OPT_H_ */ diff --git a/const.h b/const.h new file mode 100644 index 0000000..afa2bf4 --- /dev/null +++ b/const.h @@ -0,0 +1,21 @@ +/* + fuse_xattrs - Add xattrs support using sidecar files + + Copyright (C) 2016 Felipe Barriga Richards + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#ifndef FUSE_XATTRS_CONST_H +#define FUSE_XATTRS_CONST_H + +#define SIDECAR_EXT ".xattr" +// TODO: This is just arbitrary... +#define MAX_METADATA_SIZE 8 * 1024 * 1024 // 8 MiB + +#define XATTR_NAME_MAX 255 /* # chars in an extended attribute name */ +#define XATTR_SIZE_MAX 65536 /* size of an extended attribute value (64k) */ +#define XATTR_LIST_MAX 65536 /* size of extended attribute namelist (64k) */ + +#endif //FUSE_XATTRS_CONST_H diff --git a/fuse_xattrs.1.in b/fuse_xattrs.1.in new file mode 100644 index 0000000..81a4123 --- /dev/null +++ b/fuse_xattrs.1.in @@ -0,0 +1,18 @@ +.TH FUSE_XATTRS "1" "October 2016" "FUSE_XATTRS version 0.1" "User Commands" +.SH NAME +FUSE_XATTRS \- Add xattrs support using sidecar files +.SH SYNOPSIS +.SS mounting +.TP +\fBfuse_xattrs\fP \fBmountpoint\fP +.SS unmounting +.TP +\fB__UNMOUNT_COMMAND__ mountpoint\fP +.SH DESCRIPTION +FUSE_XATTRS is a way to add xattrs support to any filesystem. The attributes are stored in sidecar files. +.PP +.PD +.SH "AUTHORS" +.LP +FUSE_XATTRS has been written by Felipe Barriga Richards . +.LP diff --git a/fuse_xattrs.c b/fuse_xattrs.c new file mode 100644 index 0000000..b94182e --- /dev/null +++ b/fuse_xattrs.c @@ -0,0 +1,134 @@ +/* + fuse_xattrs - Add xattrs support using sidecar files + + Copyright (C) 2016 Felipe Barriga Richards + + Based on passthrough.c (libfuse example) + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#define FUSE_USE_VERSION 30 + +/* For pread()/pwrite()/utimensat() */ +#define _XOPEN_SOURCE 700 + +#include +#include +#include + +#include +#include + +#include "utils.h" +#include "passthrough.h" + +#include "binary_storage.h" +#include "const.h" + +static int xmp_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) +{ + if (get_namespace(name) != USER) { + debug_print("Only user namespace is supported. name=%s\n", name); + return -ENOTSUP; + } + if (strlen(name) > XATTR_NAME_MAX) { + debug_print("attribute name must be equal or smaller than %d bytes\n", XATTR_NAME_MAX); + return -ERANGE; + } + if (size > XATTR_SIZE_MAX) { + debug_print("attribute value cannot be bigger than %d bytes\n", XATTR_SIZE_MAX); + return -ENOSPC; + } + +#ifdef DEBUG + char *sanitized_value = sanitize_value(value, size); + debug_print("path=%s name=%s value=%s size=%zu XATTR_CREATE=%d XATTR_REPLACE=%d\n", + path, name, sanitized_value, size, flags & XATTR_CREATE, flags & XATTR_REPLACE); + + free(sanitized_value); +#endif + return binary_storage_write_key(path, name, value, size, flags); +} + +static int xmp_getxattr(const char *path, const char *name, char *value, size_t size) +{ + if (get_namespace(name) != USER) { + debug_print("Only user namespace is supported. name=%s\n", name); + return -ENOTSUP; + } + if (strlen(name) > XATTR_NAME_MAX) { + debug_print("attribute name must be equal or smaller than %d bytes\n", XATTR_NAME_MAX); + return -ERANGE; + } + + debug_print("path=%s name=%s size=%zu\n", path, name, size); + return binary_storage_read_key(path, name, value, size); +} + +static int xmp_listxattr(const char *path, char *list, size_t size) +{ + if (size > XATTR_LIST_MAX) { + debug_print("The size of the list of attribute names for this file exceeds the system-imposed limit.\n"); + return -E2BIG; + } + + debug_print("path=%s size=%zu\n", path, size); + return binary_storage_list_keys(path, list, size); +} + +static int xmp_removexattr(const char *path, const char *name) +{ + if (get_namespace(name) != USER) { + debug_print("Only user namespace is supported. name=%s\n", name); + return -ENOTSUP; + } + if (strlen(name) > XATTR_NAME_MAX) { + debug_print("attribute name must be equal or smaller than %d bytes\n", XATTR_NAME_MAX); + return -ERANGE; + } + + debug_print("path=%s name=%s\n", path, name); + return binary_storage_remove_key(path, name); +} + +static struct fuse_operations xmp_oper = { + .getattr = xmp_getattr, + .access = xmp_access, + .readlink = xmp_readlink, + .readdir = xmp_readdir, + .mknod = xmp_mknod, + .mkdir = xmp_mkdir, + .symlink = xmp_symlink, + .unlink = xmp_unlink, + .rmdir = xmp_rmdir, + .rename = xmp_rename, + .link = xmp_link, + .chmod = xmp_chmod, + .chown = xmp_chown, + .truncate = xmp_truncate, +#ifdef HAVE_UTIMENSAT + .utimens = xmp_utimens, +#endif + .open = xmp_open, + .read = xmp_read, + .write = xmp_write, + .statfs = xmp_statfs, + .release = xmp_release, + .fsync = xmp_fsync, +#ifdef HAVE_POSIX_FALLOCATE + .fallocate = xmp_fallocate, +#endif + .setxattr = xmp_setxattr, + .getxattr = xmp_getxattr, + .listxattr = xmp_listxattr, + .removexattr = xmp_removexattr, +}; + +int main(int argc, char *argv[]) +{ + // TODO: parse options... + umask(0); + return fuse_main(argc, argv, &xmp_oper, NULL); +} diff --git a/passthrough.c b/passthrough.c new file mode 100644 index 0000000..97c178e --- /dev/null +++ b/passthrough.c @@ -0,0 +1,305 @@ +/* + fuse_xattrs - Add xattrs support using sidecar files + + Copyright (C) 2016 Felipe Barriga Richards + + Based on passthrough.c (libfuse example) + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#define FUSE_USE_VERSION 30 + +/* For pread()/pwrite()/utimensat() */ +#define _XOPEN_SOURCE 700 + +#include +#include +#include +#include +#include +#include + +int xmp_getattr(const char *path, struct stat *stbuf) { + int res; + + res = lstat(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_access(const char *path, int mask) { + int res; + + res = access(path, mask); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_readlink(const char *path, char *buf, size_t size) { + int res; + + res = readlink(path, buf, size - 1); + if (res == -1) + return -errno; + + buf[res] = '\0'; + return 0; +} + +int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi) +{ + DIR *dp; + struct dirent *de; + + (void) offset; + (void) fi; + + dp = opendir(path); + if (dp == NULL) + return -errno; + + while ((de = readdir(dp)) != NULL) { + struct stat st; + memset(&st, 0, sizeof(st)); + st.st_ino = de->d_ino; + st.st_mode = de->d_type << 12; + if (filler(buf, de->d_name, &st, 0)) + break; + } + + closedir(dp); + return 0; +} + +int xmp_mknod(const char *path, mode_t mode, dev_t rdev) { + int res; + + /* On Linux this could just be 'mknod(path, mode, rdev)' but this + is more portable */ + if (S_ISREG(mode)) { + res = open(path, O_CREAT | O_EXCL | O_WRONLY, mode); + if (res >= 0) + res = close(res); + } else if (S_ISFIFO(mode)) + res = mkfifo(path, mode); + else + res = mknod(path, mode, rdev); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_mkdir(const char *path, mode_t mode) { + int res; + + res = mkdir(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_unlink(const char *path) { + int res; + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_rmdir(const char *path) { + int res; + + res = rmdir(path); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_symlink(const char *from, const char *to) { + int res; + + res = symlink(from, to); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_rename(const char *from, const char *to) { + int res; + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_link(const char *from, const char *to) { + int res; + + res = link(from, to); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_chmod(const char *path, mode_t mode) { + int res; + + res = chmod(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_chown(const char *path, uid_t uid, gid_t gid) { + int res; + + res = lchown(path, uid, gid); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_truncate(const char *path, off_t size) { + int res; + + res = truncate(path, size); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_UTIMENSAT +int xmp_utimens(const char *path, const struct timespec ts[2], +struct fuse_file_info *fi) +{ + (void) fi; + int res; + + /* don't use utime/utimes since they follow symlinks */ + res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW); + if (res == -1) + return -errno; + + return 0; +} +#endif + +int xmp_open(const char *path, struct fuse_file_info *fi) { + int res; + + res = open(path, fi->flags); + if (res == -1) + return -errno; + + close(res); + return 0; +} + +int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) { + int fd; + int res; + + (void) fi; + fd = open(path, O_RDONLY); + if (fd == -1) + return -errno; + + res = pread(fd, buf, size, offset); + if (res == -1) + res = -errno; + + close(fd); + return res; +} + +int xmp_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) { + int fd; + int res; + + (void) fi; + fd = open(path, O_WRONLY); + if (fd == -1) + return -errno; + + res = pwrite(fd, buf, size, offset); + if (res == -1) + res = -errno; + + close(fd); + return res; +} + +int xmp_statfs(const char *path, struct statvfs *stbuf) { + int res; + + res = statvfs(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +int xmp_release(const char *path, struct fuse_file_info *fi) { + /* Just a stub. This method is optional and can safely be left + unimplemented */ + + (void) path; + (void) fi; + return 0; +} + +int xmp_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) { + /* Just a stub. This method is optional and can safely be left + unimplemented */ + + (void) path; + (void) isdatasync; + (void) fi; + return 0; +} + +#ifdef HAVE_POSIX_FALLOCATE +int xmp_fallocate(const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + int fd; + int res; + + (void) fi; + + if (mode) + return -EOPNOTSUPP; + + fd = open(path, O_WRONLY); + if (fd == -1) + return -errno; + + res = -posix_fallocate(fd, offset, length); + + close(fd); + return res; +} +#endif + diff --git a/passthrough.h b/passthrough.h new file mode 100644 index 0000000..81f8970 --- /dev/null +++ b/passthrough.h @@ -0,0 +1,36 @@ +/* + fuse_xattrs - Add xattrs support using sidecar files + + Copyright (C) 2016 Felipe Barriga Richards + + Based on passthrough.c (libfuse example) + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include + +int xmp_getattr(const char *path, struct stat *stbuf); +int xmp_access(const char *path, int mask); +int xmp_readlink(const char *path, char *buf, size_t size); +int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi); +int xmp_mknod(const char *path, mode_t mode, dev_t rdev); +int xmp_mkdir(const char *path, mode_t mode); +int xmp_unlink(const char *path); +int xmp_rmdir(const char *path); +int xmp_symlink(const char *from, const char *to); +int xmp_rename(const char *from, const char *to); +int xmp_link(const char *from, const char *to); +int xmp_chmod(const char *path, mode_t mode); +int xmp_chown(const char *path, uid_t uid, gid_t gid); +int xmp_truncate(const char *path, off_t size); +int xmp_utimens(const char *path, const struct timespec ts[2], struct fuse_file_info *fi); +int xmp_open(const char *path, struct fuse_file_info *fi); +int xmp_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi); +int xmp_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi); +int xmp_statfs(const char *path, struct statvfs *stbuf); +int xmp_release(const char *path, struct fuse_file_info *fi); +int xmp_fsync(const char *path, int isdatasync, struct fuse_file_info *fi); +int xmp_fallocate(const char *path, int mode, off_t offset, off_t length, struct fuse_file_info *fi); + diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..a260b52 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +mkdir -p test/mount +./fuse_xattrs -o nonempty test/mount/ + +pushd test + +set +e +python3 -m unittest -v +RESULT=$? +set -e + +popd + +fusermount -zu test/mount +rm -d test/mount + +exit ${RESULT} diff --git a/test/tests.py b/test/tests.py new file mode 100755 index 0000000..7153636 --- /dev/null +++ b/test/tests.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 + + +# fuse_xattrs - Add xattrs support using sidecar files +# +# Copyright (C) 2016 Felipe Barriga Richards +# +# This program can be distributed under the terms of the GNU GPL. +# See the file COPYING. + + +import unittest +import xattr +from pathlib import Path +import os + +# TODO +# - listxattr: list too long +# - sidecar file permissions + + +class TestXAttrs(unittest.TestCase): + def setUp(self): + self.randomFile = "./mount/tmp/foo.txt" + self.randomFileSidecar = "./mount/tmp/foo.txt.xattr" + if os.path.isfile(self.randomFile): + os.remove(self.randomFile) + Path(self.randomFile).touch() + + if os.path.isfile(self.randomFileSidecar): + os.remove(self.randomFileSidecar) + + def tearDown(self): + os.remove(self.randomFile) + + def test_xattr_set(self): + xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8")) + + def test_xattr_set_name_max_length(self): + enc = "utf-8" + key = "user." + "x" * 250 + value = "x" + self.assertEqual(len(key), 255) + xattr.setxattr(self.randomFile, key, bytes(value, enc)) + + key = "user." + "x" * 251 + self.assertEqual(len(key), 256) + with self.assertRaises(OSError) as ex: + xattr.setxattr(self.randomFile, key, bytes(value, enc)) + self.assertEqual(ex.exception.errno, 34) + self.assertEqual(ex.exception.strerror, "Numerical result out of range") + + @unittest.expectedFailure + def test_xattr_set_value_max_size(self): + enc = "utf-8" + key = "user.foo" + value = "x" * (64 * 1024 + 1) # we want max 64KiB of data + with self.assertRaises(OSError) as ex: + xattr.setxattr(self.randomFile, key, bytes(value, enc)) + + # on btrfs we get "no space left on device" + self.assertEqual(ex.exception.errno, 28) + self.assertEqual(ex.exception.strerror, "No space left on device") + + # on fuse_xattr we get "Argument list too long" + # the error is thrown by fuse, not by fuse_xattr code + + def test_xattr_set_namespaces(self): + with self.assertRaises(OSError) as ex: + xattr.setxattr(self.randomFile, "system.foo", bytes("bar", "utf-8")) + self.assertEqual(ex.exception.errno, 95) + self.assertEqual(ex.exception.strerror, "Operation not supported") + + with self.assertRaises(OSError) as ex: + xattr.setxattr(self.randomFile, "trust.foo", bytes("bar", "utf-8")) + self.assertEqual(ex.exception.errno, 95) + self.assertEqual(ex.exception.strerror, "Operation not supported") + + with self.assertRaises(OSError) as ex: + xattr.setxattr(self.randomFile, "foo.foo", bytes("bar", "utf-8")) + self.assertEqual(ex.exception.errno, 95) + self.assertEqual(ex.exception.strerror, "Operation not supported") + + with self.assertRaises(PermissionError) as ex: + xattr.setxattr(self.randomFile, "security.foo", bytes("bar", "utf-8")) + self.assertEqual(ex.exception.errno, 1) + self.assertEqual(ex.exception.strerror, "Operation not permitted") + + def test_xattr_get_non_existent(self): + key = "user.foo" + with self.assertRaises(OSError) as ex: + xattr.getxattr(self.randomFile, key) + self.assertEqual(ex.exception.errno, 61) + self.assertEqual(ex.exception.strerror, "No data available") + + def test_xattr_get(self): + enc = "utf-8" + key = "user.foo" + value = "bar" + xattr.setxattr(self.randomFile, key, bytes(value, enc)) + read_value = xattr.getxattr(self.randomFile, key) + self.assertEqual(value, read_value.decode(enc)) + + def test_xattr_set_override(self): + enc = "utf-8" + key = "user.foo" + value1 = "bar" + value2 = "rab" + xattr.setxattr(self.randomFile, key, bytes(value1, enc)) + xattr.setxattr(self.randomFile, key, bytes(value2, enc)) + read_value = xattr.getxattr(self.randomFile, key) + self.assertEqual(value2, read_value.decode(enc)) + + def test_xattr_set_create(self): + enc = "utf-8" + key = "user.foo" + value1 = "bar" + value2 = "rab" + xattr.setxattr(self.randomFile, key, bytes(value1, enc), xattr.XATTR_CREATE) + with self.assertRaises(FileExistsError) as ex: + xattr.setxattr(self.randomFile, key, bytes(value2, enc), xattr.XATTR_CREATE) + self.assertEqual(ex.exception.errno, 17) + self.assertEqual(ex.exception.strerror, "File exists") + + read_value = xattr.getxattr(self.randomFile, key) + self.assertEqual(value1, read_value.decode(enc)) + + def test_xattr_set_replace(self): + enc = "utf-8" + key = "user.foo" + value = "bar" + with self.assertRaises(OSError) as ex: + xattr.setxattr(self.randomFile, key, bytes(value, enc), xattr.XATTR_REPLACE) + self.assertEqual(ex.exception.errno, 61) + self.assertEqual(ex.exception.strerror, "No data available") + + with self.assertRaises(OSError) as ex: + xattr.getxattr(self.randomFile, key) + self.assertEqual(ex.exception.errno, 61) + self.assertEqual(ex.exception.strerror, "No data available") + + def test_xattr_list_empty(self): + attrs = xattr.listxattr(self.randomFile) + self.assertEqual(len(attrs), 0) + + def test_xattr_list(self): + enc = "utf-8" + key1 = "user.foo" + key2 = "user.foo2" + key3 = "user.foo3" + value = "bar" + + # set 3 keys + xattr.setxattr(self.randomFile, key1, bytes(value, enc)) + xattr.setxattr(self.randomFile, key2, bytes(value, enc)) + xattr.setxattr(self.randomFile, key3, bytes(value, enc)) + + # get and check the list + attrs = xattr.listxattr(self.randomFile) + + self.assertEqual(len(attrs), 3) + self.assertTrue(key1 in attrs) + self.assertTrue(key2 in attrs) + self.assertTrue(key3 in attrs) + + def test_xattr_unicode(self): + enc = "utf-8" + key = "user.fooシüßてЙĘ𝄠✠" + value = "bar" + + # set + xattr.setxattr(self.randomFile, key, bytes(value, enc)) + + # list + attrs = xattr.listxattr(self.randomFile) + self.assertEqual(len(attrs), 1) + self.assertTrue(key in attrs) + + # read + read_value = xattr.getxattr(self.randomFile, key) + self.assertEqual(value, read_value.decode(enc)) + + # remove + xattr.removexattr(self.randomFile, key) + + def test_xattr_remove(self): + enc = "utf-8" + key = "user.foo" + value = "bar" + + # set + xattr.setxattr(self.randomFile, key, bytes(value, enc)) + + # remove + xattr.removexattr(self.randomFile, key) + + # list should be empty + attrs = xattr.listxattr(self.randomFile) + self.assertEqual(len(attrs), 0) + + # should fail when trying to read it + with self.assertRaises(OSError) as ex: + xattr.getxattr(self.randomFile, key) + self.assertEqual(ex.exception.errno, 61) + self.assertEqual(ex.exception.strerror, "No data available") + + # removing twice should fail + with self.assertRaises(OSError) as ex: + xattr.getxattr(self.randomFile, key) + self.assertEqual(ex.exception.errno, 61) + self.assertEqual(ex.exception.strerror, "No data available") + + +if __name__ == '__main__': + unittest.main() diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..ccb84b5 --- /dev/null +++ b/utils.c @@ -0,0 +1,59 @@ +/* + fuse_xattrs - Add xattrs support using sidecar files + + Copyright (C) 2016 Felipe Barriga Richards + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include +#include +#include + +#include "utils.h" +#include "const.h" + + +char *get_sidecar_path(const char *path) +{ + const size_t path_len = strlen(path); + const size_t sidecar_ext_len = strlen(SIDECAR_EXT); // this can be optimized + const size_t sidecar_path_len = path_len + sidecar_ext_len + 1; + char *sidecar_path = (char *) malloc(sidecar_path_len); + memset(sidecar_path, '\0', sidecar_path_len); + memcpy(sidecar_path, path, path_len); + memcpy(sidecar_path + path_len, SIDECAR_EXT, sidecar_ext_len); + + return sidecar_path; +} + +// TODO: make it work for binary data +char *sanitize_value(const char *value, size_t value_size) +{ + char *sanitized = malloc(value_size + 1); + memcpy(sanitized, value, value_size); + sanitized[value_size] = '\0'; + return sanitized; +} + +enum namespace get_namespace(const char *name) { + if (strncmp(name, "user.", strlen("user.")) == 0) { + return USER; + } + + if (strncmp(name, "system.", strlen("system.")) == 0) { + return SYSTEM; + } + + if (strncmp(name, "security.", strlen("security.")) == 0) { + return SECURITY; + } + + if (strncmp(name, "trusted.", strlen("trusted.")) == 0) { + return TRUSTED; + } + + error_print("invalid namespace for key: %s\n", name); + return ERROR; +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..6f8d0d9 --- /dev/null +++ b/utils.h @@ -0,0 +1,42 @@ +/* + fuse_xattrs - Add xattrs support using sidecar files + + Copyright (C) 2016 Felipe Barriga Richards + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + + +#ifndef FUSE_XATTRS_UTILS_H +#define FUSE_XATTRS_UTILS_H + +#define DEBUG 1 + +#include +#include + +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) + +#define debug_print(fmt, ...) \ + do { \ + if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__); \ + } while (0) + +#define error_print(fmt, ...) \ + do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILENAME__, \ + __LINE__, __func__, ##__VA_ARGS__); } while (0) + +enum namespace { + SECURITY, + SYSTEM, + TRUSTED, + USER, + ERROR +}; + +enum namespace get_namespace(const char *name); +char *get_sidecar_path(const char *path); +char *sanitize_value(const char *value, size_t value_size); + +#endif //FUSE_XATTRS_UTILS_H -- 2.39.2