--- doc/fuser.1 +++ doc/fuser.1 2008-05-16 15:18:59.000000000 +0200 @@ -80,8 +80,14 @@ List all known signal names. \fIname\fP specifies a file on a mounted file system or a block device that is mounted. All processes accessing files on that file system are listed. If a directory file is specified, it is automatically changed to -\fIname\fP/. to use any file system that might be mounted on that -directory. +\fIname\fP/. To use any file system that might be mounted on that +directory. Please note that due the required device ID comparision all +mounted file systems the +.BR stat (2) +system call will applied to every file system even on network file system +(NFS). If the NFS server does not respond or the network is down the +.BR stat (2) +may hang forever. .IP \fB\-n\ \fIspace\fP Select a different name space. The name spaces \fBfile\fP (file names, the default), \fBudp\fP (local UDP ports), and \fBtcp\fP (local TCP ports) are @@ -152,10 +158,13 @@ The \fB\-k\fP option only works on proce \fBfuser\fP will print an advice, but take no action beyond that. .SH BUGS .PP -fuser \-m /dev/sgX will show (or kill with the \-k flag) all processes, even +\fBfuser \-m \fI/dev/sgX\fR will show (or kill with the \fB\-k\fR flag) all processes, even if you don't have that device configured. There may be other devices it does this for too. .PP +\fBfuser \-m \fIname\fR may hang forever if there are NFS file systems mounted +and one of the NFS servers do not respond or the corresponding network is down. +.PP .B fuser cannot report on any processes that it doesn't have permission to look at the file descriptor table for. The most common time this problem occurs --- src/fuser.c +++ src/fuser.c 2009-03-12 10:21:40.980003767 +0100 @@ -32,9 +32,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -45,6 +47,7 @@ #include #include #include +#include #include "fuser.h" #include "signals.h" @@ -62,7 +65,7 @@ static void add_matched_proc(struct names *name_list, const pid_t pid, const uid_t uid, const char access); static void check_dir(const pid_t pid, const char *dirname, struct device_list *dev_head, struct inode_list *ino_head, const uid_t uid, const char access); 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 opt_type opts, const pid_t pid, const char *filename, char *real); static uid_t getpiduid(const pid_t pid); static int print_matches(struct names *names_head, const opt_type opts, const int sig_number); static void kill_matched_proc(struct procs *pptr, const opt_type opts, const int sig_number); @@ -72,13 +75,18 @@ static void add_device(struct device_lis void scan_mount_devices(const opt_type opts, struct mountdev_list **mount_devices); void fill_unix_cache(struct unixsocket_list **unixsocket_head); static dev_t find_net_dev(void); -static void scan_procs(struct names *names_head, struct inode_list *ino_head, struct device_list *dev_head); +static void scan_procs(const opt_type opts, struct names *names_head, struct inode_list *ino_head, struct device_list *dev_head); #ifdef NFS_CHECKS static void scan_knfsd(struct names *names_head, struct device_list *dev_head); #endif /* NFS_CHECKS */ #ifdef DEBUG static void debug_match_lists(struct names *names_head, struct inode_list *ino_head, struct device_list *dev_head); #endif +static struct nfs_points *mnts; +static int check4nfs(const char * path, char * real); + +typedef int (*stat_t)(const char*, struct stat*); +static int nfssafe(stat_t func, const char *path, struct stat *buf); static void usage (const char *errormsg) { @@ -127,7 +135,14 @@ void print_version() "For more information about these matters, see the files named COPYING.\n")); } -static void scan_procs(struct names *names_head, struct inode_list *ino_head, struct device_list *dev_head) +static int islocatedon(const char * path, const char * loc) +{ + if (!path || *path == '\0') + return 0; + return (strstr(path, loc) == path); +} + +static void scan_procs(const opt_type opts, struct names *names_head, struct inode_list *ino_head, struct device_list *dev_head) { DIR *topproc_dir; struct dirent *topproc_dent; @@ -137,6 +152,9 @@ static void scan_procs(struct names *nam pid_t pid, my_pid; uid_t uid; struct stat *cwd_stat, *exe_stat, *root_stat; + char root_real[PATH_MAX+1]; + char cwd_real[PATH_MAX+1]; + char exe_real[PATH_MAX+1]; if ( (fd_dirpath = malloc(MAX_PATHNAME)) == NULL) return; @@ -157,9 +175,10 @@ static void scan_procs(struct names *nam continue; uid = getpiduid(pid); - root_stat = get_pidstat(pid, "root"); - cwd_stat = get_pidstat(pid, "cwd"); - exe_stat = get_pidstat(pid, "exe"); + root_real[0] = cwd_real[0] = exe_real[0] = '\0'; + root_stat = get_pidstat(opts, pid, "root", root_real); + cwd_stat = get_pidstat(opts, pid, "cwd", cwd_real); + exe_stat = get_pidstat(opts, pid, "exe", exe_real); /* Scan the devices */ for (dev_tmp = dev_head ; dev_tmp != NULL ; dev_tmp = dev_tmp->next) { if (exe_stat != NULL && exe_stat->st_dev == dev_tmp->device) @@ -168,6 +187,14 @@ static void scan_procs(struct names *nam add_matched_proc(dev_tmp->name, pid, uid, ACCESS_ROOT); if (cwd_stat != NULL && cwd_stat->st_dev == dev_tmp->device) add_matched_proc(dev_tmp->name, pid, uid, ACCESS_CWD); + if ((dev_tmp->name->name_space & NAMESPACE_NFS) == 0) + continue; + if (islocatedon(&exe_real[0], dev_tmp->name->filename)) + add_matched_proc(dev_tmp->name, pid, uid, ACCESS_EXE); + if (islocatedon(&root_real[0], dev_tmp->name->filename)) + add_matched_proc(dev_tmp->name, pid, uid, ACCESS_ROOT); + if (islocatedon(&cwd_real[0], dev_tmp->name->filename)) + add_matched_proc(dev_tmp->name, pid, uid, ACCESS_CWD); } for (ino_tmp = ino_head ; ino_tmp != NULL ; ino_tmp = ino_tmp->next) { if (exe_stat != NULL) { @@ -186,9 +213,19 @@ static void scan_procs(struct names *nam add_matched_proc(ino_tmp->name, pid, uid, ACCESS_CWD); } } + if ((ino_tmp->name->name_space & NAMESPACE_NFS) == 0) + continue; + if (islocatedon(&exe_real[0], ino_tmp->name->filename)) + add_matched_proc(ino_tmp->name, pid, uid, ACCESS_EXE); + if (islocatedon(&root_real[0], ino_tmp->name->filename)) + add_matched_proc(ino_tmp->name, pid, uid, ACCESS_ROOT); + if (islocatedon(&cwd_real[0], ino_tmp->name->filename)) + add_matched_proc(ino_tmp->name, pid, uid, ACCESS_CWD); } +#ifndef __linux__ check_dir(pid, "lib", dev_head, ino_head, uid, ACCESS_MMAP); check_dir(pid, "mmap", dev_head, ino_head, uid, ACCESS_MMAP); +#endif check_dir(pid, "fd", dev_head, ino_head, uid, ACCESS_FILE); check_map(pid, "maps", dev_head, ino_head, uid, ACCESS_MMAP); @@ -325,10 +362,26 @@ int parse_mount(struct names *this_name, return 0; } -int parse_file(struct names *this_name, struct inode_list **ino_list) +int parse_file(struct names *this_name, struct inode_list **ino_list, const opt_type opts) { + char real[PATH_MAX+1] = ""; struct stat st; + real[0] = '\0'; + if (check4nfs(this_name->filename, real)) { + if ((opts & (OPT_MOUNTPOINT|OPT_MOUNTS)) == 0) { + free(this_name->filename); + this_name->filename = strdup(real); + this_name->name_space |= NAMESPACE_NFS; + add_inode(ino_list, this_name, (dev_t)-1, (ino_t)-1); + return 0; + } + } + if (real[0] != '\0') { + free(this_name->filename); + this_name->filename = strdup(real); + } + if (stat(this_name->filename, &st) != 0) { fprintf(stderr,_("Cannot stat %s: %s\n"), this_name->filename, strerror(errno)); @@ -342,34 +395,44 @@ int parse_file(struct names *this_name, return 0; } -int parse_unixsockets(struct names *this_name, struct inode_list **ino_list, struct unixsocket_list *sun_head) +int parse_unixsockets(struct names *this_name, struct inode_list **ino_list, struct unixsocket_list *sun_head, dev_t net_dev, const opt_type opts) { struct unixsocket_list *sun_tmp; struct stat st; - dev_t net_dev; - + + if (check4nfs(this_name->filename, NULL)) { + this_name->name_space |= NAMESPACE_NFS; + return 0; + } + if (stat(this_name->filename, &st) != 0) { fprintf(stderr,_("Cannot stat %s: %s\n"), this_name->filename, strerror(errno)); return -1; } - net_dev = find_net_dev(); for (sun_tmp = sun_head; sun_tmp != NULL ; sun_tmp = sun_tmp->next) { - if (sun_tmp->dev == st.st_dev && sun_tmp->inode == st.st_ino) { + if (sun_tmp->dev == st.st_dev && sun_tmp->inode == st.st_ino) { add_inode(ino_list, this_name, net_dev, sun_tmp->net_inode); - return 0; + return 0; } } return 0; } -int parse_mounts(struct names *this_name, struct mountdev_list *mounts, struct device_list **dev_list, const char opts) +int parse_mounts(struct names *this_name, struct mountdev_list *mounts, struct device_list **dev_list, const opt_type opts) { struct stat st; struct mountdev_list *mountptr; dev_t match_device; + char real[PATH_MAX+1] = ""; + + real[0] = '\0'; + if (check4nfs(this_name->filename, real)) { + this_name->name_space |= NAMESPACE_NFS; + goto skip; + } if (stat(this_name->filename, &st) != 0) { fprintf(stderr,_("Cannot stat %s: %s\n"), this_name->filename, @@ -388,6 +451,16 @@ int parse_mounts(struct names *this_name } } return 0; +skip: + match_device = -1; + for (mountptr = mounts ; mountptr != NULL ; mountptr = mountptr->next) { + if (strcmp(mountptr->dir, real) == 0) { + /*printf("Debug: adding parse_mounts() adding %s\n", + this_name->filename);*/ + add_device(dev_list, this_name, match_device); + } + } + return 0; } #ifdef WITH_IPV6 @@ -652,6 +725,85 @@ void find_net6_sockets(struct inode_list } #endif +/* + * Check path is located on a NFS partition. + */ +static int check4nfs(const char * path, char * real) +{ + char buf[PATH_MAX+1]; + const char *curr; + int deep = MAXSYMLINKS; + + if (!mnts) return 0; + + curr = path; + do { + const char *prev; + int len; + + if ((prev = strdupa(curr)) == NULL) + return 0; + + errno = 0; + if ((len = readlink(curr, buf, PATH_MAX)) < 0) + break; + buf[len] = '\0'; /* Don't be fooled by readlink(2) */ + + if (strncmp(prev, "/proc/", 6) == 0) { + curr = &buf[0]; + break; /* /proc/ provides the real path! */ + } + + if (len > 10) { + char *const ptr = &buf[len - 10]; + if (strcmp(ptr, " (deleted)") == 0) { + *ptr = '\0'; + curr = &buf[0]; + break; /* Path is deleted from VFS cache */ + } + } + + if (buf[0] != '/') { + const char *slash; + + if ((slash = strrchr(prev, '/'))) { + size_t off = slash - prev + 1; + + if (off + len > PATH_MAX) + len = PATH_MAX - off; + + memmove(&buf[off], &buf[0], len + 1); + memcpy(&buf[0], prev, off); + } + } + curr = &buf[0]; + + if (deep-- <= 0) return 0; + + } while (1); + + if (real) strcpy(real, curr); + + if (errno == EINVAL) { + const size_t nlen = strlen(curr); + struct nfs_points *p, *n, *l; + n = mnts; + l = (struct nfs_points*)0; + for (p = mnts; n; p = n) { + l = p->prev; + n = p->next; + if (nlen < p->nlen) + continue; + if (curr[p->nlen] != '\0' && curr[p->nlen] != '/') + continue; + if (!strncmp(curr, p->name, p->nlen)) + return 1; + } + } + + return 0; +} + int main(int argc, char *argv[]) { opt_type opts; @@ -676,6 +828,7 @@ int main(int argc, char *argv[]) int optc; char *option; char *nsptr; + size_t len; #ifdef WITH_IPV6 ipv4_only = ipv6_only = 0; @@ -692,7 +845,6 @@ int main(int argc, char *argv[]) #endif netdev = find_net_dev(); - scan_mount_devices(opts, &mount_devices); fill_unix_cache(&unixsockets); /* getopt doesnt like things like -SIGBLAH */ @@ -782,6 +934,10 @@ int main(int argc, char *argv[]) } continue; } + + if (!mount_devices) + scan_mount_devices(opts, &mount_devices); + /* File specifications */ if ( (this_name = malloc(sizeof(struct names))) == NULL) continue; @@ -828,10 +984,14 @@ int main(int argc, char *argv[]) break; default: /* FILE */ this_name->filename = strdup(argv[optc]); - parse_file(this_name, &match_inodes); - parse_unixsockets(this_name, &match_inodes, unixsockets); - if (opts & OPT_MOUNTPOINT || opts & OPT_MOUNTS) + len = strlen(this_name->filename); + if (len > 1 && this_name->filename[len-1] == '/') + this_name->filename[len-1] = '\0'; + parse_file(this_name, &match_inodes, opts); + parse_unixsockets(this_name, &match_inodes, unixsockets, netdev, opts); + if (opts & (OPT_MOUNTPOINT | OPT_MOUNTS)) { parse_mounts(this_name, mount_devices, &match_devices, opts); + } break; } @@ -857,22 +1017,22 @@ int main(int argc, char *argv[]) if (!ipv4_only) { #endif if (tcp_connection_list != NULL) - find_net_sockets(&match_inodes, tcp_connection_list, "tcp",netdev); + find_net_sockets(&match_inodes, tcp_connection_list, "tcp", netdev); if (udp_connection_list != NULL) - find_net_sockets(&match_inodes, udp_connection_list, "udp",netdev); + find_net_sockets(&match_inodes, udp_connection_list, "udp", netdev); #ifdef WITH_IPV6 } if (!ipv6_only) { if (tcp6_connection_list != NULL) - find_net6_sockets(&match_inodes, tcp6_connection_list, "tcp",netdev); + find_net6_sockets(&match_inodes, tcp6_connection_list, "tcp", netdev); if (udp6_connection_list != NULL) - find_net6_sockets(&match_inodes, udp6_connection_list, "udp",netdev); + find_net6_sockets(&match_inodes, udp6_connection_list, "udp", netdev); } #endif #ifdef DEBUG debug_match_lists(names_head, match_inodes, match_devices); #endif - scan_procs(names_head, match_inodes, match_devices); + scan_procs(opts, names_head, match_inodes, match_devices); #ifdef NFS_CHECKS scan_knfsd(names_head, match_devices); #endif /* NFS_CHECKS */ @@ -978,7 +1138,7 @@ static int print_matches(struct names *n } -static struct stat *get_pidstat(const pid_t pid, const char *filename) +static struct stat *get_pidstat(const opt_type opts, const pid_t pid, const char *filename, char *real) { char pathname[256]; struct stat *st; @@ -986,6 +1146,10 @@ static struct stat *get_pidstat(const pi if ( (st = malloc(sizeof(struct stat))) == NULL) return NULL; snprintf(pathname, 256, "/proc/%d/%s", pid, filename); + if (check4nfs(pathname, real)) { + if ((opts & (OPT_MOUNTPOINT|OPT_MOUNTS)) == 0) + return NULL; + } if (stat(pathname, st) != 0) return NULL; else @@ -1012,13 +1176,14 @@ static void check_dir(const pid_t pid, c while ( (direntry = readdir(dirp)) != NULL) { if (direntry->d_name[0] < '0' || direntry->d_name[0] > '9') continue; - snprintf(filepath, MAX_PATHNAME, "/proc/%d/%s/%s", pid, dirname, direntry->d_name); if (stat(filepath, &st) != 0) { fprintf(stderr, _("Cannot stat file %s: %s\n"),filepath, strerror(errno)); } else { for (dev_tmp = dev_head ; dev_tmp != NULL ; dev_tmp = dev_tmp->next) { + if (dev_tmp->name->name_space & NAMESPACE_NFS) + continue; if (st.st_dev == dev_tmp->device) { if (access == ACCESS_FILE && (lstat(filepath, &lst)==0) && (lst.st_mode & S_IWUSR)) { add_matched_proc(dev_tmp->name, pid,uid, ACCESS_FILEWR|access); @@ -1028,6 +1193,8 @@ static void check_dir(const pid_t pid, c } } for (ino_tmp = ino_head ; ino_tmp != NULL ; ino_tmp = ino_tmp->next) { + if (ino_tmp->name->name_space & NAMESPACE_NFS) + continue; if (st.st_dev == ino_tmp->device && st.st_ino == ino_tmp->inode) { if (access == ACCESS_FILE && (lstat(filepath, &lst)==0) && (lst.st_mode & S_IWUSR)) { add_matched_proc(ino_tmp->name, pid,uid, ACCESS_FILEWR|access); @@ -1039,6 +1206,8 @@ static void check_dir(const pid_t pid, c } } /* while fd_dent */ closedir(dirp); + free(dirpath); + free(filepath); } 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) @@ -1059,12 +1228,18 @@ static void check_map(const pid_t pid, c if (sscanf(line, "%*s %*s %*s %x:%x %lld", &tmp_maj, &tmp_min, &tmp_inode) == 3) { tmp_device = tmp_maj * 256 + tmp_min; - 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 (dev_tmp->name->name_space & NAMESPACE_NFS) + continue; if (dev_tmp->device == tmp_device) add_matched_proc(dev_tmp->name, pid, uid, access); - 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 (ino_tmp->name->name_space & NAMESPACE_NFS) + continue; if (ino_tmp->device == tmp_device && ino_tmp->inode == tmp_inode) add_matched_proc(ino_tmp->name, pid, uid, access); + } } } fclose(fp); @@ -1135,6 +1310,16 @@ void fill_unix_cache(struct unixsocket_l } +static inline int isnetfs(const char * type) +{ + static const char* netfs[] = {"nfs", "nfs4", "smbfs", "cifs", "afs", "ncpfs", (char*)0}; + int n; + for (n = 0; netfs[n]; n++) + if (!strcasecmp(netfs[n], type)) + return 1; + return 0; +} + /* * scan_mount_devices : Create a list of mount points and devices * This list is used later for matching purposes @@ -1144,17 +1329,53 @@ void scan_mount_devices(const opt_type o FILE *mntfp; struct mntent *mnt_ptr; struct stat st; - - if ( (mntfp = setmntent("/etc/mtab","r")) == NULL) { - fprintf(stderr, _("Cannot open /etc/mtab: %s\n"), - strerror(errno)); + + if (stat("/proc/version", &st) < 0) + mntfp = setmntent("/etc/mtab","r"); + else + mntfp = setmntent("/proc/mounts", "r"); + if (mntfp == NULL) { + fprintf(stderr, _("Cannot open /etc/mtab: %s\n"), strerror(errno)); return; } while ( (mnt_ptr = getmntent(mntfp)) != NULL) { + if (isnetfs(mnt_ptr->mnt_type)) { + /* + * Remember all NFS typed partitions, required to make check4nfs() work. + */ + struct nfs_points * p = (struct nfs_points*)malloc(sizeof(struct nfs_points)); + if (!p) + goto out; + p->name = strdup(mnt_ptr->mnt_dir); + if (!p->name) + goto out; + p->nlen = strlen(p->name); + if (mnts) + mnts->prev = p; + p->next = mnts; + p->prev = (struct nfs_points*)0; + mnts = p; + if ((opts & (OPT_MOUNTPOINT|OPT_MOUNTS)) == 0) { + add_mount_device(mount_devices, mnt_ptr->mnt_fsname, mnt_ptr->mnt_dir, (dev_t)-1); + continue; + } + if (nfssafe(stat, mnt_ptr->mnt_dir, &st) == 0) { + add_mount_device(mount_devices, mnt_ptr->mnt_fsname, mnt_ptr->mnt_dir, st.st_dev); + } else { + fprintf(stderr, _("Cannot stat file %s: %s\n"), mnt_ptr->mnt_dir, strerror(errno)); + } + continue; + } + if ((opts & (OPT_MOUNTPOINT|OPT_MOUNTS)) == 0) + continue; if (stat(mnt_ptr->mnt_dir, &st) == 0) { add_mount_device(mount_devices, mnt_ptr->mnt_fsname, mnt_ptr->mnt_dir, st.st_dev); + } else { + fprintf(stderr, _("Cannot stat file %s: %s\n"), mnt_ptr->mnt_dir, strerror(errno)); } } +out: + endmntent(mntfp); } #ifdef DEBUG @@ -1280,3 +1501,56 @@ static void scan_knfsd(struct names *nam } } #endif /* NFSCHECKS */ + +static sigjmp_buf jenv; +static int timeout = 5; +static void sigalarm(int sig) +{ + if (sig == SIGALRM) + siglongjmp(jenv, 1); +} +static int nfssafe(stat_t func, const char *path, struct stat *buf) +{ + pid_t pid = 0; + int ret = 0, pipes[4]; + + if (pipe(&pipes[0]) < 0) + goto err; + switch ((pid = fork ())) { + case -1: + close(pipes[0]); + close(pipes[1]); + goto err; + case 0: + (void) signal(SIGALRM, SIG_DFL); + close(pipes[0]); + if ((ret = func(path, buf)) == 0) + write(pipes[1], buf, sizeof(struct stat)); + close(pipes[1]); + exit(ret); + default: + close(pipes[1]); + if (sigsetjmp(jenv, 1)) { + (void) alarm(0); + (void) signal(SIGALRM, SIG_DFL); + if (waitpid(0, (int*)0, WNOHANG) == 0) + kill(pid, SIGKILL); + errno = ETIMEDOUT; + timeout = 1; + goto err; + } + (void) signal(SIGALRM, sigalarm); + (void) alarm(timeout); + if (read(pipes[0], buf, sizeof(struct stat)) == 0) { + errno = EFAULT; + ret = -1; + } + (void) alarm(0); + (void) signal(SIGALRM, SIG_DFL); + close(pipes[0]); + break; + } + return ret; +err: + return -1; +} --- src/fuser.h +++ src/fuser.h 2008-08-07 14:26:48.000000000 +0200 @@ -80,9 +80,16 @@ struct unixsocket_list { struct unixsocket_list *next; }; +struct nfs_points { + struct nfs_points *next, *prev; + char * name; + size_t nlen; +}; + #define NAMESPACE_FILE 0 #define NAMESPACE_TCP 1 #define NAMESPACE_UDP 2 +#define NAMESPACE_NFS 4 #define MAX_PATHNAME 200 #define MAX_CMDNAME 16