diff --git a/src/openvfsfuse/main.cpp b/src/openvfsfuse/main.cpp index 507aab7..6cbe07c 100644 --- a/src/openvfsfuse/main.cpp +++ b/src/openvfsfuse/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -42,7 +43,7 @@ namespace { void usage(char *name) { std::cerr << "Usage:" << std::endl // - << name << " [-h] | [-f] [-p] [-d] -i config-file -o ownerId /directory-mountpoint" << std::endl // + << name << " [-h] | [-f] [-p] [-d] [-x key1,key2,...] -i config-file -o ownerId /directory-mountpoint" << std::endl // << "Type 'man openvfsfuse' for more details" << std::endl; } @@ -57,7 +58,7 @@ std::optional processArgs(int argc, char *argv[]) bool got_p = false; - while ((res = getopt(argc, argv, "hpfdi:o:")) != -1) { + while ((res = getopt(argc, argv, "hpfdi:o:x:")) != -1) { switch (res) { case 'h': usage(argv[0]); @@ -90,6 +91,19 @@ std::optional processArgs(int argc, char *argv[]) case 'o': out.owner = optarg; break; + case 'x': { + // Parse comma-separated list of msgpack keys to strip on non-client setxattr + std::string keys(optarg); + std::istringstream ss(keys); + std::string key; + while (std::getline(ss, key, ',')) { + if (!key.empty()) { + out.stripXattrKeys.push_back(key); + } + } + std::cout << "openVFSfuse stripping xattr keys: " << keys << std::endl; + break; + } default: assert(false); break; diff --git a/src/openvfsfuse/openvfsfuse.cpp b/src/openvfsfuse/openvfsfuse.cpp index 470ab80..4a2576a 100644 --- a/src/openvfsfuse/openvfsfuse.cpp +++ b/src/openvfsfuse/openvfsfuse.cpp @@ -55,6 +55,7 @@ class VFSFuseContext , _rootHandle(open(_mountPoint.c_str(), 0)) , _appsNoHydrateFull(args.appsNoHydrateFull) , _appsNoHydrateEndsWith(args.appsNoHydrateEndsWith) + , _stripXattrKeys(args.stripXattrKeys) { assert(!_instance); _instance = this; @@ -99,6 +100,8 @@ class VFSFuseContext || std::ranges::find_if(_appsNoHydrateEndsWith, [app](const auto &a) { return app.ends_with(a); }) != _appsNoHydrateEndsWith.cend(); } + const auto &stripXattrKeys() const { return _stripXattrKeys; } + private: static VFSFuseContext *_instance; std::filesystem::path _mountPoint; @@ -106,6 +109,7 @@ class VFSFuseContext int _rootHandle; std::vector _appsNoHydrateFull; std::vector _appsNoHydrateEndsWith; + std::vector _stripXattrKeys; }; VFSFuseContext *VFSFuseContext::_instance = nullptr; @@ -671,6 +675,39 @@ static int openVFSfuse_fsync(const char *orig_path, int isdatasync, struct fuse_ /* xattr operations are optional and can safely be left unimplemented */ static int openVFSfuse_setxattr(const char *orig_path, const char *name, const char *value, size_t size, int flags) { + // When a non-client process writes user.openvfs.data, strip keys listed + // in stripXattrKeys (configured via -x flag). This prevents file managers + // from copying per-file identity fields (fileid, immutable, syncerror) + // on copy+paste, while preserving user-set metadata (comments, tags, etag). + // + // We selectively remove keys rather than stripping all openvfs xattrs + // because some fields (etag, pinstate, size) are harmless or even useful + // to keep, and future user-defined metadata should survive copies. + if (std::string_view(name) == OpenVFS::Constants::XAttributeNames::Data + && !VFSFuseContext::instance().stripXattrKeys().empty()) { + auto *context = fuse_get_context(); + if (context && context->pid != _jobs.desktopClientPid()) { + try { + auto j = nlohmann::json::from_msgpack(std::vector(value, value + size)); + bool modified = false; + for (const auto &key : VFSFuseContext::instance().stripXattrKeys()) { + if (j.contains(key)) { + j.erase(key); + modified = true; + } + } + if (modified) { + auto cleaned = nlohmann::json::to_msgpack(j); + const auto path = getInternalPath(orig_path); + openvfsfuse_log(path, "setxattr", 0, "stripped keys from copy by pid %d", context->pid); + return Xattr::setxattr(path, name, reinterpret_cast(cleaned.data()), cleaned.size(), flags); + } + } catch (...) { + // If msgpack parsing fails, pass through unchanged + } + } + } + const auto path = getInternalPath(orig_path); return Xattr::setxattr(path, name, value, size, flags); } diff --git a/src/openvfsfuse/openvfsfuse.h b/src/openvfsfuse/openvfsfuse.h index bdd0deb..72d8f90 100644 --- a/src/openvfsfuse/openvfsfuse.h +++ b/src/openvfsfuse/openvfsfuse.h @@ -23,6 +23,7 @@ struct openVFSfuse_Args std::vector fuseArgv; std::vector appsNoHydrateFull; // these apps are not permitted to cause a dehydration std::vector appsNoHydrateEndsWith; + std::vector stripXattrKeys; // msgpack keys stripped from user.openvfs.data on non-client writes }; int initializeOpenVFSFuse(openVFSfuse_Args &openVFSArgs);