second commit :)
authorFelipe Barriga Richards <spam@felipebarriga.cl>
Mon, 24 Oct 2016 01:42:10 +0000 (22:42 -0300)
committerFelipe Barriga Richards <spam@felipebarriga.cl>
Mon, 31 Oct 2016 03:29:06 +0000 (00:29 -0300)
25 files changed:
.gitignore [new file with mode: 0644]
.idea/codeStyleSettings.xml [new file with mode: 0644]
.idea/dictionaries/fbarriga.xml [new file with mode: 0644]
.idea/misc.xml [new file with mode: 0644]
.idea/modules.xml [new file with mode: 0644]
.idea/vcs.xml [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
README.md
TODO.md [new file with mode: 0644]
binary_storage.c [new file with mode: 0644]
binary_storage.h [new file with mode: 0644]
compat/fuse_opt.c [new file with mode: 0644]
compat/fuse_opt.h [new file with mode: 0644]
const.h [new file with mode: 0644]
fuse_xattrs.1.in [new file with mode: 0644]
fuse_xattrs.c [new file with mode: 0644]
passthrough.c [new file with mode: 0644]
passthrough.h [new file with mode: 0644]
run_tests.sh [new file with mode: 0755]
test/tests.py [new file with mode: 0755]
utils.c [new file with mode: 0644]
utils.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..5de50e8
--- /dev/null
@@ -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 (file)
index 0000000..5e6dd34
--- /dev/null
@@ -0,0 +1,35 @@
+<?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
diff --git a/.idea/dictionaries/fbarriga.xml b/.idea/dictionaries/fbarriga.xml
new file mode 100644 (file)
index 0000000..d0996d6
--- /dev/null
@@ -0,0 +1,9 @@
+<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
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644 (file)
index 0000000..44332e5
--- /dev/null
@@ -0,0 +1,11 @@
+<?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
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644 (file)
index 0000000..e2953b6
--- /dev/null
@@ -0,0 +1,8 @@
+<?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
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644 (file)
index 0000000..94a25f7
--- /dev/null
@@ -0,0 +1,6 @@
+<?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
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..8feeddb
--- /dev/null
@@ -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 (file)
index 0000000..d764e5e
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,11 @@
+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
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e4254d6
--- /dev/null
@@ -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 <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)
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..b81b8a8
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,4 @@
+Release 0.1 (2016-10-23)
+------------------------
+
+* Initial release
index d57d794959e6515033a2367517ba13652db8182f..62e83994a9134c4aab933c247ee5ae947e8929be 100644 (file)
--- 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 (file)
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 (file)
index 0000000..139f3a0
--- /dev/null
@@ -0,0 +1,468 @@
+/*
+  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
diff --git a/binary_storage.h b/binary_storage.h
new file mode 100644 (file)
index 0000000..0b44d5a
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+  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
diff --git a/compat/fuse_opt.c b/compat/fuse_opt.c
new file mode 100644 (file)
index 0000000..582c6ad
--- /dev/null
@@ -0,0 +1,364 @@
+/*
+    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;
+}
diff --git a/compat/fuse_opt.h b/compat/fuse_opt.h
new file mode 100644 (file)
index 0000000..9e159d5
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+    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_ */
diff --git a/const.h b/const.h
new file mode 100644 (file)
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 <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
diff --git a/fuse_xattrs.1.in b/fuse_xattrs.1.in
new file mode 100644 (file)
index 0000000..81a4123
--- /dev/null
@@ -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 <felipe {at} felipebarriga.cl>.
+.LP
diff --git a/fuse_xattrs.c b/fuse_xattrs.c
new file mode 100644 (file)
index 0000000..b94182e
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+  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);
+}
diff --git a/passthrough.c b/passthrough.c
new file mode 100644 (file)
index 0000000..97c178e
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+  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
+
diff --git a/passthrough.h b/passthrough.h
new file mode 100644 (file)
index 0000000..81f8970
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+  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);
+
diff --git a/run_tests.sh b/run_tests.sh
new file mode 100755 (executable)
index 0000000..a260b52
--- /dev/null
@@ -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 (executable)
index 0000000..7153636
--- /dev/null
@@ -0,0 +1,215 @@
+#!/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()
diff --git a/utils.c b/utils.c
new file mode 100644 (file)
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 <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;
+}
diff --git a/utils.h b/utils.h
new file mode 100644 (file)
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 <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