]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
os/chain_xattr: move chained xattr helpers into separate file/module
authorSage Weil <sage@inktank.com>
Sat, 27 Oct 2012 21:35:35 +0000 (14:35 -0700)
committerSage Weil <sage@inktank.com>
Mon, 5 Nov 2012 08:13:52 +0000 (00:13 -0800)
* Rename do_* -> chain_*.
* Move #defines to a header.
* Fix *Index users
* Implement both *xattr() and f*xattr() variants for get, set, list,
  remove.

Signed-off-by: Sage Weil <sage@inktank.com>
src/Makefile.am
src/common/xattr.h
src/os/FileStore.cc
src/os/FlatIndex.cc
src/os/IndexManager.cc
src/os/LFNIndex.cc
src/os/chain_xattr.cc [new file with mode: 0644]
src/os/chain_xattr.h [new file with mode: 0644]

index 926a5321f15e426b8ec6164972db167db8848cd5..ea5c52cabf501f74c1a30a9e76969f6fbd788dea 100644 (file)
@@ -1314,6 +1314,7 @@ noinst_LIBRARIES += libmds.a
 libos_a_SOURCES = \
        os/FileJournal.cc \
        os/FileStore.cc \
+       os/chain_xattr.cc \
        os/ObjectStore.cc \
        os/JournalingObjectStore.cc \
        os/LFNIndex.cc \
@@ -1768,6 +1769,7 @@ noinst_HEADERS = \
         msg/msg_types.h\
        objclass/objclass.h\
        os/btrfs_ioctl.h\
+       os/chain_xattr.h\
        os/hobject.h \
        os/CollectionIndex.h\
         os/FileJournal.h\
index 1d7ab5aee7889ded2102a1937100c62e29e3a3b4..30b04851fa6842bc35a55ac18c8c086b5434c099 100644 (file)
@@ -12,6 +12,8 @@
 #ifndef CEPH_EXTATTR_H
 #define CEPH_EXTATTR_H
 
+#include <sys/types.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
index 52894af7d6d0fbf41b2967d93b183f19a44f97ed..86b8597c519c3782f72e1c2a2e74be5676cf2877 100644 (file)
@@ -38,6 +38,7 @@
 #include "include/fiemap.h"
 
 #include "common/xattr.h"
+#include "chain_xattr.h"
 
 #if defined(DARWIN) || defined(__FreeBSD__)
 #include <sys/param.h>
@@ -90,9 +91,6 @@ static const __SWORD_TYPE BTRFS_SUPER_MAGIC(0x9123683E);
 # endif
 #endif
 
-#define ATTR_MAX_NAME_LEN  128
-#define ATTR_MAX_BLOCK_LEN 2048
-
 #define COMMIT_SNAP_ITEM "snap_%lld"
 #define CLUSTER_SNAP_ITEM "clustersnap_%s"
 
@@ -130,38 +128,6 @@ ostream& operator<<(ostream& out, const FileStore::OpSequencer& s)
   return out << *s.parent;
 }
 
-int do_getxattr(const char *fn, const char *name, void *val, size_t size);
-int do_fgetxattr(int fd, const char *name, void *val, size_t size);
-int do_setxattr(const char *fn, const char *name, const void *val, size_t size);
-int do_fsetxattr(int fd, const char *name, const void *val, size_t size);
-int do_setxattr(const char *fn, const char *name, const void *val, size_t size);
-int do_listxattr(const char *fn, char *names, size_t len);
-int do_removexattr(const char *fn, const char *name);
-
-static int sys_fgetxattr(int fd, const char *name, void *val, size_t size)
-{
-  int r = ::ceph_os_fgetxattr(fd, name, val, size);
-  return (r < 0 ? -errno : r);
-}
-
-static int sys_setxattr(const char *fn, const char *name, const void *val, size_t size)
-{
-  int r = ::ceph_os_setxattr(fn, name, val, size);
-  return (r < 0 ? -errno : r);
-}
-
-static int sys_removexattr(const char *fn, const char *name)
-{
-  int r = ::ceph_os_removexattr(fn, name);
-  return (r < 0 ? -errno : r);
-}
-
-int sys_listxattr(const char *fn, char *names, size_t len)
-{
-  int r = ::ceph_os_listxattr(fn, names, len);
-  return (r < 0 ? -errno : r);
-}
-
 int FileStore::get_cdir(coll_t cid, char *s, int len) 
 {
   const string &cid_str(cid.to_str());
@@ -210,7 +176,7 @@ int FileStore::lfn_getxattr(coll_t cid, const hobject_t& oid, const char *name,
   int r = lfn_find(cid, oid, &path);
   if (r < 0)
     return r;
-  r = do_getxattr(path->path(), name, val, size);
+  r = chain_getxattr(path->path(), name, val, size);
   assert(!m_filestore_fail_eio || r != -EIO);
   return r;
 }
@@ -221,7 +187,7 @@ int FileStore::lfn_setxattr(coll_t cid, const hobject_t& oid, const char *name,
   int r = lfn_find(cid, oid, &path);
   if (r < 0)
     return r;
-  r = do_setxattr(path->path(), name, val, size);
+  r = chain_setxattr(path->path(), name, val, size);
   assert(!m_filestore_fail_eio || r != -EIO);
   return r;
 }
@@ -232,7 +198,7 @@ int FileStore::lfn_removexattr(coll_t cid, const hobject_t& oid, const char *nam
   int r = lfn_find(cid, oid, &path);
   if (r < 0)
     return r;
-  r = do_removexattr(path->path(), name);
+  r = chain_removexattr(path->path(), name);
   assert(!m_filestore_fail_eio || r != -EIO);
   return r;
 }
@@ -243,7 +209,7 @@ int FileStore::lfn_listxattr(coll_t cid, const hobject_t& oid, char *names, size
   int r = lfn_find(cid, oid, &path);
   if (r < 0)
     return r;
-  r = do_listxattr(path->path(), names, len);
+  r = chain_listxattr(path->path(), names, len);
   assert(!m_filestore_fail_eio || r != -EIO);
   return r;
 }
@@ -433,280 +399,6 @@ int FileStore::lfn_unlink(coll_t cid, const hobject_t& o,
   return index->unlink(o);
 }
 
-static void get_raw_xattr_name(const char *name, int i, char *raw_name, int raw_len)
-{
-  int r;
-  int pos = 0;
-
-  while (*name) {
-    switch (*name) {
-    case '@': /* escape it */
-      pos += 2;
-      assert (pos < raw_len - 1);
-      *raw_name = '@';
-      raw_name++;
-      *raw_name = '@';
-      break;
-    default:
-      pos++;
-      assert(pos < raw_len - 1);
-      *raw_name = *name;
-      break;
-    }
-    name++;
-    raw_name++;
-  }
-
-  if (!i) {
-    *raw_name = '\0';
-  } else {
-    r = snprintf(raw_name, raw_len, "@%d", i);
-    assert(r < raw_len - pos);
-  }
-}
-
-static int translate_raw_name(const char *raw_name, char *name, int name_len, bool *is_first)
-{
-  int pos = 0;
-
-  generic_dout(10) << "translate_raw_name raw_name=" << raw_name << dendl;
-  const char *n = name;
-
-  *is_first = true;
-  while (*raw_name) {
-    switch (*raw_name) {
-    case '@': /* escape it */
-      raw_name++;
-      if (!*raw_name)
-        break;
-      if (*raw_name != '@') {
-        *is_first = false;
-        goto done;
-      }
-
-    /* fall through */
-    default:
-      *name = *raw_name;
-      break;
-    }
-    pos++;
-    assert(pos < name_len);
-    name++;
-    raw_name++;
-  }
-done:
-  *name = '\0';
-  generic_dout(10) << "translate_raw_name name=" << n << dendl;
-  return pos;
-}
-
-int do_fgetxattr_len(int fd, const char *name)
-{
-  int i = 0, total = 0;
-  char raw_name[ATTR_MAX_NAME_LEN * 2 + 16];
-  int r;
-
-  do {
-    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
-    r = sys_fgetxattr(fd, raw_name, 0, 0);
-    if (!i && r < 0)
-      return r;
-    if (r < 0)
-      break;
-    total += r;
-    i++;
-  } while (r == ATTR_MAX_BLOCK_LEN);
-
-  return total;
-}
-
-int do_getxattr(const char *fn, const char *name, void *val, size_t size)
-{
-  int fd = ::open(fn, O_RDONLY);
-  int r = 0;
-  if (fd < 0) {
-    r = -errno;
-    goto out;
-  }
-  r = do_fgetxattr(fd, name, val, size);
-  TEMP_FAILURE_RETRY(::close(fd));
- out:
-  return r;
-}
-
-int do_fgetxattr(int fd, const char *name, void *val, size_t size)
-{
-  int i = 0, pos = 0;
-  char raw_name[ATTR_MAX_NAME_LEN * 2 + 16];
-  int ret = 0;
-  int r;
-  size_t chunk_size;
-
-  if (!size)
-    return do_fgetxattr_len(fd, name);
-
-  do {
-    chunk_size = (size < ATTR_MAX_BLOCK_LEN ? size : ATTR_MAX_BLOCK_LEN);
-    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
-    size -= chunk_size;
-
-    r = sys_fgetxattr(fd, raw_name, (char *)val + pos, chunk_size);
-    if (r < 0) {
-      ret = r;
-      break;
-    }
-
-    if (r > 0)
-      pos += r;
-
-    i++;
-  } while (size && r == ATTR_MAX_BLOCK_LEN);
-
-  if (r >= 0) {
-    ret = pos;
-    /* is there another chunk? that can happen if the last read size span over
-       exactly one block */
-    if (chunk_size == ATTR_MAX_BLOCK_LEN) {
-      get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
-      r = sys_fgetxattr(fd, raw_name, 0, 0);
-      if (r > 0) { // there's another chunk.. the original buffer was too small
-        ret = -ERANGE;
-      }
-    }
-  }
-  return ret;
-}
-
-int do_setxattr(const char *fn, const char *name, const void *val, size_t size)
-{
-  int i = 0, pos = 0;
-  char raw_name[ATTR_MAX_NAME_LEN * 2 + 16];
-  int ret = 0;
-  size_t chunk_size;
-
-  do {
-    chunk_size = (size < ATTR_MAX_BLOCK_LEN ? size : ATTR_MAX_BLOCK_LEN);
-    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
-    size -= chunk_size;
-
-    int r = sys_setxattr(fn, raw_name, (char *)val + pos, chunk_size);
-    if (r < 0) {
-      ret = r;
-      break;
-    }
-    pos  += chunk_size;
-    ret = pos;
-    i++;
-  } while (size);
-
-  /* if we're exactly at a chunk size, remove the next one (if wasn't removed
-     before) */
-  if (ret >= 0 && chunk_size == ATTR_MAX_BLOCK_LEN) {
-    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
-    int r = do_removexattr(fn, raw_name);
-    if (r < 0 && r != -ENODATA)
-      ret = r;
-  }
-  
-  return ret;
-}
-
-int do_fsetxattr(int fd, const char *name, const void *val, size_t size)
-{
-  int i = 0, pos = 0;
-  char raw_name[ATTR_MAX_NAME_LEN * 2 + 16];
-  int ret = 0;
-  size_t chunk_size;
-
-  do {
-    chunk_size = (size < ATTR_MAX_BLOCK_LEN ? size : ATTR_MAX_BLOCK_LEN);
-    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
-    size -= chunk_size;
-
-    int r = ::ceph_os_fsetxattr(fd, raw_name, (char *)val + pos, chunk_size);
-    if (r < 0) {
-      ret = r;
-      break;
-    }
-    pos  += chunk_size;
-    ret = pos;
-    i++;
-  } while (size);
-
-  /* if we're exactly at a chunk size, remove the next one (if wasn't removed
-     before) */
-  if (ret >= 0 && chunk_size == ATTR_MAX_BLOCK_LEN) {
-    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
-    int r = ::ceph_os_fremovexattr(fd, raw_name);
-    if (r < 0 && r != -ENODATA)
-      ret = r;
-  }
-  
-  return ret;
-}
-
-int do_removexattr(const char *fn, const char *name) {
-  int i = 0;
-  char raw_name[ATTR_MAX_NAME_LEN * 2 + 16];
-  int r;
-
-  do {
-    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
-    r = sys_removexattr(fn, raw_name);
-    if (!i && r < 0) {
-      return r;
-    }
-    i++;
-  } while (r >= 0);
-  return 0;
-}
-
-int do_listxattr(const char *fn, char *names, size_t len) {
-  int r;
-
-  if (!len)
-    return sys_listxattr(fn, names, len);
-
-  r = sys_listxattr(fn, 0, 0);
-  if (r < 0)
-    return r;
-
-  size_t total_len = r  * 2; // should be enough
-  char *full_buf = (char *)malloc(total_len * 2);
-  if (!full_buf)
-    return -ENOMEM;
-
-  r = sys_listxattr(fn, full_buf, total_len);
-  if (r < 0)
-    return r;
-
-  char *p = full_buf;
-  char *end = full_buf + r;
-  char *dest = names;
-  char *dest_end = names + len;
-
-  while (p < end) {
-    char name[ATTR_MAX_NAME_LEN * 2 + 16];
-    int attr_len = strlen(p);
-    bool is_first;
-    int name_len = translate_raw_name(p, name, sizeof(name), &is_first);
-    if (is_first)  {
-      if (dest + name_len > dest_end) {
-        r = -ERANGE;
-        goto done;
-      }
-      strcpy(dest, name);
-      dest += name_len + 1;
-    }
-    p += attr_len + 1;
-  }
-  r = dest - names;
-
-done:
-  free(full_buf);
-  return r;
-}
-
 FileStore::FileStore(const std::string &base, const std::string &jdev, const char *name, bool do_update) :
   internal_name(name),
   basedir(base), journalpath(jdev),
@@ -1346,9 +1038,9 @@ int FileStore::_detect_fs()
     return ret;
   }
 
-  int ret = do_setxattr(fn, "user.test", &x, sizeof(x));
+  int ret = chain_setxattr(fn, "user.test", &x, sizeof(x));
   if (ret >= 0)
-    ret = do_getxattr(fn, "user.test", &y, sizeof(y));
+    ret = chain_getxattr(fn, "user.test", &y, sizeof(y));
   if ((ret < 0) || (x != y)) {
     derr << "Extended attributes don't appear to work. ";
     if (ret)
@@ -1362,11 +1054,11 @@ int FileStore::_detect_fs()
 
   char buf[1000];
   memset(buf, 0, sizeof(buf)); // shut up valgrind
-  do_setxattr(fn, "user.test", &buf, sizeof(buf));
-  do_setxattr(fn, "user.test2", &buf, sizeof(buf));
-  do_setxattr(fn, "user.test3", &buf, sizeof(buf));
-  do_setxattr(fn, "user.test4", &buf, sizeof(buf));
-  ret = do_setxattr(fn, "user.test5", &buf, sizeof(buf));
+  chain_setxattr(fn, "user.test", &buf, sizeof(buf));
+  chain_setxattr(fn, "user.test2", &buf, sizeof(buf));
+  chain_setxattr(fn, "user.test3", &buf, sizeof(buf));
+  chain_setxattr(fn, "user.test4", &buf, sizeof(buf));
+  ret = chain_setxattr(fn, "user.test5", &buf, sizeof(buf));
   if (ret == -ENOSPC) {
     if (!g_conf->filestore_xattr_use_omap) {
       derr << "limited size xattrs -- enable filestore_xattr_use_omap" << dendl;
@@ -1377,11 +1069,11 @@ int FileStore::_detect_fs()
       derr << "limited size xattrs -- filestore_xattr_use_omap enabled" << dendl;
     }
   }
-  do_removexattr(fn, "user.test");
-  do_removexattr(fn, "user.test2");
-  do_removexattr(fn, "user.test3");
-  do_removexattr(fn, "user.test4");
-  do_removexattr(fn, "user.test5");
+  chain_removexattr(fn, "user.test");
+  chain_removexattr(fn, "user.test2");
+  chain_removexattr(fn, "user.test3");
+  chain_removexattr(fn, "user.test4");
+  chain_removexattr(fn, "user.test5");
 
   ::unlink(fn);
   TEMP_FAILURE_RETRY(::close(tmpfd));
@@ -2525,7 +2217,7 @@ void FileStore::_set_replay_guard(int fd,
   bufferlist v(40);
   ::encode(spos, v);
   ::encode(in_progress, v);
-  int r = do_fsetxattr(fd, REPLAY_GUARD_XATTR, v.c_str(), v.length());
+  int r = chain_fsetxattr(fd, REPLAY_GUARD_XATTR, v.c_str(), v.length());
   if (r < 0) {
     r = -errno;
     derr << "fsetxattr " << REPLAY_GUARD_XATTR << " got " << cpp_strerror(r) << dendl;
@@ -2554,7 +2246,7 @@ void FileStore::_close_replay_guard(int fd, const SequencerPosition& spos)
   ::encode(spos, v);
   bool in_progress = false;
   ::encode(in_progress, v);
-  int r = do_fsetxattr(fd, REPLAY_GUARD_XATTR, v.c_str(), v.length());
+  int r = chain_fsetxattr(fd, REPLAY_GUARD_XATTR, v.c_str(), v.length());
   if (r < 0) {
     r = -errno;
     derr << "fsetxattr " << REPLAY_GUARD_XATTR << " got " << cpp_strerror(r) << dendl;
@@ -2608,7 +2300,7 @@ int FileStore::_check_replay_guard(int fd, const SequencerPosition& spos)
     return 1;
 
   char buf[100];
-  int r = do_fgetxattr(fd, REPLAY_GUARD_XATTR, buf, sizeof(buf));
+  int r = chain_fgetxattr(fd, REPLAY_GUARD_XATTR, buf, sizeof(buf));
   if (r < 0) {
     dout(20) << "_check_replay_guard no xattr" << dendl;
     assert(!m_filestore_fail_eio || r != -EIO);
@@ -3952,15 +3644,15 @@ int FileStore::_getattr(coll_t cid, const hobject_t& oid, const char *name, buff
 int FileStore::_getattr(const char *fn, const char *name, bufferptr& bp)
 {
   char val[100];
-  int l = do_getxattr(fn, name, val, sizeof(val));
+  int l = chain_getxattr(fn, name, val, sizeof(val));
   if (l >= 0) {
     bp = buffer::create(l);
     memcpy(bp.c_str(), val, l);
   } else if (l == -ERANGE) {
-    l = do_getxattr(fn, name, 0, 0);
+    l = chain_getxattr(fn, name, 0, 0);
     if (l > 0) {
       bp = buffer::create(l);
-      l = do_getxattr(fn, name, bp.c_str(), l);
+      l = chain_getxattr(fn, name, bp.c_str(), l);
     }
   }
   assert(!m_filestore_fail_eio || l != -EIO);
@@ -4028,18 +3720,18 @@ int FileStore::_getattrs(const char *fn, map<string,bufferptr>& aset, bool user_
 {
   // get attr list
   char names1[100];
-  int len = do_listxattr(fn, names1, sizeof(names1)-1);
+  int len = chain_listxattr(fn, names1, sizeof(names1)-1);
   char *names2 = 0;
   char *name = 0;
   if (len == -ERANGE) {
-    len = do_listxattr(fn, 0, 0);
+    len = chain_listxattr(fn, 0, 0);
     if (len < 0) {
       assert(!m_filestore_fail_eio || len != -EIO);
       return len;
     }
     dout(10) << " -ERANGE, len is " << len << dendl;
     names2 = new char[len+1];
-    len = do_listxattr(fn, names2, len);
+    len = chain_listxattr(fn, names2, len);
     dout(10) << " -ERANGE, got " << len << dendl;
     if (len < 0) {
       assert(!m_filestore_fail_eio || len != -EIO);
@@ -4086,8 +3778,8 @@ int FileStore::_getattrs(const char *fn, map<string,bufferptr>& aset, bool user_
 int FileStore::getattr(coll_t cid, const hobject_t& oid, const char *name, bufferptr &bp)
 {
   dout(15) << "getattr " << cid << "/" << oid << " '" << name << "'" << dendl;
-  char n[ATTR_MAX_NAME_LEN];
-  get_attrname(name, n, ATTR_MAX_NAME_LEN);
+  char n[CHAIN_XATTR_MAX_NAME_LEN];
+  get_attrname(name, n, CHAIN_XATTR_MAX_NAME_LEN);
   int r = _getattr(cid, oid, n, bp);
   if (r == -ENODATA && g_conf->filestore_xattr_use_omap) {
     map<string, bufferlist> got;
@@ -4180,8 +3872,8 @@ int FileStore::_setattrs(coll_t cid, const hobject_t& oid, map<string,bufferptr>
   for (map<string,bufferptr>::iterator p = aset.begin();
        p != aset.end();
        ++p) {
-    char n[ATTR_MAX_NAME_LEN];
-    get_attrname(p->first.c_str(), n, ATTR_MAX_NAME_LEN);
+    char n[CHAIN_XATTR_MAX_NAME_LEN];
+    get_attrname(p->first.c_str(), n, CHAIN_XATTR_MAX_NAME_LEN);
     if (g_conf->filestore_xattr_use_omap) {
       if (p->second.length() > g_conf->filestore_max_inline_xattr_size) {
        if (inline_set.count(p->first)) {
@@ -4217,7 +3909,7 @@ int FileStore::_setattrs(coll_t cid, const hobject_t& oid, map<string,bufferptr>
     // ??? Why do we skip setting all the other attrs if one fails?
     r = lfn_setxattr(cid, oid, n, val, p->second.length());
     if (r < 0) {
-      derr << "FileStore::_setattrs: do_setxattr returned " << r << dendl;
+      derr << "FileStore::_setattrs: chain_setxattr returned " << r << dendl;
       break;
     }
   }
@@ -4251,8 +3943,8 @@ int FileStore::_rmattr(coll_t cid, const hobject_t& oid, const char *name,
                       const SequencerPosition &spos)
 {
   dout(15) << "rmattr " << cid << "/" << oid << " '" << name << "'" << dendl;
-  char n[ATTR_MAX_NAME_LEN];
-  get_attrname(name, n, ATTR_MAX_NAME_LEN);
+  char n[CHAIN_XATTR_MAX_NAME_LEN];
+  get_attrname(name, n, CHAIN_XATTR_MAX_NAME_LEN);
   int r = lfn_removexattr(cid, oid, n);
   if (r == -ENODATA && g_conf->filestore_xattr_use_omap) {
     Index index;
@@ -4283,8 +3975,8 @@ int FileStore::_rmattrs(coll_t cid, const hobject_t& oid,
   int r = _getattrs(cid, oid, aset);
   if (r >= 0) {
     for (map<string,bufferptr>::iterator p = aset.begin(); p != aset.end(); p++) {
-      char n[ATTR_MAX_NAME_LEN];
-      get_attrname(p->first.c_str(), n, ATTR_MAX_NAME_LEN);
+      char n[CHAIN_XATTR_MAX_NAME_LEN];
+      get_attrname(p->first.c_str(), n, CHAIN_XATTR_MAX_NAME_LEN);
       r = lfn_removexattr(cid, oid, n);
       if (r < 0)
        break;
@@ -4326,7 +4018,7 @@ int FileStore::collection_getattr(coll_t c, const char *name,
   dout(15) << "collection_getattr " << fn << " '" << name << "' len " << size << dendl;
   char n[PATH_MAX];
   get_attrname(name, n, PATH_MAX);
-  int r = do_getxattr(fn, n, value, size);   
+  int r = chain_getxattr(fn, n, value, size);   
   dout(10) << "collection_getattr " << fn << " '" << name << "' len " << size << " = " << r << dendl;
   assert(!m_filestore_fail_eio || r != -EIO);
   return r;
@@ -4368,7 +4060,7 @@ int FileStore::_collection_setattr(coll_t c, const char *name,
   dout(10) << "collection_setattr " << fn << " '" << name << "' len " << size << dendl;
   char n[PATH_MAX];
   get_attrname(name, n, PATH_MAX);
-  int r = do_setxattr(fn, n, value, size);
+  int r = chain_setxattr(fn, n, value, size);
   dout(10) << "collection_setattr " << fn << " '" << name << "' len " << size << " = " << r << dendl;
   return r;
 }
@@ -4380,7 +4072,7 @@ int FileStore::_collection_rmattr(coll_t c, const char *name)
   dout(15) << "collection_rmattr " << fn << dendl;
   char n[PATH_MAX];
   get_attrname(name, n, PATH_MAX);
-  int r = do_removexattr(fn, n);
+  int r = chain_removexattr(fn, n);
   dout(10) << "collection_rmattr " << fn << " = " << r << dendl;
   return r;
 }
@@ -4397,7 +4089,7 @@ int FileStore::_collection_setattrs(coll_t cid, map<string,bufferptr>& aset)
        ++p) {
     char n[PATH_MAX];
     get_attrname(p->first.c_str(), n, PATH_MAX);
-    r = do_setxattr(fn, n, p->second.c_str(), p->second.length());
+    r = chain_setxattr(fn, n, p->second.c_str(), p->second.length());
     if (r < 0) break;
   }
   dout(10) << "collection_setattrs " << fn << " = " << r << dendl;
index b3d79f541951bceb867622d686346238f17baa68..e6bf79d81450d2151fd5b631dcd46eb8c598ad24 100644 (file)
@@ -23,6 +23,8 @@
 #include "osd/osd_types.h"
 #include <errno.h>
 
+#include "chain_xattr.h"
+
 using ceph::crypto::SHA1;
 
 /*
@@ -47,9 +49,6 @@ using ceph::crypto::SHA1;
 
 #define FILENAME_PREFIX_LEN (FILENAME_SHORT_LEN - FILENAME_HASH_LEN - (sizeof(FILENAME_COOKIE) - 1) - FILENAME_EXTRA)
 
-int do_getxattr(const char *fn, const char *name, void *val, size_t size);
-int do_setxattr(const char *fn, const char *name, const void *val, size_t size);
-
 void FlatIndex::set_ref(std::tr1::shared_ptr<CollectionIndex> ref) {
   self_ref = ref;
 }
@@ -128,7 +127,7 @@ static void lfn_translate(const char *path, const char *name, char *new_name, in
   char buf[PATH_MAX];
 
   snprintf(buf, sizeof(buf), "%s/%s", path, name);
-  int r = do_getxattr(buf, LFN_ATTR, new_name, len - 1);
+  int r = chain_getxattr(buf, LFN_ATTR, new_name, len - 1);
   if (r < 0)
     strncpy(new_name, name, len);
   else
@@ -249,7 +248,7 @@ static int lfn_get(const char *coll_path, const hobject_t& oid, char *pathname,
     int r;
 
     build_filename(filename, len - path_len, lfn, i);
-    r = do_getxattr(pathname, LFN_ATTR, buf, sizeof(buf));
+    r = chain_getxattr(pathname, LFN_ATTR, buf, sizeof(buf));
     if (r < 0)
       r = -errno;
     if (r > 0) {
@@ -287,7 +286,7 @@ int FlatIndex::created(const hobject_t &hoid, const char *path) {
   }
   assert(long_name[actual_len] == '\0');
   assert(long_name[actual_len - 1] != '\0');
-  int r = do_setxattr(path, LFN_ATTR, long_name, actual_len);
+  int r = chain_setxattr(path, LFN_ATTR, long_name, actual_len);
   if (r < 0)
     return r;
   return 0;
@@ -367,7 +366,7 @@ static int get_hobject_from_oinfo(const char *dir, const char *file,
   bufferptr bp(PATH_MAX);
   snprintf(path, sizeof(path), "%s/%s", dir, file);
   // Hack, user.ceph._ is the attribute used to store the object info
-  int r = do_getxattr(path, "user.ceph._", bp.c_str(), bp.length());
+  int r = chain_getxattr(path, "user.ceph._", bp.c_str(), bp.length());
   if (r < 0)
     return r;
   bufferlist bl;
index 82120ebf7b02fb49c3d45975014a7cf7020ad8fd..2165036550ff97f6ddb655ff4b9e00b4433c2f03 100644 (file)
 #include "FlatIndex.h"
 #include "CollectionIndex.h"
 
-int do_getxattr(const char *fn, const char *name, void *val, size_t size);
-int do_setxattr(const char *fn, const char *name, const void *val, size_t size);
+#include "chain_xattr.h"
 
 static int set_version(const char *path, uint32_t version) {
   bufferlist bl;
   ::encode(version, bl);
-  return do_setxattr(path, "user.cephos.collection_version", bl.c_str(), 
+  return chain_setxattr(path, "user.cephos.collection_version", bl.c_str(), 
                     bl.length());
 }
 
 static int get_version(const char *path, uint32_t *version) {
   bufferptr bp(PATH_MAX);
-  int r = do_getxattr(path, "user.cephos.collection_version", 
+  int r = chain_getxattr(path, "user.cephos.collection_version", 
                      bp.c_str(), bp.length());
   if (r < 0) {
     if (r != -ENOENT) {
index f73865a93b818e1c928f2367f6b83212e4539aee..fc4a0d223e6240d6d680bbf4615480d5fe8cdfcb 100644 (file)
@@ -29,8 +29,8 @@
 #include "common/debug.h"
 #include "include/buffer.h"
 #include "common/ceph_crypto.h"
-
 #include "include/compat.h"
+#include "chain_xattr.h"
 
 #include "LFNIndex.h"
 using ceph::crypto::SHA1;
@@ -48,10 +48,6 @@ const int LFNIndex::FILENAME_PREFIX_LEN =  FILENAME_SHORT_LEN - FILENAME_HASH_LE
                                                                FILENAME_COOKIE.size() - 
                                                                FILENAME_EXTRA;
 
-int do_getxattr(const char *fn, const char *name, void *val, size_t size);
-int do_setxattr(const char *fn, const char *name, const void *val, size_t size);
-int do_removexattr(const char *fn, const char *name);
-
 /* Public methods */
 
 void LFNIndex::set_ref(std::tr1::shared_ptr<CollectionIndex> ref) {
@@ -272,7 +268,7 @@ static int get_hobject_from_oinfo(const char *dir, const char *file,
   bufferptr bp(PATH_MAX);
   snprintf(path, sizeof(path), "%s/%s", dir, file);
   // Hack, user.ceph._ is the attribute used to store the object info
-  int r = do_getxattr(path, "user.ceph._", bp.c_str(), bp.length());
+  int r = chain_getxattr(path, "user.ceph._", bp.c_str(), bp.length());
   if (r < 0)
     return r;
   bufferlist bl;
@@ -405,7 +401,7 @@ int LFNIndex::add_attr_path(const vector<string> &path,
                            const string &attr_name, 
                            bufferlist &attr_value) {
   string full_path = get_full_path_subdir(path);
-  return do_setxattr(full_path.c_str(), mangle_attr_name(attr_name).c_str(),
+  return chain_setxattr(full_path.c_str(), mangle_attr_name(attr_name).c_str(),
                     reinterpret_cast<void *>(attr_value.c_str()),
                     attr_value.length());
 }
@@ -417,7 +413,7 @@ int LFNIndex::get_attr_path(const vector<string> &path,
   size_t size = 1024; // Initial
   while (1) {
     bufferptr buf(size);
-    int r = do_getxattr(full_path.c_str(), mangle_attr_name(attr_name).c_str(),
+    int r = chain_getxattr(full_path.c_str(), mangle_attr_name(attr_name).c_str(),
                         reinterpret_cast<void *>(buf.c_str()),
                         size);
     if (r > 0) {
@@ -440,7 +436,7 @@ int LFNIndex::remove_attr_path(const vector<string> &path,
                               const string &attr_name) {
   string full_path = get_full_path_subdir(path);
   string mangled_attr_name = mangle_attr_name(attr_name);
-  return do_removexattr(full_path.c_str(), mangled_attr_name.c_str());
+  return chain_removexattr(full_path.c_str(), mangled_attr_name.c_str());
 }
   
 string LFNIndex::lfn_generate_object_name_keyless(const hobject_t &hoid) {
@@ -614,7 +610,7 @@ int LFNIndex::lfn_get_name(const vector<string> &path,
   for ( ; ; ++i) {
     candidate = lfn_get_short_name(hoid, i);
     candidate_path = get_full_path(path, candidate);
-    r = do_getxattr(candidate_path.c_str(), get_lfn_attr().c_str(), buf, sizeof(buf));
+    r = chain_getxattr(candidate_path.c_str(), get_lfn_attr().c_str(), buf, sizeof(buf));
     if (r < 0) {
       if (errno != ENODATA && errno != ENOENT)
        return -errno;
@@ -655,7 +651,7 @@ int LFNIndex::lfn_created(const vector<string> &path,
     return 0;
   string full_path = get_full_path(path, mangled_name);
   string full_name = lfn_generate_object_name(hoid);
-  return do_setxattr(full_path.c_str(), get_lfn_attr().c_str(), 
+  return chain_setxattr(full_path.c_str(), get_lfn_attr().c_str(), 
                     full_name.c_str(), full_name.size());
 }
 
@@ -720,7 +716,7 @@ int LFNIndex::lfn_translate(const vector<string> &path,
   // Get lfn_attr
   string full_path = get_full_path(path, short_name);
   char attr[PATH_MAX];
-  int r = do_getxattr(full_path.c_str(), get_lfn_attr().c_str(), attr, sizeof(attr) - 1);
+  int r = chain_getxattr(full_path.c_str(), get_lfn_attr().c_str(), attr, sizeof(attr) - 1);
   if (r < 0)
     return -errno;
   if (r < (int)sizeof(attr))
diff --git a/src/os/chain_xattr.cc b/src/os/chain_xattr.cc
new file mode 100644 (file)
index 0000000..bab0272
--- /dev/null
@@ -0,0 +1,424 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "chain_xattr.h"
+
+#include <inttypes.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <errno.h>
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <string.h>
+#include <stdio.h>
+#include "include/assert.h"
+
+#if defined(__linux__)
+#include <linux/fs.h>
+#endif
+
+#if defined(__FreeBSD__)
+#include "include/inttypes.h"
+#endif
+
+#include "common/xattr.h"
+
+
+static void get_raw_xattr_name(const char *name, int i, char *raw_name, int raw_len)
+{
+  int r;
+  int pos = 0;
+
+  while (*name) {
+    switch (*name) {
+    case '@': /* escape it */
+      pos += 2;
+      assert (pos < raw_len - 1);
+      *raw_name = '@';
+      raw_name++;
+      *raw_name = '@';
+      break;
+    default:
+      pos++;
+      assert(pos < raw_len - 1);
+      *raw_name = *name;
+      break;
+    }
+    name++;
+    raw_name++;
+  }
+
+  if (!i) {
+    *raw_name = '\0';
+  } else {
+    r = snprintf(raw_name, raw_len, "@%d", i);
+    assert(r < raw_len - pos);
+  }
+}
+
+static int translate_raw_name(const char *raw_name, char *name, int name_len, bool *is_first)
+{
+  int pos = 0;
+
+  *is_first = true;
+  while (*raw_name) {
+    switch (*raw_name) {
+    case '@': /* escape it */
+      raw_name++;
+      if (!*raw_name)
+        break;
+      if (*raw_name != '@') {
+        *is_first = false;
+        goto done;
+      }
+
+    /* fall through */
+    default:
+      *name = *raw_name;
+      break;
+    }
+    pos++;
+    assert(pos < name_len);
+    name++;
+    raw_name++;
+  }
+done:
+  *name = '\0';
+  return pos;
+}
+
+
+// setxattr
+
+static int getxattr_len(const char *fn, const char *name)
+{
+  int i = 0, total = 0;
+  char raw_name[CHAIN_XATTR_MAX_NAME_LEN * 2 + 16];
+  int r;
+
+  do {
+    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+    r = sys_getxattr(fn, raw_name, 0, 0);
+    if (!i && r < 0)
+      return r;
+    if (r < 0)
+      break;
+    total += r;
+    i++;
+  } while (r == CHAIN_XATTR_MAX_BLOCK_LEN);
+
+  return total;
+}
+
+int chain_getxattr(const char *fn, const char *name, void *val, size_t size)
+{
+  int i = 0, pos = 0;
+  char raw_name[CHAIN_XATTR_MAX_NAME_LEN * 2 + 16];
+  int ret = 0;
+  int r;
+  size_t chunk_size;
+
+  if (!size)
+    return getxattr_len(fn, name);
+
+  do {
+    chunk_size = (size < CHAIN_XATTR_MAX_BLOCK_LEN ? size : CHAIN_XATTR_MAX_BLOCK_LEN);
+    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+    size -= chunk_size;
+
+    r = sys_getxattr(fn, raw_name, (char *)val + pos, chunk_size);
+    if (r < 0) {
+      ret = r;
+      break;
+    }
+
+    if (r > 0)
+      pos += r;
+
+    i++;
+  } while (size && r == CHAIN_XATTR_MAX_BLOCK_LEN);
+
+  if (r >= 0) {
+    ret = pos;
+    /* is there another chunk? that can happen if the last read size span over
+       exactly one block */
+    if (chunk_size == CHAIN_XATTR_MAX_BLOCK_LEN) {
+      get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+      r = sys_getxattr(fn, raw_name, 0, 0);
+      if (r > 0) { // there's another chunk.. the original buffer was too small
+        ret = -ERANGE;
+      }
+    }
+  }
+  return ret;
+}
+
+static int chain_fgetxattr_len(int fd, const char *name)
+{
+  int i = 0, total = 0;
+  char raw_name[CHAIN_XATTR_MAX_NAME_LEN * 2 + 16];
+  int r;
+
+  do {
+    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+    r = sys_fgetxattr(fd, raw_name, 0, 0);
+    if (!i && r < 0)
+      return r;
+    if (r < 0)
+      break;
+    total += r;
+    i++;
+  } while (r == CHAIN_XATTR_MAX_BLOCK_LEN);
+
+  return total;
+}
+
+int chain_fgetxattr(int fd, const char *name, void *val, size_t size)
+{
+  int i = 0, pos = 0;
+  char raw_name[CHAIN_XATTR_MAX_NAME_LEN * 2 + 16];
+  int ret = 0;
+  int r;
+  size_t chunk_size;
+
+  if (!size)
+    return chain_fgetxattr_len(fd, name);
+
+  do {
+    chunk_size = (size < CHAIN_XATTR_MAX_BLOCK_LEN ? size : CHAIN_XATTR_MAX_BLOCK_LEN);
+    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+    size -= chunk_size;
+
+    r = sys_fgetxattr(fd, raw_name, (char *)val + pos, chunk_size);
+    if (r < 0) {
+      ret = r;
+      break;
+    }
+
+    if (r > 0)
+      pos += r;
+
+    i++;
+  } while (size && r == CHAIN_XATTR_MAX_BLOCK_LEN);
+
+  if (r >= 0) {
+    ret = pos;
+    /* is there another chunk? that can happen if the last read size span over
+       exactly one block */
+    if (chunk_size == CHAIN_XATTR_MAX_BLOCK_LEN) {
+      get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+      r = sys_fgetxattr(fd, raw_name, 0, 0);
+      if (r > 0) { // there's another chunk.. the original buffer was too small
+        ret = -ERANGE;
+      }
+    }
+  }
+  return ret;
+}
+
+
+// setxattr
+
+int chain_setxattr(const char *fn, const char *name, const void *val, size_t size)
+{
+  int i = 0, pos = 0;
+  char raw_name[CHAIN_XATTR_MAX_NAME_LEN * 2 + 16];
+  int ret = 0;
+  size_t chunk_size;
+
+  do {
+    chunk_size = (size < CHAIN_XATTR_MAX_BLOCK_LEN ? size : CHAIN_XATTR_MAX_BLOCK_LEN);
+    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+    size -= chunk_size;
+
+    int r = ::ceph_os_setxattr(fn, raw_name, (char *)val + pos, chunk_size);
+    if (r < 0) {
+      ret = r;
+      break;
+    }
+    pos  += chunk_size;
+    ret = pos;
+    i++;
+  } while (size);
+
+  /* if we're exactly at a chunk size, remove the next one (if wasn't removed
+     before) */
+  if (ret >= 0 && chunk_size == CHAIN_XATTR_MAX_BLOCK_LEN) {
+    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+    int r = ::ceph_os_removexattr(fn, raw_name);
+    if (r < 0 && r != -ENODATA)
+      ret = r;
+  }
+  
+  return ret;
+}
+
+int chain_fsetxattr(int fd, const char *name, const void *val, size_t size)
+{
+  int i = 0, pos = 0;
+  char raw_name[CHAIN_XATTR_MAX_NAME_LEN * 2 + 16];
+  int ret = 0;
+  size_t chunk_size;
+
+  do {
+    chunk_size = (size < CHAIN_XATTR_MAX_BLOCK_LEN ? size : CHAIN_XATTR_MAX_BLOCK_LEN);
+    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+    size -= chunk_size;
+
+    int r = ::ceph_os_fsetxattr(fd, raw_name, (char *)val + pos, chunk_size);
+    if (r < 0) {
+      ret = r;
+      break;
+    }
+    pos  += chunk_size;
+    ret = pos;
+    i++;
+  } while (size);
+
+  /* if we're exactly at a chunk size, remove the next one (if wasn't removed
+     before) */
+  if (ret >= 0 && chunk_size == CHAIN_XATTR_MAX_BLOCK_LEN) {
+    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+    int r = ::ceph_os_fremovexattr(fd, raw_name);
+    if (r < 0 && r != -ENODATA)
+      ret = r;
+  }
+  
+  return ret;
+}
+
+
+// removexattr
+
+int chain_removexattr(const char *fn, const char *name)
+{
+  int i = 0;
+  char raw_name[CHAIN_XATTR_MAX_NAME_LEN * 2 + 16];
+  int r;
+
+  do {
+    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+    r = ::ceph_os_removexattr(fn, raw_name);
+    if (!i && r < 0) {
+      return r;
+    }
+    i++;
+  } while (r >= 0);
+  return 0;
+}
+
+int chain_fremovexattr(int fd, const char *name)
+{
+  int i = 0;
+  char raw_name[CHAIN_XATTR_MAX_NAME_LEN * 2 + 16];
+  int r;
+
+  do {
+    get_raw_xattr_name(name, i, raw_name, sizeof(raw_name));
+    r = ::ceph_os_fremovexattr(fd, raw_name);
+    if (!i && r < 0) {
+      return r;
+    }
+    i++;
+  } while (r >= 0);
+  return 0;
+}
+
+
+// listxattr
+
+int chain_listxattr(const char *fn, char *names, size_t len) {
+  int r;
+
+  if (!len)
+    return ::ceph_os_listxattr(fn, names, len);
+
+  r = ::ceph_os_listxattr(fn, 0, 0);
+  if (r < 0)
+    return r;
+
+  size_t total_len = r  * 2; // should be enough
+  char *full_buf = (char *)malloc(total_len * 2);
+  if (!full_buf)
+    return -ENOMEM;
+
+  r = ::ceph_os_listxattr(fn, full_buf, total_len);
+  if (r < 0)
+    return r;
+
+  char *p = full_buf;
+  char *end = full_buf + r;
+  char *dest = names;
+  char *dest_end = names + len;
+
+  while (p < end) {
+    char name[CHAIN_XATTR_MAX_NAME_LEN * 2 + 16];
+    int attr_len = strlen(p);
+    bool is_first;
+    int name_len = translate_raw_name(p, name, sizeof(name), &is_first);
+    if (is_first)  {
+      if (dest + name_len > dest_end) {
+        r = -ERANGE;
+        goto done;
+      }
+      strcpy(dest, name);
+      dest += name_len + 1;
+    }
+    p += attr_len + 1;
+  }
+  r = dest - names;
+
+done:
+  free(full_buf);
+  return r;
+}
+
+int chain_flistxattr(int fd, char *names, size_t len) {
+  int r;
+
+  if (!len)
+    return ::ceph_os_flistxattr(fd, names, len);
+
+  r = ::ceph_os_flistxattr(fd, 0, 0);
+  if (r < 0)
+    return r;
+
+  size_t total_len = r  * 2; // should be enough
+  char *full_buf = (char *)malloc(total_len * 2);
+  if (!full_buf)
+    return -ENOMEM;
+
+  r = ::ceph_os_flistxattr(fd, full_buf, total_len);
+  if (r < 0)
+    return r;
+
+  char *p = full_buf;
+  char *end = full_buf + r;
+  char *dest = names;
+  char *dest_end = names + len;
+
+  while (p < end) {
+    char name[CHAIN_XATTR_MAX_NAME_LEN * 2 + 16];
+    int attr_len = strlen(p);
+    bool is_first;
+    int name_len = translate_raw_name(p, name, sizeof(name), &is_first);
+    if (is_first)  {
+      if (dest + name_len > dest_end) {
+        r = -ERANGE;
+        goto done;
+      }
+      strcpy(dest, name);
+      dest += name_len + 1;
+    }
+    p += attr_len + 1;
+  }
+  r = dest - names;
+
+done:
+  free(full_buf);
+  return r;
+}
diff --git a/src/os/chain_xattr.h b/src/os/chain_xattr.h
new file mode 100644 (file)
index 0000000..7e8312f
--- /dev/null
@@ -0,0 +1,73 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef __CEPH_OSD_CHAIN_XATTR_H
+#define __CEPH_OSD_CHAIN_XATTR_H
+
+#include "common/xattr.h"
+
+#include <errno.h>
+
+#define CHAIN_XATTR_MAX_NAME_LEN  128
+#define CHAIN_XATTR_MAX_BLOCK_LEN 2048
+
+
+// wrappers to hide annoying errno handling.
+
+static inline int sys_fgetxattr(int fd, const char *name, void *val, size_t size)
+{
+  int r = ::ceph_os_fgetxattr(fd, name, val, size);
+  return (r < 0 ? -errno : r);
+}
+static inline int sys_getxattr(const char *fn, const char *name, void *val, size_t size)
+{
+  int r = ::ceph_os_getxattr(fn, name, val, size);
+  return (r < 0 ? -errno : r);
+}
+
+static inline int sys_setxattr(const char *fn, const char *name, const void *val, size_t size)
+{
+  int r = ::ceph_os_setxattr(fn, name, val, size);
+  return (r < 0 ? -errno : r);
+}
+static inline int sys_fsetxattr(int fd, const char *name, const void *val, size_t size)
+{
+  int r = ::ceph_os_fsetxattr(fd, name, val, size);
+  return (r < 0 ? -errno : r);
+}
+
+static inline int sys_listxattr(const char *fn, char *names, size_t len)
+{
+  int r = ::ceph_os_listxattr(fn, names, len);
+  return (r < 0 ? -errno : r);
+}
+static inline int sys_flistxattr(int fd, char *names, size_t len)
+{
+  int r = ::ceph_os_flistxattr(fd, names, len);
+  return (r < 0 ? -errno : r);
+}
+
+static inline int sys_removexattr(const char *fn, const char *name)
+{
+  int r = ::ceph_os_removexattr(fn, name);
+  return (r < 0 ? -errno : r);
+}
+static inline int sys_fremovexattr(int fd, const char *name)
+{
+  int r = ::ceph_os_fremovexattr(fd, name);
+  return (r < 0 ? -errno : r);
+}
+
+
+// wrappers to chain large values across multiple xattrs
+
+int chain_getxattr(const char *fn, const char *name, void *val, size_t size);
+int chain_fgetxattr(int fd, const char *name, void *val, size_t size);
+int chain_setxattr(const char *fn, const char *name, const void *val, size_t size);
+int chain_fsetxattr(int fd, const char *name, const void *val, size_t size);
+int chain_listxattr(const char *fn, char *names, size_t len);
+int chain_flistxattr(int fd, char *names, size_t len);
+int chain_removexattr(const char *fn, const char *name);
+int chain_fremovexattr(int fd, const char *name);
+
+#endif