--- /dev/null
+#
+# 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
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectCodeStyleSettingsManager">
+ <option name="PER_PROJECT_SETTINGS">
+ <value>
+ <Objective-C-extensions>
+ <file>
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
+ </file>
+ <class>
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
+ </class>
+ <extensions>
+ <pair source="cpp" header="h" />
+ <pair source="c" header="h" />
+ </extensions>
+ </Objective-C-extensions>
+ </value>
+ </option>
+ <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<component name="ProjectDictionaryState">
+ <dictionary name="fbarriga">
+ <words>
+ <w>fbarriga</w>
+ <w>xattr</w>
+ <w>xattrs</w>
+ </words>
+ </dictionary>
+</component>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
+ <component name="CidrRootsConfiguration">
+ <excludeRoots>
+ <file path="$PROJECT_DIR$/build/test" />
+ <file path="$PROJECT_DIR$/test/mount" />
+ </excludeRoots>
+ </component>
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.5.2 virtualenv at ~/virt_env_python3" project-jdk-type="Python SDK" />
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/fuse_xattrs.iml" filepath="$PROJECT_DIR$/.idea/fuse_xattrs.iml" />
+ </modules>
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+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
--- /dev/null
+Current Maintainer
+------------------
+
+Felipe Barriga Richards <felipe {at} felipebarriga.cl>
+
+
+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
--- /dev/null
+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 <sys/types.h>
+ #include <sys/xattr.h>
+ 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)
--- /dev/null
+Release 0.1 (2016-10-23)
+------------------------
+
+* Initial release
-# 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/
--- /dev/null
+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
--- /dev/null
+/*
+ fuse_xattrs - Add xattrs support using sidecar files
+
+ Copyright (C) 2016 Felipe Barriga Richards <felipe {at} felipebarriga.cl>
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "binary_storage.h"
+#include "utils.h"
+#include "const.h"
+
+#include <attr/xattr.h>
+
+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
--- /dev/null
+/*
+ fuse_xattrs - Add xattrs support using sidecar files
+
+ Copyright (C) 2016 Felipe Barriga Richards <felipe {at} felipebarriga.cl>
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+*/
+#include <stdio.h>
+
+#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
--- /dev/null
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPL.
+ See the file COPYING.LIB
+*/
+
+#include "fuse_opt.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+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;
+}
--- /dev/null
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
+
+ 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 <stddef.h> 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_ */
--- /dev/null
+/*
+ fuse_xattrs - Add xattrs support using sidecar files
+
+ Copyright (C) 2016 Felipe Barriga Richards <felipe {at} felipebarriga.cl>
+
+ 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
--- /dev/null
+.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 <felipe {at} felipebarriga.cl>.
+.LP
--- /dev/null
+/*
+ fuse_xattrs - Add xattrs support using sidecar files
+
+ Copyright (C) 2016 Felipe Barriga Richards <felipe {at} felipebarriga.cl>
+
+ 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <fuse.h>
+#include <sys/xattr.h>
+
+#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);
+}
--- /dev/null
+/*
+ fuse_xattrs - Add xattrs support using sidecar files
+
+ Copyright (C) 2016 Felipe Barriga Richards <felipe {at} felipebarriga.cl>
+
+ 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 <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+
+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
+
--- /dev/null
+/*
+ fuse_xattrs - Add xattrs support using sidecar files
+
+ Copyright (C) 2016 Felipe Barriga Richards <felipe {at} felipebarriga.cl>
+
+ Based on passthrough.c (libfuse example)
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+*/
+
+#include <fuse.h>
+
+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);
+
--- /dev/null
+#!/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}
--- /dev/null
+#!/usr/bin/env python3
+
+
+# fuse_xattrs - Add xattrs support using sidecar files
+#
+# Copyright (C) 2016 Felipe Barriga Richards <felipe {at} felipebarriga.cl>
+#
+# 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()
--- /dev/null
+/*
+ fuse_xattrs - Add xattrs support using sidecar files
+
+ Copyright (C) 2016 Felipe Barriga Richards <felipe {at} felipebarriga.cl>
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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;
+}
--- /dev/null
+/*
+ fuse_xattrs - Add xattrs support using sidecar files
+
+ Copyright (C) 2016 Felipe Barriga Richards <felipe {at} felipebarriga.cl>
+
+ 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 <string.h>
+#include <stdio.h>
+
+#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