New interfaces for fetching extended (and selective) stat information.
Additionally, applications can specify AT_NO_ATTR_SYNC in the flags to
indicate that they want to do a "lazy" statx that just hands out the
inode info from the cache, or AT_SYMLINK_NOFOLLOW to avoid following
symlinks when walking the path.
Signed-off-by: Jeff Layton <jlayton@redhat.com>
%defattr(-,root,root,-)
%dir %{_includedir}/cephfs
%{_includedir}/cephfs/libcephfs.h
+%{_includedir}/cephfs/ceph_statx.h
%{_libdir}/libcephfs.so
%files -n python-cephfs
usr/include/cephfs/libcephfs.h
+usr/include/cephfs/ceph_statx.h
usr/lib/libcephfs.so
#include "include/assert.h"
#include "include/stat.h"
+#include "include/cephfs/ceph_statx.h"
+
#if HAVE_GETGROUPLIST
#include <grp.h>
#include <pwd.h>
return r;
}
+unsigned Client::statx_to_mask(unsigned int flags, unsigned int want)
+{
+ unsigned mask = 0;
+
+ /* if NO_ATTR_SYNC is set, then we don't need any -- just use what's in cache */
+ if (flags & AT_NO_ATTR_SYNC)
+ goto out;
+
+ /* Always set PIN to distinguish from AT_NO_ATTR_SYNC case */
+ mask |= CEPH_CAP_PIN;
+ if (want & (CEPH_STATX_MODE|CEPH_STATX_UID|CEPH_STATX_GID|CEPH_STATX_RDEV|CEPH_STATX_BTIME))
+ mask |= CEPH_CAP_AUTH_SHARED;
+ if (want & CEPH_STATX_NLINK)
+ mask |= CEPH_CAP_LINK_SHARED;
+ if (want & (CEPH_STATX_ATIME|CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_SIZE|CEPH_STATX_BLOCKS))
+ mask |= CEPH_CAP_FILE_SHARED;
+
+out:
+ return mask;
+}
+
+int Client::statx(const char *relpath, struct ceph_statx *stx,
+ unsigned int want, unsigned int flags)
+{
+ ldout(cct, 3) << "statx enter (relpath " << relpath << " want " << want << ")" << dendl;
+ Mutex::Locker lock(client_lock);
+ tout(cct) << "statx" << std::endl;
+ tout(cct) << relpath << std::endl;
+ filepath path(relpath);
+ InodeRef in;
+
+ unsigned mask = statx_to_mask(flags, want);
+
+ int r = path_walk(path, &in, flags & AT_SYMLINK_NOFOLLOW, mask);
+ if (r < 0)
+ return r;
+
+ if (mask && !in->caps_issued_mask(mask)) {
+ r = _getattr(in, mask);
+ if (r < 0) {
+ ldout(cct, 3) << "statx exit on error!" << dendl;
+ return r;
+ }
+ }
+
+ fill_statx(in, mask, stx);
+ ldout(cct, 3) << "statx exit (relpath " << relpath << " mask " << stx->stx_mask << ")" << dendl;
+ return r;
+}
+
int Client::lstat(const char *relpath, struct stat *stbuf,
frag_info_t *dirstat, int mask)
{
return in->caps_issued();
}
+void Client::fill_statx(Inode *in, unsigned int mask, struct ceph_statx *stx)
+{
+ ldout(cct, 10) << "fill_statx on " << in->ino << " snap/dev" << in->snapid
+ << " mode 0" << oct << in->mode << dec
+ << " mtime " << in->mtime << " ctime " << in->ctime << dendl;
+ memset(stx, 0, sizeof(struct ceph_statx));
+
+ /*
+ * If mask is 0, then the caller set AT_NO_ATTR_SYNC. Reset the mask
+ * so that all bits are set.
+ */
+ if (!mask)
+ mask = ~0;
+
+ /* These are always considered to be available */
+ stx->stx_dev_major = in->snapid >> 32;
+ stx->stx_dev_minor = (uint32_t)in->snapid;
+ stx->stx_blksize = MAX(in->layout.stripe_unit, 4096);
+
+ if (use_faked_inos())
+ stx->stx_ino = in->faked_ino;
+ else
+ stx->stx_ino = in->ino;
+ stx->stx_rdev_minor = MINOR(in->rdev);
+ stx->stx_rdev_major = MAJOR(in->rdev);
+ stx->stx_mask |= (CEPH_STATX_INO|CEPH_STATX_RDEV);
+
+ if (mask & CEPH_CAP_AUTH_SHARED) {
+ stx->stx_uid = in->uid;
+ stx->stx_gid = in->gid;
+ stx->stx_mode = in->mode;
+ stx->stx_btime = in->btime.sec();
+ stx->stx_btime_ns = in->btime.nsec();
+ stx->stx_mask |= (CEPH_STATX_MODE|CEPH_STATX_UID|CEPH_STATX_GID|CEPH_STATX_BTIME);
+ }
+
+ if (mask & CEPH_CAP_LINK_SHARED) {
+ stx->stx_nlink = in->nlink;
+ stx->stx_mask |= CEPH_STATX_NLINK;
+ }
+
+ if (mask & CEPH_CAP_FILE_SHARED) {
+ if (in->ctime > in->mtime) {
+ stx->stx_ctime = in->ctime.sec();
+ stx->stx_ctime_ns = in->ctime.nsec();
+ } else {
+ stx->stx_ctime = in->mtime.sec();
+ stx->stx_ctime_ns = in->mtime.nsec();
+ }
+ stx->stx_atime = in->atime.sec();
+ stx->stx_atime_ns = in->atime.nsec();
+ stx->stx_mtime = in->mtime.sec();
+ stx->stx_mtime_ns = in->mtime.nsec();
+
+ if (in->is_dir()) {
+ if (cct->_conf->client_dirsize_rbytes)
+ stx->stx_size = in->rstat.rbytes;
+ else
+ stx->stx_size = in->dirstat.size();
+ stx->stx_blocks = 1;
+ } else {
+ stx->stx_size = in->size;
+ stx->stx_blocks = (in->size + 511) >> 9;
+ }
+ stx->stx_mask |= (CEPH_STATX_ATIME|CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_SIZE|CEPH_STATX_BLOCKS);
+ }
+}
+
void Client::touch_dn(Dentry *dn)
{
lru.lru_touch(dn);
return res;
}
+int Client::ll_getattrx(Inode *in, struct ceph_statx *stx, unsigned int want,
+ unsigned int flags, int uid, int gid)
+{
+ Mutex::Locker lock(client_lock);
+
+ int res = 0;
+ unsigned mask = statx_to_mask(flags, want);
+
+ if (mask && !in->caps_issued_mask(mask))
+ res = _ll_getattr(in, uid, gid);
+
+ if (res == 0)
+ fill_statx(in, mask, stx);
+ ldout(cct, 3) << "ll_getattrx " << _get_vino(in) << " = " << res << dendl;
+ return res;
+}
+
int Client::ll_setattr(Inode *in, struct stat *attr, int mask, int uid,
int gid)
{
int fill_stat(InodeRef& in, struct stat *st, frag_info_t *dirstat=0, nest_info_t *rstat=0) {
return fill_stat(in.get(), st, dirstat, rstat);
}
+
+ void fill_statx(Inode *in, unsigned int mask, struct ceph_statx *stx);
+ void fill_statx(InodeRef& in, unsigned int mask, struct ceph_statx *stx) {
+ return fill_statx(in.get(), mask, stx);
+ }
+
void touch_dn(Dentry *dn);
// trim cache.
int symlink(const char *existing, const char *newname);
// inode stuff
+ unsigned statx_to_mask(unsigned int flags, unsigned int want);
int stat(const char *path, struct stat *stbuf, frag_info_t *dirstat=0, int mask=CEPH_STAT_CAP_INODE_ALL);
+ int statx(const char *path, struct ceph_statx *stx, unsigned int want, unsigned int flags);
int lstat(const char *path, struct stat *stbuf, frag_info_t *dirstat=0, int mask=CEPH_STAT_CAP_INODE_ALL);
int lstatlite(const char *path, struct statlite *buf);
bool ll_forget(Inode *in, int count);
bool ll_put(Inode *in);
int ll_getattr(Inode *in, struct stat *st, int uid = -1, int gid = -1);
+ int ll_getattrx(Inode *in, struct ceph_statx *stx, unsigned int want,
+ unsigned int flags, int uid = -1, int gid = -1);
int ll_setattr(Inode *in, struct stat *st, int mask, int uid = -1,
int gid = -1);
int ll_getxattr(Inode *in, const char *name, void *value, size_t size,
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * scalable distributed file system
+ *
+ * Copyright (C) Jeff Layton <jlayton@redhat.com>
+ *
+ * Heavily borrowed from David Howells' draft statx patchset.
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef CEPH_CEPH_STATX_H
+#define CEPH_CEPH_STATX_H
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Since the xstat patches are still a work in progress, we borrow its data
+ * structures and #defines to implement ceph_getattrx. Once the xstat stuff
+ * has been merged we should drop this and switch over to using that instead.
+ */
+struct ceph_statx {
+ uint32_t stx_mask;
+ uint32_t stx_information;
+ uint32_t stx_blksize;
+ uint32_t stx_nlink;
+ uint32_t stx_gen;
+ uint32_t stx_uid;
+ uint32_t stx_gid;
+ uint16_t stx_mode;
+ uint16_t __spare0[1];
+ uint64_t stx_ino;
+ uint64_t stx_size;
+ uint64_t stx_blocks;
+ uint64_t stx_version;
+ int64_t stx_atime;
+ int64_t stx_btime;
+ int64_t stx_ctime;
+ int64_t stx_mtime;
+ int32_t stx_atime_ns;
+ int32_t stx_btime_ns;
+ int32_t stx_ctime_ns;
+ int32_t stx_mtime_ns;
+ uint32_t stx_rdev_major;
+ uint32_t stx_rdev_minor;
+ uint32_t stx_dev_major;
+ uint32_t stx_dev_minor;
+ uint64_t __spare1[16];
+};
+
+#define CEPH_STATX_MODE 0x00000001U /* Want/got stx_mode */
+#define CEPH_STATX_NLINK 0x00000002U /* Want/got stx_nlink */
+#define CEPH_STATX_UID 0x00000004U /* Want/got stx_uid */
+#define CEPH_STATX_GID 0x00000008U /* Want/got stx_gid */
+#define CEPH_STATX_RDEV 0x00000010U /* Want/got stx_rdev */
+#define CEPH_STATX_ATIME 0x00000020U /* Want/got stx_atime */
+#define CEPH_STATX_MTIME 0x00000040U /* Want/got stx_mtime */
+#define CEPH_STATX_CTIME 0x00000080U /* Want/got stx_ctime */
+#define CEPH_STATX_INO 0x00000100U /* Want/got stx_ino */
+#define CEPH_STATX_SIZE 0x00000200U /* Want/got stx_size */
+#define CEPH_STATX_BLOCKS 0x00000400U /* Want/got stx_blocks */
+#define CEPH_STATX_BASIC_STATS 0x000007ffU /* The stuff in the normal stat struct */
+#define CEPH_STATX_BTIME 0x00000800U /* Want/got stx_btime */
+#define CEPH_STATX_GEN 0x00002000U /* Want/got stx_gen */
+#define CEPH_STATX_ALL_STATS 0x00003fffU /* All supported stats */
+
+/* statx request flags. Callers can set these in the "flags" field */
+#ifndef AT_NO_ATTR_SYNC
+#define AT_NO_ATTR_SYNC 0x4000 /* Don't sync attributes with the server */
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CEPH_STATX_H */
+
#include <stdbool.h>
#include <fcntl.h>
+#include "ceph_statx.h"
+
#ifdef __cplusplus
extern "C" {
#endif
*/
int ceph_stat(struct ceph_mount_info *cmount, const char *path, struct stat *stbuf);
+/**
+ * Get a file's extended statistics and attributes.
+ *
+ * @param cmount the ceph mount handle to use for performing the stat.
+ * @param path the file or directory to get the statistics of.
+ * @param stx the ceph_statx struct that will be filled in with the file's statistics.
+ * @param want bitfield of CEPH_STATX_* flags showing designed attributes
+ * @param flags bitfield that can be used to set AT_* modifier flags (only AT_NO_ATTR_SYNC and AT_SYMLINK_NOFOLLOW)
+ * @returns 0 on success or negative error code on failure.
+ */
+int ceph_statx(struct ceph_mount_info *cmount, const char *path, struct ceph_statx *stx,
+ unsigned int want, unsigned int flags);
+
/**
* Get a file's statistics and attributes, without following symlinks.
*
struct stat *attr);
int ceph_ll_getattr(struct ceph_mount_info *cmount, struct Inode *in,
struct stat *attr, int uid, int gid);
+int ceph_ll_getattrx(struct ceph_mount_info *cmount, struct Inode *in,
+ struct ceph_statx *stx, unsigned int want, unsigned int flags,
+ int uid, int gid);
int ceph_ll_setattr(struct ceph_mount_info *cmount, struct Inode *in,
struct stat *st, int mask, int uid, int gid);
int ceph_ll_open(struct ceph_mount_info *cmount, struct Inode *in, int flags,
return cmount->get_client()->stat(path, stbuf);
}
+extern "C" int ceph_statx(struct ceph_mount_info *cmount, const char *path,
+ struct ceph_statx *stx, unsigned int want, unsigned int flags)
+{
+ if (!cmount->is_mounted())
+ return -ENOTCONN;
+ return cmount->get_client()->statx(path, stx, want, flags);
+}
+
extern "C" int ceph_lstat(struct ceph_mount_info *cmount, const char *path,
struct stat *stbuf)
{
return (cmount->get_client()->ll_getattr(in, attr, uid, gid));
}
+extern "C" int ceph_ll_getattrx(class ceph_mount_info *cmount,
+ Inode *in, struct ceph_statx *stx,
+ unsigned int want, unsigned int flags,
+ int uid, int gid)
+{
+ return (cmount->get_client()->ll_getattrx(in, stx, want, flags, uid, gid));
+}
+
extern "C" int ceph_ll_setattr(class ceph_mount_info *cmount,
Inode *in, struct stat *st,
int mask, int uid, int gid)
ceph_shutdown(cmount);
}
+
+TEST(LibCephFS, Btime) {
+ struct ceph_mount_info *cmount;
+ ASSERT_EQ(ceph_create(&cmount, NULL), 0);
+ ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0);
+ ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL));
+ ASSERT_EQ(ceph_mount(cmount, "/"), 0);
+
+ char filename[32];
+ sprintf(filename, "/getattrx%x", getpid());
+
+ ceph_unlink(cmount, filename);
+ int fd = ceph_open(cmount, filename, O_RDWR|O_CREAT|O_EXCL, 0666);
+ ASSERT_LT(0, fd);
+
+ struct ceph_statx stx;
+ ASSERT_EQ(ceph_statx(cmount, filename, &stx, CEPH_STATX_CTIME|CEPH_STATX_BTIME, 0), 0);
+ ASSERT_TRUE(stx.stx_mask & (CEPH_STATX_CTIME|CEPH_STATX_BTIME));
+ ASSERT_EQ(stx.stx_btime, stx.stx_ctime);
+ ASSERT_EQ(stx.stx_btime_ns, stx.stx_ctime_ns);
+
+ int64_t old_btime = stx.stx_btime;
+ int32_t old_btime_ns = stx.stx_btime_ns;
+
+ /* Now sleep, do a chmod and verify that the ctime changed, but btime didn't */
+ sleep(1);
+ ASSERT_EQ(ceph_chmod(cmount, filename, 0644), 0);
+ ASSERT_EQ(ceph_statx(cmount, filename, &stx, CEPH_STATX_BTIME, 0), 0);
+ ASSERT_TRUE(stx.stx_mask & CEPH_STATX_BTIME);
+ ASSERT_EQ(stx.stx_btime, old_btime);
+ ASSERT_EQ(stx.stx_btime_ns, old_btime_ns);
+ ASSERT_FALSE(old_btime == stx.stx_ctime && old_btime_ns == stx.stx_ctime_ns);
+
+ ceph_shutdown(cmount);
+}