Namespaces are disabled by default (can be enabled at runtime with a flag). OSX compa...
authorFelipe Barriga Richards <spam@felipebarriga.cl>
Sun, 28 May 2017 01:08:01 +0000 (21:08 -0400)
committerFelipe Barriga Richards <spam@felipebarriga.cl>
Sun, 28 May 2017 01:08:01 +0000 (21:08 -0400)
CMakeLists.txt
ChangeLog
README.md
binary_storage.c
fuse_xattrs.c
passthrough.c
passthrough.h
run_tests.sh
test/tests.py
xattrs_config.h

index c61c483368afe83aa47486aac76385bfe67eea26..b7403a4d63fe796cb3d4ac4fb66d6a6b8268e963 100644 (file)
@@ -34,11 +34,11 @@ endif()
 include (CheckCSourceCompiles)
 check_c_source_compiles ("
   #include <sys/types.h>
-  #include <attr/xattr.h>
+  #include <sys/xattr.h>
   int main() { return 1; }
-  " HAVE_ATTR_XATTR_H)
-if(NOT HAVE_ATTR_XATTR_H)
-    message(FATAL_ERROR "attr/xattr.h not found")
+  " HAVE_SYS_XATTR_H)
+if(NOT HAVE_SYS_XATTR_H)
+    message(FATAL_ERROR "sys/xattr.h not found")
 endif()
 
 # set required definitions
@@ -68,10 +68,18 @@ set(SOURCE_FILES
 
 add_executable(fuse_xattrs ${SOURCE_FILES})
 
-target_link_libraries (
-        fuse_xattrs
-        fuse
-)
+if(APPLE)
+    target_link_libraries (
+            fuse_xattrs
+            osxfuse
+    )
+else()
+    target_link_libraries (
+            fuse_xattrs
+            fuse
+    )
+endif()
+
 
 install (TARGETS fuse_xattrs DESTINATION bin)
 install (
index 2712ccd1983cc56c3ac1a893a5733f4f1306d001..5ec8686d3f609ff3f57e869632c738debb5ffac4 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+Release 0.4 (2017-05-27)
+------------------------
+
+* Now compiles and run on OSX (probably also on BSD)
+* Namespaces can be enabled on runtime (default: disabled)
+
 Release 0.3 (2017-05-21)
 ------------------------
 
index ef681053609bb3a339cfd6d44576180531d4dffd..ea636f03b7c0d429d8c071d0d3d2a52267299153 100644 (file)
--- a/README.md
+++ b/README.md
@@ -36,6 +36,11 @@ http://github.com/libfuse/libfuse.
     cmake ..
     make
 
+Dependencies:
+
+    cmake
+    osxfuse (macOS only)
+
 ## Code Coverage
 
     mkdir build && cd build
@@ -43,6 +48,10 @@ http://github.com/libfuse/libfuse.
     make
     make fuse_xattrs_coverage
 
+Dependencies:
+
+    easy_install-3.6 xattr==0.9.1
+    
 ## Installing
 
     make install
index 5e8743619744b6cfbf4c787cef51f545241f9509..c782d8672123924ac72d155614c716f8c8faacb2 100644 (file)
 #include "utils.h"
 #include "fuse_xattrs_config.h"
 
-#include <attr/xattr.h>
+#include <sys/xattr.h>
+
+#ifndef ENOATTR
+    #define ENOATTR ENODATA
+#endif
 
 struct on_memory_attr {
     u_int16_t name_size;
@@ -197,7 +201,7 @@ int __write_to_file(FILE *file, const char *name, const char *value, const size_
 
 #ifdef DEBUG
     char *sanitized_value = sanitize_value(value, value_size);
-    debug_print("name='%s' name_size=%zu sanitized_value='%s' value_size=%zu\n", name, name_size, sanitized_value, value_size);
+    debug_print("name='%s' name_size=%hu sanitized_value='%s' value_size=%zu\n", name, name_size, sanitized_value, value_size);
     free(sanitized_value);
 #endif
 
@@ -230,7 +234,7 @@ int __write_to_file(FILE *file, const char *name, const char *value, const size_
  * @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.
+ * @return On success, zero is returned.  On failure, -errno is returned.
  */
 int binary_storage_write_key(const char *path, const char *name, const char *value, size_t size, int flags)
 {
@@ -245,7 +249,7 @@ int binary_storage_write_key(const char *path, const char *name, const char *val
 
     if (buffer == NULL && buffer_size == -ENOENT && flags & XATTR_REPLACE) {
         error_print("No xattr. (flag XATTR_REPLACE)");
-        return -ENODATA;
+        return -ENOATTR;
     }
 
     if (buffer == NULL && buffer_size != -ENOENT) {
@@ -415,7 +419,7 @@ int binary_storage_list_keys(const char *path, char *list, size_t size)
 
     if (size == 0 && res > XATTR_LIST_MAX) {
         // FIXME: we should return the size or an error ?
-        return -E2BIG;
+        return -ENOSPC;
     }
 
     return (int)res;
@@ -474,4 +478,4 @@ int binary_storage_remove_key(const char *path, const char *name)
     fclose(file);
     free(buffer);
     return res;
-}
\ No newline at end of file
+}
index 242e1717f1d27e43aeda694e77e260ba7dd6eeb3..3d3a5a493abed3208166a44fc4167bc1ea694a4d 100644 (file)
 /* For pread()/pwrite()/utimensat() */
 #define _XOPEN_SOURCE 700
 
-/* For get_current_dir_name */
-#define _GNU_SOURCE
-
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <stddef.h>
+#include <assert.h>
+
+#ifdef __APPLE__
+    #include <osxfuse/fuse.h>
+#else
+    #include <fuse.h>
+#endif
 
-#include <fuse.h>
 #include <sys/xattr.h>
+#include <sys/param.h>
 
 #include "fuse_xattrs_config.h"
 
@@ -40,7 +44,7 @@ static int xmp_setxattr(const char *path, const char *name, const char *value, s
         return -ENOENT;
     }
 
-    if (get_namespace(name) != USER) {
+    if (xattrs_config.enable_namespaces == 1 && get_namespace(name) != USER) {
         debug_print("Only user namespace is supported. name=%s\n", name);
         return -ENOTSUP;
     }
@@ -75,7 +79,7 @@ static int xmp_getxattr(const char *path, const char *name, char *value, size_t
         return -ENOENT;
     }
 
-    if (get_namespace(name) != USER) {
+    if (xattrs_config.enable_namespaces == 1 && get_namespace(name) != USER) {
         debug_print("Only user namespace is supported. name=%s\n", name);
         return -ENOTSUP;
     }
@@ -92,6 +96,18 @@ static int xmp_getxattr(const char *path, const char *name, char *value, size_t
     return rtval;
 }
 
+#ifdef __APPLE__
+    static int xmp_setxattr_apple(const char *path, const char *name, const char *value, size_t size, int flags, u_int32_t position) {
+        assert(position == 0);
+        return xmp_setxattr(path, name, value, size, flags);
+    }
+
+    static int xmp_getxattr_apple(const char *path, const char *name, char *value, size_t size, u_int32_t position) {
+        assert(position == 0);
+        return xmp_getxattr(path, name, value, size);
+    }
+#endif
+
 static int xmp_listxattr(const char *path, char *list, size_t size)
 {
     if (xattrs_config.show_sidecar == 0 && filename_is_sidecar(path) == 1)  {
@@ -117,7 +133,7 @@ static int xmp_removexattr(const char *path, const char *name)
         return -ENOENT;
     }
 
-    if (get_namespace(name) != USER) {
+    if (xattrs_config.enable_namespaces == 1 && get_namespace(name) != USER) {
         debug_print("Only user namespace is supported. name=%s\n", name);
         return -ENOTSUP;
     }
@@ -161,8 +177,13 @@ static struct fuse_operations xmp_oper = {
 #ifdef HAVE_POSIX_FALLOCATE
         .fallocate   = xmp_fallocate,
 #endif
+#ifdef __APPLE__
+        .setxattr    = xmp_setxattr_apple,
+        .getxattr    = xmp_getxattr_apple,
+#else
         .setxattr    = xmp_setxattr,
         .getxattr    = xmp_getxattr,
+#endif
         .listxattr   = xmp_listxattr,
         .removexattr = xmp_removexattr,
 };
@@ -188,7 +209,8 @@ const char *sanitized_source_directory(const char *path) {
         return absolute_path;
     }
 
-    char *pwd = get_current_dir_name();
+    static char cwd[MAXPATHLEN];
+    char *pwd = getcwd(cwd, sizeof(cwd));
     size_t len = strlen(pwd) + 1 + strlen(path) + 1;
     int has_trailing_backslash = (path[strlen(path)-1] == '/');
     if (!has_trailing_backslash)
@@ -217,7 +239,8 @@ enum {
 #define FUSE_XATTRS_OPT(t, p, v) { t, offsetof(struct xattrs_config, p), v }
 
 static struct fuse_opt xattrs_opts[] = {
-        FUSE_XATTRS_OPT("show_sidecar",    show_sidecar, 1),
+        FUSE_XATTRS_OPT("show_sidecar",         show_sidecar,       1),
+        FUSE_XATTRS_OPT("enable_namespaces",    enable_namespaces,  1),
 
         FUSE_OPT_KEY("-V",                 KEY_VERSION),
         FUSE_OPT_KEY("--version",          KEY_VERSION),
@@ -249,6 +272,7 @@ static int xattrs_opt_proc(void *data, const char *arg, int key,
                             "\n"
                             "FUSE XATTRS options:\n"
                             "    -o show_sidecar  don't hide sidecar files\n"
+                            "    -o enable_namespaces  enable namespaces checks\n"
                             "\n", outargs->argv[0]);
 
             fuse_opt_add_arg(outargs, "-ho");
index cacc6b49e93f8730c5846dfdf6950bb06ecee45d..b827da98968c4645bae6b7bbfb919df3c39b94f5 100644 (file)
 /* For pread()/pwrite()/utimensat() */
 #define _XOPEN_SOURCE 700
 
-#include <fuse.h>
+#ifdef __APPLE__
+    #include <osxfuse/fuse.h>
+#else
+    #include <fuse.h>
+#endif
+
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
index 81f8970b922799c46eda9eaeb2d1a718f198fccd..928ec4a2c9210518839bde104c5d7225b37e91cb 100644 (file)
@@ -9,7 +9,11 @@
   See the file COPYING.
 */
 
-#include <fuse.h>
+#ifdef __APPLE__
+    #include <osxfuse/fuse.h>
+#else
+    #include <fuse.h>
+#endif
 
 int xmp_getattr(const char *path, struct stat *stbuf);
 int xmp_access(const char *path, int mask);
index 1f2fbf14f4f3e3f6e0e4db7ae2e04c07fd43bd27..5a0b4a0ffb64c2565b27d04cc88a01c11df281f5 100755 (executable)
@@ -2,7 +2,7 @@
 
 mkdir -p test/mount
 mkdir -p test/source
-./fuse_xattrs -o nonempty test/source/ test/mount/
+./fuse_xattrs -o enable_namespaces test/source/ test/mount/
 
 if [ $? -ne 0 ]; then
     echo "Error mounting the filesystem."
@@ -19,7 +19,12 @@ set -e
 
 popd
 
-fusermount -zu test/mount
+if [[ "$OSTYPE" == "darwin"* ]]; then
+    umount test/mount
+else
+    fusermount -uz test/mount
+fi
+
 rm -d test/source
 rm -d test/mount
 
index 304676f2f17c0484a5bd630b4564e8303baba33a..0525b94ee436027e5ce7f27a4f6a9c806a94da80 100755 (executable)
@@ -13,17 +13,26 @@ import unittest
 import xattr
 from pathlib import Path
 import os
+import errno
+import sys
 
 if xattr.__version__ != '0.9.1':
     print("WARNING, only tested with xattr version 0.9.1")
 
+# Linux / BSD Compatibility
+ENOATTR = errno.ENODATA
+if hasattr(errno, "ENOATTR"):
+    ENOATTR = errno.ENOATTR
+
 # TODO
 # - listxattr: list too long
 # - sidecar file permissions
 # - corrupt metadata files
 
+skipNamespaceTests = False
+
 
-class TestXAttrs(unittest.TestCase):
+class TestXAttrBase(unittest.TestCase):
     def setUp(self):
         self.sourceDir = "./source/"
         self.mountDir = "./mount/"
@@ -52,65 +61,19 @@ class TestXAttrs(unittest.TestCase):
         if os.path.isfile(self.randomFileSidecar):
             os.remove(self.randomFileSidecar)
 
-    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
+##########################################################################################
+# The following tests are generic for any FS that supports extended attributes
+class TestGenericXAttrs(TestXAttrBase):
 
-    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_set(self):
+        xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
 
     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")
+        self.assertEqual(ex.exception.errno, ENOATTR)
 
     def test_xattr_get(self):
         enc = "utf-8"
@@ -120,6 +83,7 @@ class TestXAttrs(unittest.TestCase):
         read_value = xattr.getxattr(self.randomFile, key)
         self.assertEqual(value, read_value.decode(enc))
 
+    # FIXME: On OSX fuse is replacing the return value 0 with -1
     def test_xattr_set_empty(self):
         enc = "utf-8"
         key = "user.foo"
@@ -158,13 +122,11 @@ class TestXAttrs(unittest.TestCase):
         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")
+        self.assertEqual(ex.exception.errno, ENOATTR)
 
         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")
+        self.assertEqual(ex.exception.errno, ENOATTR)
 
     def test_xattr_list_empty(self):
         attrs = xattr.listxattr(self.randomFile)
@@ -228,14 +190,79 @@ class TestXAttrs(unittest.TestCase):
         # 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")
+        self.assertEqual(ex.exception.errno, ENOATTR)
 
         # 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")
+        self.assertEqual(ex.exception.errno, ENOATTR)
+
+    def test_xattr_set_name_max_length(self):
+        max_len = 255
+        if sys.platform != 'linux':
+            max_len = 127   # OSX VFS only support names up to 127 bytes
+
+        enc = "utf-8"
+        key = "user." + "x" * (max_len - 5)
+        value = "x"
+        self.assertEqual(len(key), max_len)
+        xattr.setxattr(self.randomFile, key, bytes(value, enc))  # NOTE: WILL FAIL IF RUNNING ON HFS+
+
+        key = "user." + "x" * (max_len - 5 + 1)
+        self.assertEqual(len(key), max_len + 1)
+        with self.assertRaises(OSError) as ex:
+            xattr.setxattr(self.randomFile, key, bytes(value, enc))
+
+        if sys.platform == 'linux':
+            self.assertEqual(ex.exception.errno, errno.ERANGE)  # This is the behavior of BTRFS
+        else:
+            self.assertEqual(ex.exception.errno, errno.ENAMETOOLONG)
+
+    #########################################################
+    # On Linux: The test fails as the max value is detected before reaching the filesystem
+    # On OSX: Works on fuse_xattr but fails on HFS+ (doesn't has limit)
+    @unittest.skipIf(sys.platform == "linux", "Skipping test on Linux")
+    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))  # NOTE: This test fail on HFS+ (doesn't have limit)
+
+        self.assertEqual(ex.exception.errno, errno.ENOSPC)
+
+        # on fuse_xattr we get "Argument list too long"
+        # the error is thrown by fuse, not by fuse_xattr code
+
+    #########################################################
+    # Those tests are going to pass on Linux but fail on BSD
+    # BSD doesn't support namespaces.
+    @unittest.skipIf(skipNamespaceTests, "Namespace tests disabled")
+    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")
+
+
+######################################################################
+# The following tests should fail if they are running on a normal FS
+class TestFuseXATTR(TestXAttrBase):
 
     def test_hide_sidecar(self):
         xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
@@ -251,7 +278,7 @@ class TestXAttrs(unittest.TestCase):
         self.assertTrue(self.randomFilename in files_source)
         self.assertTrue(sidecarFilename in files_source)
 
-    def test_create_new_file(self):
+    def test_fuse_xattr_create_new_file(self):
         test_filename = "test_create_new_file"
         self.assertFalse(os.path.isfile(self.sourceDir + test_filename))
         self.assertFalse(os.path.isfile(self.mountDir + test_filename))
@@ -262,7 +289,7 @@ class TestXAttrs(unittest.TestCase):
         # FIXME: if one assert fails, the file isn't going to be deleted
         os.remove(self.mountDir + test_filename)
 
-    def test_remove_file_with_sidecar(self):
+    def test_fuse_xattr_remove_file_with_sidecar(self):
         xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
         self.assertTrue(os.path.isfile(self.randomFile))
         self.assertTrue(os.path.isfile(self.randomSourceFile))
@@ -273,7 +300,7 @@ class TestXAttrs(unittest.TestCase):
         self.assertFalse(os.path.isfile(self.randomSourceFile))
         self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
 
-    def test_remove_file_without_sidecar(self):
+    def test_fuse_xattr_remove_file_without_sidecar(self):
         self.assertTrue(os.path.isfile(self.randomFile))
         self.assertTrue(os.path.isfile(self.randomSourceFile))
         self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
index b3a2917dc5042cbbb3da8d2a98b735f64eb69b23..87e133b76311287257e135c5aae1a3e88c93241e 100644 (file)
@@ -12,6 +12,7 @@
 
 struct xattrs_config {
     const int show_sidecar;
+    const int enable_namespaces;
     const char *source_dir;
     size_t source_dir_size;