From e5ba304260af6a93c7af03f6bc29e1d3472ef442 Mon Sep 17 00:00:00 2001 From: Werner Fink Date: Wed, 23 Nov 2022 14:51:53 +0100 Subject: [PATCH] Use mountinfo to be able to use the mount identity which allows to distinguish different mounts with the same device number as it happens with NFS shares. Smaller cleanup as support of chroot environments and older systems. Add support for name_to_handle_at() system call to get the real mount ID for each file Support also BtrFS with its various subvolumes On BtrFS stat(2) on binary does not see subvol dev Allow not unique mounts as well as not unique mountpoint Fuser does not show open kvm storage image files such as qcow2 files. Patch from Ali Abdallah Correct last change by using our own namespace to determine the appropriate mount ID for e.g. qcow2 files. Determine the namespace of a process only once to speed up the parsing of fdinfo. Add a fallback if the system call name_to_handle_at() is not supported by the used file system. Avoid foreign namespaces as well as mount IDs for sockets. Expand paths also for named sockets. Signed-off-by: Werner Fink --- configure.ac | 18 +- src/fuser.c | 763 +++++++++++++++++++++++++++++++++++++++++++++------ src/fuser.h | 26 +- 3 files changed, 721 insertions(+), 86 deletions(-) diff --git configure.ac configure.ac index 723cf02..3599fea 100644 --- configure.ac +++ configure.ac @@ -12,6 +12,7 @@ AC_USE_SYSTEM_EXTENSIONS(_GNU_SOURCE) AC_PROG_CXX AC_PROG_LN_S AC_PROG_CC +AC_PROG_CC_STDC AC_DEFUN([PSMISC_PROG_PO4A], [ AC_REQUIRE([AM_NLS]) @@ -62,6 +63,19 @@ if test "$enable_apparmor" = "yes"; then fi AC_SUBST([DL_LIB]) +# Use /proc/self/mountinfo if available +if test -e /proc/self/mountinfo ; then + AC_DEFINE([HAS_MOUNTINFO], [1], [System has /proc/self/mountinfo which can used instead /proc(/self)/mounts]) +fi +# Use /proc/self/fdinfo if available +if test -e /proc/self/fdinfo ; then + AC_DEFINE([HAS_FDINFO], [1], [System has /proc/self/fdinfo for informations on file descriptors]) +fi + +# Check for Linux specific name_to_handle_at(2) system call for getting mount IDs +AC_CHECK_FUNC([name_to_handle_at],[ + AC_DEFINE([HAS_NAME_TO_HANDLE_AT], [1], [System has name_to_handle_at(2) system call])]) + AC_CHECK_DECLS([SYS_statx], [has_syscall_statx="yes"], [has_syscall_statx="no"], @@ -112,9 +126,9 @@ AC_SUBST([TERMCAP_LIB]) dnl Checks for header files. AC_HEADER_DIRENT - +AC_HEADER_STDC AC_HEADER_SYS_WAIT -AC_CHECK_HEADERS([arpa/inet.h fcntl.h langinfo.h libintl.h limits.h locale.h mntent.h netdb.h netinet/in.h stdlib.h string.h sys/ioctl.h sys/socket.h termios.h unistd.h]) +AC_CHECK_HEADERS([arpa/inet.h fcntl.h langinfo.h libintl.h limits.h locale.h mntent.h netdb.h netinet/in.h stdlib.h string.h sys/ioctl.h sys/mount.h sys/param.h sys/stat.h sys/socket.h sys/types.h termios.h unistd.h]) dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST diff --git src/fuser.c src/fuser.c index f2bd3e9..8e4c853 100644 --- src/fuser.c +++ src/fuser.c @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include #include @@ -87,7 +89,7 @@ static void check_map(const pid_t pid, const char *filename, struct device_list *dev_head, struct inode_list *ino_head, const uid_t uid, const char access); -static struct stat *get_pidstat(const pid_t pid, const char *filename); +static struct stat *get_pidstat(const pid_t pid, const char *filename, int *id); static uid_t getpiduid(const pid_t pid); static int print_matches(struct names *names_head, const opt_type opts, const int sig_number); @@ -96,9 +98,9 @@ static int kill_matched_proc(struct procs *pptr, const opt_type opts, /*int parse_mount(struct names *this_name, struct device_list **dev_list);*/ static void add_device(struct device_list **dev_list, - struct names *this_name, dev_t device); -void fill_unix_cache(struct unixsocket_list **unixsocket_head); -void clear_unix_cache(struct unixsocket_list **unixsocket_head); + struct names *this_name, dev_t device, dev_t subvol); +static void fill_unix_cache(struct unixsocket_list **unixsocket_head); +static void clear_unix_cache(struct unixsocket_list **unixsocket_head); static void atexit_clear_unix_cache(); static dev_t find_net_dev(void); static void scan_procs( @@ -123,6 +125,15 @@ static void debug_match_lists( struct device_list *dev_head); #endif +static list_t mntinfo = { &mntinfo, &mntinfo }; +static void clear_mntinfo(void) __attribute__ ((__destructor__)); +static void init_mntinfo(void) __attribute__ ((__constructor__)); +static int get_fdinfo(const pid_t pid, const char *fd, struct fdinfo *info, const ino_t ns); +#if defined(HAS_NAME_TO_HANDLE_AT) +static ino_t get_namespace(const pid_t pid); +static int get_mountid(const char *path); +#endif +static int find_mountpoint(const char *path, mntinfo_t **mountinfo); static char *expandpath(const char *path); static struct unixsocket_list *unixsockets = NULL; static struct names *names_head = NULL, *names_tail = NULL; @@ -167,6 +178,7 @@ static void usage( fprintf(stderr, _(" -4,--ipv4 search IPv4 sockets only\n" " -6,--ipv6 search IPv6 sockets only\n")); #endif + fprintf(stderr, _(" - reset options\n\n")); fprintf(stderr, _(" udp/tcp names: [local_port][,[rmt_host][,[rmt_port]]]\n\n")); exit(1); } @@ -209,6 +221,7 @@ static void scan_procs( struct stat *cwd_stat = NULL; struct stat *exe_stat = NULL; struct stat *root_stat = NULL; + int cwd_id, exe_id, root_id; if (topproc_dent->d_name[0] < '0' || topproc_dent->d_name[0] > '9') /* Not a process */ continue; @@ -218,49 +231,64 @@ static void scan_procs( continue; uid = getpiduid(pid); - cwd_stat = get_pidstat(pid, "cwd"); - exe_stat = get_pidstat(pid, "exe"); - root_stat = get_pidstat(pid, "root"); + cwd_stat = get_pidstat(pid, "cwd", &cwd_id); + exe_stat = get_pidstat(pid, "exe", &exe_id); + root_stat = get_pidstat(pid, "root", &root_id); cwd_dev = cwd_stat ? cwd_stat->st_dev : 0; exe_dev = exe_stat ? exe_stat->st_dev : 0; root_dev = root_stat ? root_stat->st_dev : 0; /* Scan the devices */ - for (dev_tmp = dev_head; dev_tmp != NULL; - dev_tmp = dev_tmp->next) { - if (exe_dev == dev_tmp->device) - add_matched_proc(dev_tmp->name, pid, uid, - ACCESS_EXE); - if (root_dev == dev_tmp->device) - add_matched_proc(dev_tmp->name, pid, uid, - ACCESS_ROOT); - if (cwd_dev == dev_tmp->device) - add_matched_proc(dev_tmp->name, pid, uid, - ACCESS_CWD); + for (dev_tmp = dev_head; dev_tmp != NULL; dev_tmp = dev_tmp->next) + { + struct subvol *vol_tmp; + char access = 0; + if (exe_dev == dev_tmp->device && dev_tmp->mnt_id == exe_id) + access |= ACCESS_EXE; + if (root_dev == dev_tmp->device && dev_tmp->mnt_id == root_id) + access |= ACCESS_ROOT; + if (cwd_dev == dev_tmp->device && dev_tmp->mnt_id == cwd_id) + access |= ACCESS_CWD; + for (vol_tmp = dev_tmp->vol; vol_tmp != NULL; vol_tmp = vol_tmp->next) + { + /* For BtrFS sub volumes there are different + mount ids for the same device */ + if (exe_dev == vol_tmp->device && vol_tmp->mnt_id == exe_id) + access |= ACCESS_EXE; + if (root_dev == vol_tmp->device && vol_tmp->mnt_id == root_id) + access |= ACCESS_ROOT; + if (cwd_dev == vol_tmp->device && vol_tmp->mnt_id == cwd_id) + access |= ACCESS_CWD; + } + if (access) + add_matched_proc(dev_tmp->name, pid, uid, access); } - for (ino_tmp = ino_head; ino_tmp != NULL; - ino_tmp = ino_tmp->next) { - if (exe_dev == ino_tmp->device) { + for (ino_tmp = ino_head; ino_tmp != NULL; ino_tmp = ino_tmp->next) + { + if (exe_dev == ino_tmp->device && ino_tmp->mnt_id == exe_id) + { if (!exe_stat) - exe_stat = get_pidstat(pid, "exe"); + exe_stat = get_pidstat(pid, "exe", NULL); if (exe_stat && exe_stat->st_dev == ino_tmp->device && exe_stat->st_ino == ino_tmp->inode) add_matched_proc(ino_tmp->name, pid, uid, ACCESS_EXE); } - if (root_dev == ino_tmp->device) { + if (root_dev == ino_tmp->device && ino_tmp->mnt_id == root_id) + { if (!root_stat) - root_stat = get_pidstat(pid, "root"); + root_stat = get_pidstat(pid, "root", NULL); if (root_stat && root_stat->st_dev == ino_tmp->device && root_stat->st_ino == ino_tmp->inode) add_matched_proc(ino_tmp->name, pid, uid, ACCESS_ROOT); } - if (cwd_dev == ino_tmp->device) { + if (cwd_dev == ino_tmp->device && ino_tmp->mnt_id == cwd_id) + { if (!cwd_stat) - cwd_stat = get_pidstat(pid, "cwd"); + cwd_stat = get_pidstat(pid, "cwd", NULL); if (cwd_stat && cwd_stat->st_dev == ino_tmp->device && cwd_stat->st_ino == ino_tmp->inode) @@ -302,20 +330,48 @@ static void add_inode( ino_tmp->name = this_name; ino_tmp->device = device; ino_tmp->inode = inode; + ino_tmp->mnt_id = this_name->mnt_id; ino_tmp->next = ino_head; *ino_list = ino_tmp; } + +static void add_subvol( + struct subvol **head, + dev_t device, + int mnt_id) +{ + struct subvol *vol_tmp, *vol_head; + + if ((vol_tmp = + (struct subvol *)malloc(sizeof(struct subvol))) == NULL) + return; + vol_head = *head; + vol_tmp->device = device; + vol_tmp->mnt_id = mnt_id; + vol_tmp->next = vol_head; + *head = vol_tmp; +} static void add_device( struct device_list **dev_list, struct names *this_name, - dev_t device) + dev_t device, + dev_t subvol) { struct device_list *dev_tmp, *dev_head; #ifdef DEBUG fprintf(stderr, "add_device(%s %u\n", this_name->filename, - (unsigned int)device); -#endif /* DEBUG */ + (unsigned int)device); +#endif /* DEBUG */ + /* For BtrFS there are many sub volumes all on the same block device */ + for (dev_tmp = *dev_list; dev_tmp != NULL; dev_tmp = dev_tmp->next) + if (dev_tmp->device == device) + { + if (dev_tmp->device != subvol) + add_subvol(&dev_tmp->vol, subvol, this_name->mnt_id); + *dev_list = dev_tmp; + return; + } if ((dev_tmp = (struct device_list *)malloc(sizeof(struct device_list))) == NULL) @@ -323,6 +379,10 @@ static void add_device( dev_head = *dev_list; dev_tmp->name = this_name; dev_tmp->device = device; + dev_tmp->mnt_id = this_name->mnt_id; + dev_tmp->vol = NULL; + if (dev_tmp->device != subvol) + add_subvol(&dev_tmp->vol, subvol, this_name->mnt_id); dev_tmp->next = dev_head; *dev_list = dev_tmp; } @@ -480,6 +540,7 @@ int parse_file( struct inode_list **ino_list, const opt_type opts) { + mntinfo_t *mountinfo; char *new = expandpath(this_name->filename); if (new) { @@ -487,7 +548,8 @@ int parse_file( free(this_name->filename); this_name->filename = strdup(new); } - if (statn(this_name->filename, STATX_INO|STATX_TYPE, &(this_name->st)) != 0 ) + if (statn(this_name->filename, STATX_INO|STATX_TYPE, &(this_name->st)) != 0 || + find_mountpoint(this_name->filename, &mountinfo) != 0) { if (errno == ENOENT) fprintf(stderr, @@ -498,10 +560,12 @@ int parse_file( this_name->filename, strerror(errno)); return -1; } + this_name->mnt_id = mountinfo->id; #ifdef DEBUG - printf("adding file %s %lX %lX\n", this_name->filename, + printf("adding file %s %lX %lX %d nfs=%s\n", this_name->filename, (unsigned long)this_name->st.st_dev, - (unsigned long)this_name->st.st_ino); + (unsigned long)this_name->st.st_ino, + mountinfo->id, mountinfo->isremote ? "yes" : "no"); #endif /* DEBUG */ add_inode(ino_list, this_name, this_name->st.st_dev, this_name->st.st_ino); @@ -537,12 +601,45 @@ int parse_mounts( const opt_type opts) { dev_t match_device; + list_t *ptr; + int count; if (S_ISBLK(this_name->st.st_mode)) match_device = this_name->st.st_rdev; else match_device = this_name->st.st_dev; - add_device(dev_list, this_name, match_device); + + count = 0; + list_for_each(ptr, &mntinfo) + { + mntinfo_t *mnt = list_entry(ptr, mntinfo_t); + if (match_device != mnt->dev) + continue; + if (S_ISBLK(this_name->st.st_mode)) + { + /* Correct mount IDs check if a block device + * was specified */ + this_name->mnt_id = mnt->id; + add_device(dev_list, this_name, match_device, mnt->dev); + if (mnt->dev == mnt->vol) + count++; + else count = 1; + continue; + } + if (this_name->mnt_id != mnt->id) + continue; + count++; + } + if (count == 0) + { + errno = ENOENT; + fprintf(stderr, _("Specified filename %s has no mountpoint.\n"), + this_name->filename); + return -1; + } + if (S_ISBLK(this_name->st.st_mode)) + return 0; + add_device(dev_list, this_name, match_device, match_device); return 0; } @@ -1010,6 +1107,22 @@ static void free_inodes( /* * Free up structures allocated in add_device */ + +static void free_subvol( + struct subvol **volumes) +{ + struct subvol *vol_tmp, *vol_next; + + vol_tmp = *volumes; + while (vol_tmp != NULL) + { + vol_next = vol_tmp->next; + free(vol_tmp); + vol_tmp = vol_next; + } + *volumes =NULL; +} + static void free_devices( struct device_list **match_devices) { @@ -1018,6 +1131,8 @@ static void free_devices( device_tmp = *match_devices; while(device_tmp != NULL) { + if (device_tmp->vol) + free_subvol(&device_tmp->vol); device_next = device_tmp->next; free(device_tmp); device_tmp = device_next; @@ -1273,8 +1388,8 @@ int main(int argc, char *argv[]) } #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 */ + 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 */ @@ -1589,7 +1704,8 @@ static int print_matches( static struct stat *get_pidstat( const pid_t pid, - const char *filename) + const char *filename, + int *id) { char pathname[PATH_MAX]; struct stat *st; @@ -1602,6 +1718,16 @@ static struct stat *get_pidstat( free(st); return NULL; } + + if (id) + { + mntinfo_t *info; + char *new = expandpath(pathname); + if (new && find_mountpoint(new, &info) == 0) + *id = info->id; + else *id = -1; + } + return st; } @@ -1621,10 +1747,11 @@ static void check_dir( struct inode_list *ino_tmp; struct device_list *dev_tmp; struct unixsocket_list *sock_tmp; - struct stat st, lst; + struct stat st; char *dirpath; char filepath[PATH_MAX]; char real_filepath[PATH_MAX]; + ino_t ns; if (asprintf(&dirpath, "/proc/%d/%s", pid, dirname) < 0) return; @@ -1635,6 +1762,12 @@ static void check_dir( } free(dirpath); +#if defined(HAS_NAME_TO_HANDLE_AT) + ns = get_namespace(pid); +#else + ns = -1; +#endif + while ((direntry = readdir(dirp)) != NULL) { if (direntry->d_name[0] < '0' || direntry->d_name[0] > '9') @@ -1643,7 +1776,7 @@ static void check_dir( snprintf(filepath, sizeof filepath - 1, "/proc/%d/%s/%s", pid, dirname, direntry->d_name); - if (statn(filepath, STATX_INO, &st) != 0) + if (statn(filepath, STATX_INO, &st) != 0) { if (errno != ENOENT && errno != ENOTDIR && errno != EACCES) { @@ -1651,6 +1784,9 @@ static void check_dir( filepath, strerror(errno)); } } else { + struct fdinfo fd; + int fdret; + thedev = st.st_dev; if (thedev == netdev) { @@ -1666,11 +1802,32 @@ static void check_dir( } } } + + memset(&fd, 0, sizeof(struct fdinfo)); + fdret = get_fdinfo(pid, direntry->d_name, &fd, ns); + for (dev_tmp = dev_head; dev_tmp != NULL; dev_tmp = dev_tmp->next) { if (thedev != dev_tmp->device) - continue; + { + struct subvol *vol_tmp; + int subvol_found = 0; + + for (vol_tmp = dev_tmp->vol; vol_tmp != NULL; + vol_tmp = vol_tmp->next) + { + /* Check for BtrFS sub volumes as well */ + if (thedev == vol_tmp->device) + { + subvol_found++; + break; + } + } + + if (!subvol_found) + continue; + } /* check the paths match if it is not a block device or socket */ if (! S_ISBLK(dev_tmp->name->st.st_mode) @@ -1686,10 +1843,14 @@ static void check_dir( strlen(dev_tmp->name->filename)) != 0) continue; } + if (fdret != 0) + continue; + if (fd.mnt_id != dev_tmp->mnt_id) + continue; } + if (access == ACCESS_FILE - && (lstat(filepath, &lst) == 0) - && (lst.st_mode & S_IWUSR)) + && (fd.flags & (O_WRONLY|O_RDWR))) { add_matched_proc(dev_tmp->name, pid, uid, ACCESS_FILEWR | access); @@ -1709,9 +1870,10 @@ static void check_dir( } if (st.st_ino == ino_tmp->inode) { + if (fdret != 0) + continue; if (access == ACCESS_FILE - && (lstat(filepath, &lst) == 0) - && (lst.st_mode & S_IWUSR)) + && (fd.flags & (O_WRONLY|O_RDWR))) { add_matched_proc(ino_tmp->name, pid, uid, ACCESS_FILEWR | access); @@ -1740,7 +1902,6 @@ static void check_map( FILE *fp; unsigned long long tmp_inode; unsigned int tmp_maj, tmp_min; - dev_t tmp_device; if (asprintf(&pathname, "/proc/%d/%s", pid, filename) < 0) return; @@ -1752,18 +1913,47 @@ static void check_map( free(pathname); while (fgets(line, BUFSIZ, fp)) { - if (sscanf(line, "%*s %*s %*s %x:%x %lld", - &tmp_maj, &tmp_min, &tmp_inode) == 3) + char *scanned_path = NULL; + if (sscanf(line, "%*s %*s %*s %x:%x %lld %ms", + &tmp_maj, &tmp_min, &tmp_inode, &scanned_path) == 4) { - tmp_device = tmp_maj * 256 + tmp_min; + dev_t tmp_device = makedev(tmp_maj, tmp_min); + int mnt_id = -1; + + if (scanned_path) + { + if (*scanned_path == '/') + { + mntinfo_t *mountinfo; + if (find_mountpoint(scanned_path, &mountinfo) == 0) + mnt_id = mountinfo->id; + } + free(scanned_path); + } + for (dev_tmp = dev_head; dev_tmp != NULL; dev_tmp = dev_tmp->next) - if (dev_tmp->device == tmp_device) + { + struct subvol *vol_tmp; + + if (dev_tmp->device == tmp_device && mnt_id == dev_tmp->mnt_id) add_matched_proc(dev_tmp->name, pid, uid, access); + + for (vol_tmp = dev_tmp->vol; vol_tmp != NULL; vol_tmp = vol_tmp->next) + { + /* For BtrFS sub volumes there are different + mount ids for the same device */ + if (vol_tmp->device == tmp_device + && mnt_id == vol_tmp->mnt_id) + + add_matched_proc(dev_tmp->name, pid, uid, access); + } + } for (ino_tmp = ino_head; ino_tmp != NULL; ino_tmp = ino_tmp->next) if (ino_tmp->device == tmp_device && ino_tmp->inode == tmp_inode) add_matched_proc(ino_tmp->name, pid, uid, access); - } + } else if (scanned_path) + free(scanned_path); } fclose(fp); } @@ -1789,7 +1979,7 @@ static uid_t getpiduid( * fill_unix_cache : Create a list of Unix sockets * This list is used later for matching purposes */ -void fill_unix_cache( +static void fill_unix_cache( struct unixsocket_list **unixsocket_head) { FILE *fp; @@ -1808,6 +1998,8 @@ void fill_unix_cache( { char *path; char *scanned_path = NULL; + int mnt_id = -1; + mntinfo_t *mountinfo; if (sscanf(line, "%*x: %*x %*x %*x %*x %*d %llu %ms", &scanned_inode, &scanned_path) != 2) { @@ -1820,11 +2012,13 @@ void fill_unix_cache( path = scanned_path; if (*scanned_path == '@') scanned_path++; - if (statn(scanned_path, STATX_INO, &st) < 0) + if (statn(scanned_path, STATX_INO, &st) < 0) { free(path); continue; } + if (find_mountpoint(scanned_path, &mountinfo) == 0) + mnt_id = mountinfo->id; if ((newsocket = (struct unixsocket_list *) malloc(sizeof(struct unixsocket_list))) == NULL) { @@ -1834,6 +2028,7 @@ void fill_unix_cache( newsocket->sun_name = strdup(scanned_path); newsocket->inode = st.st_ino; newsocket->dev = st.st_dev; + newsocket->mnt_id = mnt_id; newsocket->net_inode = scanned_inode; newsocket->next = *unixsocket_head; *unixsocket_head = newsocket; @@ -1846,7 +2041,7 @@ void fill_unix_cache( /* * Free up the list of Unix sockets */ -void clear_unix_cache( +static void clear_unix_cache( struct unixsocket_list **unixsocket_head) { while(*unixsocket_head != NULL) @@ -2037,47 +2232,32 @@ static void scan_mounts( { struct device_list *dev_tmp; struct inode_list *ino_tmp; - FILE *fp; - char line[BUFSIZ]; - char *find_mountp; - char *find_space; struct stat st; + list_t *ptr; if ( (ino_head == NULL) && (dev_head == NULL) ) return; - - if ((fp = fopen(PROC_MOUNTS, "r")) == NULL) + list_for_each(ptr, &mntinfo) { - fprintf(stderr, "Cannot open %s\n", PROC_MOUNTS); - return; - } - while (fgets(line, BUFSIZ, fp) != NULL) - { - if ((find_mountp = strchr(line, ' ')) == NULL) - continue; - find_mountp++; - if ((find_space = strchr(find_mountp, ' ')) == NULL) - continue; - *find_space = '\0'; - if (statn(find_mountp, STATX_INO, &st) != 0) + mntinfo_t *mnt = list_entry(ptr, mntinfo_t); + const char *find_mountp = mnt->mpoint; + + if (statn(find_mountp, STATX_INO, &st) != 0) continue; + /* Scan the devices */ - for (dev_tmp = dev_head; dev_tmp != NULL; - dev_tmp = dev_tmp->next) + for (dev_tmp = dev_head; dev_tmp != NULL; dev_tmp = dev_tmp->next) { - if (st.st_dev == dev_tmp->device) + if (st.st_dev == dev_tmp->device && mnt->id == dev_tmp->mnt_id) add_special_proc(dev_tmp->name, PTYPE_MOUNT, 0, find_mountp); } - for (ino_tmp = ino_head; ino_tmp != NULL; - ino_tmp = ino_tmp->next) + for (ino_tmp = ino_head; ino_tmp != NULL; ino_tmp = ino_tmp->next) { - if (st.st_dev == ino_tmp->device - && st.st_ino == ino_tmp->inode) + if (st.st_dev == ino_tmp->device && st.st_ino == ino_tmp->inode) add_special_proc(ino_tmp->name, PTYPE_MOUNT, 0, find_mountp); } } - fclose(fp); } static void scan_swaps( @@ -2113,7 +2293,7 @@ static void scan_swaps( if (*find_space == '\0') continue; } - if (statn(line, STATX_INO, &st) != 0) + if (statn(line, STATX_INO, &st) != 0) continue; /* Scan the devices */ @@ -2134,6 +2314,385 @@ static void scan_swaps( fclose(fp); } +/* + * 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 for remote file system. + */ + +static mntinfo_t* add_mntinfo( + const char *mpoint, + const char *type, + int mid, + int parid, + dev_t dev) +{ + 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 = dev; + mnt->id = mid; + if (strncmp("nfs", type, 3) == 0 || strncmp("afs", type, 3) == 0 || strncmp("autofs", type, 6)) + mnt->isremote = 1; + else mnt->isremote = 0; + /* E.g. sub volumes of BtrFS do not show main device number in + /proc/self/mountinfo, for now just map the device to this */ + mnt->vol = dev; + return mnt; +} + +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 type[256]; + char mpoint[PATH_MAX *4 + 1]; // octal escaping takes 4 chars per 1 char + char devname[PATH_MAX]; + int mid, parid; + mntinfo_t *mntinf; +#if defined(HAS_MOUNTINFO) + uint maj, min; +#endif + FILE *mnt; + + if (!list_empty(&mntinfo)) + return; +#if defined(HAS_MOUNTINFO) + /* + * We could also loop over all mount namespaces, that is not only + * over /proc/self/ns/mnt. But this expands this list as well as + * we can use name_to_handle_at(2) in our get_mountid() function. + */ + if ((mnt = fopen(PROC_MOUNTINFO, "r")) == (FILE *) 0) + return; + while (fscanf (mnt, "%i %i %u:%u %*s %s %*[^-] - %s %s %*[^\n]", + &mid, &parid, &maj, &min, &mpoint[0], &type[0], &devname[0]) == 7) + { + struct stat st; + mntinf = add_mntinfo(mpoint, type, mid, parid, makedev(maj, min)); + if (mntinf && strncmp(devname, "/dev/", 5) == 0 && statn(devname, 0, &st) == 0) + { + if (st.st_rdev != 0 && mntinf->dev != st.st_rdev) + { + mntinf->vol = st.st_rdev; + statn(mpoint, 0, &st); + mntinf->dev = st.st_dev; /* stat(2) on binary does not see subvol dev */ + } + } + } +#else + if ((mnt = fopen(PROC_MOUNTS, "r")) == (FILE *) 0) + return; + mid = 1; + parid = -1; + while (fscanf (mnt, "%s %s %s %*[^\n]", &devname[0], &mpoint[0], &type[0]) == 3) + { + struct stat st; + if (statn(mpoint, 0, &st) != 0) + { + if (errno != EACCES) + { + fprintf(stderr, _("Cannot stat %s: %s\n"), mpoint, strerror(errno)); + exit(1); + } + st.st_dev = (dev_t)-1; + } + mntinf = add_mntinfo(mpoint, type, mid++, parid, st.st_dev); + if (mntinf && strncmp(devname, "/dev/", 5) == 0 && statn(devname, 0, &st) == 0) + { + if (st.st_rdev != 0 && mntinf->dev != st.st_rdev) + mntinf->vol = st.st_rdev; + } + } +#endif + fclose(mnt); +} + +static int get_fdinfo( + const pid_t pid, + const char *fd, + struct fdinfo *info, + const ino_t ns) +{ + int ret = 0; + char pathname[512]; + int mnt_id = 0, flags = 0; +#if defined(HAS_FDINFO) /* Here we get the private namespace as well */ + const static char delimiters[] = ": \t\n"; + char line[BUFSIZ]; + FILE *fp; +# if defined(HAS_NAME_TO_HANDLE_AT) + char *realname; +# endif + + snprintf(pathname, sizeof(pathname)-1, "/proc/%d/fdinfo/%s", pid, fd); + if ((fp = fopen(pathname, "r")) == NULL) + goto out; + + while (fgets(line, BUFSIZ, fp) && ret < 1) + { + char *xp, *vp, *ep; + unsigned long ul; + xp = strtok(&line[0], delimiters); + if (!xp || *xp == 0) + continue; + vp = strtok(NULL, delimiters); + if (!vp || *vp == 0) + continue; + if (strcmp(xp, "flags") == 0 && (ul = strtoul(vp, &ep, 0)) != ULONG_MAX && ep && *ep == 0) + { + info->flags = (mode_t)ul; + flags++; + ret++; + break; + } + } + fclose(fp); +out: +# if defined(HAS_NAME_TO_HANDLE_AT) + snprintf(pathname, sizeof(pathname)-1, "/proc/%d/fd/%s", pid, fd); + realname = expandpath(pathname); + if (realname) + { + info->mnt_id = get_mountid(realname); + mnt_id++; + ret++; + } +# endif +#endif + if (!flags || !mnt_id) + { + struct stat lst; + + snprintf(pathname, sizeof(pathname)-1, "/proc/%d/fd/%s", pid, fd); + if (!flags && lstatn(pathname, STATX_MODE, &lst) == 0) + { + if (lst.st_mode & S_IWUSR) + info->flags |= O_WRONLY; + ret++; + } + + if (!mnt_id) + { + realname = expandpath(pathname); + if (realname) + { + mntinfo_t *mountinfo; + if (find_mountpoint(realname, &mountinfo) == 0) + { + info->mnt_id = mountinfo->id; + ret++; + } + } + } + } + + return ret == 2 ? 0 : -1; +} + +#if defined(HAS_NAME_TO_HANDLE_AT) +static ino_t get_namespace( + const pid_t pid) +{ + char pathname[512]; + struct stat st; + snprintf(pathname, sizeof(pathname)-1, "/proc/%d/ns/mnt", pid); + if (statn(pathname, STATX_INO, &st) == 0) + return st.st_ino; + return -1; +} + +static int get_mountid( + const char *path) +{ + union fh_u { + struct file_handle handle; + char buffer[sizeof(struct file_handle) + MAX_HANDLE_SZ]; + } fh = { .handle.handle_bytes = MAX_HANDLE_SZ }; + int mnt_id = -1; + + errno = 0; + if (name_to_handle_at(0, path, &fh.handle, &mnt_id, 0) == -1) + { + const static char delimiters[] = ": \t\n"; + char fdinfo[PATH_MAX+1]; + char line[BUFSIZ]; + FILE* fp; + int fd; + + if (errno != EOPNOTSUPP) + return -1; + fd = open(path, O_PATH); + if (fd < 0) + return -1; + + snprintf(fdinfo, PATH_MAX, "/proc/self/fdinfo/%d", fd); + if ((fp = fopen(fdinfo, "r")) == NULL) + { + close(fd); + return -1; + } + while (fgets(line, BUFSIZ, fp)) + { + char *xp, *vp, *ep; + unsigned long ul; + xp = strtok(&line[0], delimiters); + if (!xp || *xp == 0) + continue; + vp = strtok(NULL, delimiters); + if (!vp || *vp == 0) + continue; + if (strcmp(xp, "mnt_id") == 0 && (ul = strtoul(vp, &ep, 0)) != ULONG_MAX && ep && *ep == 0) + { + mnt_id = (int)ul; + break; + } + } + fclose(fp); + close(fd); + } + return mnt_id; +} +#endif + +static int find_mountpoint( + const char *path, + mntinfo_t **mountinfo) +{ + char *use, *end; + ssize_t nlen; +#if defined(HAS_NAME_TO_HANDLE_AT) + int mnt_id = get_mountid(path); +#endif + int ret = -1; + + *mountinfo = NULL; + +#if defined(HAS_NAME_TO_HANDLE_AT) + if (mnt_id >= 0) + { + list_t *ptr; + + errno = ENOENT; + list_for_each(ptr, &mntinfo) + { + mntinfo_t *mnt = list_entry(ptr, mntinfo_t); + + if (mnt_id != mnt->id) + continue; + + ret = 0; + errno = 0; + *mountinfo = mnt; + break; + } + if (*mountinfo) + goto out; + + if (strlen(path) == 1 && path[0] == '/') + { + struct stat st; + + /* could be a chroot or a container */ + + if (statn(path, 0, &st) != 0) + { + if (errno != EACCES) + { + fprintf(stderr, _("Cannot stat %s: %s\n"), + path, strerror(errno)); + exit(1); + } + st.st_dev = (dev_t)-1; + } + ret = 0; + errno = 0; + *mountinfo = add_mntinfo(path, "unkown", mnt_id, -1, st.st_dev); + goto out; + } + } +#endif + use = strdup(path); + if (!use) + goto out; + + nlen = strlen(use); + end = use+nlen; + errno = ENOENT; + do { + list_t *ptr; + if (*end == '/') + { + if (end == use) { /* root file system */ + end++; + if (nlen == 1) + { + struct stat st; + + /* could be a chroot or a container */ + + if (statn(use, 0, &st) != 0) + { + if (errno != EACCES) + { + fprintf(stderr, _("Cannot stat %s: %s\n"), + use, strerror(errno)); + exit(1); + } + st.st_dev = (dev_t)-1; + } + ret = 0; + errno = 0; + *mountinfo = add_mntinfo(use, "unkown", 0, -1, st.st_dev); + break; + } + } + } + *end = '\0'; + nlen = end-use; + list_for_each(ptr, &mntinfo) + { + mntinfo_t *mnt = list_entry(ptr, mntinfo_t); + + if (nlen != mnt->nlen) + continue; + + if (strcmp(use, mnt->mpoint)) + continue; + + ret = 0; + errno = 0; + *mountinfo = mnt; + break; + } + + } while (!*mountinfo && (end = strrchr(use, '/'))); + free(use); +out: + return ret; +} + /* * Somehow the realpath(3) glibc function call, nevertheless * it avoids lstat(2) system calls. @@ -2211,6 +2770,52 @@ char *expandpath( } lnkbuf[n] = '\0'; /* Don't be fooled by readlink(2) */ + /* + * Expand to real path of named socket if any + */ + if (lnkbuf[0] != '/' && strncmp("socket:[", lnkbuf, 8) == 0) + { + FILE *fp; + char *inode; + char line[BUFSIZ]; + if ((inode = strchr(&lnkbuf[8], ']'))) + { + *inode = '\0'; + inode = &lnkbuf[8]; + } + + if (!inode || (fp = fopen(PROC_SOCKETS, "r")) == NULL) + { + /*fprintf(stderr, "Cannot open %s\n", PROC_SOCKETS); */ + return (char *)0; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + char *named = NULL; + unsigned long snode; + + if (*line == 'N') + continue; + + if (sscanf(line, "%*x: %*x %*x %*x %*x %*x %lu %ms", + &snode, &named) == 2) + { + char *ep; + unsigned long oul = strtoul(inode, &ep, 0); + if (oul == snode) { + ep = named; + if (*ep == '@') + ep++; + n = strlen(ep); + memcpy(lnkbuf, ep, n); + lnkbuf[n] = '\0'; + } + free (named); + } + } + fclose(fp); + } + len = strlen(end); if ((n + len) > PATH_MAX) { diff --git src/fuser.h src/fuser.h index 4500ec5..f90dad9 100644 --- src/fuser.h +++ src/fuser.h @@ -37,10 +37,16 @@ struct procs { #define PTYPE_KNFSD 2 #define PTYPE_SWAP 3 +struct fdinfo { + mode_t flags; + int mnt_id; +}; + struct names { char *filename; unsigned char name_space; struct stat st; + int mnt_id; struct procs *matched_procs; struct names *next; }; @@ -65,12 +71,21 @@ struct inode_list { struct names *name; dev_t device; ino_t inode; + int mnt_id; struct inode_list *next; }; +struct subvol { + dev_t device; + int mnt_id; + struct subvol *next; +}; + struct device_list { struct names *name; + struct subvol *vol; dev_t device; + int mnt_id; struct device_list *next; }; @@ -79,6 +94,7 @@ struct unixsocket_list { ino_t inode; ino_t net_inode; dev_t dev; + int mnt_id; struct unixsocket_list *next; }; @@ -87,18 +103,16 @@ struct mount_list { struct mount_list *next; }; -#if defined (__GNUC__) && defined(WITH_MOUNTINFO_LIST) -# include "lists.h" +#include "lists.h" typedef struct mntinfo_s { list_t this; int id, parid; + char isremote; dev_t dev; size_t nlen; + dev_t vol; char *mpoint; } mntinfo_t; -#else -# undef WITH_MOUNTINFO_LIST -#endif #define NAMESPACE_FILE 0 #define NAMESPACE_TCP 1 @@ -109,5 +123,7 @@ typedef struct mntinfo_s { #endif /* PATH_MAX */ #define KNFSD_EXPORTS "/proc/fs/nfs/exports" +#define PROC_MOUNTINFO "/proc/self/mountinfo" +#define PROC_SOCKETS "/proc/self/net/unix" #define PROC_MOUNTS "/proc/mounts" #define PROC_SWAPS "/proc/swaps" -- 2.35.3