@@ -7,6 +7,7 @@ lustre-y := dcache.o dir.o file.o llite_lib.o llite_nfs.o \
xattr.o xattr_cache.o xattr_security.o \
super25.o statahead.o glimpse.o lcommon_cl.o lcommon_misc.o \
vvp_dev.o vvp_page.o vvp_io.o vvp_object.o \
+ llite_foreign.o llite_foreign_symlink.o \
lproc_llite.o pcc.o
lustre-$(CONFIG_LUSTRE_FS_POSIX_ACL) += acl.o
@@ -248,8 +248,18 @@ static int ll_revalidate_dentry(struct dentry *dentry,
return 1;
/* Symlink - always valid as long as the dentry was found */
- if (dentry->d_inode && S_ISLNK(dentry->d_inode->i_mode))
- return 1;
+ /* only special case is to prevent ELOOP error from VFS during open
+ * of a foreign symlink file/dir with O_NOFOLLOW, like it happens for
+ * real symlinks. This will allow to open foreign symlink file/dir
+ * for get[dir]stripe/unlock ioctl()s.
+ */
+ if (dentry->d_inode && dentry->d_inode->i_op->get_link) {
+ if (!S_ISLNK(dentry->d_inode->i_mode) &&
+ !(lookup_flags & LOOKUP_FOLLOW))
+ return 0;
+ else
+ return 1;
+ }
/*
* VFS warns us that this is the second go around and previous
@@ -1641,6 +1641,17 @@ static long ll_dir_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
ptlrpc_req_finished(root_request);
return rc;
}
+
+ case LL_IOC_UNLOCK_FOREIGN:
+ /* if not a foreign symlink do nothing */
+ if (ll_foreign_is_removable(dentry, true)) {
+ CDEBUG(D_INFO,
+ "prevent rmdir of non-foreign dir ("DFID")\n",
+ PFID(ll_inode2fid(inode)));
+ return -EOPNOTSUPP;
+ }
+ return 0;
+
case LL_IOC_RMFID:
return ll_rmfid(file, (void __user *)arg);
case LL_IOC_LOV_SWAP_LAYOUTS:
@@ -2342,12 +2342,12 @@ static int ll_lov_setstripe(struct inode *inode, struct file *file,
}
rc = cl_object_layout_get(env, obj, &cl);
- if (!rc && cl.cl_is_composite)
+ if (rc >= 0 && cl.cl_is_composite)
rc = ll_layout_write_intent(inode, LAYOUT_INTENT_WRITE,
&ext);
cl_env_put(env, &refcheck);
- if (rc)
+ if (rc < 0)
goto out;
}
@@ -3001,7 +3001,7 @@ int ll_file_lock_ahead(struct file *file, struct llapi_lu_ladvise *ladvise)
CDEBUG(D_VFSTRACE,
"Lock request: file=%pd, inode=%p, mode=%s start=%llu, end=%llu\n",
dentry, dentry->d_inode,
- user_lockname[ladvise->lla_lockahead_mode], (__u64) start, end);
+ user_lockname[ladvise->lla_lockahead_mode], (u64) start, end);
cl_mode = cl_mode_user_to_kernel(ladvise->lla_lockahead_mode);
if (cl_mode < 0) {
@@ -4086,6 +4086,20 @@ static int ll_heat_set(struct inode *inode, enum lu_heat_flag flags)
return -EOPNOTSUPP;
return llcrypt_ioctl_get_key_status(file, (void __user *)arg);
#endif
+
+ case LL_IOC_UNLOCK_FOREIGN: {
+ struct dentry *dentry = file_dentry(file);
+
+ /* if not a foreign symlink do nothing */
+ if (ll_foreign_is_removable(dentry, true)) {
+ CDEBUG(D_INFO,
+ "prevent unlink of non-foreign file ("DFID")\n",
+ PFID(ll_inode2fid(inode)));
+ return -EOPNOTSUPP;
+ }
+ return 0;
+ }
+
default:
return obd_iocontrol(cmd, ll_i2dtexp(inode), 0, NULL,
(void __user *)arg);
@@ -4842,10 +4856,9 @@ static int ll_merge_md_attr(struct inode *inode)
return 0;
}
-int ll_getattr(const struct path *path, struct kstat *stat,
- u32 request_mask, unsigned int flags)
+int ll_getattr_dentry(struct dentry *de, struct kstat *stat, u32 request_mask,
+ unsigned int flags, bool foreign)
{
- struct dentry *de = path->dentry;
struct inode *inode = d_inode(de);
struct ll_sb_info *sbi = ll_i2sbi(inode);
struct ll_inode_info *lli = ll_i2info(inode);
@@ -4872,7 +4885,10 @@ int ll_getattr(const struct path *path, struct kstat *stat,
if (rc < 0)
return rc;
- if (S_ISREG(inode->i_mode)) {
+ /* foreign file/dir are always of zero length, so don't
+ * need to validate size.
+ */
+ if (S_ISREG(inode->i_mode) && !foreign) {
bool cached;
if (!need_glimpse)
@@ -4919,7 +4935,8 @@ int ll_getattr(const struct path *path, struct kstat *stat,
}
} else {
/* If object isn't regular a file then don't validate size. */
- if (ll_dir_striped(inode)) {
+ /* foreign dir is not striped dir */
+ if (ll_dir_striped(inode) && !foreign) {
rc = ll_merge_md_attr(inode);
if (rc < 0)
return rc;
@@ -4948,7 +4965,13 @@ int ll_getattr(const struct path *path, struct kstat *stat,
stat->rdev = inode->i_rdev;
stat->ino = inode->i_ino;
}
- stat->mode = inode->i_mode;
+
+ /* foreign symlink to be exposed as a real symlink */
+ if (!foreign)
+ stat->mode = inode->i_mode;
+ else
+ stat->mode = (inode->i_mode & ~S_IFMT) | S_IFLNK;
+
stat->uid = inode->i_uid;
stat->gid = inode->i_gid;
stat->atime = inode->i_atime;
@@ -4991,6 +5014,13 @@ int ll_getattr(const struct path *path, struct kstat *stat,
return 0;
}
+int ll_getattr(const struct path *path, struct kstat *stat,
+ u32 request_mask, unsigned int flags)
+{
+ return ll_getattr_dentry(path->dentry, stat, request_mask, flags,
+ false);
+}
+
int cl_falloc(struct inode *inode, int mode, loff_t offset, loff_t len)
{
struct lu_env *env;
@@ -5319,7 +5349,7 @@ int ll_layout_conf(struct inode *inode, const struct cl_object_conf *conf)
}
out:
cl_env_put(env, &refcheck);
- return rc;
+ return rc < 0 ? rc : 0;
}
/* Fetch layout from MDT with getxattr request, if it's not ready yet */
new file mode 100644
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+
+#ifndef LLITE_FOREIGN_SYMLINK_H
+#define LLITE_FOREIGN_SYMLINK_H
+
+/* llite/llite_foreign_symlink.c */
+ssize_t foreign_symlink_enable_show(struct kobject *kobj,
+ struct attribute *attr, char *buf);
+ssize_t foreign_symlink_enable_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buffer, size_t count);
+ssize_t foreign_symlink_prefix_show(struct kobject *kobj,
+ struct attribute *attr, char *buf);
+ssize_t foreign_symlink_prefix_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buffer, size_t count);
+ssize_t foreign_symlink_upcall_show(struct kobject *kobj,
+ struct attribute *attr, char *buf);
+ssize_t foreign_symlink_upcall_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buffer, size_t count);
+ssize_t foreign_symlink_upcall_info_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buffer, size_t count);
+extern const struct inode_operations ll_foreign_file_symlink_inode_operations;
+extern const struct inode_operations ll_foreign_dir_symlink_inode_operations;
+
+#endif /* LLITE_FOREIGN_SYMLINK_H */
new file mode 100644
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (c) 2020 Intel Corporation.
+ */
+#define DEBUG_SUBSYSTEM S_LLITE
+
+#include "llite_internal.h"
+
+static void ll_manage_foreign_file(struct inode *inode,
+ struct lov_foreign_md *lfm)
+{
+ struct ll_sb_info *sbi = ll_i2sbi(inode);
+
+ if (le32_to_cpu(lfm->lfm_type) == LU_FOREIGN_TYPE_SYMLINK) {
+ CDEBUG(D_INFO,
+ "%s: inode %p of fid "DFID": Foreign file of type symlink, faking a symlink\n",
+ sbi->ll_fsname, inode, PFID(ll_inode2fid(inode)));
+ /* change inode_operations to add symlink methods, and clear
+ * IOP_NOFOLLOW to ensure file will be treated as a symlink
+ * by Kernel (see in * d_flags_for_inode()).
+ */
+ inode->i_op = &ll_foreign_file_symlink_inode_operations;
+ inode->i_opflags &= ~IOP_NOFOLLOW;
+ } else {
+ CDEBUG(D_INFO,
+ "%s: inode %p of fid "DFID": Foreign file of type %ux, nothing special to do\n",
+ sbi->ll_fsname, inode, PFID(ll_inode2fid(inode)),
+ le32_to_cpu(lfm->lfm_type));
+ }
+}
+
+static void ll_manage_foreign_dir(struct inode *inode,
+ struct lmv_foreign_md *lfm)
+{
+ struct ll_sb_info *sbi = ll_i2sbi(inode);
+
+ if (lfm->lfm_type == LU_FOREIGN_TYPE_SYMLINK) {
+ CDEBUG(D_INFO,
+ "%s: inode %p of fid "DFID": Foreign dir of type symlink, faking a symlink\n",
+ sbi->ll_fsname, inode, PFID(ll_inode2fid(inode)));
+ /* change inode_operations to add symlink methods
+ * IOP_NOFOLLOW should not be set for dirs
+ */
+ inode->i_op = &ll_foreign_dir_symlink_inode_operations;
+ } else {
+ CDEBUG(D_INFO,
+ "%s: inode %p of fid "DFID": Foreign dir of type %ux, nothing special to do\n",
+ sbi->ll_fsname, inode, PFID(ll_inode2fid(inode)),
+ le32_to_cpu(lfm->lfm_type));
+ }
+}
+
+int ll_manage_foreign(struct inode *inode, struct lustre_md *lmd)
+{
+ int rc = 0;
+
+ /* apply any foreign file/dir policy */
+ if (S_ISREG((inode)->i_mode)) {
+ struct ll_inode_info *lli = ll_i2info(inode);
+ struct cl_object *obj = lli->lli_clob;
+
+ if (lmd->layout.lb_buf && lmd->layout.lb_len != 0) {
+ struct lov_foreign_md *lfm = lmd->layout.lb_buf;
+
+ if (lfm->lfm_magic == LOV_MAGIC_FOREIGN)
+ ll_manage_foreign_file(inode, lfm);
+ goto out;
+ }
+
+ if (obj) {
+ struct lov_foreign_md lfm = {
+ .lfm_magic = LOV_MAGIC,
+ };
+ struct cl_layout cl = {
+ .cl_buf.lb_buf = &lfm,
+ .cl_buf.lb_len = sizeof(lfm),
+ };
+ struct lu_env *env;
+ u16 refcheck;
+
+ env = cl_env_get(&refcheck);
+ if (IS_ERR(env)) {
+ rc = PTR_ERR(env);
+ goto out;
+ }
+ rc = cl_object_layout_get(env, obj, &cl);
+ /* error is likely to be -ERANGE because of the small
+ * buffer we use, only the content is significant here
+ */
+ if (rc < 0 && rc != -ERANGE) {
+ cl_env_put(env, &refcheck);
+ goto out;
+ }
+ if (lfm.lfm_magic == LOV_MAGIC_FOREIGN)
+ ll_manage_foreign_file(inode, &lfm);
+ cl_env_put(env, &refcheck);
+ }
+ } else if (S_ISDIR((inode)->i_mode)) {
+ if (lmd->lfm &&
+ lmd->lfm->lfm_magic == LMV_MAGIC_FOREIGN) {
+ ll_manage_foreign_dir(inode, lmd->lfm);
+ } else {
+ struct ll_inode_info *lli = ll_i2info(inode);
+ struct lmv_foreign_md *lfm;
+
+ down_read(&lli->lli_lsm_sem);
+ lfm = (struct lmv_foreign_md *)(lli->lli_lsm_md);
+ if (lfm && lfm->lfm_magic == LMV_MAGIC_FOREIGN)
+ ll_manage_foreign_dir(inode, lfm);
+ up_read(&lli->lli_lsm_sem);
+ }
+ }
+out:
+ return rc;
+}
+
+/* dentry must be spliced to inode (dentry->d_inode != NULL) !!! */
+bool ll_foreign_is_openable(struct dentry *dentry, unsigned int flags)
+{
+ /* check for faked symlink here as they should not be opened (unless
+ * O_NOFOLLOW!) and thus wants ll_atomic_open() to return 1 from
+ * finish_no_open() in order to get follow_link() to be called in both
+ * path_lookupat() and path_openupat().
+ * This will not break regular symlink handling as they have
+ * been treated/filtered upstream.
+ */
+ if (d_is_symlink(dentry) && !S_ISLNK(dentry->d_inode->i_mode) &&
+ !(flags & O_NOFOLLOW))
+ return false;
+
+ return true;
+}
+
+static bool should_preserve_foreign_file(struct lov_foreign_md *lfm,
+ struct ll_inode_info *lli, bool unset)
+{
+ /* for now, only avoid foreign fake symlink file removal */
+
+ if (unset)
+ if (lfm->lfm_type == LU_FOREIGN_TYPE_SYMLINK) {
+ set_bit(LLIF_FOREIGN_REMOVABLE, &lli->lli_flags);
+ return true;
+ } else {
+ return false;
+ }
+ else
+ return lfm->lfm_type == LU_FOREIGN_TYPE_SYMLINK &&
+ !test_bit(LLIF_FOREIGN_REMOVABLE, &lli->lli_flags);
+}
+
+static bool should_preserve_foreign_dir(struct lmv_foreign_md *lfm,
+ struct ll_inode_info *lli, bool unset)
+{
+ /* for now, only avoid foreign fake symlink dir removal */
+
+ if (unset)
+ if (lfm->lfm_type == LU_FOREIGN_TYPE_SYMLINK) {
+ set_bit(LLIF_FOREIGN_REMOVABLE, &lli->lli_flags);
+ return true;
+ } else {
+ return false;
+ }
+ else
+ return lfm->lfm_type == LU_FOREIGN_TYPE_SYMLINK &&
+ !test_bit(LLIF_FOREIGN_REMOVABLE, &lli->lli_flags);
+}
+
+/* XXX
+ * instead of fetching type from foreign LOV/LMV, we may simply
+ * check (d_is_symlink(dentry) && !S_ISLNK(dentry->d_inode->i_mode))
+ * to identify a fake symlink
+ */
+bool ll_foreign_is_removable(struct dentry *dentry, bool unset)
+{
+ struct inode *inode = dentry->d_inode;
+ struct qstr *name = &dentry->d_name;
+ bool preserve_foreign = false;
+ int rc = 0;
+
+ if (!inode)
+ return 0;
+
+ /* some foreign types may not be allowed to be unlinked in order to
+ * keep references with external objects
+ */
+ if (S_ISREG(inode->i_mode)) {
+ struct ll_inode_info *lli = ll_i2info(inode);
+ struct cl_object *obj = lli->lli_clob;
+
+ if (obj) {
+ struct lov_foreign_md lfm = {
+ .lfm_magic = LOV_MAGIC,
+ };
+ struct cl_layout cl = {
+ .cl_buf.lb_buf = &lfm,
+ .cl_buf.lb_len = sizeof(lfm),
+ };
+ struct lu_env *env;
+ u16 refcheck;
+
+ env = cl_env_get(&refcheck);
+ if (IS_ERR(env)) {
+ rc = PTR_ERR(env);
+ goto out;
+ }
+ rc = cl_object_layout_get(env, obj, &cl);
+ /* error is likely to be -ERANGE because of the small
+ * buffer we use, only the content is significant here
+ */
+ if (rc < 0 && rc != -ERANGE) {
+ cl_env_put(env, &refcheck);
+ goto out;
+ } else {
+ rc = 0;
+ }
+ if (lfm.lfm_magic == LOV_MAGIC_FOREIGN)
+ preserve_foreign =
+ should_preserve_foreign_file(&lfm, lli,
+ unset);
+ cl_env_put(env, &refcheck);
+ if (preserve_foreign) {
+ CDEBUG(D_INFO,
+ "%s unlink of foreign file (%.*s, "DFID")\n",
+ unset ? "allow" : "prevent",
+ name->len, name->name,
+ PFID(ll_inode2fid(inode)));
+ return false;
+ }
+ } else {
+ CDEBUG(D_INFO,
+ "unable to check if file (%.*s, "DFID") is foreign...\n",
+ name->len, name->name,
+ PFID(ll_inode2fid(inode)));
+ /* XXX should we prevent removal ?? */
+ }
+ } else if (S_ISDIR(inode->i_mode)) {
+ struct ll_inode_info *lli = ll_i2info(inode);
+ struct lmv_foreign_md *lfm;
+
+ down_read(&lli->lli_lsm_sem);
+ lfm = (struct lmv_foreign_md *)(lli->lli_lsm_md);
+ if (!lfm)
+ CDEBUG(D_INFO,
+ "unable to check if dir (%.*s, "DFID") is foreign...\n",
+ name->len, name->name,
+ PFID(ll_inode2fid(inode)));
+ else if (lfm->lfm_magic == LMV_MAGIC_FOREIGN)
+ preserve_foreign = should_preserve_foreign_dir(lfm, lli,
+ unset);
+ up_read(&lli->lli_lsm_sem);
+ if (preserve_foreign) {
+ CDEBUG(D_INFO,
+ "%s unlink of foreign dir (%.*s, "DFID")\n",
+ unset ? "allow" : "prevent",
+ name->len, name->name,
+ PFID(ll_inode2fid(inode)));
+ return false;
+ }
+ }
+
+out:
+ return true;
+}
new file mode 100644
@@ -0,0 +1,758 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (c) 2020 Intel Corporation.
+ */
+/*
+ * Foreign symlink implementation.
+ *
+ * Methods in this source file allow to construct a relative path from the
+ * LOV/LMV foreign content, to complement it with a prefix, and then to
+ * expose it to the VFS as a symlink destination.
+ * The default/internal mechanism simply takes the full foreign free string
+ * as the relative path, and for more complex internal formats an upcall has
+ * been implemented to provide format's details (presently just in terms of
+ * constant strings and substrings positions in EA, but this can be enhanced)
+ * to llite layer.
+ */
+
+#include <linux/fs.h>
+#include <linux/fs_struct.h>
+#include <linux/mm.h>
+#include <linux/stat.h>
+#include <linux/version.h>
+#define DEBUG_SUBSYSTEM S_LLITE
+
+#include "llite_internal.h"
+
+/* allocate space for "/<prefix>/<suffix>'\0'" and copy prefix in,
+ * returns start position for suffix in *destname
+ * must be called with ll_foreign_symlink_sem locked for read, to
+ * protect against sbi->ll_foreign_symlink_prefix change
+ * on output, provides position where to start prefix complement
+ */
+static int foreign_symlink_alloc_and_copy_prefix(struct ll_sb_info *sbi,
+ struct inode *inode,
+ char **destname,
+ size_t suffix_size)
+{
+ size_t prefix_size, full_size;
+
+ /* allocate enough for "/<prefix>/<suffix>'\0'" */
+ prefix_size = sbi->ll_foreign_symlink_prefix_size - 1;
+ full_size = suffix_size + prefix_size + 3;
+ if (full_size > PATH_MAX) {
+ CERROR("%s: inode "DFID": resolved destination path too long\n",
+ sbi->ll_fsname, PFID(ll_inode2fid(inode)));
+ return -EINVAL;
+ }
+ *destname = kzalloc(full_size, GFP_KERNEL);
+ if (!*destname)
+ return -ENOMEM;
+
+ memcpy(*destname + 1, sbi->ll_foreign_symlink_prefix,
+ prefix_size);
+ (*destname)[0] = '/';
+ (*destname)[prefix_size + 1] = '/';
+
+ return prefix_size + 2;
+}
+
+/* if no upcall registered, default foreign symlink parsing method
+ * is to use the full lfm_value as a relative path to complement
+ * foreign_prefix
+ */
+static int ll_foreign_symlink_default_parse(struct ll_sb_info *sbi,
+ struct inode *inode,
+ struct lov_foreign_md *lfm,
+ char **destname)
+{
+ int suffix_pos;
+
+ down_read(&sbi->ll_foreign_symlink_sem);
+ suffix_pos = foreign_symlink_alloc_and_copy_prefix(sbi, inode,
+ destname,
+ lfm->lfm_length);
+ up_read(&sbi->ll_foreign_symlink_sem);
+
+ if (suffix_pos < 0)
+ return suffix_pos;
+
+ memcpy(*destname + suffix_pos, lfm->lfm_value,
+ lfm->lfm_length);
+ (*destname)[suffix_pos + lfm->lfm_length] = '\0';
+
+ return 0;
+}
+
+/* if an upcall has been registered, foreign symlink will be
+ * constructed as per upcall provided format
+ * presently we only support a serie of constant strings and sub-strings
+ * to be taken from lfm_value content
+ */
+static int ll_foreign_symlink_upcall_parse(struct ll_sb_info *sbi,
+ struct inode *inode,
+ struct lov_foreign_md *lfm,
+ char **destname)
+{
+ int pos = 0, suffix_pos = -1, items_size = 0;
+ struct ll_foreign_symlink_upcall_item *foreign_symlink_items =
+ sbi->ll_foreign_symlink_upcall_items;
+ int i = 0, rc = 0;
+
+ down_read(&sbi->ll_foreign_symlink_sem);
+
+ /* compute size of relative path of destination path
+ * could be done once during upcall items/infos reading
+ * and stored as new ll_sb_info field
+ */
+ for (i = 0; i < sbi->ll_foreign_symlink_upcall_nb_items; i++) {
+ switch (foreign_symlink_items[i].type) {
+ case STRING_TYPE:
+ items_size += foreign_symlink_items[i].size;
+ break;
+ case POSLEN_TYPE:
+ items_size += foreign_symlink_items[i].len;
+ break;
+ case EOB_TYPE:
+ /* should be the last item */
+ break;
+ default:
+ CERROR("%s: unexpected type '%u' found in items\n",
+ sbi->ll_fsname, foreign_symlink_items[i].type);
+ rc = -EINVAL;
+ goto failed;
+ }
+ }
+
+ suffix_pos = foreign_symlink_alloc_and_copy_prefix(sbi, inode, destname,
+ items_size);
+ if (suffix_pos < 0) {
+ rc = suffix_pos;
+ goto failed;
+ }
+
+ /* rescan foreign_symlink_items[] to create faked symlink dest path */
+ i = 0;
+ while (foreign_symlink_items[i].type != EOB_TYPE) {
+ if (foreign_symlink_items[i].type == STRING_TYPE) {
+ memcpy(*destname + suffix_pos + pos,
+ foreign_symlink_items[i].string,
+ foreign_symlink_items[i].size);
+ pos += foreign_symlink_items[i].size;
+ } else if (foreign_symlink_items[i].type == POSLEN_TYPE) {
+ if (lfm->lfm_length < foreign_symlink_items[i].pos +
+ foreign_symlink_items[i].len) {
+ CERROR("%s: "DFID" foreign EA too short to find (%u,%u) item\n",
+ sbi->ll_fsname,
+ PFID(ll_inode2fid(inode)),
+ foreign_symlink_items[i].pos,
+ foreign_symlink_items[i].len);
+ rc = -EINVAL;
+ goto failed;
+ }
+ memcpy(*destname + suffix_pos + pos,
+ lfm->lfm_value + foreign_symlink_items[i].pos,
+ foreign_symlink_items[i].len);
+ pos += foreign_symlink_items[i].len;
+ } else {
+ CERROR("%s: unexpected type '%u' found in items\n",
+ sbi->ll_fsname, foreign_symlink_items[i].type);
+ rc = -EINVAL;
+ goto failed;
+ }
+ i++;
+ }
+failed:
+ up_read(&sbi->ll_foreign_symlink_sem);
+
+ if (rc != 0 && suffix_pos >= 0) {
+ kvfree(*destname);
+ *destname = NULL;
+ }
+
+ return rc;
+}
+
+static int ll_foreign_symlink_parse(struct ll_sb_info *sbi,
+ struct inode *inode,
+ struct lov_foreign_md *lfm,
+ char **destname)
+{
+ int rc;
+
+ /* if no user-land upcall registered, assuming whole free field
+ * of foreign LOV is relative path of faked symlink destination,
+ * to be completed by prefix
+ */
+ if (!(sbi->ll_flags & LL_SBI_FOREIGN_SYMLINK_UPCALL))
+ rc = ll_foreign_symlink_default_parse(sbi, inode, lfm,
+ destname);
+ else /* upcall is available */
+ rc = ll_foreign_symlink_upcall_parse(sbi, inode, lfm,
+ destname);
+ return rc;
+}
+
+/* Don't need lli_size_mutex locked as LOV/LMV are EAs
+ * and should not be stored in data blocks
+ */
+static int ll_foreign_readlink_internal(struct inode *inode, char **symname)
+{
+ struct ll_inode_info *lli = ll_i2info(inode);
+ struct ll_sb_info *sbi = ll_i2sbi(inode);
+ struct lov_foreign_md *lfm = NULL;
+ char *destname = NULL;
+ size_t lfm_size = 0;
+ int rc;
+
+ if (S_ISREG(inode->i_mode)) {
+ struct cl_object *obj = lli->lli_clob;
+ struct cl_layout cl = {
+ .cl_buf.lb_len = 0, /* to get real size */
+ };
+ struct lu_env *env;
+ u16 refcheck;
+
+ if (!obj) {
+ CERROR("%s: inode "DFID": can not get layout, no cl_object\n",
+ sbi->ll_fsname, PFID(ll_inode2fid(inode)));
+ rc = -EINVAL;
+ goto failed;
+ }
+
+ env = cl_env_get(&refcheck);
+ if (IS_ERR(env))
+ return PTR_ERR(env);
+ /* get layout size */
+ rc = cl_object_layout_get(env, obj, &cl);
+ if (rc <= 0) {
+ CERROR("%s: inode "DFID": error trying to get layout size : %d\n",
+ sbi->ll_fsname, PFID(ll_inode2fid(inode)), rc);
+ cl_env_put(env, &refcheck);
+ return rc;
+ }
+ lfm = kzalloc(rc, GFP_KERNEL);
+ if (!lfm) {
+ CERROR("%s: inode "DFID": can not allocate enough mem to get layout\n",
+ sbi->ll_fsname, PFID(ll_inode2fid(inode)));
+ cl_env_put(env, &refcheck);
+ return -ENOMEM;
+ }
+ cl.cl_buf.lb_len = rc;
+ cl.cl_buf.lb_buf = lfm;
+ /* get layout */
+ rc = cl_object_layout_get(env, obj, &cl);
+ if (rc <= 0) {
+ CERROR("%s: inode "DFID": error trying to get layout : %d\n",
+ sbi->ll_fsname, PFID(ll_inode2fid(inode)), rc);
+ kfree(lfm);
+ cl_env_put(env, &refcheck);
+ return rc;
+ }
+ lfm_size = cl.cl_buf.lb_len;
+ cl_env_put(env, &refcheck);
+ } else if (S_ISDIR(inode->i_mode)) {
+ down_read(&lli->lli_lsm_sem);
+
+ /* should be casted lmv_foreign_md, but it is ok as both foreign LOV
+ * and LMV formats are identical, and then we also only need
+ * one set of parsing routines for both foreign files and dirs!
+ */
+ lfm = (struct lov_foreign_md *)(lli->lli_lsm_md);
+ if (lfm) {
+ CDEBUG(D_INFO, "%s: inode "DFID": LMV cached found\n",
+ sbi->ll_fsname, PFID(ll_inode2fid(inode)));
+ } else {
+ CERROR("%s: inode "DFID": cannot get layout, no LMV cached\n",
+ sbi->ll_fsname, PFID(ll_inode2fid(inode)));
+ rc = -EINVAL;
+ goto failed;
+ }
+ } else {
+ CERROR("%s: inode "DFID": not a regular file nor directory\n",
+ sbi->ll_fsname, PFID(ll_inode2fid(inode)));
+ rc = -EINVAL;
+ goto failed;
+ }
+
+ /* XXX no assert nor double check of magic, length and type ? */
+
+ rc = ll_foreign_symlink_parse(sbi, inode, lfm, &destname);
+failed:
+ if (S_ISDIR(inode->i_mode))
+ up_read(&lli->lli_lsm_sem);
+
+ if (S_ISREG(inode->i_mode) && lfm)
+ kfree(lfm);
+
+ if (!rc) {
+ *symname = destname;
+ CDEBUG(D_INFO,
+ "%s: inode "DFID": faking symlink to dest '%s'\n",
+ sbi->ll_fsname, PFID(ll_inode2fid(inode)), destname);
+ }
+
+ return rc;
+}
+
+static void ll_foreign_put_link(void *cookie)
+{
+ /* to avoid allocating an unnecessary big buffer, and since ways to
+ * build the symlink path from foreign LOV/LMV can be multiple and
+ * not constant. So it size is not known and we need to use
+ * strlen(cookie)+1 to determine its size and to avoid false positive
+ * to be reported by memory leak check code
+ */
+ kvfree(cookie);
+}
+
+static const char *ll_foreign_get_link(struct dentry *dentry,
+ struct inode *inode,
+ struct delayed_call *done)
+{
+ char *symname = NULL;
+ int rc;
+
+ CDEBUG(D_VFSTRACE, "VFS Op\n");
+ if (!dentry)
+ return ERR_PTR(-ECHILD);
+ rc = ll_foreign_readlink_internal(inode, &symname);
+
+ /*
+ * symname must be freed when we are done
+ *
+ * XXX we may avoid the need to do so if we use
+ * lli_symlink_name cache to retain symname and
+ * let ll_clear_inode free it...
+ */
+ set_delayed_call(done, ll_foreign_put_link, symname);
+ return rc ? ERR_PTR(rc) : symname;
+}
+
+/*
+ * Should only be called for already in-use/cache foreign dir inode
+ * when foreign fake-symlink behaviour has been enabled afterward
+ */
+static struct dentry *ll_foreign_dir_lookup(struct inode *parent,
+ struct dentry *dentry,
+ unsigned int flags)
+{
+ CDEBUG(D_VFSTRACE, "VFS Op:name=%.*s, dir="DFID"(%p)\n",
+ dentry->d_name.len, dentry->d_name.name,
+ PFID(ll_inode2fid(parent)), parent);
+
+ return ERR_PTR(-ENODATA);
+}
+
+static bool has_same_mount_namespace(struct ll_sb_info *sbi)
+{
+ int rc;
+
+ rc = (sbi->ll_mnt.mnt == current->fs->root.mnt);
+ if (!rc)
+ LCONSOLE_WARN("%s: client mount %s and '%s.%d' not in same mnt-namespace\n",
+ sbi->ll_fsname, sbi->ll_kset.kobj.name,
+ current->comm, current->pid);
+
+ return rc;
+}
+
+ssize_t foreign_symlink_enable_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct ll_sb_info *sbi = container_of(kobj, struct ll_sb_info,
+ ll_kset.kobj);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ !!(sbi->ll_flags & LL_SBI_FOREIGN_SYMLINK));
+}
+
+/*
+ * XXX
+ * There should be already in-use/cached inodes of foreign files/dirs who
+ * will not-be/continue-to-be handled as fake-symlink, depending if
+ * feature is being enabled/disabled, until being revalidated.
+ * Also, does it require sbi->ll_lock protection ?
+ */
+ssize_t foreign_symlink_enable_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buffer, size_t count)
+{
+ struct ll_sb_info *sbi = container_of(kobj, struct ll_sb_info,
+ ll_kset.kobj);
+ unsigned int val;
+ int rc;
+
+ if (!has_same_mount_namespace(sbi))
+ return -EINVAL;
+
+ rc = kstrtouint(buffer, 10, &val);
+ if (rc)
+ return rc;
+
+ if (val)
+ sbi->ll_flags |= LL_SBI_FOREIGN_SYMLINK;
+ else
+ sbi->ll_flags &= ~LL_SBI_FOREIGN_SYMLINK;
+
+ return count;
+}
+
+ssize_t foreign_symlink_prefix_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct ll_sb_info *sbi = container_of(kobj, struct ll_sb_info,
+ ll_kset.kobj);
+ ssize_t size;
+
+ down_read(&sbi->ll_foreign_symlink_sem);
+ size = snprintf(buf, PAGE_SIZE, "%s\n", sbi->ll_foreign_symlink_prefix);
+ up_read(&sbi->ll_foreign_symlink_sem);
+
+ return size;
+}
+
+ssize_t foreign_symlink_prefix_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buffer, size_t count)
+{
+ struct ll_sb_info *sbi = container_of(kobj, struct ll_sb_info,
+ ll_kset.kobj);
+ char *new, *old;
+ size_t new_len, old_len;
+
+ if (!has_same_mount_namespace(sbi))
+ return -EINVAL;
+
+ /* XXX strip buffer of any CR/LF,space,... ?? */
+
+ /* check buffer looks like a valid absolute path */
+ if (*buffer != '/') {
+ CERROR("foreign symlink prefix must be an absolute path\n");
+ return -EINVAL;
+ }
+ new_len = strnlen(buffer, count);
+ if (new_len < count)
+ CDEBUG(D_INFO, "NUL byte found in %zu bytes\n", count);
+ if (new_len > PATH_MAX) {
+ CERROR("%s: foreign symlink prefix length %zu > PATH_MAX\n",
+ sbi->ll_fsname, new_len);
+ return -EINVAL;
+ }
+ new = kzalloc(new_len + 1, GFP_KERNEL);
+ if (!new) {
+ CERROR("%s: can not allocate space for foreign path prefix\n",
+ sbi->ll_fsname);
+ return -ENOSPC;
+ }
+
+ down_write(&sbi->ll_foreign_symlink_sem);
+ old_len = sbi->ll_foreign_symlink_prefix_size;
+ old = sbi->ll_foreign_symlink_prefix;
+ memcpy(new, buffer, new_len);
+ *(new + new_len) = '\0';
+
+ sbi->ll_foreign_symlink_prefix = new;
+ sbi->ll_foreign_symlink_prefix_size = new_len + 1;
+ up_write(&sbi->ll_foreign_symlink_sem);
+
+ kfree(old);
+
+ return new_len;
+}
+
+ssize_t foreign_symlink_upcall_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ ssize_t size;
+ struct ll_sb_info *sbi = container_of(kobj, struct ll_sb_info,
+ ll_kset.kobj);
+
+ down_read(&sbi->ll_foreign_symlink_sem);
+ size = snprintf(buf, PAGE_SIZE, "%s\n", sbi->ll_foreign_symlink_upcall);
+ up_read(&sbi->ll_foreign_symlink_sem);
+
+ return size;
+}
+
+ssize_t foreign_symlink_upcall_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buffer, size_t count)
+{
+ struct ll_sb_info *sbi = container_of(kobj, struct ll_sb_info,
+ ll_kset.kobj);
+ char *old = NULL, *new = NULL;
+ size_t new_len;
+
+ if (!has_same_mount_namespace(sbi))
+ return -EINVAL;
+
+ /* XXX strip buffer of any CR/LF,space,... ?? */
+
+ /* check buffer looks like a valid absolute path */
+ if (*buffer != '/' && strcmp(buffer, "none")) {
+ CERROR("foreign symlink upcall must be an absolute path\n");
+ return -EINVAL;
+ }
+ new_len = strnlen(buffer, count);
+ if (new_len < count)
+ CDEBUG(D_INFO, "NULL byte found in %zu bytes\n", count);
+ if (new_len > PATH_MAX) {
+ CERROR("%s: foreign symlink upcall path length %zu > PATH_MAX\n",
+ sbi->ll_fsname, new_len);
+ return -EINVAL;
+ }
+
+ new = kzalloc(new_len + 1, GFP_KERNEL);
+ if (!new) {
+ CERROR("%s: can not allocate space for foreign symlink upcall path\n",
+ sbi->ll_fsname);
+ return -ENOSPC;
+ }
+ memcpy(new, buffer, new_len);
+ *(new + new_len) = '\0';
+
+ down_write(&sbi->ll_foreign_symlink_sem);
+ old = sbi->ll_foreign_symlink_upcall;
+
+ sbi->ll_foreign_symlink_upcall = new;
+ /* LL_SBI_FOREIGN_SYMLINK_UPCALL will be set by
+ * foreign_symlink_upcall_info_store() upon valid being provided
+ * by upcall
+ * XXX there is a potential race if there are multiple concurent
+ * attempts to set upcall path and execution occur in different
+ * order, we may end up using the format provided by a different
+ * upcall than the one set in ll_foreign_symlink_upcall
+ */
+ sbi->ll_flags &= ~LL_SBI_FOREIGN_SYMLINK_UPCALL;
+ up_write(&sbi->ll_foreign_symlink_sem);
+
+ if (strcmp(new, "none")) {
+ char *argv[] = {
+ [0] = new,
+ /* sbi sysfs object name */
+ [1] = (char *)sbi->ll_kset.kobj.name,
+ [2] = NULL
+ };
+ char *envp[] = {
+ [0] = "HOME=/",
+ [1] = "PATH=/sbin:/usr/sbin",
+ [2] = NULL
+ };
+ int rc;
+
+ rc = call_usermodehelper(new, argv, envp, UMH_WAIT_EXEC);
+ if (rc < 0)
+ CERROR("%s: error invoking foreign symlink upcall %s: rc %d\n",
+ sbi->ll_fsname, new, rc);
+ else
+ CDEBUG(D_INFO, "%s: invoked upcall %s\n",
+ sbi->ll_fsname, new);
+ }
+
+ kvfree(old);
+
+ return new_len;
+}
+
+/* foreign_symlink_upcall_info_store() stores format items in
+ * foreign_symlink_items[], and foreign_symlink_upcall_parse()
+ * uses it to parse each foreign symlink LOV/LMV EAs
+ */
+ssize_t foreign_symlink_upcall_info_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buffer, size_t count)
+{
+ struct ll_sb_info *sbi = container_of(kobj, struct ll_sb_info,
+ ll_kset.kobj);
+ struct ll_foreign_symlink_upcall_item items[MAX_NB_UPCALL_ITEMS], *item;
+ struct ll_foreign_symlink_upcall_item *new_items, *old_items;
+ size_t remaining = count;
+ int nb_items = 0, old_nb_items, i, rc = 0;
+
+ if (!has_same_mount_namespace(sbi))
+ return -EINVAL;
+
+ /* parse buffer to check validity of infos and fill symlink format
+ * descriptors
+ */
+
+ if (count % sizeof(u32) != 0) {
+ CERROR("%s: invalid size '%zu' of infos buffer returned by foreign symlink upcall\n",
+ sbi->ll_fsname, count);
+ return -EINVAL;
+ }
+
+ /* evaluate number of items provided */
+ while (remaining > 0) {
+ item = (struct ll_foreign_symlink_upcall_item *)
+ &buffer[count - remaining];
+ switch (item->type) {
+ case STRING_TYPE: {
+ /* a constant string following */
+ if (item->size >= remaining -
+ offsetof(struct ll_foreign_symlink_upcall_item,
+ bytestring) - sizeof(item->type)) {
+ /* size of string must not overflow remaining
+ * bytes minus EOB_TYPE item
+ */
+ CERROR("%s: constant string too long in infos buffer returned by foreign symlink upcall\n",
+ sbi->ll_fsname);
+ rc = -EINVAL;
+ goto failed;
+ }
+ items[nb_items].string = kzalloc(item->size,
+ GFP_KERNEL);
+ if (!items[nb_items].string) {
+ CERROR("%s: constant string allocation has failed for constant string of size %zu\n",
+ sbi->ll_fsname, item->size);
+ rc = -ENOMEM;
+ goto failed;
+ }
+ memcpy(items[nb_items].string,
+ item->bytestring, item->size);
+ items[nb_items].size = item->size;
+ /* string items to fit on u32 boundary */
+ remaining = remaining - STRING_ITEM_SZ(item->size);
+ break;
+ }
+ case POSLEN_TYPE: {
+ /* a tuple (pos,len) following to delimit a sub-string
+ * in lfm_value
+ */
+ items[nb_items].pos = item->pos;
+ items[nb_items].len = item->len;
+ remaining -= POSLEN_ITEM_SZ;
+ break;
+ }
+ case EOB_TYPE:
+ if (remaining != sizeof(item->type)) {
+ CERROR("%s: early end of infos buffer returned by foreign symlink upcall\n",
+ sbi->ll_fsname);
+ rc = -EINVAL;
+ goto failed;
+ }
+ remaining -= sizeof(item->type);
+ break;
+ default:
+ CERROR("%s: wrong type '%u' encountered at pos %zu , with %zu remaining bytes, in infos buffer returned by foreign symlink upcall\n",
+ sbi->ll_fsname, (u32)buffer[count - remaining],
+ count - remaining, remaining);
+ rc = -EINVAL;
+ goto failed;
+ }
+
+ items[nb_items].type = item->type;
+ nb_items++;
+ if (nb_items >= MAX_NB_UPCALL_ITEMS) {
+ CERROR("%s: too many items in infos buffer returned by foreign symlink upcall\n",
+ sbi->ll_fsname);
+ rc = -EINVAL;
+ goto failed;
+ }
+ }
+ /* valid format has been provided by foreign symlink user upcall */
+ new_items = kvmalloc_array(nb_items,
+ sizeof(struct ll_foreign_symlink_upcall_item),
+ GFP_KERNEL);
+ if (!new_items) {
+ CERROR("%s: constant string allocation has failed for constant string of size %zu\n",
+ sbi->ll_fsname, nb_items *
+ sizeof(struct ll_foreign_symlink_upcall_item));
+ rc = -ENOMEM;
+ goto failed;
+ }
+ for (i = 0; i < nb_items; i++)
+ *((struct ll_foreign_symlink_upcall_item *)new_items + i) =
+ items[i];
+
+ down_write(&sbi->ll_foreign_symlink_sem);
+ old_items = sbi->ll_foreign_symlink_upcall_items;
+ old_nb_items = sbi->ll_foreign_symlink_upcall_nb_items;
+ sbi->ll_foreign_symlink_upcall_items = new_items;
+ sbi->ll_foreign_symlink_upcall_nb_items = nb_items;
+ sbi->ll_flags |= LL_SBI_FOREIGN_SYMLINK_UPCALL;
+ up_write(&sbi->ll_foreign_symlink_sem);
+
+ /* free old_items */
+ if (old_items) {
+ for (i = 0 ; i < old_nb_items; i++)
+ if (old_items[i].type == STRING_TYPE)
+ kfree(old_items[i].string);
+
+ kvfree(old_items);
+ }
+
+failed:
+ /* clean items[] and free any strings */
+ if (rc != 0) {
+ for (i = 0; i < nb_items; i++) {
+ switch (items[i].type) {
+ case STRING_TYPE:
+ kfree(items[i].string);
+ items[i].string = NULL;
+ items[i].size = 0;
+ break;
+ case POSLEN_TYPE:
+ items[i].pos = 0;
+ items[i].len = 0;
+ break;
+ case EOB_TYPE:
+ break;
+ default:
+ CERROR("%s: wrong '%u'type encountered in foreign symlink upcall items\n",
+ sbi->ll_fsname, items[i].type);
+ rc = -EINVAL;
+ goto failed;
+ }
+ items[i].type = 0;
+ }
+ }
+
+ return rc == 0 ? count : rc;
+}
+
+static int ll_foreign_symlink_getattr(const struct path *path, struct kstat *stat,
+ u32 request_mask, unsigned int flags)
+{
+ return ll_getattr_dentry(path->dentry, stat, request_mask, flags,
+ true);
+}
+
+const struct inode_operations ll_foreign_file_symlink_inode_operations = {
+ .setattr = ll_setattr,
+ .get_link = ll_foreign_get_link,
+ .getattr = ll_foreign_symlink_getattr,
+ .permission = ll_inode_permission,
+ .listxattr = ll_listxattr,
+};
+
+const struct inode_operations ll_foreign_dir_symlink_inode_operations = {
+ .lookup = ll_foreign_dir_lookup,
+ .setattr = ll_setattr,
+ .get_link = ll_foreign_get_link,
+ .getattr = ll_foreign_symlink_getattr,
+ .permission = ll_inode_permission,
+ .listxattr = ll_listxattr,
+};
@@ -51,6 +51,7 @@
#include "vvp_internal.h"
#include "pcc.h"
+#include "foreign_symlink.h"
/** Only used on client-side for indicating the tail of dir hash/offset. */
#define LL_DIR_END_OFF 0x7fffffffffffffffULL
@@ -102,6 +103,8 @@ enum ll_file_flags {
* local inode atime.
*/
LLIF_UPDATE_ATIME = 4,
+ /* foreign file/dir can be unlinked unconditionnaly */
+ LLIF_FOREIGN_REMOVABLE = 5,
/* setting encryption context in progress */
LLIF_SET_ENC_CTX = 6,
};
@@ -619,6 +622,10 @@ enum stats_track_type {
#define LL_SBI_FILE_HEAT 0x4000000 /* file heat support */
#define LL_SBI_TEST_DUMMY_ENCRYPTION 0x8000000 /* test dummy encryption */
#define LL_SBI_ENCRYPT 0x10000000 /* client side encryption */
+#define LL_SBI_FOREIGN_SYMLINK 0x20000000 /* foreign fake-symlink support */
+/* foreign fake-symlink upcall registered */
+#define LL_SBI_FOREIGN_SYMLINK_UPCALL 0x40000000
+
#define LL_SBI_FLAGS { \
"nolck", \
"checksum", \
@@ -649,6 +656,8 @@ enum stats_track_type {
"file_heat", \
"test_dummy_encryption", \
"noencrypt", \
+ "foreign_symlink", \
+ "foreign_symlink_upcall", \
}
/*
@@ -761,6 +770,19 @@ struct ll_sb_info {
/* Persistent Client Cache */
struct pcc_super ll_pcc_super;
+
+ /* to protect vs updates in all following foreign symlink fields */
+ struct rw_semaphore ll_foreign_symlink_sem;
+ /* foreign symlink path prefix */
+ char *ll_foreign_symlink_prefix;
+ /* full prefix size including leading '\0' */
+ size_t ll_foreign_symlink_prefix_size;
+ /* foreign symlink path upcall */
+ char *ll_foreign_symlink_upcall;
+ /* foreign symlink path upcall infos */
+ struct ll_foreign_symlink_upcall_item *ll_foreign_symlink_upcall_items;
+ /* foreign symlink path upcall nb infos */
+ unsigned int ll_foreign_symlink_upcall_nb_items;
};
#define SBI_DEFAULT_HEAT_DECAY_WEIGHT ((80 * 256 + 50) / 100)
@@ -951,6 +973,11 @@ static inline bool ll_sbi_has_file_heat(struct ll_sb_info *sbi)
return !!(sbi->ll_flags & LL_SBI_FILE_HEAT);
}
+static inline bool ll_sbi_has_foreign_symlink(struct ll_sb_info *sbi)
+{
+ return !!(sbi->ll_flags & LL_SBI_FOREIGN_SYMLINK);
+}
+
void ll_ras_enter(struct file *f, loff_t pos, size_t count);
/* llite/lcommon_misc.c */
@@ -1063,6 +1090,8 @@ enum ldlm_mode ll_take_md_lock(struct inode *inode, u64 bits,
int ll_md_real_close(struct inode *inode, fmode_t fmode);
int ll_getattr(const struct path *path, struct kstat *stat,
u32 request_mask, unsigned int flags);
+int ll_getattr_dentry(struct dentry *de, struct kstat *stat, u32 request_mask,
+ unsigned int flags, bool foreign);
#ifdef CONFIG_LUSTRE_FS_POSIX_ACL
struct posix_acl *ll_get_acl(struct inode *inode, int type);
int ll_set_acl(struct inode *inode, struct posix_acl *acl, int type);
@@ -1459,7 +1488,7 @@ static inline int cl_agl(struct inode *inode)
int ll_file_lock_ahead(struct file *file, struct llapi_lu_ladvise *ladvise);
int cl_io_get(struct inode *inode, struct lu_env **envout,
- struct cl_io **ioout, __u16 *refcheck);
+ struct cl_io **ioout, u16 *refcheck);
static inline int ll_glimpse_size(struct inode *inode)
{
@@ -1671,5 +1700,9 @@ inline void ll_sbi_set_encrypt(struct ll_sb_info *sbi, bool set)
{
}
#endif /* !CONFIG_FS_ENCRYPTION */
+/* llite/llite_foreign.c */
+int ll_manage_foreign(struct inode *inode, struct lustre_md *lmd);
+bool ll_foreign_is_openable(struct dentry *dentry, unsigned int flags);
+bool ll_foreign_is_removable(struct dentry *dentry, bool unset);
#endif /* LLITE_INTERNAL_H */
@@ -122,6 +122,28 @@ static struct ll_sb_info *ll_init_sbi(void)
goto out_destroy_ra;
}
+ /* initialize foreign symlink prefix path */
+ sbi->ll_foreign_symlink_prefix = kasprintf(GFP_KERNEL, "/mnt/");
+ if (!sbi->ll_foreign_symlink_prefix) {
+ rc = -ENOMEM;
+ goto out_destroy_ra;
+ }
+ sbi->ll_foreign_symlink_prefix_size = sizeof("/mnt/");
+
+ /* initialize foreign symlink upcall path, none by default */
+ sbi->ll_foreign_symlink_upcall = kasprintf(GFP_KERNEL, "none");
+ if (!sbi->ll_foreign_symlink_upcall) {
+ rc = -ENOMEM;
+ goto out_destroy_ra;
+ }
+
+ sbi->ll_foreign_symlink_upcall_items = NULL;
+ sbi->ll_foreign_symlink_upcall_nb_items = 0;
+ init_rwsem(&sbi->ll_foreign_symlink_sem);
+ /* foreign symlink support (LL_SBI_FOREIGN_SYMLINK in ll_flags)
+ * not enabled by default
+ */
+
sbi->ll_ra_info.ra_max_pages =
min(pages / 32, SBI_DEFAULT_READ_AHEAD_MAX);
sbi->ll_ra_info.ra_max_pages_per_file =
@@ -170,6 +192,12 @@ static struct ll_sb_info *ll_init_sbi(void)
sbi->ll_heat_period_second = SBI_DEFAULT_HEAT_PERIOD_SECOND;
return sbi;
out_destroy_ra:
+ kfree(sbi->ll_foreign_symlink_upcall);
+ kfree(sbi->ll_foreign_symlink_prefix);
+ if (sbi->ll_cache) {
+ cl_cache_decref(sbi->ll_cache);
+ sbi->ll_cache = NULL;
+ }
destroy_workqueue(sbi->ll_ra_info.ll_readahead_wq);
out_pcc:
pcc_super_fini(&sbi->ll_pcc_super);
@@ -190,6 +218,23 @@ static void ll_free_sbi(struct super_block *sb)
cl_cache_decref(sbi->ll_cache);
sbi->ll_cache = NULL;
}
+ kfree(sbi->ll_foreign_symlink_prefix);
+ sbi->ll_foreign_symlink_prefix = NULL;
+ kfree(sbi->ll_foreign_symlink_upcall);
+ sbi->ll_foreign_symlink_upcall = NULL;
+ if (sbi->ll_foreign_symlink_upcall_items) {
+ int i;
+ int nb_items = sbi->ll_foreign_symlink_upcall_nb_items;
+ struct ll_foreign_symlink_upcall_item *items;
+
+ items = sbi->ll_foreign_symlink_upcall_items;
+ for (i = 0 ; i < nb_items; i++)
+ if (items[i].type == STRING_TYPE)
+ kfree(items[i].string);
+
+ kvfree(items);
+ sbi->ll_foreign_symlink_upcall_items = NULL;
+ }
pcc_super_fini(&sbi->ll_pcc_super);
kfree(sbi);
}
@@ -958,6 +1003,57 @@ static int ll_options(char *options, struct ll_sb_info *sbi)
#endif
goto next;
}
+ tmp = ll_set_opt("foreign_symlink", s1, LL_SBI_FOREIGN_SYMLINK);
+ if (tmp) {
+ int prefix_pos = sizeof("foreign_symlink=") - 1;
+ int equal_pos = sizeof("foreign_symlink=") - 2;
+
+ /* non-default prefix provided ? */
+ if (strlen(s1) >= sizeof("foreign_symlink=") &&
+ *(s1 + equal_pos) == '=') {
+ char *old = sbi->ll_foreign_symlink_prefix;
+ size_t old_len =
+ sbi->ll_foreign_symlink_prefix_size;
+
+ /* path must be absolute */
+ if (*(s1 + sizeof("foreign_symlink=") -
+ 1) != '/') {
+ LCONSOLE_ERROR_MSG(0x152,
+ "foreign prefix '%s' must be an absolute path\n",
+ s1 + prefix_pos);
+ return -EINVAL;
+ }
+ /* last option ? */
+ s2 = strchrnul(s1 + prefix_pos, ',');
+
+ if (sbi->ll_foreign_symlink_prefix) {
+ sbi->ll_foreign_symlink_prefix = NULL;
+ sbi->ll_foreign_symlink_prefix_size = 0;
+ }
+ /* alloc for path length and '\0' */
+ sbi->ll_foreign_symlink_prefix = kmalloc(s2 - (s1 + prefix_pos) + 1,
+ GFP_KERNEL);
+ if (!sbi->ll_foreign_symlink_prefix) {
+ /* restore previous */
+ sbi->ll_foreign_symlink_prefix = old;
+ sbi->ll_foreign_symlink_prefix_size =
+ old_len;
+ return -ENOMEM;
+ }
+ kfree(old);
+ strncpy(sbi->ll_foreign_symlink_prefix,
+ s1 + prefix_pos,
+ s2 - (s1 + prefix_pos));
+ sbi->ll_foreign_symlink_prefix_size =
+ s2 - (s1 + prefix_pos) + 1;
+ } else {
+ LCONSOLE_ERROR_MSG(0x152,
+ "invalid %s option\n", s1);
+ }
+ /* enable foreign symlink support */
+ *flags |= tmp;
+ goto next;
+ }
LCONSOLE_ERROR_MSG(0x152, "Unknown option '%s', won't mount.\n",
s1);
return -EINVAL;
@@ -2763,6 +2859,10 @@ int ll_prep_inode(struct inode **inode, struct ptlrpc_request *req,
if (default_lmv_deleted)
ll_update_default_lsm_md(*inode, &md);
+
+ /* we may want to apply some policy for foreign file/dir */
+ if (ll_sbi_has_foreign_symlink(sbi))
+ rc = ll_manage_foreign(*inode, &md);
out:
/* cleanup will be done if necessary */
md_free_lustre_md(sbi->ll_md_exp, &md);
@@ -2974,6 +3074,11 @@ int ll_show_options(struct seq_file *seq, struct dentry *dentry)
else
seq_puts(seq, ",noencrypt");
+ if (sbi->ll_flags & LL_SBI_FOREIGN_SYMLINK) {
+ seq_puts(seq, ",foreign_symlink=");
+ seq_puts(seq, sbi->ll_foreign_symlink_prefix);
+ }
+
return 0;
}
@@ -290,6 +290,14 @@ static ssize_t client_type_show(struct kobject *kobj, struct attribute *attr,
}
LUSTRE_RO_ATTR(client_type);
+LUSTRE_RW_ATTR(foreign_symlink_enable);
+
+LUSTRE_RW_ATTR(foreign_symlink_prefix);
+
+LUSTRE_RW_ATTR(foreign_symlink_upcall);
+
+LUSTRE_WO_ATTR(foreign_symlink_upcall_info);
+
static ssize_t fstype_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
@@ -1551,6 +1559,10 @@ struct ldebugfs_vars lprocfs_llite_obd_vars[] = {
&lustre_attr_filestotal.attr,
&lustre_attr_filesfree.attr,
&lustre_attr_client_type.attr,
+ &lustre_attr_foreign_symlink_enable.attr,
+ &lustre_attr_foreign_symlink_prefix.attr,
+ &lustre_attr_foreign_symlink_upcall.attr,
+ &lustre_attr_foreign_symlink_upcall_info.attr,
&lustre_attr_fstype.attr,
&lustre_attr_uuid.attr,
&lustre_attr_max_read_ahead_mb.attr,
@@ -48,7 +48,7 @@
static int ll_create_it(struct inode *dir, struct dentry *dentry,
struct lookup_intent *it,
void *secctx, u32 secctxlen, bool encrypt,
- void *encctx, __u32 encctxlen);
+ void *encctx, u32 encctxlen);
/* called from iget5_locked->find_inode() under inode_hash_lock spinlock */
static int ll_test_inode(struct inode *inode, void *opaque)
@@ -597,6 +597,23 @@ struct dentry *ll_splice_alias(struct inode *inode, struct dentry *de)
} else {
struct dentry *new = d_splice_alias(inode, de);
+ /* this needs only to be done for foreign symlink dirs as
+ * DCACHE_SYMLINK_TYPE is already set by d_flags_for_inode()
+ * kernel routine for files with symlink ops (ie, real symlink)
+ */
+ if (inode && ll_sbi_has_foreign_symlink(ll_i2sbi(inode)) &&
+ inode->i_op->get_link) {
+ CDEBUG(D_INFO,
+ "%s: inode "DFID": faking foreign dir as a symlink\n",
+ ll_i2sbi(inode)->ll_fsname,
+ PFID(ll_inode2fid(inode)));
+ spin_lock(&de->d_lock);
+ /* like d_flags_for_inode() already does for files */
+ de->d_flags = (de->d_flags & ~DCACHE_ENTRY_TYPE) |
+ DCACHE_SYMLINK_TYPE;
+ spin_unlock(&de->d_lock);
+ }
+
if (IS_ERR(new))
CDEBUG(D_DENTRY,
"splice inode %p as %pd gives error %lu\n",
@@ -1187,8 +1204,8 @@ static int ll_atomic_open(struct inode *dir, struct dentry *dentry,
}
}
- if (d_really_is_positive(dentry) &&
- it_disposition(it, DISP_OPEN_OPEN)) {
+ if (dentry->d_inode && it_disposition(it, DISP_OPEN_OPEN) &&
+ ll_foreign_is_openable(dentry, open_flags)) {
/* Open dentry. */
if (S_ISFIFO(d_inode(dentry)->i_mode)) {
/* We cannot call open here as it might
@@ -1270,7 +1287,7 @@ static struct inode *ll_create_node(struct inode *dir, struct lookup_intent *it)
static int ll_create_it(struct inode *dir, struct dentry *dentry,
struct lookup_intent *it,
void *secctx, u32 secctxlen, bool encrypt,
- void *encctx, __u32 encctxlen)
+ void *encctx, u32 encctxlen)
{
struct inode *inode;
u64 bits = 0;
@@ -1581,6 +1598,10 @@ static int ll_unlink(struct inode *dir, struct dentry *dchild)
CDEBUG(D_VFSTRACE, "VFS Op:name=%pd,dir=%lu/%u(%p)\n",
dchild, dir->i_ino, dir->i_generation, dir);
+ /* some foreign file/dir may not be allowed to be unlinked */
+ if (!ll_foreign_is_removable(dchild, false))
+ return -EPERM;
+
op_data = ll_prep_md_op_data(NULL, dir, NULL,
dchild->d_name.name,
dchild->d_name.len,
@@ -1647,6 +1668,10 @@ static int ll_rmdir(struct inode *dir, struct dentry *dchild)
CDEBUG(D_VFSTRACE, "VFS Op:name=%pd, dir=" DFID "(%p)\n",
dchild, PFID(ll_inode2fid(dir)), dir);
+ /* some foreign dir may not be allowed to be removed */
+ if (!ll_foreign_is_removable(dchild, false))
+ return -EPERM;
+
op_data = ll_prep_md_op_data(NULL, dir, NULL,
dchild->d_name.name,
dchild->d_name.len,
@@ -1152,12 +1152,12 @@ static int pcc_get_layout_info(struct inode *inode, struct cl_layout *clt)
return PTR_ERR(env);
rc = cl_object_layout_get(env, lli->lli_clob, clt);
- if (rc)
+ if (rc < 0)
CDEBUG(D_INODE, "Cannot get layout for "DFID"\n",
PFID(ll_inode2fid(inode)));
cl_env_put(env, &refcheck);
- return rc;
+ return rc < 0 ? rc : 0;
}
static int pcc_fid2dataset_fullpath(char *buf, int sz, struct lu_fid *fid,
@@ -37,6 +37,7 @@
#include "llite_internal.h"
+/* Must be called with lli_size_mutex locked */
static int ll_readlink_internal(struct inode *inode,
struct ptlrpc_request **request, char **symname)
{
@@ -2177,7 +2177,8 @@ static int lov_object_layout_get(const struct lu_env *env,
rc = lov_lsm_pack(lsm, buf->lb_buf, buf->lb_len);
lov_lsm_put(lsm);
- return rc < 0 ? rc : 0;
+ /* return error or number of bytes */
+ return rc;
}
static loff_t lov_object_maxbytes(struct cl_object *obj)
@@ -163,8 +163,14 @@ static ssize_t lov_lsm_pack_foreign(const struct lov_stripe_md *lsm, void *buf,
if (buf_size == 0)
return lfm_size;
- if (buf_size < lfm_size)
+ /* if buffer too small return ERANGE but copy the size the
+ * caller has requested anyway. This may be useful to get
+ * only the header without the need to alloc the full size
+ */
+ if (buf_size < lfm_size) {
+ memcpy(lfm, lsm_foreign(lsm), buf_size);
return -ERANGE;
+ }
/* full foreign LOV is already avail in its cache
* no need to translate format fields to little-endian
@@ -189,8 +195,10 @@ ssize_t lov_lsm_pack(const struct lov_stripe_md *lsm, void *buf,
if (lsm->lsm_magic == LOV_MAGIC_V1 || lsm->lsm_magic == LOV_MAGIC_V3)
return lov_lsm_pack_v1v3(lsm, buf, buf_size);
- if (lsm->lsm_magic == LOV_MAGIC_FOREIGN)
+ if (lsm->lsm_magic == LOV_MAGIC_FOREIGN) {
+ pr_info("calling lov_lsm_pack_foreign\n");
return lov_lsm_pack_foreign(lsm, buf, buf_size);
+ }
lmm_size = lov_comp_md_size(lsm);
if (buf_size == 0)
@@ -370,6 +378,7 @@ int lov_getstripe(const struct lu_env *env, struct lov_object *obj,
lmm_size = lov_lsm_pack(lsm, lmmk, lmmk_size);
if (lmm_size < 0) {
+ pr_info("lov_lsm_pack return rc = %zd\n", lmm_size);
rc = lmm_size;
goto out_free;
}
@@ -360,6 +360,7 @@ struct ll_ioc_lease_id {
#define LL_IOC_LMV_SETSTRIPE _IOWR('f', 240, struct lmv_user_md)
#define LL_IOC_LMV_GETSTRIPE _IOWR('f', 241, struct lmv_user_md)
#define LL_IOC_RMFID _IOR('f', 242, struct fid_array)
+#define LL_IOC_UNLOCK_FOREIGN _IO('f', 242)
#define LL_IOC_SET_LEASE _IOWR('f', 243, struct ll_ioc_lease)
#define LL_IOC_SET_LEASE_OLD _IOWR('f', 243, long)
#define LL_IOC_GET_LEASE _IO('f', 244)
@@ -769,7 +770,7 @@ struct lustre_foreign_type {
**/
enum lustre_foreign_types {
LU_FOREIGN_TYPE_NONE = 0,
- LU_FOREIGN_TYPE_DAOS = 0xda05,
+ LU_FOREIGN_TYPE_SYMLINK = 0xda05,
/* must be the max/last one */
LU_FOREIGN_TYPE_UNKNOWN = 0xffffffff,
};
@@ -2280,6 +2281,52 @@ struct fid_array {
};
#define OBD_MAX_FIDS_IN_ARRAY 4096
+/* more types could be defined upon need for more complex
+ * format to be used in foreign symlink LOV/LMV EAs, like
+ * one to describe a delimiter string and occurence number
+ * of delimited sub-string, ...
+ */
+enum ll_foreign_symlink_upcall_item_type {
+ EOB_TYPE = 1,
+ STRING_TYPE = 2,
+ POSLEN_TYPE = 3,
+};
+
+/* may need to be modified to allow for more format items to be defined, and
+ * like for ll_foreign_symlink_upcall_item_type enum
+ */
+struct ll_foreign_symlink_upcall_item {
+ __u32 type;
+ union {
+ struct {
+ __u32 pos;
+ __u32 len;
+ };
+ struct {
+ size_t size;
+ union {
+ /* internal storage of constant string */
+ char *string;
+ /* upcall stores constant string in a raw */
+ char bytestring[0];
+ };
+ };
+ };
+};
+
+#define POSLEN_ITEM_SZ (offsetof(struct ll_foreign_symlink_upcall_item, len) + \
+ sizeof(((struct ll_foreign_symlink_upcall_item *)0)->len))
+#define STRING_ITEM_SZ(sz) ( \
+ offsetof(struct ll_foreign_symlink_upcall_item, bytestring) + \
+ (sz + sizeof(__u32) - 1) / sizeof(__u32) * sizeof(__u32))
+
+/* presently limited to not cause max stack frame size to be reached
+ * because of temporary automatic array of
+ * "struct ll_foreign_symlink_upcall_item" presently used in
+ * foreign_symlink_upcall_info_store()
+ */
+#define MAX_NB_UPCALL_ITEMS 32
+
#if defined(__cplusplus)
}
#endif