]> granicus.if.org Git - psmisc/commitdiff
Use new statx(2) system call to avoid hangs on NFS
authorWerner Fink <werner@suse.de>
Mon, 14 Nov 2022 09:18:13 +0000 (20:18 +1100)
committerCraig Small <csmall@dropbear.xyz>
Mon, 14 Nov 2022 09:18:13 +0000 (20:18 +1100)
Instead of using a timeout hack to guard against NFS hanging
fuser, use the statx system call when available.

Signed-off-by: Craig Small <csmall@dropbear.xyz>
ChangeLog
Makefile.am
configure.ac
src/fuser.c
src/statx.c [new file with mode: 0644]
src/statx.h [new file with mode: 0644]

index ff6121dfbccbbdf4196d3c8eaf0759dbc811969d..ea1eb1899bdc4665eba10855d8a422414557ffd0 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,7 @@ Changes in NEXT
         * buildsys: Fix DEJAGNU work-around Debian #1015089
        * killall: Use kill if pidfd_send_signal fails Debian #1015228
        * fuser: Do not mention nonexistent - reset option #42
+       * fuser: Use modern statn where possible
        * pstree: Better AppArmor support !30
 
 Changes in 23.5
index e116166502280fdfb20ffc4dd969bb1011c0a127..70e4c9a3d9358f40db8eb8cd323c55f38d6a290b 100644 (file)
@@ -81,8 +81,8 @@ src_fuser_SOURCES = \
                    src/fuser.h \
                    src/lists.h
 
-if WANT_TIMEOUT_STAT
-src_fuser_SOURCES += src/timeout.c src/timeout.h
+if HAVE_SYSCALL_STATX
+src_fuser_SOURCES += src/statx.c src/statx.h
 endif
 src_fuser_LDADD = @LIBINTL@
 src_killall_SOURCES = src/killall.c src/comm.h src/signals.c src/signals.h src/i18n.h
index 487071a71ec4960c80285d2378051f896aa96b27..d37dacabdeda71551104a22ffe7d986e6080afae 100644 (file)
@@ -76,17 +76,21 @@ if test "$enable_timeout_stat" = "static"; then
 fi
 AM_CONDITIONAL([WANT_TIMEOUT_STAT], [test "$enable_timeout_stat" = "static"])
 
-# Use string search for network based file systems but only if the system
-# has /proc/self/mountinfo
-AC_SUBST([WITH_MOUNTINFO_LIST])
-AC_ARG_ENABLE([mountinfo_list],
-  [AS_HELP_STRING([--enable-mountinfo-list], [Use the list in /proc/self/mountinfo to replace stat(2) syscall on network file systems shares])],
-  [enable_mountinfo_list="yes"],
-  [enable_mountinfo_list="no"])
-if test "$enable_mountinfo_list" = "yes" -a -e /proc/self/mountinfo ; then
-  AC_DEFINE([WITH_MOUNTINFO_LIST], [1], [Use list in /proc/self/mountinfo to replace stat calls])
-fi
-
+AC_CHECK_HEADERS([sys/syscall.h])
+AC_CHECK_DECLS([SYS_statx],
+  [has_syscall_statx="yes"],
+  [has_syscall_statx="no"],
+  [[#include <sys/syscall.h>]]
+)
+AC_CHECK_FUNCS([statx])
+# Check for linux specific statx(2) system call
+AC_SUBST([HAS_SYSCALL_STATX])
+AC_ARG_ENABLE([disable_statx],
+  [AS_HELP_STRING([--disable-statx], [Do not use linux specific statx(2) system call as replacement for stat(2), lstat(2), and fstat(2)])],
+  [enable_syscall_statx="no"],
+  [enable_syscall_statx=$has_syscall_statx])
+AM_CONDITIONAL([HAVE_SYSCALL_STATX], [test "$enable_syscall_statx" = "yes"])
 # Enable hardened compile and link flags
 AC_ARG_ENABLE([harden_flags],
   [AS_HELP_STRING([--disable-harden-flags], [disable hardened compilier and linker flags])],
index 22bd4dcab063351065fa26e2508231daab6d1106..f2bd3e90b64b1b883dcd9b630b6f59ea0c8a2711 100644 (file)
@@ -63,7 +63,7 @@
 #include "fuser.h"
 #include "signals.h"
 #include "i18n.h"
-#include "timeout.h"
+#include "statx.h"
 #include "comm.h"
 
 //#define DEBUG 1
@@ -123,12 +123,6 @@ static void debug_match_lists(
     struct device_list *dev_head);
 #endif
 
-#if defined(WITH_MOUNTINFO_LIST)
-static void clear_mntinfo(void) __attribute__ ((__destructor__));
-static void init_mntinfo(void) __attribute__ ((__constructor__));
-static int mntstat(const char *path, struct stat *buf);
-#endif
-static stat_t thestat = stat;
 static char *expandpath(const char *path);
 static struct unixsocket_list *unixsockets = NULL;
 static struct names *names_head = NULL, *names_tail = NULL;
@@ -493,7 +487,7 @@ int parse_file(
             free(this_name->filename);
         this_name->filename = strdup(new);
     }
-    if (timeout(thestat, this_name->filename, &(this_name->st), 5) != 0)
+    if (statn(this_name->filename, STATX_INO|STATX_TYPE, &(this_name->st)) != 0 )
     {
         if (errno == ENOENT)
             fprintf(stderr,
@@ -1214,9 +1208,7 @@ int main(int argc, char *argv[])
                     opts |= OPT_INTERACTIVE;
                     break;
                 case 'I':
-#if defined(WITH_MOUNTINFO_LIST)
                     opts |= OPT_ALWAYSSTAT;
-#endif
                     break;
                 case 'k':
                     opts |= OPT_KILL;
@@ -1280,9 +1272,9 @@ int main(int argc, char *argv[])
             continue;
         }
 
-#if defined(WITH_MOUNTINFO_LIST)
-        if ((opts & OPT_ALWAYSSTAT) == 0)
-            thestat = mntstat;
+#if defined(HAVE_DECL_SYS_STATX) && HAVE_DECL_SYS_STATX == 1
+               if ((opts & OPT_ALWAYSSTAT))
+                       stat_flags = 0;         /* Triggers sync with e.g. remote NFS server even on autofs */
 #endif
         /* an option */
         /* Not an option, must be a file specification */
@@ -1605,7 +1597,7 @@ static struct stat *get_pidstat(
     if ((st = (struct stat *)malloc(sizeof(struct stat))) == NULL)
         return NULL;
     snprintf(pathname, PATH_MAX-1, "/proc/%d/%s", pid, filename);
-    if (timeout(thestat, pathname, st, 5) != 0)
+    if (statn(pathname, STATX_UID|STATX_INO|STATX_TYPE, st) != 0)
     {
         free(st);
         return NULL;
@@ -1651,7 +1643,7 @@ static void check_dir(
         snprintf(filepath, sizeof filepath - 1, "/proc/%d/%s/%s",
              pid, dirname, direntry->d_name);
 
-        if (timeout(thestat, filepath, &st, 5) != 0)
+       if (statn(filepath, STATX_INO, &st) != 0)
         {
             if (errno != ENOENT && errno != ENOTDIR)
             {
@@ -1709,7 +1701,7 @@ static void check_dir(
             {
                 if (thedev != ino_tmp->device)
                     continue;
-                if (!st.st_ino && timeout(thestat, filepath, &st, 5) != 0)
+                if (!st.st_ino && statn(filepath, STATX_INO, &st) != 0)
                 {
                     fprintf(stderr, _("Cannot stat file %s: %s\n"),
                             filepath, strerror(errno));
@@ -1784,7 +1776,7 @@ static uid_t getpiduid(
 
     if (asprintf(&pathname, "/proc/%d", pid) < 0)
         return 0;
-    if (timeout(thestat, pathname, &st, 5) != 0)
+    if (statn(pathname, STATX_UID, &st) != 0)
     {
         free(pathname);
         return 0;
@@ -1828,7 +1820,7 @@ void fill_unix_cache(
         path = scanned_path;
         if (*scanned_path == '@')
             scanned_path++;
-        if (timeout(thestat, scanned_path, &st, 5) < 0)
+       if (statn(scanned_path, STATX_INO, &st) < 0)
         {
             free(path);
             continue;
@@ -1974,7 +1966,7 @@ static dev_t find_net_dev(void)
         fprintf(stderr, _("Cannot open a network socket.\n"));
         return -1;
     }
-    if (fstat(skt, &st) != 0)
+    if (fstatn(skt, STATX_INO, &st) != 0)
     {
         fprintf(stderr, _("Cannot find socket's device number.\n"));
         close(skt);
@@ -2016,7 +2008,7 @@ static void scan_knfsd(
             continue;
 
         *find_space = '\0';
-        if (timeout(thestat, line, &st, 5) != 0)
+       if (statn(line, STATX_INO, &st) != 0)
             continue;
 
         /* Scan the devices */
@@ -2068,8 +2060,7 @@ static void scan_mounts(
         if ((find_space = strchr(find_mountp, ' ')) == NULL)
             continue;
         *find_space = '\0';
-
-        if (timeout(thestat, find_mountp, &st, 5) != 0)
+       if (statn(find_mountp, STATX_INO, &st) != 0)
             continue;
         /* Scan the devices */
         for (dev_tmp = dev_head; dev_tmp != NULL;
@@ -2122,7 +2113,7 @@ static void scan_swaps(
             if (*find_space == '\0')
                 continue;
         }
-        if (timeout(thestat, line, &st, 5) != 0)
+       if (statn(line, STATX_INO, &st) != 0)
             continue;
 
         /* Scan the devices */
@@ -2143,146 +2134,6 @@ static void scan_swaps(
     fclose(fp);
 }
 
-#if defined(WITH_MOUNTINFO_LIST)
-/*
- * Use /proc/self/mountinfo of modern linux system to determine
- * the device numbers of the mount points. Use this to avoid the
- * stat(2) system call wherever possible.
- */
-
-static list_t mntinfo = { &mntinfo, &mntinfo };
-
-static void clear_mntinfo(void)
-{
-    list_t *ptr, *tmp;
-
-    list_for_each_safe(ptr, tmp, &mntinfo)
-    {
-        mntinfo_t *mnt = list_entry(ptr, mntinfo_t);
-        delete(ptr);
-        free(mnt);
-    }
-}
-
-static void init_mntinfo(void)
-{
-    char mpoint[PATH_MAX *4 + 1]; // octal escaping takes 4 chars per 1 char
-    int mid, parid, max = 0;
-    uint maj, min;
-    list_t sort;
-    FILE *mnt;
-
-    if (!list_empty(&mntinfo))
-        return;
-    if ((mnt = fopen("/proc/self/mountinfo", "r")) == (FILE *) 0)
-        return;
-    while (fscanf
-           (mnt, "%i %i %u:%u %*s %s %*[^\n]",
-            &mid, &parid, &maj, &min, &mpoint[0]) == 5)
-    {
-        const size_t nlen = strlen(mpoint);
-        mntinfo_t *restrict mnt;
-        if (posix_memalign ((void *)&mnt, sizeof(void *),
-                            alignof(mntinfo_t) + (nlen + 1)) != 0)
-        {
-            fprintf(stderr,
-                _("Cannot allocate memory for matched proc: %s\n"),
-                strerror(errno));
-            exit(1);
-        }
-        append(mnt, mntinfo);
-        mnt->mpoint = ((char *)mnt) + alignof(mntinfo_t);
-        strcpy(mnt->mpoint, mpoint);
-        mnt->nlen = nlen;
-        mnt->parid = parid;
-        mnt->dev = makedev(maj, min);
-        mnt->id = mid;
-        if (mid > max)
-            max = mid;
-    }
-    fclose(mnt);
-
-    /* Sort mount points accordingly to the reverse mount order */
-    initial(&sort);
-    for (mid = 1; mid <= max; mid++)
-    {
-        list_t *ptr, *tmp;
-        list_for_each_safe(ptr, tmp, &mntinfo)
-        {
-            mntinfo_t *mnt = list_entry(ptr, mntinfo_t);
-            if (mid != mnt->id)
-                continue;
-            move_head(ptr, &sort);
-            break;
-        }
-        list_for_each_safe(ptr, tmp, &mntinfo)
-        {
-            mntinfo_t *mnt = list_entry(ptr, mntinfo_t);
-            if (mid != mnt->parid)
-                continue;
-            move_head(ptr, &sort);
-        }
-    }
-    if (!list_empty(&mntinfo))
-    {
-#ifdef EBADE
-        errno = EBADE;
-#else
-        errno = ENOENT;
-#endif                /* EBADE */
-    }
-    join(&sort, &mntinfo);
-}
-
-/*
- * Determine device of links below /proc/
- */
-static int mntstat(
-    const char *path,
-    struct stat *buf)
-{
-    char name[PATH_MAX + 1];
-    const char *use;
-    ssize_t nlen;
-    list_t *ptr;
-
-    if ((use = realpath(path, name)) == NULL || *use != '/')
-    {
-        if (errno == ENOENT)
-            return -1;
-        /*
-         * Could be a special file (socket, pipe, inotify)
-         */
-        errno = 0;
-        return stat(path, buf);
-    }
-
-    nlen = strlen(use);
-    list_for_each(ptr, &mntinfo)
-    {
-        mntinfo_t *mnt = list_entry(ptr, mntinfo_t);
-        if (nlen < mnt->nlen)
-            continue;
-        if (mnt->nlen == 1) /* root fs is the last entry */
-        {
-            buf->st_dev = mnt->dev;
-            buf->st_ino = 0;
-            return 0;
-        }
-        if (use[mnt->nlen] != '\0' && use[mnt->nlen] != '/')
-            continue;
-        if (strncmp(use, mnt->mpoint, mnt->nlen) == 0)
-        {
-            buf->st_dev = mnt->dev;
-            buf->st_ino = 0;
-            return 0;
-        }
-    }
-    errno = ENOENT;
-    return -1;
-}
-#endif                /* WITH_MOUNTINFO_LIST */
-
 /*
  * Somehow the realpath(3) glibc function call, nevertheless
  * it avoids lstat(2) system calls.
diff --git a/src/statx.c b/src/statx.c
new file mode 100644 (file)
index 0000000..a598c89
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * statx.c - Map modern statx(2) system call to older stat(2), lstat(2),
+ *          and fstat(2) replacements named {,l,f}statn()
+ *
+ * Copyright (C) 2018 Werner Fink
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ *  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 for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef HAVE_STATX
+# define _ASM_GENERIC_FCNTL_H  /* Avoid collisions between asm/fcntl.h and bits/fcntl.h ! */
+# include <linux/fcntl.h>      /* Definition of AT_* and AT_STATX_* constants ! */
+#endif
+#include <fcntl.h>             /* Definition of AT_* constants */
+#include <sys/stat.h>
+#ifndef HAVE_STATX
+# ifndef STATX_TYPE
+#  include <linux/stat.h>      /* Provides 'struct statx' and STATX_* ! */
+# endif
+#endif
+#include <sys/sysmacros.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+int stat_flags = AT_NO_AUTOMOUNT|AT_STATX_DONT_SYNC;
+
+int statn(const char *pathname, unsigned int mask, struct stat *st)
+{
+    int flags = stat_flags;
+    int dirfd = pathname && *pathname == '/' ? 0 : AT_FDCWD;
+    int ret;
+    struct statx stx;
+
+#ifndef HAVE_STATX
+    ret = syscall(SYS_statx, dirfd, pathname, flags, mask, &stx);
+#else
+    ret = statx(dirfd, pathname, flags, mask, &stx);
+#endif
+    if (ret >= 0) {
+       st->st_dev  = makedev(stx.stx_dev_major,  stx.stx_dev_minor);
+       st->st_rdev = makedev(stx.stx_rdev_major, stx.stx_rdev_minor);
+
+       st->st_ino  = stx.stx_ino;
+       st->st_mode = stx.stx_mode;
+       st->st_nlink = stx.stx_nlink;
+       st->st_uid = stx.stx_uid;
+       st->st_gid = stx.stx_gid;
+       st->st_size = stx.stx_size;
+       st->st_blksize = stx.stx_blksize;
+       st->st_blocks = stx.stx_blocks;
+
+       st->st_atim.tv_sec = stx.stx_atime.tv_sec;
+       st->st_atim.tv_nsec = stx.stx_atime.tv_nsec;
+       st->st_mtim.tv_sec = stx.stx_mtime.tv_sec;
+       st->st_mtim.tv_nsec = stx.stx_mtime.tv_nsec;
+       st->st_ctim.tv_sec = stx.stx_ctime.tv_sec;
+       st->st_ctim.tv_nsec = stx.stx_ctime.tv_nsec;
+    }
+    return ret;
+}
+
+int fstatn(int fd, unsigned int mask, struct stat *st)
+{
+    int flags = AT_EMPTY_PATH|stat_flags;
+    int ret;
+    struct statx stx;
+
+#ifndef HAVE_STATX
+    ret = syscall(SYS_statx, fd, "", flags, mask, &stx);
+#else
+    ret = statx(fd, "", flags, mask, &stx);
+#endif
+    if (ret >= 0) {
+       st->st_dev  = makedev(stx.stx_dev_major,  stx.stx_dev_minor);
+       st->st_rdev = makedev(stx.stx_rdev_major, stx.stx_rdev_minor);
+
+       st->st_ino  = stx.stx_ino;
+       st->st_mode = stx.stx_mode;
+       st->st_nlink = stx.stx_nlink;
+       st->st_uid = stx.stx_uid;
+       st->st_gid = stx.stx_gid;
+       st->st_size = stx.stx_size;
+       st->st_blksize = stx.stx_blksize;
+       st->st_blocks = stx.stx_blocks;
+
+       st->st_atim.tv_sec = stx.stx_atime.tv_sec;
+       st->st_atim.tv_nsec = stx.stx_atime.tv_nsec;
+       st->st_mtim.tv_sec = stx.stx_mtime.tv_sec;
+       st->st_mtim.tv_nsec = stx.stx_mtime.tv_nsec;
+       st->st_ctim.tv_sec = stx.stx_ctime.tv_sec;
+       st->st_ctim.tv_nsec = stx.stx_ctime.tv_nsec;
+    }
+    return ret;
+}
+
+int lstatn(const char *pathname, unsigned int mask, struct stat *st)
+{
+    int flags = AT_SYMLINK_NOFOLLOW|stat_flags;
+    int dirfd = pathname && *pathname == '/' ? 0 : AT_FDCWD;
+    int ret;
+    struct statx stx;
+
+#ifndef HAVE_STATX
+    ret = syscall(SYS_statx, dirfd, pathname, flags, mask, &stx);
+#else
+    ret = statx(dirfd, pathname, flags, mask, &stx);
+#endif
+    if (ret >= 0) {
+       st->st_dev  = makedev(stx.stx_dev_major,  stx.stx_dev_minor);
+       st->st_rdev = makedev(stx.stx_rdev_major, stx.stx_rdev_minor);
+
+       st->st_ino  = stx.stx_ino;
+       st->st_mode = stx.stx_mode;
+       st->st_nlink = stx.stx_nlink;
+       st->st_uid = stx.stx_uid;
+       st->st_gid = stx.stx_gid;
+       st->st_size = stx.stx_size;
+       st->st_blksize = stx.stx_blksize;
+       st->st_blocks = stx.stx_blocks;
+
+       st->st_atim.tv_sec = stx.stx_atime.tv_sec;
+       st->st_atim.tv_nsec = stx.stx_atime.tv_nsec;
+       st->st_mtim.tv_sec = stx.stx_mtime.tv_sec;
+       st->st_mtim.tv_nsec = stx.stx_mtime.tv_nsec;
+       st->st_ctim.tv_sec = stx.stx_ctime.tv_sec;
+       st->st_ctim.tv_nsec = stx.stx_ctime.tv_nsec;
+    }
+    return ret;
+}
diff --git a/src/statx.h b/src/statx.h
new file mode 100644 (file)
index 0000000..fdd0137
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * statx.h - Map modern statx(2) system call to older stat(2), lstat(2),
+ *           and fstat(2) replacements named {,l,f}statn()
+ *
+ * Copyright (C) 2018 Werner Fink
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ *  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 for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _STATX_H
+#define _STATX_H
+
+extern int stat_flags;
+#if defined(HAVE_DECL_SYS_STATX) && HAVE_DECL_SYS_STATX == 1
+# ifndef HAVE_STATX
+#  define _ASM_GENERIC_FCNTL_H /* Avoid collisions between asm/fcntl.h and bits/fcntl.h ! */
+#  include <linux/fcntl.h>     /* Definition of AT_* and AT_STATX_* constants ! */
+#  ifndef STATX_TYPE
+#   include <linux/stat.h>     /* Provides 'struct statx' and STATX_* ! */
+#  endif
+# endif
+extern int statn(const char*, unsigned int, struct stat*);
+extern int fstatn(int, unsigned int, struct stat*);
+extern int lstatn(const char*, unsigned int, struct stat*);
+#else
+extern inline int
+statn(const char *path, unsigned int mask __attribute__((unused)), struct stat *st)
+{
+    return stat(path, st);
+}
+extern inline int
+fstatn(int fd, unsigned int mask __attribute__((unused)), struct stat *st)
+{
+    return fstat(fd, st);
+}
+extern inline int
+lstatn(const char *path, unsigned int mask __attribute__((unused)), struct stat *st)
+{
+    return lstat(path, st);
+}
+#define STATX_TYPE             0
+#define STATX_MODE             0
+#define STATX_NLINK            0
+#define STATX_UID              0
+#define STATX_GID              0
+#define STATX_ATIME            0
+#define STATX_MTIME            0
+#define STATX_CTIME            0
+#define STATX_INO              0
+#define STATX_SIZE             0
+#define STATX_BLOCKS           0
+#define STATX_BASIC_STATS      0
+#define STATX_BTIME            0
+#define STATX_ALL              0
+#endif
+#endif