Skip to content

Commit

Permalink
Linux 4.5 compat: get_link() / put_link()
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Tim Chase <[email protected]>
Signed-off-by: Chunwei Chen <[email protected]>
Issue openzfs#4228
  • Loading branch information
behlendorf authored and Chunwei Chen committed Feb 6, 2016
1 parent b988c90 commit 3197205
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 81 deletions.
24 changes: 0 additions & 24 deletions config/kernel-follow-link-nameidata.m4

This file was deleted.

100 changes: 100 additions & 0 deletions config/kernel-get-link.m4
Original file line number Diff line number Diff line change
@@ -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 <linux/fs.h>
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 <linux/fs.h>
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 <linux/fs.h>
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 <linux/fs.h>
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
])
])
])
23 changes: 0 additions & 23 deletions config/kernel-put-link-nameidata.m4

This file was deleted.

60 changes: 60 additions & 0 deletions config/kernel-put-link.m4
Original file line number Diff line number Diff line change
@@ -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 <linux/fs.h>
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 <linux/fs.h>
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)
])
])
])
])
2 changes: 1 addition & 1 deletion config/kernel.m4
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
125 changes: 92 additions & 33 deletions module/zfs/zpl_inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
#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;
Expand All @@ -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)
nd_set_link(nd, ERR_PTR(error));
kmem_free(iov.iov_base, MAXPATHLEN);
else
nd_set_link(nd, link);
*link = iov.iov_base;

return (NULL);
#else
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));
else
return (*symlink_cookie = link);
#endif

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;

#ifdef HAVE_PUT_LINK_NAMEIDATA
static void
zpl_put_link(struct dentry *dentry, struct nameidata *nd, void *ptr)
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)
{
const char *link = nd_get_link(nd);
char *link = NULL;
int error;

if (!IS_ERR(link))
kmem_free(link, MAXPATHLEN);
error = zpl_get_link_common(dentry, dentry->d_inode, &link);
if (error)
return (ERR_PTR(error));

return (*cookie = link);
}
#else
static void
zpl_put_link(struct inode *unused, void *symlink_cookie)
#elif defined(HAVE_FOLLOW_LINK_NAMEIDATA)
static void *
zpl_follow_link(struct dentry *dentry, struct nameidata *nd)
{
kmem_free(symlink_cookie, MAXPATHLEN);
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);
}
#endif

Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 3197205

Please sign in to comment.