From 44ba89e4e046391df5001e48b9469f7c0ef254f7 Mon Sep 17 00:00:00 2001 From: Felipe Barriga Richards Date: Sat, 27 May 2017 21:08:01 -0400 Subject: [PATCH] Namespaces are disabled by default (can be enabled at runtime with a flag). OSX compatibility. --- CMakeLists.txt | 24 +++++--- ChangeLog | 6 ++ README.md | 9 +++ binary_storage.c | 16 +++-- fuse_xattrs.c | 42 ++++++++++--- passthrough.c | 7 ++- passthrough.h | 6 +- run_tests.sh | 9 ++- test/tests.py | 155 ++++++++++++++++++++++++++++------------------- xattrs_config.h | 1 + 10 files changed, 184 insertions(+), 91 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c61c483..b7403a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,11 +34,11 @@ endif() include (CheckCSourceCompiles) check_c_source_compiles (" #include - #include + #include 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 ( diff --git a/ChangeLog b/ChangeLog index 2712ccd..5ec8686 100644 --- 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) ------------------------ diff --git a/README.md b/README.md index ef68105..ea636f0 100644 --- 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 diff --git a/binary_storage.c b/binary_storage.c index 5e87436..c782d86 100644 --- a/binary_storage.c +++ b/binary_storage.c @@ -17,7 +17,11 @@ #include "utils.h" #include "fuse_xattrs_config.h" -#include +#include + +#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 +} diff --git a/fuse_xattrs.c b/fuse_xattrs.c index 242e171..3d3a5a4 100644 --- a/fuse_xattrs.c +++ b/fuse_xattrs.c @@ -14,17 +14,21 @@ /* For pread()/pwrite()/utimensat() */ #define _XOPEN_SOURCE 700 -/* For get_current_dir_name */ -#define _GNU_SOURCE - #include #include #include #include #include +#include + +#ifdef __APPLE__ + #include +#else + #include +#endif -#include #include +#include #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"); diff --git a/passthrough.c b/passthrough.c index cacc6b4..b827da9 100644 --- a/passthrough.c +++ b/passthrough.c @@ -14,7 +14,12 @@ /* For pread()/pwrite()/utimensat() */ #define _XOPEN_SOURCE 700 -#include +#ifdef __APPLE__ + #include +#else + #include +#endif + #include #include #include diff --git a/passthrough.h b/passthrough.h index 81f8970..928ec4a 100644 --- a/passthrough.h +++ b/passthrough.h @@ -9,7 +9,11 @@ See the file COPYING. */ -#include +#ifdef __APPLE__ + #include +#else + #include +#endif int xmp_getattr(const char *path, struct stat *stbuf); int xmp_access(const char *path, int mask); diff --git a/run_tests.sh b/run_tests.sh index 1f2fbf1..5a0b4a0 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -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 diff --git a/test/tests.py b/test/tests.py index 304676f..0525b94 100755 --- a/test/tests.py +++ b/test/tests.py @@ -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)) diff --git a/xattrs_config.h b/xattrs_config.h index b3a2917..87e133b 100644 --- a/xattrs_config.h +++ b/xattrs_config.h @@ -12,6 +12,7 @@ struct xattrs_config { const int show_sidecar; + const int enable_namespaces; const char *source_dir; size_t source_dir_size; -- 2.39.2