From b3c9e2caf570d0c7093de33fb5280541865b32d9 Mon Sep 17 00:00:00 2001 From: Brian Behlendorf Date: Thu, 14 Jan 2016 13:25:10 -0500 Subject: [PATCH] Linux 4.5 compat: get_link() / put_link() The follow_link() interface was retired in favor of get_link(). In the process of phasing in get_link() the Linux kernel went through two different versions. The first of which depended on put_link() and the final version on a delayed done function. - Improved configure checks for .follow_link, .get_link, .put_link. - Interfaces checked from newest to oldest. - Strict checking for each possible known interface. - Configure fails when no known interface is available. - Both versions .get_link are detected and supported as well two previous versions of .follow_link. Signed-off-by: Brian Behlendorf Signed-off-by: Tim Chase Signed-off-by: Chunwei Chen Issue #4228 --- config/kernel-follow-link-nameidata.m4 | 24 ----- config/kernel-get-link.m4 | 100 ++++++++++++++++++ config/kernel-put-link-nameidata.m4 | 23 ----- config/kernel-put-link.m4 | 60 +++++++++++ config/kernel.m4 | 2 +- module/zfs/zpl_inode.c | 137 ++++++++++++++++++------- 6 files changed, 259 insertions(+), 87 deletions(-) delete mode 100644 config/kernel-follow-link-nameidata.m4 create mode 100644 config/kernel-get-link.m4 delete mode 100644 config/kernel-put-link-nameidata.m4 create mode 100644 config/kernel-put-link.m4 diff --git a/config/kernel-follow-link-nameidata.m4 b/config/kernel-follow-link-nameidata.m4 deleted file mode 100644 index 88c85accbe..0000000000 --- a/config/kernel-follow-link-nameidata.m4 +++ /dev/null @@ -1,24 +0,0 @@ -dnl # -dnl # 4.2 API change -dnl # This kernel retired the nameidata structure which forced the -dnl # restructuring of the follow_link() prototype and how it is called. -dnl # We check for the new interface rather than detecting the old one. -dnl # -AC_DEFUN([ZFS_AC_KERNEL_FOLLOW_LINK], [ - AC_MSG_CHECKING([whether iops->follow_link() passes nameidata]) - ZFS_LINUX_TRY_COMPILE([ - #include - const char *follow_link(struct dentry *de, void **cookie) - { return "symlink"; } - static struct inode_operations iops __attribute__ ((unused)) = { - .follow_link = follow_link, - }; - ],[ - ],[ - AC_MSG_RESULT(no) - ],[ - AC_MSG_RESULT(yes) - AC_DEFINE(HAVE_FOLLOW_LINK_NAMEIDATA, 1, - [iops->follow_link() nameidata]) - ]) -]) diff --git a/config/kernel-get-link.m4 b/config/kernel-get-link.m4 new file mode 100644 index 0000000000..022c49c54d --- /dev/null +++ b/config/kernel-get-link.m4 @@ -0,0 +1,100 @@ +dnl # +dnl # Supported get_link() interfaces checked newest to oldest. +dnl # +AC_DEFUN([ZFS_AC_KERNEL_FOLLOW_LINK], [ + dnl # + dnl # 4.2 API change + dnl # - This kernel retired the nameidata structure. + dnl # + AC_MSG_CHECKING([whether iops->follow_link() passes cookie]) + ZFS_LINUX_TRY_COMPILE([ + #include + const char *follow_link(struct dentry *de, + void **cookie) { return "symlink"; } + static struct inode_operations + iops __attribute__ ((unused)) = { + .follow_link = follow_link, + }; + ],[ + ],[ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_FOLLOW_LINK_COOKIE, 1, + [iops->follow_link() cookie]) + ],[ + dnl # + dnl # 2.6.32 API + dnl # + AC_MSG_RESULT(no) + AC_MSG_CHECKING( + [whether iops->follow_link() passes nameidata]) + ZFS_LINUX_TRY_COMPILE([ + #include + void *follow_link(struct dentry *de, struct + nameidata *nd) { return (void *)NULL; } + static struct inode_operations + iops __attribute__ ((unused)) = { + .follow_link = follow_link, + }; + ],[ + ],[ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_FOLLOW_LINK_NAMEIDATA, 1, + [iops->follow_link() nameidata]) + ],[ + AC_MSG_ERROR(no; please file a bug report) + ]) + ]) +]) + +AC_DEFUN([ZFS_AC_KERNEL_GET_LINK], [ + dnl # + dnl # 4.5 API change + dnl # The get_link interface has added a delayed done call and + dnl # used it to retire the put_link() interface. + dnl # + AC_MSG_CHECKING([whether iops->get_link() passes delayed]) + ZFS_LINUX_TRY_COMPILE([ + #include + const char *get_link(struct dentry *de, struct inode *ip, + struct delayed_call *done) { return "symlink"; } + static struct inode_operations + iops __attribute__ ((unused)) = { + .get_link = get_link, + }; + ],[ + ],[ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_GET_LINK_DELAYED, 1, + [iops->get_link() delayed]) + ],[ + dnl # + dnl # 4.5 API change + dnl # The follow_link() interface has been replaced by + dnl # get_link() which behaves the same as before except: + dnl # - An inode is passed as a separate argument + dnl # - When called in RCU mode a NULL dentry is passed. + dnl # + AC_MSG_RESULT(no) + AC_MSG_CHECKING([whether iops->get_link() passes cookie]) + ZFS_LINUX_TRY_COMPILE([ + #include + const char *get_link(struct dentry *de, struct + inode *ip, void **cookie) { return "symlink"; } + static struct inode_operations + iops __attribute__ ((unused)) = { + .get_link = get_link, + }; + ],[ + ],[ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_GET_LINK_COOKIE, 1, + [iops->get_link() cookie]) + ],[ + dnl # + dnl # Check for the follow_link APIs. + dnl # + AC_MSG_RESULT(no) + ZFS_AC_KERNEL_FOLLOW_LINK + ]) + ]) +]) diff --git a/config/kernel-put-link-nameidata.m4 b/config/kernel-put-link-nameidata.m4 deleted file mode 100644 index 0181ae515a..0000000000 --- a/config/kernel-put-link-nameidata.m4 +++ /dev/null @@ -1,23 +0,0 @@ -dnl # -dnl # 4.2 API change -dnl # This kernel retired the nameidata structure which forced the -dnl # restructuring of the put_link() prototype and how it is called. -dnl # We check for the new interface rather than detecting the old one. -dnl # -AC_DEFUN([ZFS_AC_KERNEL_PUT_LINK], [ - AC_MSG_CHECKING([whether iops->put_link() passes nameidata]) - ZFS_LINUX_TRY_COMPILE([ - #include - void put_link(struct inode *ip, void *cookie) { return; } - static struct inode_operations iops __attribute__ ((unused)) = { - .put_link = put_link, - }; - ],[ - ],[ - AC_MSG_RESULT(no) - ],[ - AC_MSG_RESULT(yes) - AC_DEFINE(HAVE_PUT_LINK_NAMEIDATA, 1, - [iops->put_link() nameidata]) - ]) -]) diff --git a/config/kernel-put-link.m4 b/config/kernel-put-link.m4 new file mode 100644 index 0000000000..a0bb36ef27 --- /dev/null +++ b/config/kernel-put-link.m4 @@ -0,0 +1,60 @@ +dnl # +dnl # Supported symlink APIs +dnl # +AC_DEFUN([ZFS_AC_KERNEL_PUT_LINK], [ + dnl # + dnl # 4.5 API change + dnl # get_link() uses delayed done, there is no put_link() interface. + dnl # + ZFS_LINUX_TRY_COMPILE([ + #if !defined(HAVE_GET_LINK_DELAYED) + #error "Expecting get_link() delayed done" + #endif + ],[ + ],[ + AC_DEFINE(HAVE_PUT_LINK_DELAYED, 1, [iops->put_link() delayed]) + ],[ + dnl # + dnl # 4.2 API change + dnl # This kernel retired the nameidata structure. + dnl # + AC_MSG_CHECKING([whether iops->put_link() passes cookie]) + ZFS_LINUX_TRY_COMPILE([ + #include + void put_link(struct inode *ip, void *cookie) + { return; } + static struct inode_operations + iops __attribute__ ((unused)) = { + .put_link = put_link, + }; + ],[ + ],[ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_PUT_LINK_COOKIE, 1, + [iops->put_link() cookie]) + ],[ + dnl # + dnl # 2.6.32 API + dnl # + AC_MSG_RESULT(no) + AC_MSG_CHECKING( + [whether iops->put_link() passes nameidata]) + ZFS_LINUX_TRY_COMPILE([ + #include + void put_link(struct dentry *de, struct + nameidata *nd, void *ptr) { return; } + static struct inode_operations + iops __attribute__ ((unused)) = { + .put_link = put_link, + }; + ],[ + ],[ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_PUT_LINK_NAMEIDATA, 1, + [iops->put_link() nameidata]) + ],[ + AC_MSG_ERROR(no; please file a bug report) + ]) + ]) + ]) +]) diff --git a/config/kernel.m4 b/config/kernel.m4 index c655a9a1dd..bac7d4d43a 100644 --- a/config/kernel.m4 +++ b/config/kernel.m4 @@ -58,7 +58,7 @@ AC_DEFUN([ZFS_AC_CONFIG_KERNEL], [ ZFS_AC_KERNEL_MKDIR_UMODE_T ZFS_AC_KERNEL_LOOKUP_NAMEIDATA ZFS_AC_KERNEL_CREATE_NAMEIDATA - ZFS_AC_KERNEL_FOLLOW_LINK + ZFS_AC_KERNEL_GET_LINK ZFS_AC_KERNEL_PUT_LINK ZFS_AC_KERNEL_TRUNCATE_RANGE ZFS_AC_KERNEL_AUTOMOUNT diff --git a/module/zfs/zpl_inode.c b/module/zfs/zpl_inode.c index e0e8ee3ac5..bd0d5129c1 100644 --- a/module/zfs/zpl_inode.c +++ b/module/zfs/zpl_inode.c @@ -339,26 +339,42 @@ zpl_symlink(struct inode *dir, struct dentry *dentry, const char *name) return (error); } -#ifdef HAVE_FOLLOW_LINK_NAMEIDATA -static void * -zpl_follow_link(struct dentry *dentry, struct nameidata *nd) -#else -const char * -zpl_follow_link(struct dentry *dentry, void **symlink_cookie) -#endif +#if defined(HAVE_PUT_LINK_COOKIE) +static void +zpl_put_link(struct inode *unused, void *cookie) { + kmem_free(cookie, MAXPATHLEN); +} +#elif defined(HAVE_PUT_LINK_NAMEIDATA) +static void +zpl_put_link(struct dentry *dentry, struct nameidata *nd, void *ptr) +{ + const char *link = nd_get_link(nd); + + if (!IS_ERR(link)) + kmem_free(link, MAXPATHLEN); +} +#elif defined(HAVE_PUT_LINK_DELAYED) +static void +zpl_put_link(void *ptr) +{ + kmem_free(ptr, MAXPATHLEN); +} +#endif + +static int +zpl_get_link_common(struct dentry *dentry, struct inode *ip, char **link) +{ + fstrans_cookie_t cookie; cred_t *cr = CRED(); - struct inode *ip = dentry->d_inode; struct iovec iov; uio_t uio; - char *link; int error; - fstrans_cookie_t cookie; crhold(cr); - + *link = NULL; iov.iov_len = MAXPATHLEN; - iov.iov_base = link = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + iov.iov_base = kmem_zalloc(MAXPATHLEN, KM_SLEEP); uio.uio_iov = &iov; uio.uio_iovcnt = 1; @@ -369,41 +385,78 @@ zpl_follow_link(struct dentry *dentry, void **symlink_cookie) cookie = spl_fstrans_mark(); error = -zfs_readlink(ip, &uio, cr); spl_fstrans_unmark(cookie); - - if (error) - kmem_free(link, MAXPATHLEN); - crfree(cr); -#ifdef HAVE_FOLLOW_LINK_NAMEIDATA + if (error) + kmem_free(iov.iov_base, MAXPATHLEN); + else + *link = iov.iov_base; + + return (error); +} + +#if defined(HAVE_GET_LINK_DELAYED) +const char * +zpl_get_link(struct dentry *dentry, struct inode *inode, + struct delayed_call *done) +{ + char *link = NULL; + int error; + + if (!dentry) + return (ERR_PTR(-ECHILD)); + + error = zpl_get_link_common(dentry, inode, &link); + if (error) + return (ERR_PTR(error)); + + set_delayed_call(done, zpl_put_link, link); + + return (link); +} +#elif defined(HAVE_GET_LINK_COOKIE) +const char * +zpl_get_link(struct dentry *dentry, struct inode *inode, void **cookie) +{ + char *link = NULL; + int error; + + if (!dentry) + return (ERR_PTR(-ECHILD)); + + error = zpl_get_link_common(dentry, inode, &link); + if (error) + return (ERR_PTR(error)); + + return (*cookie = link); +} +#elif defined(HAVE_FOLLOW_LINK_COOKIE) +const char * +zpl_follow_link(struct dentry *dentry, void **cookie) +{ + char *link = NULL; + int error; + + error = zpl_get_link_common(dentry, dentry->d_inode, &link); + if (error) + return (ERR_PTR(error)); + + return (*cookie = link); +} +#elif defined(HAVE_FOLLOW_LINK_NAMEIDATA) +static void * +zpl_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + char *link = NULL; + int error; + + error = zpl_get_link_common(dentry, dentry->d_inode, &link); if (error) nd_set_link(nd, ERR_PTR(error)); else nd_set_link(nd, link); return (NULL); -#else - if (error) - return (ERR_PTR(error)); - else - return (*symlink_cookie = link); -#endif -} - -#ifdef HAVE_PUT_LINK_NAMEIDATA -static void -zpl_put_link(struct dentry *dentry, struct nameidata *nd, void *ptr) -{ - const char *link = nd_get_link(nd); - - if (!IS_ERR(link)) - kmem_free(link, MAXPATHLEN); -} -#else -static void -zpl_put_link(struct inode *unused, void *symlink_cookie) -{ - kmem_free(symlink_cookie, MAXPATHLEN); } #endif @@ -591,8 +644,14 @@ const struct inode_operations zpl_dir_inode_operations = { const struct inode_operations zpl_symlink_inode_operations = { .readlink = generic_readlink, +#if defined(HAVE_GET_LINK_DELAYED) || defined(HAVE_GET_LINK_COOKIE) + .get_link = zpl_get_link, +#elif defined(HAVE_FOLLOW_LINK_COOKIE) || defined(HAVE_FOLLOW_LINK_NAMEIDATA) .follow_link = zpl_follow_link, +#endif +#if defined(HAVE_PUT_LINK_COOKIE) || defined(HAVE_PUT_LINK_NAMEIDATA) .put_link = zpl_put_link, +#endif .setattr = zpl_setattr, .getattr = zpl_getattr, .setxattr = generic_setxattr,