diff --git a/fs/ceph/export.c b/fs/ceph/export.c index 7df550c13d7f..8470457015f6 100644 --- a/fs/ceph/export.c +++ b/fs/ceph/export.c @@ -21,18 +21,69 @@ struct ceph_nfs_confh { u64 ino, parent_ino; } __attribute__ ((packed)); +/* + * fh for snapped inode + */ +struct ceph_nfs_snapfh { + u64 ino; + u64 snapid; + u64 parent_ino; + u32 hash; +} __attribute__ ((packed)); + +static int ceph_encode_snapfh(struct inode *inode, u32 *rawfh, int *max_len, + struct inode *parent_inode) +{ + const static int snap_handle_length = + sizeof(struct ceph_nfs_snapfh) >> 2; + struct ceph_nfs_snapfh *sfh = (void *)rawfh; + u64 snapid = ceph_snap(inode); + bool no_parent = true; + + if (*max_len < snap_handle_length) { + *max_len = snap_handle_length; + return FILEID_INVALID; + } + + if (snapid != CEPH_SNAPDIR) { + struct inode *dir; + struct dentry *dentry = d_find_alias(inode); + if (!dentry) + return -EINVAL; + + rcu_read_lock(); + dir = d_inode_rcu(dentry->d_parent); + if (ceph_snap(dir) != CEPH_SNAPDIR) { + sfh->parent_ino = ceph_ino(dir); + sfh->hash = ceph_dentry_hash(dir, dentry); + no_parent = false; + } + rcu_read_unlock(); + dput(dentry); + } + + sfh->ino = ceph_ino(inode); + sfh->snapid = snapid; + if (no_parent) { + sfh->parent_ino = sfh->ino; + sfh->hash = 0; + } + + *max_len = snap_handle_length; + return FILEID_BTRFS_WITH_PARENT; +} + static int ceph_encode_fh(struct inode *inode, u32 *rawfh, int *max_len, struct inode *parent_inode) { + const static int handle_length = + sizeof(struct ceph_nfs_fh) >> 2; + const static int connected_handle_length = + sizeof(struct ceph_nfs_confh) >> 2; int type; - struct ceph_nfs_fh *fh = (void *)rawfh; - struct ceph_nfs_confh *cfh = (void *)rawfh; - int connected_handle_length = sizeof(*cfh)/4; - int handle_length = sizeof(*fh)/4; - /* don't re-export snaps */ if (ceph_snap(inode) != CEPH_NOSNAP) - return -EINVAL; + return ceph_encode_snapfh(inode, rawfh, max_len, parent_inode); if (parent_inode && (*max_len < connected_handle_length)) { *max_len = connected_handle_length; @@ -43,6 +94,7 @@ static int ceph_encode_fh(struct inode *inode, u32 *rawfh, int *max_len, } if (parent_inode) { + struct ceph_nfs_confh *cfh = (void *)rawfh; dout("encode_fh %llx with parent %llx\n", ceph_ino(inode), ceph_ino(parent_inode)); cfh->ino = ceph_ino(inode); @@ -50,6 +102,7 @@ static int ceph_encode_fh(struct inode *inode, u32 *rawfh, int *max_len, *max_len = connected_handle_length; type = FILEID_INO32_GEN_PARENT; } else { + struct ceph_nfs_fh *fh = (void *)rawfh; dout("encode_fh %llx\n", ceph_ino(inode)); fh->ino = ceph_ino(inode); *max_len = handle_length; @@ -100,6 +153,73 @@ static struct dentry *__fh_to_dentry(struct super_block *sb, u64 ino) return d_obtain_alias(inode); } +static struct dentry *__snapfh_to_dentry(struct super_block *sb, + struct ceph_nfs_snapfh *sfh, + bool want_parent) +{ + struct ceph_mds_client *mdsc = ceph_sb_to_client(sb)->mdsc; + struct ceph_mds_request *req; + struct inode *inode; + struct ceph_vino vino; + int mask; + int err; + + if (want_parent) { + vino.ino = sfh->parent_ino; + if (sfh->snapid == CEPH_SNAPDIR) + vino.snap = CEPH_NOSNAP; + else if (sfh->ino == sfh->parent_ino) + vino.snap = CEPH_SNAPDIR; + else + vino.snap = sfh->snapid; + } else { + vino.ino = sfh->ino; + vino.snap = sfh->snapid; + } + inode = ceph_find_inode(sb, vino); + if (inode) + return d_obtain_alias(inode); + + req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPINO, + USE_ANY_MDS); + if (IS_ERR(req)) + return ERR_CAST(req); + + mask = CEPH_STAT_CAP_INODE; + if (ceph_security_xattr_wanted(d_inode(sb->s_root))) + mask |= CEPH_CAP_XATTR_SHARED; + req->r_args.lookupino.mask = cpu_to_le32(mask); + if (vino.snap < CEPH_NOSNAP) { + req->r_args.lookupino.snapid = cpu_to_le64(vino.snap); + if (!want_parent && sfh->ino != sfh->parent_ino) { + req->r_args.lookupino.parent = cpu_to_le64(sfh->parent_ino); + req->r_args.lookupino.hash = cpu_to_le32(sfh->hash); + } + } + + req->r_ino1 = vino; + req->r_num_caps = 1; + err = ceph_mdsc_do_request(mdsc, NULL, req); + inode = req->r_target_inode; + if (inode) { + if (vino.snap == CEPH_SNAPDIR) + inode = ceph_get_snapdir(inode); + else if (ceph_snap(inode) == vino.snap) + ihold(inode); + else + inode = NULL; + } + ceph_mdsc_put_request(req); + if (!inode) + return ERR_PTR(-ESTALE); + if (inode->i_nlink == 0) { + iput(inode); + return ERR_PTR(-ESTALE); + } + + return d_obtain_alias(inode); +} + /* * convert regular fh to dentry */ @@ -109,6 +229,11 @@ static struct dentry *ceph_fh_to_dentry(struct super_block *sb, { struct ceph_nfs_fh *fh = (void *)fid->raw; + if (fh_type == FILEID_BTRFS_WITH_PARENT) { + struct ceph_nfs_snapfh *sfh = (void *)fid->raw; + return __snapfh_to_dentry(sb, sfh, false); + } + if (fh_type != FILEID_INO32_GEN && fh_type != FILEID_INO32_GEN_PARENT) return NULL; @@ -162,12 +287,26 @@ static struct dentry *__get_parent(struct super_block *sb, static struct dentry *ceph_get_parent(struct dentry *child) { - /* don't re-export snaps */ - if (ceph_snap(d_inode(child)) != CEPH_NOSNAP) - return ERR_PTR(-EINVAL); + struct inode *inode = d_inode(child); + + if (ceph_snap(inode) == CEPH_SNAPDIR) + return __fh_to_dentry(inode->i_sb, ceph_ino(inode)); + + if (ceph_snap(inode) < CEPH_NOSNAP) { + struct dentry *dn; + struct inode* dir; + if (!d_is_dir(child)) + return ERR_PTR(-EINVAL); + + dn = __fh_to_dentry(inode->i_sb, ceph_ino(inode)); + if (IS_ERR_OR_NULL(dn)) + return dn; + dir = ceph_get_snapdir(d_inode(dn)); + dput(dn); + return d_obtain_alias(dir); + } - dout("get_parent %p ino %llx.%llx\n", - child, ceph_vinop(d_inode(child))); + dout("get_parent %p ino %llx.%llx\n", child, ceph_vinop(inode)); return __get_parent(child->d_sb, child, 0); } @@ -181,6 +320,11 @@ static struct dentry *ceph_fh_to_parent(struct super_block *sb, struct ceph_nfs_confh *cfh = (void *)fid->raw; struct dentry *dentry; + if (fh_type == FILEID_BTRFS_WITH_PARENT) { + struct ceph_nfs_snapfh *sfh = (void *)fid->raw; + return __snapfh_to_dentry(sb, sfh, true); + } + if (fh_type != FILEID_INO32_GEN_PARENT) return NULL; if (fh_len < sizeof(*cfh) / 4) @@ -193,14 +337,84 @@ static struct dentry *ceph_fh_to_parent(struct super_block *sb, return dentry; } +struct getdents_callback { + struct dir_context ctx; + struct file *file; + u64 ino; + u64 snap; + char *name; + unsigned sequence; + bool found; +}; + +static int filldir_one(struct dir_context *ctx, const char *name, int len, + loff_t pos, u64 ino, unsigned int d_type) +{ + struct getdents_callback *buf = + container_of(ctx, struct getdents_callback, ctx); + struct ceph_file_info *fi = buf->file->private_data; + struct ceph_mds_reply_info_parsed *rinfo; + + if (!fi->last_readdir) + return -1; + + rinfo = &fi->last_readdir->r_reply_info; + if (!rinfo || !rinfo->dir_nr) + return true; + + chunk_offset = rinfo->dir_entries[0].offset; + return new_pos < chunk_offset || + is_hash_order(new_pos) != is_hash_order(chunk_offset); + + + + int result = 0; + buf->sequence++; +} + +static int __get_snap_name(struct dentry *parent, char *name, + struct dentry *child) +{ + struct inode *inode = d_inode(child); + struct inode *dir = d_inode(child); + struct ceph_fs_client *fsc = ceph_inode_to_client(inode); + struct dir_context ctx = { + .actor = filldir_noop, + }; + struct file *file; + + if (ceph_ino(inode) != ceph_ino(dir)) + return -EINVAL; + + if (ceph_snap(inode) == CEPH_SNAPDIR) { + if (ceph_ino(dir) == CEPH_NOSNAP) { + return -EINVAL; + strcpy(name, fsc->mount_options->snapdir_name); + return 0; + } + + if (ceph_snap(dir) != CEPH_SNAPDIR) + return -EINVAL; + + file = dentry_open(path, O_RDONLY, cred); + if (IS_ERR(file)) + goto out; + + iterate_dir(file, &ctx) +} + static int ceph_get_name(struct dentry *parent, char *name, struct dentry *child) { struct ceph_mds_client *mdsc; struct ceph_mds_request *req; + struct inode *inode = d_inode(child); int err; - mdsc = ceph_inode_to_client(d_inode(child))->mdsc; + if (ceph_snap(inode) != CEPH_NOSNAP) + return __get_snap_name(parent, name, child); + + mdsc = ceph_inode_to_client(inode)->mdsc; req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPNAME, USE_ANY_MDS); if (IS_ERR(req)) @@ -208,8 +422,8 @@ static int ceph_get_name(struct dentry *parent, char *name, inode_lock(d_inode(parent)); - req->r_inode = d_inode(child); - ihold(d_inode(child)); + req->r_inode = inode; + ihold(inode); req->r_ino2 = ceph_vino(d_inode(parent)); req->r_parent = d_inode(parent); set_bit(CEPH_MDS_R_PARENT_LOCKED, &req->r_req_flags); @@ -223,10 +437,10 @@ static int ceph_get_name(struct dentry *parent, char *name, memcpy(name, rinfo->dname, rinfo->dname_len); name[rinfo->dname_len] = 0; dout("get_name %p ino %llx.%llx name %s\n", - child, ceph_vinop(d_inode(child)), name); + child, ceph_vinop(inode), name); } else { dout("get_name %p ino %llx.%llx err %d\n", - child, ceph_vinop(d_inode(child)), err); + child, ceph_vinop(inode), err); } ceph_mdsc_put_request(req); diff --git a/include/linux/ceph/ceph_fs.h b/include/linux/ceph/ceph_fs.h index b422170b791a..4abb0fea4fd6 100644 --- a/include/linux/ceph/ceph_fs.h +++ b/include/linux/ceph/ceph_fs.h @@ -434,6 +434,12 @@ union ceph_mds_request_args { __le64 length; /* num bytes to lock from start */ __u8 wait; /* will caller wait for lock to become available? */ } __attribute__ ((packed)) filelock_change; + struct { + __le32 mask; /* CEPH_CAP_* */ + __le64 snapid; + __le64 parent; + __le32 hash; + } __attribute__ ((packed)) lookupino; } __attribute__ ((packed)); #define CEPH_MDS_FLAG_REPLAY 1 /* this is a replayed op */