diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/comm.h | 42 | ||||
-rw-r--r-- | src/fuser.c | 2237 | ||||
-rw-r--r-- | src/fuser.h | 113 | ||||
-rw-r--r-- | src/i18n.h | 26 | ||||
-rw-r--r-- | src/killall.c | 1069 | ||||
-rw-r--r-- | src/lists.h | 373 | ||||
-rw-r--r-- | src/peekfd.c | 466 | ||||
-rw-r--r-- | src/prtstat.c | 341 | ||||
-rw-r--r-- | src/prtstat.h | 21 | ||||
-rw-r--r-- | src/pslog.c | 175 | ||||
-rw-r--r-- | src/pstree.c | 1568 | ||||
-rw-r--r-- | src/signals.c | 86 | ||||
-rw-r--r-- | src/signals.h | 19 | ||||
-rw-r--r-- | src/signames.c | 5 | ||||
-rw-r--r-- | src/socket_test.c | 55 | ||||
-rw-r--r-- | src/statx.c | 159 | ||||
-rw-r--r-- | src/statx.h | 73 |
17 files changed, 6828 insertions, 0 deletions
diff --git a/src/comm.h b/src/comm.h new file mode 100644 index 0000000..d08e538 --- /dev/null +++ b/src/comm.h @@ -0,0 +1,42 @@ +/* + * comm.h - command name length definition + * + * Copyright 1995 Werner Almesberger + * Copyright 2012-2024 Craig Small <csmall@dropbear.xyz> + * + * + * 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 COMM_H +#define COMM_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* + * COMM_LEN should be the same size as TASK_COMM_LEN in the Linux source + * at include/linux/sched.h + */ +#define COMM_LEN 64 + +/* + * Older kernels had only 16 characters, which means we may have to check this + * too + */ +#define OLD_COMM_LEN 16 + +#endif diff --git a/src/fuser.c b/src/fuser.c new file mode 100644 index 0000000..0c14128 --- /dev/null +++ b/src/fuser.c @@ -0,0 +1,2237 @@ +/* + * fuser.c - identify processes using files + * + * Based on fuser.c Copyright (C) 1993-2005 Werner Almesberger and Craig Small + * + * Completely re-written + * Copyright (C) 2005-2024 Craig Small <csmall@dropbear.xyz> + * + * 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 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/sysmacros.h> +#include <sys/wait.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <pwd.h> +#include <netdb.h> +#include <dirent.h> +#include <unistd.h> +#include <ctype.h> +#include <mntent.h> +#include <signal.h> +#include <getopt.h> +#include <setjmp.h> +#include <limits.h> +/* MAXSYMLINKS is a BSDism. If it doesn't exist, fall back to SYMLINK_MAX, + which is the POSIX name. */ +#ifndef MAXSYMLINKS +#define MAXSYMLINKS SYMLINK_MAX +#endif + +#ifdef ENABLE_NLS +#include <locale.h> +#endif + +#include "fuser.h" +#include "signals.h" +#include "i18n.h" +#include "statx.h" +#include "comm.h" + +//#define DEBUG 1 + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif /* PATH_MAX */ + +#define NAME_FIELD 20 /* space reserved for file name */ +/* Function defines */ +static void add_matched_proc(struct names *name_list, const pid_t pid, + const uid_t uid, const char access); +static void add_special_proc(struct names *name_list, const char ptype, + const uid_t uid, const char *command); +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, struct unixsocket_list *sockets, + dev_t netdev); +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 uid_t getpiduid(const pid_t pid); +static int print_matches(struct names *names_head, const opt_type opts, + const int sig_number); +static int kill_matched_proc(struct procs *pptr, const opt_type opts, + const int sig_number); + +/*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); +static void atexit_clear_unix_cache(); +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, + struct unixsocket_list *sockets, dev_t netdev); +static void scan_knfsd( + struct names *names_head, struct inode_list *ino_head, + struct device_list *dev_head); +static void scan_mounts( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head); +static void scan_swaps( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head); +#ifdef DEBUG +static void debug_match_lists( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head); +#endif + +static char *expandpath(const char *path); +static struct unixsocket_list *unixsockets = NULL; +static struct names *names_head = NULL, *names_tail = NULL; +static struct ip_connections *tcp_connection_list = NULL; +static struct ip_connections *udp_connection_list = NULL; +#ifdef WITH_IPV6 +static struct ip6_connections *tcp6_connection_list = NULL; +static struct ip6_connections *udp6_connection_list = NULL; +#endif +static struct device_list *match_devices = NULL; +static struct inode_list *match_inodes = NULL; + +static void usage( + const char *errormsg) +{ + if (errormsg != NULL) + fprintf(stderr, "%s\n", errormsg); + + fprintf(stderr, + _ + ("Usage: fuser [-fIMuvw] [-a|-s] [-4|-6] [-c|-m|-n SPACE]\n" + " [-k [-i] [-SIGNAL]] NAME...\n" + " fuser -l\n" + " fuser -V\n" + "Show which processes use the named files, sockets, or filesystems.\n\n" + " -a,--all display unused files too\n" + " -i,--interactive ask before killing (ignored without -k)\n" + " -I,--inode use always inodes to compare files\n" + " -k,--kill kill processes accessing the named file\n" + " -l,--list-signals list available signal names\n" + " -m,--mount show all processes using the named filesystems or\n" + " block device\n" + " -M,--ismountpoint fulfill request only if NAME is a mount point\n" + " -n,--namespace SPACE search in this name space (file, udp, or tcp)\n" + " -s,--silent silent operation\n" + " -SIGNAL send this signal instead of SIGKILL\n" + " -u,--user display user IDs\n" + " -v,--verbose verbose output\n" + " -w,--writeonly kill only processes with write access\n" + " -V,--version display version information\n")); +#ifdef WITH_IPV6 + fprintf(stderr, _(" -4,--ipv4 search IPv4 sockets only\n" + " -6,--ipv6 search IPv6 sockets only\n")); +#endif + fprintf(stderr, _(" udp/tcp names: [local_port][,[rmt_host][,[rmt_port]]]\n\n")); + exit(1); +} + +void print_version() +{ + fprintf(stderr, _("fuser (PSmisc) %s\n"), VERSION); + fprintf(stderr, + _("Copyright (C) 1993-2024 Werner Almesberger and Craig Small\n\n")); + fprintf(stderr, + _("PSmisc comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under\n" + "the terms of the GNU General Public License.\n" + "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, struct unixsocket_list *sockets, + dev_t netdev) +{ + DIR *topproc_dir; + struct dirent *topproc_dent; + struct inode_list *ino_tmp; + struct device_list *dev_tmp; + pid_t pid, my_pid; + uid_t uid; + + if ( (ino_head == NULL) && (dev_head == NULL) ) + return; + + if ((topproc_dir = opendir("/proc")) == NULL) { + fprintf(stderr, _("Cannot open /proc directory: %s\n"), + strerror(errno)); + exit(1); + } + my_pid = getpid(); + while ((topproc_dent = readdir(topproc_dir)) != NULL) { + dev_t cwd_dev, exe_dev, root_dev; + struct stat *cwd_stat = NULL; + struct stat *exe_stat = NULL; + struct stat *root_stat = NULL; + + if (topproc_dent->d_name[0] < '0' || topproc_dent->d_name[0] > '9') /* Not a process */ + continue; + pid = atoi(topproc_dent->d_name); + /* Dont print myself */ + if (pid == my_pid) + continue; + uid = getpiduid(pid); + + cwd_stat = get_pidstat(pid, "cwd"); + exe_stat = get_pidstat(pid, "exe"); + root_stat = get_pidstat(pid, "root"); + 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 (ino_tmp = ino_head; ino_tmp != NULL; + ino_tmp = ino_tmp->next) { + if (exe_dev == ino_tmp->device) { + if (!exe_stat) + exe_stat = get_pidstat(pid, "exe"); + 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_stat) + root_stat = get_pidstat(pid, "root"); + 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_stat) + cwd_stat = get_pidstat(pid, "cwd"); + if (cwd_stat + && cwd_stat->st_dev == ino_tmp->device + && cwd_stat->st_ino == ino_tmp->inode) + add_matched_proc(ino_tmp->name, pid, + uid, ACCESS_CWD); + } + } + if (root_stat) + free(root_stat); + if (cwd_stat) + free(cwd_stat); + if (exe_stat) + free(exe_stat); +#if !defined (__linux__) && !defined (__CYGWIN__) + check_dir(pid, "lib", dev_head, ino_head, uid, ACCESS_MMAP, + sockets, netdev); + check_dir(pid, "mmap", dev_head, ino_head, uid, ACCESS_MMAP, + sockets, netdev); +#endif + check_dir(pid, "fd", dev_head, ino_head, uid, ACCESS_FILE, + sockets, netdev); + check_map(pid, "maps", dev_head, ino_head, uid, ACCESS_MMAP); + + } /* while topproc_dent */ + closedir(topproc_dir); +} + +static void add_inode( + struct inode_list **ino_list, + struct names *this_name, + dev_t device, ino_t inode) +{ + struct inode_list *ino_tmp, *ino_head; + + if ((ino_tmp = + (struct inode_list *)malloc(sizeof(struct inode_list))) == NULL) + return; + ino_head = *ino_list; + ino_tmp->name = this_name; + ino_tmp->device = device; + ino_tmp->inode = inode; + ino_tmp->next = ino_head; + *ino_list = ino_tmp; +} + +static void add_device( + struct device_list **dev_list, + struct names *this_name, + dev_t device) +{ + struct device_list *dev_tmp, *dev_head; +#ifdef DEBUG + fprintf(stderr, "add_device(%s %u\n", this_name->filename, + (unsigned int)device); +#endif /* DEBUG */ + + if ((dev_tmp = + (struct device_list *)malloc(sizeof(struct device_list))) == NULL) + return; + dev_head = *dev_list; + dev_tmp->name = this_name; + dev_tmp->device = device; + dev_tmp->next = dev_head; + *dev_list = dev_tmp; +} + +static void add_ip_conn( + struct ip_connections **ip_list, const char *protocol, + struct names *this_name, const int lcl_port, const int rmt_port, + unsigned long rmt_address) +{ + struct ip_connections *ip_tmp, *ip_head; + + if ((ip_tmp = + (struct ip_connections *)malloc(sizeof(struct ip_connections))) == + NULL) + return; + ip_head = *ip_list; + ip_tmp->name = this_name; + ip_tmp->lcl_port = lcl_port; + ip_tmp->rmt_port = rmt_port; + ip_tmp->rmt_address.s_addr = rmt_address; + ip_tmp->next = ip_head; + + *ip_list = ip_tmp; +} + +#ifdef WITH_IPV6 +static void add_ip6_conn( + struct ip6_connections **ip_list, + const char *protocol, + struct names *this_name, + const int lcl_port, + const int rmt_port, + struct in6_addr rmt_address) +{ + struct ip6_connections *ip_tmp, *ip_head; + + if ((ip_tmp = + (struct ip6_connections *)malloc(sizeof(struct ip6_connections))) + == NULL) + return; + ip_head = *ip_list; + ip_tmp->name = this_name; + ip_tmp->lcl_port = lcl_port; + ip_tmp->rmt_port = rmt_port; + memcpy(&(ip_tmp->rmt_address), &(rmt_address), sizeof(struct in6_addr)); + ip_tmp->next = ip_head; + + *ip_list = ip_tmp; +} +#endif + +/* Adds a normal process only */ +static void add_matched_proc( + struct names *name_list, + const pid_t pid, + const uid_t uid, + const char access) +{ + struct procs *pptr, *last_proc; + char *pathname; + char cmdname[101], *cptr; + int cmdlen; + FILE *fp; + + last_proc = NULL; + for (pptr = name_list->matched_procs; pptr != NULL; pptr = pptr->next) + { + last_proc = pptr; + if (pptr->pid == pid) + { + pptr->access |= access; + return; + } + } + /* Not found */ + if ((pptr = (struct procs *)malloc(sizeof(struct procs))) == NULL) + { + fprintf(stderr, + _("Cannot allocate memory for matched proc: %s\n"), + strerror(errno)); + return; + } + pptr->pid = pid; + pptr->uid = uid; + pptr->access = access; + pptr->proc_type = PTYPE_NORMAL; + pptr->next = NULL; + /* set command name */ + pptr->command = NULL; + + fp = NULL; + pathname = NULL; + if ((asprintf(&pathname, "/proc/%d/stat", pid) > 0) && + ((fp = fopen(pathname, "r")) != NULL) && + (fscanf(fp, "%*d (%100[^)]", cmdname) == 1)) + if ((pptr->command = (char *)malloc(COMM_LEN + 1)) != NULL) + { + cmdlen = 0; + for (cptr = cmdname; cmdlen < COMM_LEN && *cptr; + cptr++) + { + if (isprint(*cptr)) + pptr->command[cmdlen++] = *cptr; + else if (cmdlen < (COMM_LEN - 4)) + cmdlen += + sprintf(&(pptr->command[cmdlen]), + "\\%03o", *cptr); + } + pptr->command[cmdlen] = '\0'; + } + if (last_proc == NULL) + name_list->matched_procs = pptr; + else + last_proc->next = pptr; + if (pathname) + free(pathname); + if (fp) + fclose(fp); +} + +/* Adds a knfsd etc process */ +static void add_special_proc( + struct names *name_list, + const char ptype, + const uid_t uid, + const char *command) +{ + struct procs *pptr; + + for (pptr = name_list->matched_procs; pptr != NULL; pptr = pptr->next) + { + if (pptr->proc_type == ptype) + return; + } + if ((pptr = malloc(sizeof(struct procs))) == NULL) + { + fprintf(stderr, + _("Cannot allocate memory for matched proc: %s\n"), + strerror(errno)); + return; + } + pptr->pid = 0; + pptr->uid = uid; + pptr->access = 0; + pptr->proc_type = ptype; + /* Append the special processes */ + pptr->next = name_list->matched_procs; + name_list->matched_procs = pptr; + /* set command name */ + pptr->command = strdup(command); +} + +int parse_file( + struct names *this_name, + struct inode_list **ino_list, + const opt_type opts) +{ + char *new = expandpath(this_name->filename); + if (new) + { + if (this_name->filename) + free(this_name->filename); + this_name->filename = strdup(new); + } + if (statn(this_name->filename, STATX_INO|STATX_TYPE, &(this_name->st)) != 0 ) + { + if (errno == ENOENT) + fprintf(stderr, + _("Specified filename %s does not exist.\n"), + this_name->filename); + else + fprintf(stderr, _("Cannot stat %s: %s\n"), + this_name->filename, strerror(errno)); + return -1; + } +#ifdef DEBUG + printf("adding file %s %lX %lX\n", this_name->filename, + (unsigned long)this_name->st.st_dev, + (unsigned long)this_name->st.st_ino); +#endif /* DEBUG */ + add_inode(ino_list, this_name, this_name->st.st_dev, + this_name->st.st_ino); + return 0; +} + +int parse_unixsockets( + struct names *this_name, + struct inode_list **ino_list, + struct unixsocket_list *sun_head) +{ + struct unixsocket_list *sun_tmp; + dev_t net_dev; + + net_dev = find_net_dev(); + + for (sun_tmp = sun_head; sun_tmp != NULL; sun_tmp = sun_tmp->next) + { + if (sun_tmp->dev == this_name->st.st_dev + && sun_tmp->inode == this_name->st.st_ino) + { + add_inode(ino_list, this_name, net_dev, + sun_tmp->net_inode); + return 0; + } + } + return 0; +} + +int parse_mounts( + struct names *this_name, + struct device_list **dev_list, + const opt_type opts) +{ + dev_t match_device; + + 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); + return 0; +} + +#ifdef WITH_IPV6 +int parse_inet( + struct names *this_name, + const int ipv4_only, + const int ipv6_only, + struct ip_connections **ip_list, + struct ip6_connections **ip6_list) +#else +int parse_inet( + struct names *this_name, + struct ip_connections **ip_list) +#endif +{ + struct addrinfo *res, *resptr; + struct addrinfo hints; + int errcode; + char *lcl_port_str, *rmt_addr_str, *rmt_port_str, *tmpstr, *tmpstr2; + in_port_t lcl_port; + struct sockaddr_in *sin; +#ifdef WITH_IPV6 + struct sockaddr_in6 *sin6; +#endif + char hostspec[100]; + char *protocol; + int i; + + if ((protocol = strchr(this_name->filename, '/')) == NULL) + return -1; + protocol++; + if (protocol[0] == '\0') + return -1; + for (i = 0; + i < 99 && this_name->filename[i] != '\0' + && this_name->filename[i] != '/'; i++) + hostspec[i] = this_name->filename[i]; + hostspec[i] = '\0'; + + lcl_port_str = rmt_addr_str = rmt_port_str = NULL; + /* Split out the names */ + if ((tmpstr = strchr(hostspec, ',')) == NULL) + { + /* Single option */ + lcl_port_str = strdup(hostspec); + } else { + if (tmpstr == hostspec) + lcl_port_str = NULL; + else { + *tmpstr = '\0'; + lcl_port_str = strdup(hostspec); + } + tmpstr++; + if (*tmpstr != '\0') { + if ((tmpstr2 = strchr(tmpstr, ',')) == NULL) + { + /* Only 2 options */ + rmt_addr_str = tmpstr; + } else { + if (tmpstr2 == tmpstr) + rmt_addr_str = NULL; + else { + rmt_addr_str = tmpstr; + *tmpstr2 = '\0'; + } + tmpstr2++; + if (*tmpstr2 != '\0') + rmt_port_str = tmpstr2; + } + } + } +#ifdef DEBUG + printf("parsed to lp %s rh %s rp %s\n", lcl_port_str, rmt_addr_str, + rmt_port_str); +#endif + + memset(&hints, 0, sizeof(hints)); +#ifdef WITH_IPV6 + if (ipv6_only) + { + hints.ai_family = PF_INET6; + } else if (ipv4_only) + { + hints.ai_family = PF_INET; + } else + hints.ai_family = PF_UNSPEC; +#else + hints.ai_family = PF_INET; +#endif + if (strcmp(protocol, "tcp") == 0) + hints.ai_socktype = SOCK_STREAM; + else + hints.ai_socktype = SOCK_DGRAM; + + if (lcl_port_str == NULL) + { + lcl_port = 0; + } else { + /* Resolve local port first */ + if ((errcode = + getaddrinfo(NULL, lcl_port_str, &hints, &res)) != 0) + { + fprintf(stderr, _("Cannot resolve local port %s: %s\n"), + lcl_port_str, gai_strerror(errcode)); + free(lcl_port_str); + return -1; + } + free(lcl_port_str); + if (res == NULL) + return -1; + switch (res->ai_family) + { + case AF_INET: + lcl_port = + ((struct sockaddr_in *)(res->ai_addr))->sin_port; + break; +#ifdef WITH_IPV6 + case AF_INET6: + lcl_port = + ((struct sockaddr_in6 *)(res->ai_addr))->sin6_port; + break; +#endif + default: + fprintf(stderr, _("Unknown local port AF %d\n"), + res->ai_family); + freeaddrinfo(res); + return -1; + } + freeaddrinfo(res); + } + res = NULL; + if (rmt_addr_str == NULL && rmt_port_str == NULL) + { + add_ip_conn(ip_list, protocol, this_name, ntohs(lcl_port), 0, + INADDR_ANY); +#ifdef WITH_IPV6 + add_ip6_conn(ip6_list, protocol, this_name, ntohs(lcl_port), 0, + in6addr_any); +#endif + return 0; + } else { + /* Resolve remote address and port */ + if (getaddrinfo(rmt_addr_str, rmt_port_str, &hints, &res) == 0) + { + for (resptr = res; resptr != NULL; + resptr = resptr->ai_next) + { + switch (resptr->ai_family) + { + case AF_INET: + sin = (struct sockaddr_in *) + resptr->ai_addr; + if (rmt_addr_str == NULL) + { + add_ip_conn(ip_list, protocol, this_name, + ntohs(lcl_port), ntohs(sin->sin_port), + INADDR_ANY); + } else { + add_ip_conn(ip_list, protocol, this_name, + ntohs(lcl_port), ntohs(sin->sin_port), + sin->sin_addr.s_addr); + } + break; +#ifdef WITH_IPV6 + case AF_INET6: + sin6 = (struct sockaddr_in6 *) resptr->ai_addr; + if (rmt_addr_str == NULL) + { + add_ip6_conn(ip6_list, protocol, this_name, + ntohs(lcl_port), ntohs(sin6->sin6_port), + in6addr_any); + } else { + add_ip6_conn(ip6_list, protocol, this_name, + ntohs(lcl_port), ntohs(sin6->sin6_port), + sin6->sin6_addr); + } + break; +#endif + } + } /*while */ + freeaddrinfo(res); + return 0; + } + } + return 1; +} + +void find_net_sockets( + struct inode_list **ino_list, + struct ip_connections *conn_list, + const char *protocol, + dev_t netdev) +{ + FILE *fp; + char pathname[200], line[BUFSIZ]; + unsigned long loc_port, rmt_port; + unsigned long rmt_addr; + unsigned long long scanned_inode; + ino_t inode; + struct ip_connections *conn_tmp; + + if (snprintf(pathname, 200, "/proc/net/%s", protocol) < 0) + return; + + if ((fp = fopen(pathname, "r")) == NULL) + { + fprintf(stderr, _("Cannot open protocol file \"%s\": %s\n"), + pathname, strerror(errno)); + return; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + if (sscanf + (line, + "%*u: %*x:%lx %08lx:%lx %*x %*x:%*x %*x:%*x %*x %*d %*d %llu", + &loc_port, &rmt_addr, &rmt_port, &scanned_inode) != 4) + continue; +#ifdef DEBUG + printf("Found IPv4 *:%lu with %s:%lu\n", loc_port, + inet_ntoa(*((struct in_addr *)&rmt_addr)), rmt_port); +#endif /* DEBUG */ + inode = scanned_inode; + for (conn_tmp = conn_list; conn_tmp != NULL; + conn_tmp = conn_tmp->next) + { +#ifdef DEBUG + printf(" Comparing with *.%lu %s:%lu\n", + conn_tmp->lcl_port, + inet_ntoa(conn_tmp->rmt_address), + conn_tmp->rmt_port); +#endif + if ((conn_tmp->lcl_port == 0 + || conn_tmp->lcl_port == loc_port) + && (conn_tmp->rmt_port == 0 + || conn_tmp->rmt_port == rmt_port) + && (conn_tmp->rmt_address.s_addr == INADDR_ANY + || + (memcmp + (&(conn_tmp->rmt_address), &(rmt_addr), + 4) == 0))) + { + /* add inode to list */ +#ifdef DEBUG + printf("Added inode!\n"); +#endif /* DEBUG */ + add_inode(ino_list, conn_tmp->name, netdev, inode); + } + } + + } + fclose(fp); +} + +#ifdef WITH_IPV6 +void find_net6_sockets( + struct inode_list **ino_list, + struct ip6_connections *conn_list, + const char *protocol, + const dev_t netdev) +{ + FILE *fp; + char pathname[200], line[BUFSIZ]; + unsigned long loc_port, rmt_port; + struct in6_addr rmt_addr; + unsigned int tmp_addr[4]; + char rmt_addr6str[INET6_ADDRSTRLEN]; + struct ip6_connections *conn_tmp; + unsigned long long scanned_inode; + ino_t inode; + + if (snprintf(pathname, 200, "/proc/net/%s6", protocol) < 0) + return; + + if ((fp = fopen(pathname, "r")) == NULL) + { +#ifdef DEBUG + printf("Cannot open protocol file \"%s\": %s\n", pathname, + strerror(errno)); +#endif /* DEBUG */ + return; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + if (sscanf + (line, + "%*u: %*x:%lx %08x%08x%08x%08x:%lx %*x %*x:%*x %*x:%*x %*x %*d %*d %llu", + &loc_port, &(tmp_addr[0]), &(tmp_addr[1]), &(tmp_addr[2]), + &(tmp_addr[3]), &rmt_port, &scanned_inode) != 7) + continue; + inode = scanned_inode; + rmt_addr.s6_addr32[0] = tmp_addr[0]; + rmt_addr.s6_addr32[1] = tmp_addr[1]; + rmt_addr.s6_addr32[2] = tmp_addr[2]; + rmt_addr.s6_addr32[3] = tmp_addr[3]; + inet_ntop(AF_INET6, &rmt_addr, rmt_addr6str, INET6_ADDRSTRLEN); +#ifdef DEBUG + printf("Found IPv6 %ld with %s:%ld\n", loc_port, rmt_addr6str, + rmt_port); +#endif /* DEBUG */ + for (conn_tmp = conn_list; conn_tmp != NULL; + conn_tmp = conn_tmp->next) + { + inet_ntop(AF_INET6, &conn_tmp->rmt_address, + rmt_addr6str, INET6_ADDRSTRLEN); +#ifdef DEBUG + printf(" Comparing with *.%lu %s:%lu ...\n", + conn_tmp->lcl_port, rmt_addr6str, + conn_tmp->rmt_port); +#endif /* DEBUG */ + if ((conn_tmp->lcl_port == 0 + || conn_tmp->lcl_port == loc_port) + && (conn_tmp->rmt_port == 0 + || conn_tmp->rmt_port == rmt_port) + && + (memcmp(&(conn_tmp->rmt_address), &in6addr_any, 16) + == 0 + || + (memcmp(&(conn_tmp->rmt_address), &(rmt_addr), 16) + == 0))) + { + add_inode(ino_list, conn_tmp->name, netdev, inode); + } + } + } + fclose(fp); +} +#endif + +static void read_proc_mounts( + struct mount_list **mnt_list) +{ + FILE *fp; + char line[BUFSIZ]; + char *find_mountp; + char *find_space; + struct mount_list *mnt_tmp; + + if ((fp = fopen(PROC_MOUNTS, "r")) == NULL) + { + 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 ((mnt_tmp = malloc(sizeof(struct mount_list))) == NULL) + continue; + if ((mnt_tmp->mountpoint = strdup(find_mountp)) == NULL) + { + free(mnt_tmp); + continue; + } + mnt_tmp->next = *mnt_list; + *mnt_list = mnt_tmp; + } + fclose(fp); +} + +static void free_proc_mounts( + struct mount_list *mnt_list) +{ + struct mount_list *mnt_tmp, *mnt_next; + + mnt_tmp = mnt_list; + while (mnt_tmp != NULL) + { + mnt_next = mnt_tmp->next; + free(mnt_tmp->mountpoint); + free(mnt_tmp); + mnt_tmp = mnt_next; + } +} + +/* + * Free up structures allocated in add_matched_proc and add_special_proc + */ +static void free_matched_procs( + struct procs *matched_procs) +{ + struct procs *procs_tmp, *procs_next; + + procs_tmp = matched_procs; + while (procs_tmp != NULL) + { + procs_next = procs_tmp->next; + if (procs_tmp->command) + free(procs_tmp->command); + free(procs_tmp); + procs_tmp = procs_next; + } +} + +static void free_names() +{ + while(names_head != NULL) + { + struct names *this_name = names_head; + names_head = this_name->next; + free_matched_procs(this_name->matched_procs); + free(this_name->filename); + free(this_name); + } + names_tail = NULL; +} + +/* + * Free up structures allocated in add_ip_conn and add_ip6_conn + */ +static void free_connection( + struct ip_connections **conn) +{ + struct ip_connections *conn_tmp, *conn_next; + + conn_tmp = *conn; + while(conn_tmp != NULL) + { + conn_next = conn_tmp->next; + free(conn_tmp); + conn_tmp = conn_next; + } + *conn = NULL; +} + +#ifdef WITH_IPV6 +static void free_connection6( + struct ip6_connections **conn) +{ + struct ip6_connections *conn_tmp, *conn_next; + + conn_tmp = *conn; + while(conn_tmp != NULL) + { + conn_next = conn_tmp->next; + free(conn_tmp); + conn_tmp = conn_next; + } + *conn = NULL; +} +#endif + +/* + * Free up structures allocated in add_inode + */ +static void free_inodes( + struct inode_list **match_inodes) +{ + struct inode_list *inode_tmp, *inode_next; + + inode_tmp = *match_inodes; + while(inode_tmp != NULL) + { + inode_next = inode_tmp->next; + free(inode_tmp); + inode_tmp = inode_next; + } + *match_inodes = NULL; +} + +/* + * Free up structures allocated in add_device + */ +static void free_devices( + struct device_list **match_devices) +{ + struct device_list *device_tmp, *device_next; + + device_tmp = *match_devices; + while(device_tmp != NULL) + { + device_next = device_tmp->next; + free(device_tmp); + device_tmp = device_next; + } + *match_devices = NULL; +} + +static void atexit_free_lists() +{ + free_connection(&tcp_connection_list); + free_connection(&udp_connection_list); +#ifdef WITH_IPV6 + free_connection6(&tcp6_connection_list); + free_connection6(&udp6_connection_list); +#endif + + free_inodes(&match_inodes); + free_devices(&match_devices); + + free_names(); +} + + +static int is_mountpoint( + struct mount_list **mnt_list, + char *arg) +{ + char *p; + struct mount_list *mnt_tmp; + + if (*arg == '\0') + return 0; + /* Remove trailing slashes. */ + for (p = arg; *p != '\0'; p++) + /*do nothing*/; + while (*(--p) == '/' && p > arg) + *p = '\0'; + + for (mnt_tmp = *mnt_list; mnt_tmp != NULL; mnt_tmp = mnt_tmp->next) + if (!strcmp(mnt_tmp->mountpoint, arg)) + return 1; + return 0; +} + +static void check_mountpoints( + struct mount_list **mnt_list, + struct names **head, + struct names **tail) +{ + struct names *this, *last; + + last = NULL; + for(this = *head; this != NULL; this = this->next) + { + if (this->name_space == NAMESPACE_FILE && + !is_mountpoint(mnt_list, this->filename)) + { + fprintf(stderr, + _("Specified filename %s is not a mountpoint.\n"), + this->filename); + /* Remove from list */ + if (last) + last->next = this->next; + if (*head == this) + *head = this->next; + if (*tail == this) + *tail = last; + } else { + last = this; + } + } +} + +int main(int argc, char *argv[]) +{ + opt_type opts; + int sig_number; +#ifdef WITH_IPV6 + int ipv4_only, ipv6_only; +#endif + unsigned char default_namespace = NAMESPACE_FILE; + + dev_t netdev; + struct names *this_name; + int argc_cnt; + char *current_argv, *option; + char option_buf[3]; + struct option *optr; + char *nsptr; + int skip_argv; + + struct option options[] = { + {"all", 0, NULL, 'a'}, + {"kill", 0, NULL, 'k'}, + {"interactive", 0, NULL, 'i'}, + {"inode", 0, NULL, 'I'}, + {"list-signals", 0, NULL, 'l'}, + {"mount", 0, NULL, 'm'}, + {"ismountpoint", 0, NULL, 'M'}, + {"namespace", 1, NULL, 'n'}, + {"silent", 0, NULL, 's'}, + {"user", 0, NULL, 'u'}, + {"verbose", 0, NULL, 'v'}, + {"writeonly", 0, NULL, 'w'}, + {"version", 0, NULL, 'V'}, +#ifdef WITH_IPV6 + {"ipv4", 0, NULL, '4'}, + {"ipv6", 0, NULL, '6'}, +#endif + {0, 0, 0, 0} + }; + +#ifdef WITH_IPV6 + ipv4_only = ipv6_only = 0; +#endif + this_name = NULL; + opts = 0; + sig_number = SIGKILL; + +#ifdef ENABLE_NLS + /* Set up the i18n */ + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + netdev = find_net_dev(); +#ifndef __CYGWIN__ /* Cygwin doesn't support /proc/net/unix */ + fill_unix_cache(&unixsockets); + atexit(atexit_clear_unix_cache); +#endif + atexit(atexit_free_lists); + + for (argc_cnt = 1; argc_cnt < argc; argc_cnt++) + { + current_argv = argv[argc_cnt]; + if (current_argv[0] == '-') /* its an option */ + { + if (current_argv[1] == '-') /* its a long option */ + { + if (current_argv[2] == '\0') /* -- */ + break; + /* Parse the long options */ + option = option_buf; + for (optr = options; optr->name != NULL; optr++) + { + if (strcmp(current_argv + 2, optr->name) == 0) + { + sprintf(option_buf, "-%c", (char)optr->val); + break; + } + } + if (optr->name == NULL) + { + fprintf(stderr, _("%s: Invalid option %s\n"), + argv[0], current_argv); + usage(NULL); + } + } else { + option = current_argv; + } + skip_argv = 0; + while (*(++option) != '\0' && !skip_argv)/* skips over the - */ + { + switch (*option) + { +#ifdef WITH_IPV6 + case '4': + ipv4_only = 1; + break; + case '6': + ipv6_only = 1; + break; +#endif /* WITH_IPV6 */ + case 'a': + opts |= OPT_ALLFILES; + break; + case 'c': + opts |= OPT_MOUNTS; + break; + case 'f': + /* ignored */ + break; + case 'h': + usage(NULL); + break; + case 'i': + opts |= OPT_INTERACTIVE; + break; + case 'I': + opts |= OPT_ALWAYSSTAT; + break; + case 'k': + opts |= OPT_KILL; + break; + case 'l': + list_signals(); + return 0; + case 'm': + opts |= OPT_MOUNTS; + break; + case 'M': + opts |= OPT_ISMOUNTPOINT; + break; + case 'n': + argc_cnt++; + if (argc_cnt >= argc) + { + usage(_ + ("Namespace option requires an argument.")); + } + skip_argv = 1; + //while(option != '\0') option++; + if (strcmp(argv[argc_cnt], "tcp") == 0) + default_namespace = NAMESPACE_TCP; + else if (strcmp(argv[argc_cnt], "udp") == 0) + default_namespace = NAMESPACE_UDP; + else if (strcmp(argv[argc_cnt], "file") == 0) + default_namespace = NAMESPACE_FILE; + else + usage(_ + ("Invalid namespace name")); + break; + case 's': + opts |= OPT_SILENT; + break; + case 'u': + opts |= OPT_USER; + break; + case 'v': + opts |= OPT_VERBOSE; + break; + case 'w': + opts |= OPT_WRITE; + break; + case 'V': + print_version(); + return 0; + default: + if (isupper(*option) || isdigit(*option)) + { + sig_number = get_signal(current_argv + 1, argv[0]); + skip_argv = 1; + break; + } + fprintf(stderr, "%s: Invalid option %c\n", + argv[0], *option); + usage(NULL); + break; + } /* switch */ + } /* while option */ + continue; + } + +#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 */ + if ((this_name = malloc(sizeof(struct names))) == NULL) + continue; + this_name->next = NULL; + /* try to find namespace spec */ + this_name->name_space = default_namespace; + if (((nsptr = strchr(current_argv, '/')) != NULL) + && (nsptr != current_argv)) + { + if (strcmp(nsptr + 1, "tcp") == 0) + { + this_name->name_space = NAMESPACE_TCP; + *nsptr = '\0'; + } else if (strcmp(nsptr + 1, "udp") == 0) + { + this_name->name_space = NAMESPACE_UDP; + *nsptr = '\0'; + } else if (strcmp(nsptr + 1, "file") == 0) + { + this_name->name_space = NAMESPACE_FILE; + *nsptr = '\0'; + } + } + this_name->matched_procs = NULL; + if (opts & (OPT_MOUNTS | OPT_ISMOUNTPOINT) + && this_name->name_space != NAMESPACE_FILE) + { + free(this_name); + usage(_ + ("You can only use files with mountpoint options")); + } + switch (this_name->name_space) + { + case NAMESPACE_TCP: + if (asprintf + (&(this_name->filename), "%s/tcp", + current_argv) > 0) + { +#ifdef WITH_IPV6 + parse_inet(this_name, ipv4_only, ipv6_only, + &tcp_connection_list, &tcp6_connection_list); +#else + parse_inet(this_name, &tcp_connection_list); +#endif + } + break; + case NAMESPACE_UDP: + if (asprintf (&(this_name->filename), "%s/udp", current_argv) > 0) + { +#ifdef WITH_IPV6 + parse_inet(this_name, ipv4_only, ipv6_only, + &udp_connection_list, &udp6_connection_list); +#else + parse_inet(this_name, &udp_connection_list); +#endif + } + break; + default: /* FILE */ + this_name->filename = strdup(current_argv); + if (parse_file(this_name, &match_inodes, opts) == 0) + { + if (opts & OPT_MOUNTS) + parse_mounts(this_name, &match_devices, opts); + else + parse_unixsockets(this_name, &match_inodes, unixsockets); + } + break; + } + + if (names_head == NULL) + names_head = this_name; + if (names_tail != NULL) + names_tail->next = this_name; + names_tail = this_name; + } /* for across the argvs */ + if (names_head == NULL) + usage(_("No process specification given")); + + /* Check if -M flag was used and if so check mounts */ + if (opts & OPT_ISMOUNTPOINT) + { + struct mount_list *mounts = NULL; + read_proc_mounts(&mounts); + check_mountpoints(&mounts, &names_head, &names_tail); + free_proc_mounts(mounts); + } + + if (opts & OPT_SILENT) + { + opts &= ~OPT_VERBOSE; + opts &= ~OPT_USER; + if (opts & OPT_ALLFILES) + usage(_ + ("all option cannot be used with silent option.")); + } +#ifdef WITH_IPV6 + if (ipv4_only && ipv6_only) + usage(_ + ("You cannot search for only IPv4 and only IPv6 sockets at the same time")); + if (!ipv6_only) + { +#endif + if (tcp_connection_list != NULL) + 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); +#ifdef WITH_IPV6 + } + if (!ipv4_only) + { + if (tcp6_connection_list != NULL) + 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); + } +#endif + free_connection(&tcp_connection_list); + free_connection(&udp_connection_list); +#ifdef WITH_IPV6 + free_connection6(&tcp6_connection_list); + free_connection6(&udp6_connection_list); +#endif +#ifdef DEBUG + debug_match_lists(names_head, match_inodes, match_devices); +#endif + scan_procs(names_head, match_inodes, match_devices, unixsockets, netdev); +#ifndef __CYGWIN__ /* Cygwin doesn't support /proc/net/unix */ + clear_unix_cache(&unixsockets); +#endif + scan_knfsd(names_head, match_inodes, match_devices); + scan_mounts(names_head, match_inodes, match_devices); + scan_swaps(names_head, match_inodes, match_devices); + free_inodes(&match_inodes); + free_devices(&match_devices); + return print_matches(names_head, opts, sig_number); +} + +/* + * returns 0 if match, 1 if no match + */ +static int print_matches( + struct names *names_head, + const opt_type opts, + const int sig_number) +{ + struct names *nptr; + struct procs *pptr; + char head = 0; + char first = 1; + int len = 0; + struct passwd *pwent = NULL; + int have_match = 0; + int have_kill = 0; + int name_has_procs = 0; + + for (nptr = names_head; nptr != NULL; nptr = nptr->next) + { + if (opts & OPT_SILENT) + { + for (pptr = nptr->matched_procs; pptr != NULL; + pptr = pptr->next) + { + if (pptr->proc_type != PTYPE_NORMAL) + continue; + + have_match = 1; + } + } else { /* We're not silent */ + if ((opts & OPT_ALLFILES) == 0) + { + name_has_procs = 0; + if (opts & OPT_VERBOSE) + { + if (nptr->matched_procs) + name_has_procs = 1; + } else { + for (pptr = nptr->matched_procs; + pptr != NULL; pptr = pptr->next) + { + if (pptr->proc_type == PTYPE_NORMAL) + { + name_has_procs = 1; + break; + } + } + } + } + if (name_has_procs == 1 || opts & OPT_ALLFILES) + { + if (head == 0 && opts & OPT_VERBOSE) + { + fprintf(stderr, + _("%*s USER PID ACCESS COMMAND\n"), NAME_FIELD, ""); + head = 1; + } + + fprintf(stderr, "%s:", nptr->filename); + len = strlen(nptr->filename) + 1; + } + + first = 1; + for (pptr = nptr->matched_procs; pptr != NULL; pptr = pptr->next) + { + /* Suppress any special "processes" */ + if (!(opts & OPT_VERBOSE) + && (pptr->proc_type != PTYPE_NORMAL)) + continue; + + have_match = 1; + if (opts & (OPT_VERBOSE | OPT_USER)) + { + if (pwent == NULL || pwent->pw_uid != pptr->uid) + pwent = getpwuid(pptr->uid); + } + if (len > NAME_FIELD && (opts & OPT_VERBOSE)) + { + putc('\n', stderr); + len = 0; + } + if ((opts & OPT_VERBOSE) || first) + while (len++ < NAME_FIELD) + putc(' ', stderr); + if (opts & OPT_VERBOSE) + { + if (pwent == NULL) + fprintf(stderr, " %-8s ", + _("(unknown)")); + else + fprintf(stderr, " %-8s ", + pwent->pw_name); + } + if (pptr->proc_type == PTYPE_NORMAL) + printf(" %5d", pptr->pid); + else + printf("kernel"); + fflush(stdout); + if (opts & OPT_VERBOSE) + { + switch (pptr->proc_type) + { + case PTYPE_KNFSD: + fprintf(stderr, " knfsd "); + break; + case PTYPE_MOUNT: + fprintf(stderr, " mount "); + break; + case PTYPE_SWAP: + fprintf(stderr, " swap "); + break; + default: + fprintf(stderr, " %c%c%c%c%c ", + pptr->access & ACCESS_FILE ? + (pptr->access & ACCESS_FILEWR ? 'F' : 'f') : '.', + pptr->access & ACCESS_ROOT ? 'r' : '.', + pptr->access & ACCESS_CWD ? 'c' : '.', + pptr->access & ACCESS_EXE ? 'e' : '.', + (pptr->access & ACCESS_MMAP) + && !(pptr-> access & ACCESS_EXE) ? 'm' : '.'); + } /* switch */ + } else { + if (pptr->access & ACCESS_ROOT) + putc('r', stderr); + if (pptr->access & ACCESS_CWD) + putc('c', stderr); + if (pptr->access & ACCESS_EXE) + putc('e', stderr); + else if (pptr->access & ACCESS_MMAP) + putc('m', stderr); + } + if (opts & OPT_USER) + { + if (pwent == NULL) + fprintf(stderr, " %-8s ", _("(unknown)")); + else + fprintf(stderr, "(%s)", pwent->pw_name); + } + if (opts & OPT_VERBOSE) + { + if (pptr->command == NULL) + fprintf(stderr, "???\n"); + else + fprintf(stderr, "%s\n", pptr->command); + } + len = 0; + first = 0; + } + if (opts & OPT_VERBOSE) + { + /* put a newline if showing all files and no procs */ + if (nptr->matched_procs == NULL && (opts & OPT_ALLFILES)) + putc('\n', stderr); + } else { + if (name_has_procs || (opts & OPT_ALLFILES)) + putc('\n', stderr); + } + } /* be silent */ + if (opts & OPT_KILL) + have_kill |= kill_matched_proc(nptr->matched_procs, + opts, sig_number); + + } /* next name */ + if (opts & OPT_KILL) + return (have_kill == 1 ? 0 : 1); + else + return (have_match == 1 ? 0 : 1); + +} + +static struct stat *get_pidstat( + const pid_t pid, + const char *filename) +{ + char pathname[PATH_MAX]; + struct stat *st; + + if ((st = (struct stat *)malloc(sizeof(struct stat))) == NULL) + return NULL; + snprintf(pathname, PATH_MAX-1, "/proc/%d/%s", pid, filename); + if (statn(pathname, STATX_UID|STATX_INO|STATX_TYPE, st) != 0) + { + free(st); + return NULL; + } + return st; +} + +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, + struct unixsocket_list *sockets, + dev_t netdev) +{ + DIR *dirp; + dev_t thedev; + struct dirent *direntry; + struct inode_list *ino_tmp; + struct device_list *dev_tmp; + struct unixsocket_list *sock_tmp; + struct stat st, lst; + char *dirpath; + char filepath[PATH_MAX]; + char real_filepath[PATH_MAX]; + + if (asprintf(&dirpath, "/proc/%d/%s", pid, dirname) < 0) + return; + if ((dirp = opendir(dirpath)) == NULL) + { + free(dirpath); + return; + } + free(dirpath); + + while ((direntry = readdir(dirp)) != NULL) + { + if (direntry->d_name[0] < '0' || direntry->d_name[0] > '9') + continue; + + snprintf(filepath, sizeof filepath - 1, "/proc/%d/%s/%s", + pid, dirname, direntry->d_name); + + if (statn(filepath, STATX_INO, &st) != 0) + { + if (errno != ENOENT && errno != ENOTDIR && errno != EACCES) + { + fprintf(stderr, _("Cannot stat file %s: %s\n"), + filepath, strerror(errno)); + } + } else { + thedev = st.st_dev; + if (thedev == netdev) + { + for (sock_tmp = sockets; sock_tmp != NULL; + sock_tmp = sock_tmp->next) + { + if (sock_tmp->net_inode == st.st_ino) + { + st.st_ino = sock_tmp->inode; + st.st_dev = sock_tmp->dev; + thedev = sock_tmp->dev; + break; + } + } + } + for (dev_tmp = dev_head; dev_tmp != NULL; + dev_tmp = dev_tmp->next) + { + if (thedev != dev_tmp->device) + continue; + + /* check the paths match if it is not a block device or socket */ + if (! S_ISBLK(dev_tmp->name->st.st_mode) + && !S_ISSOCK(st.st_mode)) + { + if (readlink(filepath, real_filepath, PATH_MAX-1) < 0) + { + if (strncmp(dev_tmp->name->filename, filepath, + strlen(dev_tmp->name->filename)) != 0) + continue; + } else { + if (strncmp(dev_tmp->name->filename, real_filepath, + strlen(dev_tmp->name->filename)) != 0) + continue; + } + } + if (access == ACCESS_FILE + && (lstat(filepath, &lst) == 0) + && (lst.st_mode & S_IWUSR)) + { + add_matched_proc(dev_tmp->name, pid, uid, + ACCESS_FILEWR | access); + } else { + add_matched_proc(dev_tmp->name, pid, uid, access); + } + } + for (ino_tmp = ino_head; ino_tmp != NULL; ino_tmp = ino_tmp->next) + { + if (thedev != ino_tmp->device) + continue; + if (!st.st_ino && statn(filepath, STATX_INO, &st) != 0) + { + fprintf(stderr, _("Cannot stat file %s: %s\n"), + filepath, strerror(errno)); + continue; + } + if (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); + } else { + add_matched_proc(ino_tmp->name, pid, uid, access); + } + } + } + } + } /* while fd_dent */ + closedir(dirp); +} + +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) +{ + char *pathname; + char line[BUFSIZ]; + struct inode_list *ino_tmp; + struct device_list *dev_tmp; + 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; + if ((fp = fopen(pathname, "r")) == NULL) + { + free(pathname); + return; + } + free(pathname); + while (fgets(line, BUFSIZ, fp)) + { + 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) + 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) + if (ino_tmp->device == tmp_device + && ino_tmp->inode == tmp_inode) + add_matched_proc(ino_tmp->name, pid, uid, access); + } + } + fclose(fp); +} + +static uid_t getpiduid( + const pid_t pid) +{ + char *pathname; + struct stat st; + + if (asprintf(&pathname, "/proc/%d", pid) < 0) + return 0; + if (statn(pathname, STATX_UID, &st) != 0) + { + free(pathname); + return 0; + } + free(pathname); + return st.st_uid; +} + +/* + * fill_unix_cache : Create a list of Unix sockets + * This list is used later for matching purposes + */ +void fill_unix_cache( + struct unixsocket_list **unixsocket_head) +{ + FILE *fp; + char line[BUFSIZ]; + unsigned long long scanned_inode; + struct stat st; + struct unixsocket_list *newsocket; + + if ((fp = fopen("/proc/net/unix", "r")) == NULL) + { + fprintf(stderr, _("Cannot open /proc/net/unix: %s\n"), + strerror(errno)); + return; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + char *path; + char *scanned_path = NULL; + if (sscanf(line, "%*x: %*x %*x %*x %*x %*d %llu %ms", + &scanned_inode, &scanned_path) != 2) + { + if (scanned_path) + free(scanned_path); + continue; + } + if (scanned_path == NULL) + continue; + path = scanned_path; + if (*scanned_path == '@') + scanned_path++; + if (statn(scanned_path, STATX_INO, &st) < 0) + { + free(path); + continue; + } + if ((newsocket = (struct unixsocket_list *) + malloc(sizeof(struct unixsocket_list))) == NULL) + { + free(path); + continue; + } + newsocket->sun_name = strdup(scanned_path); + newsocket->inode = st.st_ino; + newsocket->dev = st.st_dev; + newsocket->net_inode = scanned_inode; + newsocket->next = *unixsocket_head; + *unixsocket_head = newsocket; + free(path); + } /* while */ + + fclose(fp); +} + +/* + * Free up the list of Unix sockets + */ +void clear_unix_cache( + struct unixsocket_list **unixsocket_head) +{ + while(*unixsocket_head != NULL) + { + struct unixsocket_list *oldsocket = *unixsocket_head; + *unixsocket_head = oldsocket->next; + free(oldsocket->sun_name); + free(oldsocket); + } +} + +static void atexit_clear_unix_cache() +{ + clear_unix_cache(&unixsockets); +} + +#ifdef DEBUG +/* often not used, doesn't need translation */ +static void debug_match_lists( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head) +{ + struct names *nptr; + struct inode_list *iptr; + struct device_list *dptr; + + fprintf(stderr, "Specified Names:\n"); + for (nptr = names_head; nptr != NULL; nptr = nptr->next) + { + fprintf(stderr, "\t%s %c\n", nptr->filename, nptr->name_space); + } + fprintf(stderr, "\nInodes:\n"); + for (iptr = ino_head; iptr != NULL; iptr = iptr->next) + { + fprintf(stderr, " Dev:%0lx Inode:(%0ld) 0x%0lx => %s\n", + (unsigned long)iptr->device, (unsigned long)iptr->inode, + (unsigned long)iptr->inode, iptr->name->filename); + } + fprintf(stderr, "\nDevices:\n"); + for (dptr = dev_head; dptr != NULL; dptr = dptr->next) + { + fprintf(stderr, "\tDev:%0lx\n", (unsigned long)dptr->device); + } +} + +#endif + +/* 0 = no, 1=yes */ +static int ask( + const pid_t pid) +{ + int res; + size_t len = 0; + char *line = NULL; + + fflush(stdout); + while (1) + { + fprintf(stderr, _("Kill process %d ? (y/N) "), pid); + fflush(stderr); + if (getline(&line, &len, stdin) < 0) + return 0; + if (line[0] == '\n') + { + free(line); + return 0; + } + res = rpmatch(line); + if (res >= 0) + { + free(line); + return res; + } + } /* while */ +} + +static int kill_matched_proc( + struct procs *proc_head, + const opt_type opts, + const int sig_number) +{ + struct procs *pptr; + pid_t mypid; + int ret = 0; + + mypid = getpid(); + + for (pptr = proc_head; pptr != NULL; pptr = pptr->next) + { + if (pptr->pid == mypid) + continue; /* dont kill myself */ + if (pptr->proc_type != PTYPE_NORMAL) + continue; + if ((opts & OPT_WRITE) && ((pptr->access & ACCESS_FILEWR) == 0)) + continue; + if ((opts & OPT_INTERACTIVE) && (ask(pptr->pid) == 0)) + continue; + if (kill(pptr->pid, sig_number) < 0) + { + fprintf(stderr, _("Could not kill process %d: %s\n"), + pptr->pid, strerror(errno)); + continue; + } + ret = 1; + } + return ret; +} + +static dev_t find_net_dev(void) +{ + int skt; + struct stat st; + + if ((skt = socket(PF_INET, SOCK_DGRAM, 0)) < 0) + { + fprintf(stderr, _("Cannot open a network socket.\n")); + return -1; + } + if (fstatn(skt, STATX_INO, &st) != 0) + { + fprintf(stderr, _("Cannot find socket's device number.\n")); + close(skt); + return -1; + } + close(skt); + return st.st_dev; +} + +static void scan_knfsd( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head) +{ + struct device_list *dev_tmp; + struct inode_list *ino_tmp; + FILE *fp; + char line[BUFSIZ]; + char *find_space; + struct stat st; + + if ( (ino_head == NULL) && (dev_head == NULL) ) + return; + + + if ((fp = fopen(KNFSD_EXPORTS, "r")) == NULL) + { +#ifdef DEBUG + printf("Cannot open %s\n", KNFSD_EXPORTS); +#endif + return; + } + while (fgets(line, BUFSIZ, fp) != NULL) + { + if (line[0] == '#') + continue; + + if ((find_space = strpbrk(line, " \t")) == NULL) + continue; + + *find_space = '\0'; + if (statn(line, STATX_INO, &st) != 0) + continue; + + /* Scan the devices */ + for (dev_tmp = dev_head; dev_tmp != NULL; + dev_tmp = dev_tmp->next) + { + if (st.st_dev == dev_tmp->device) + add_special_proc(dev_tmp->name, PTYPE_KNFSD, 0, line); + } + + 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) + add_special_proc(ino_tmp->name, PTYPE_KNFSD, 0, line); + } + } + fclose(fp); +} + +static void scan_mounts( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head) +{ + struct device_list *dev_tmp; + struct inode_list *ino_tmp; + FILE *fp; + char line[BUFSIZ]; + char *find_mountp; + char *find_space; + struct stat st; + + if ( (ino_head == NULL) && (dev_head == NULL) ) + return; + + + if ((fp = fopen(PROC_MOUNTS, "r")) == NULL) + { + 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) + continue; + /* Scan the devices */ + for (dev_tmp = dev_head; dev_tmp != NULL; + dev_tmp = dev_tmp->next) + { + if (st.st_dev == dev_tmp->device) + add_special_proc(dev_tmp->name, PTYPE_MOUNT, 0, find_mountp); + } + 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) + add_special_proc(ino_tmp->name, PTYPE_MOUNT, 0, find_mountp); + } + } + fclose(fp); +} + +static void scan_swaps( + struct names *names_head, + struct inode_list *ino_head, + struct device_list *dev_head) +{ + struct device_list *dev_tmp; + struct inode_list *ino_tmp; + FILE *fp; + char line[BUFSIZ]; + char *find_space; + struct stat st; + + if ( (ino_head == NULL) && (dev_head == NULL) ) + return; + + if ((fp = fopen(PROC_SWAPS, "r")) == NULL) + { + /*fprintf(stderr, "Cannot open %s\n", PROC_SWAPS); */ + return; + } + /* lines are filename type */ + while (fgets(line, BUFSIZ, fp) != NULL) + { + if ((find_space = strchr(line, ' ')) == NULL) + continue; + *find_space = '\0'; + find_space++; + while (*find_space == ' ') + { + find_space++; + if (*find_space == '\0') + continue; + } + if (statn(line, STATX_INO, &st) != 0) + continue; + + /* Scan the devices */ + for (dev_tmp = dev_head; dev_tmp != NULL; + dev_tmp = dev_tmp->next) + { + if (st.st_dev == dev_tmp->device) + add_special_proc(dev_tmp->name, PTYPE_SWAP, 0, line); + } + 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) + add_special_proc(ino_tmp->name, PTYPE_SWAP, 0, line); + } + } + fclose(fp); +} + +/* + * Somehow the realpath(3) glibc function call, nevertheless + * it avoids lstat(2) system calls. + */ +static char real[PATH_MAX + 1]; +char *expandpath( + const char *path) +{ + char tmpbuf[PATH_MAX + 1]; + const char *start, *end; + char *curr, *dest; + int deep = MAXSYMLINKS; + + if (!path || *path == '\0') + return (char *)0; + + curr = &real[0]; + + if (*path != '/') + { + if (!getcwd(curr, PATH_MAX)) + return (char *)0; + dest = strchr(curr, '\0'); + } else { + *curr = '/'; + dest = curr + 1; + } + + for (start = end = path; *start; start = end) + { + while (*start == '/') + ++start; + + for (end = start; *end && *end != '/'; ++end) ; + + if (end - start == 0) + break; + else if (end - start == 1 && start[0] == '.') + { + ; + } else if (end - start == 2 && start[0] == '.' && start[1] == '.') + { + if (dest > curr + 1) + while ((--dest)[-1] != '/') ; + } else { + char lnkbuf[PATH_MAX + 1]; + size_t len; + ssize_t n; + + if (dest[-1] != '/') + *dest++ = '/'; + + if (dest + (end - start) > curr + PATH_MAX) + { + errno = ENAMETOOLONG; + return (char *)0; + } + + dest = mempcpy(dest, start, end - start); + *dest = '\0'; + + if (deep-- < 0) + { + errno = ELOOP; + return (char *)0; + } + + errno = 0; + if ((n = readlink(curr, lnkbuf, PATH_MAX)) < 0) + { + deep = MAXSYMLINKS; + if (errno == EINVAL) + continue; /* Not a symlink */ + return (char *)0; + } + lnkbuf[n] = '\0'; /* Don't be fooled by readlink(2) */ + + len = strlen(end); + if ((n + len) > PATH_MAX) + { + errno = ENAMETOOLONG; + return (char *)0; + } + + memmove(&tmpbuf[n], end, len + 1); + path = end = memcpy(tmpbuf, lnkbuf, n); + + if (lnkbuf[0] == '/') + dest = curr + 1; + else if (dest > curr + 1) + while ((--dest)[-1] != '/') ; + + } + } + + if (dest > curr + 1 && dest[-1] == '/') + --dest; + *dest = '\0'; + + return curr; +} diff --git a/src/fuser.h b/src/fuser.h new file mode 100644 index 0000000..4500ec5 --- /dev/null +++ b/src/fuser.h @@ -0,0 +1,113 @@ + +/* Option Flags */ +typedef unsigned short opt_type; + +#define OPT_VERBOSE 1 +#define OPT_ALLFILES 2 +#define OPT_MOUNTS 4 +#define OPT_KILL 8 +#define OPT_INTERACTIVE 16 +#define OPT_SILENT 32 +#define OPT_USER 64 +#define OPT_ISMOUNTPOINT 128 +#define OPT_WRITE 256 +#define OPT_ALWAYSSTAT 512 + +struct procs { + pid_t pid; + uid_t uid; + char access; + char proc_type; + char *username; + char *command; + struct procs *next; +}; + +/* For the access field above */ +#define ACCESS_CWD 1 +#define ACCESS_EXE 2 +#define ACCESS_FILE 4 +#define ACCESS_ROOT 8 +#define ACCESS_MMAP 16 +#define ACCESS_FILEWR 32 + +/* For the proc_type field above */ +#define PTYPE_NORMAL 0 +#define PTYPE_MOUNT 1 +#define PTYPE_KNFSD 2 +#define PTYPE_SWAP 3 + +struct names { + char *filename; + unsigned char name_space; + struct stat st; + struct procs *matched_procs; + struct names *next; +}; + +struct ip_connections { + struct names *name; + unsigned long lcl_port; + unsigned long rmt_port; + struct in_addr rmt_address; + struct ip_connections *next; +}; + +struct ip6_connections { + struct names *name; + unsigned long lcl_port; + unsigned long rmt_port; + struct in6_addr rmt_address; + struct ip6_connections *next; +}; + +struct inode_list { + struct names *name; + dev_t device; + ino_t inode; + struct inode_list *next; +}; + +struct device_list { + struct names *name; + dev_t device; + struct device_list *next; +}; + +struct unixsocket_list { + char *sun_name; + ino_t inode; + ino_t net_inode; + dev_t dev; + struct unixsocket_list *next; +}; + +struct mount_list { + char *mountpoint; + struct mount_list *next; +}; + +#if defined (__GNUC__) && defined(WITH_MOUNTINFO_LIST) +# include "lists.h" +typedef struct mntinfo_s { + list_t this; + int id, parid; + dev_t dev; + size_t nlen; + char *mpoint; +} mntinfo_t; +#else +# undef WITH_MOUNTINFO_LIST +#endif + +#define NAMESPACE_FILE 0 +#define NAMESPACE_TCP 1 +#define NAMESPACE_UDP 2 + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif /* PATH_MAX */ + +#define KNFSD_EXPORTS "/proc/fs/nfs/exports" +#define PROC_MOUNTS "/proc/mounts" +#define PROC_SWAPS "/proc/swaps" diff --git a/src/i18n.h b/src/i18n.h new file mode 100644 index 0000000..a0b4439 --- /dev/null +++ b/src/i18n.h @@ -0,0 +1,26 @@ +/* i18n.h - common i18n declarations for psmisc programs. */ + +#ifndef I18N_H +#define I18N_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef ENABLE_NLS +#include <locale.h> +#include <libintl.h> +#define _(String) gettext (String) +#else +#define _(String) (String) +#endif + +#endif + +#ifndef HAVE_RPMATCH +#define rpmatch(line) \ + ( (line == NULL)? -1 : \ + (*line == 'y' || *line == 'Y')? 1 : \ + (*line == 'n' || *line == 'N')? 0 : \ + -1 ) +#endif diff --git a/src/killall.c b/src/killall.c new file mode 100644 index 0000000..81dcc4b --- /dev/null +++ b/src/killall.c @@ -0,0 +1,1069 @@ +/* + * killall.c - kill processes by name or list PIDs + * + * Copyright (C) 1993-2002 Werner Almesberger + * Copyright (C) 2002-2024 Craig Small <csmall@dropbear.xyz> + * + * 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 + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <dirent.h> +#include <signal.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <fcntl.h> +#include <getopt.h> +#include <pwd.h> +#include <regex.h> +#include <ctype.h> +#include <assert.h> + +#ifdef WITH_SELINUX +#include <dlfcn.h> +#include <selinux/selinux.h> +#endif /*WITH_SELINUX*/ + +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif /* HAVE_LOCALE_H */ + +#include "i18n.h" +#include "comm.h" +#include "signals.h" + +#define PROC_BASE "/proc" +#define MAX_NAMES (int)(sizeof(unsigned long)*8) + +#define TSECOND "s" +#define TMINUTE "m" +#define THOUR "h" +#define TDAY "d" +#define TWEEK "w" +#define TMONTH "M" +#define TYEAR "y" + +#define TMAX_SECOND 31536000 +#define TMAX_MINUTE 525600 +#define TMAX_HOUR 8760 +#define TMAX_DAY 365 +#define TMAX_WEEK 48 +#define TMAX_MONTH 12 +#define TMAX_YEAR 1 + +#define ER_REGFAIL -1 +#define ER_NOMEM -2 +#define ER_UNKWN -3 +#define ER_OOFRA -4 + +static pid_t opt_ns_pid = 0; + +static int verbose = 0, exact = 0, interactive = 0, reg = 0, + quiet = 0, wait_until_dead = 0, process_group = 0, + ignore_case = 0; +static long younger_than = 0, older_than = 0; + +typedef struct NAMEINFO { + const char *name; + int name_length; + struct stat st; +} NAMEINFO; + +static int +ask (char *name, pid_t pid, const int signal) +{ + int res; + size_t len; + char *line; + + line = NULL; + len = 0; + + do { + if (signal == SIGTERM) + printf (_("Kill %s(%s%d) ? (y/N) "), name, process_group ? "pgid " : "", + pid); + else + printf (_("Signal %s(%s%d) ? (y/N) "), name, process_group ? "pgid " : "", + pid); + + fflush (stdout); + + if (getline (&line, &len, stdin) < 0) + return 0; + /* Check for default */ + if (line[0] == '\n') { + free(line); + return 0; + } + res = rpmatch(line); + if (res >= 0) { + free(line); + return res; + } + } while(1); + /* Never should get here */ +} + +static double +uptime() +{ + char * savelocale; + char buf[2048]; + FILE* file; + if (!(file=fopen( PROC_BASE "/uptime", "r"))) { + fprintf(stderr, "killall: error opening uptime file\n"); + exit(1); + } + savelocale = setlocale(LC_NUMERIC,"C"); + if (fscanf(file, "%2047s", buf) == EOF) perror("uptime"); + fclose(file); + setlocale(LC_NUMERIC,savelocale); + return atof(buf); +} + +/* process age from jiffies to seconds via uptime */ +static double process_age(const unsigned long long jf) +{ + double age; + double sc_clk_tck = sysconf(_SC_CLK_TCK); + assert(sc_clk_tck > 0); + age = uptime() - jf / sc_clk_tck; + if (age < 0L) + return 0L; + return age; +} + +/* returns requested time interval in seconds, + negative indicates error has occurred + */ +static long +parse_time_units(const char* age) +{ + char *unit; + long num; + + num = strtol(age,&unit,10); + if (age == unit) /* no digits found */ + return -1; + if (unit[0] == '\0') /* no units found */ + return -1; + + switch(unit[0]) { + case 's': + return num; + case 'm': + return (num * 60); + case 'h': + return (num * 60 * 60); + case 'd': + return (num * 60 * 60 * 24); + case 'w': + return (num * 60 * 60 * 24 * 7); + case 'M': + return (num * 60 * 60 * 24 * 7 * 4); + case 'y': + return (num * 60 * 60 * 24 * 7 * 4 * 12); + } + return -1; +} + +enum ns_type { + IPCNS = 0, + MNTNS, + NETNS, + PIDNS, + USERNS, + UTSNS +}; + +static const char *ns_names[] = { + [IPCNS] = "ipc", + [MNTNS] = "mnt", + [NETNS] = "net", + [PIDNS] = "pid", + [USERNS] = "user", + [UTSNS] = "uts", +}; + +#define NUM_NS 6 + +const char *get_ns_name(int id) { + if (id >= NUM_NS) + return NULL; + return ns_names[id]; +} + +static int get_ns(pid_t pid, int id) { + struct stat st; + char buff[50]; + snprintf(buff, sizeof(buff), "/proc/%i/ns/%s", pid, get_ns_name(id)); + if (stat(buff, &st)) + return 0; + else + return st.st_ino; +} + +static int +match_process_uid(const int pidfd, uid_t uid) +{ + char buf[128]; + uid_t puid; + FILE *f; + int fd; + int re = -1; + + if ( (fd = openat(pidfd, "status", O_RDONLY, 0)) < 0) + return 0; + if (!(f = fdopen (fd, "r"))) + { + close(fd); + return 0; + } + + while (fgets(buf, sizeof buf, f)) + { + if (sscanf (buf, "Uid:\t%d", &puid)) + { + re = uid==puid; + break; + } + } + close(fd); + if (re==-1) + { + fprintf(stderr, _("killall: Cannot get UID from process status\n")); + exit(1); + } + return re; +} + +/* Match on the given scontext to the process context + * Return 0 on a match + */ +static int +match_process_context(const pid_t pid, const regex_t *scontext) +{ + static void (*my_freecon)(char*) = NULL; + static int (*my_getpidcon)(pid_t pid, char **context) = NULL; + static int selinux_enabled = 0; + char *lcontext; + int retval = 1; + +#ifdef WITH_SELINUX + static int tried_load = 0; + static int (*my_is_selinux_enabled)(void) = NULL; + + if(!my_getpidcon && !tried_load){ + void *handle = dlopen("libselinux.so.1", RTLD_NOW); + if(handle) { + my_freecon = dlsym(handle, "freecon"); + if(dlerror()) + my_freecon = NULL; + my_getpidcon = dlsym(handle, "getpidcon"); + if(dlerror()) + my_getpidcon = NULL; + my_is_selinux_enabled = dlsym(handle, "is_selinux_enabled"); + if(dlerror()) + my_is_selinux_enabled = 0; + else + selinux_enabled = my_is_selinux_enabled(); + } + tried_load++; + } +#endif /* WITH_SELINUX */ + + if (my_getpidcon && selinux_enabled && !my_getpidcon(pid, &lcontext)) { + retval = (regexec(scontext, lcontext, 0, NULL, 0) ==0); + my_freecon(lcontext); + } else { + FILE *file; + char path[50]; + char readbuf[BUFSIZ+1]; + snprintf(path, sizeof path, "/proc/%d/attr/current", pid); + if ( (file = fopen(path, "r")) != NULL) { + if (fgets(readbuf, BUFSIZ, file) != NULL) { + retval = (regexec(scontext, readbuf, 0, NULL, 0)==0); + } + fclose(file); + } + } + return retval; +} + +static int +my_send_signal( + const int pidfd, + const pid_t pid, + const int sig) +{ +#ifdef __NR_pidfd_send_signal + if (pid > 0) /* Not PGID */ + { + int ret = syscall(__NR_pidfd_send_signal, pidfd, sig, NULL, 0); + if (ret >= 0 || errno != ENOSYS) + return ret; + // fall through if no such syscall + } +#endif + return kill(pid, sig); +} + +static void +free_regexp_list(regex_t *reglist, int names) +{ + int i; + for (i = 0; i < names; i++) + regfree(®list[i]); + free(reglist); +} + +static regex_t * +build_regexp_list(int names, char **namelist) +{ + int i; + regex_t *reglist; + int flag = REG_EXTENDED|REG_NOSUB; + + if (!(reglist = malloc (sizeof (regex_t) * names))) + { + perror ("malloc"); + exit (1); + } + + if (ignore_case) + flag |= REG_ICASE; + + for (i = 0; i < names; i++) + { + if (regcomp(®list[i], namelist[i], flag) != 0) + { + fprintf(stderr, _("killall: Bad regular expression: %s\n"), namelist[i]); + free_regexp_list(reglist, i); + exit (1); + } + } + return reglist; +} + +static NAMEINFO * +build_nameinfo(const int names, char **namelist) +{ + int i; + NAMEINFO *ni = NULL; + if ( (ni = malloc(sizeof(NAMEINFO) * names)) == NULL) + return NULL; + + for (i = 0; i < names; i++) + { + ni[i].name = namelist[i]; + ni[i].st.st_dev = 0; + if (!strchr (namelist[i], '/')) + { + ni[i].name_length = strlen (namelist[i]); + } + else if (stat (namelist[i], &(ni[i].st)) < 0) + { + perror (namelist[i]); + free(ni); + return NULL; + } + } + return ni; +} + +static int +load_process_name_and_age(char *comm, double *process_age_sec, + const int pidfd, int load_age) +{ + int fd; + FILE *file; + char buf[1024]; + char *startcomm, *endcomm; + unsigned lencomm; + *process_age_sec = 0; + + if ( (fd = openat(pidfd, "stat", O_RDONLY, 0)) < 0) + return -1; + if (!(file = fdopen (fd, "r"))) + { + close(fd); + return -1; + } + if (fgets(buf, 1024, file) == NULL) + { + close(fd); + return -1; + } + close(fd); + if ( NULL == ( startcomm = strchr(buf, '('))) + return -1; + startcomm++; + if ( NULL == ( endcomm = strrchr(startcomm, ')'))) + return -1; + lencomm = endcomm - startcomm; + if (lencomm > COMM_LEN -1) + lencomm = COMM_LEN -1; + strncpy(comm, startcomm, lencomm); + comm[lencomm] = '\0'; + + endcomm += 2; // skip ") " + if (load_age) + { + unsigned long long proc_stt_jf = 0; + if (sscanf(endcomm, "%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %Lu", + &proc_stt_jf) != 1) + { + return -1; + } + *process_age_sec = process_age(proc_stt_jf); + } + return lencomm; +} + +static int +load_proc_cmdline(const int pidfd, const pid_t pid, const char *comm, const int check_comm_length, char **command, int *got_long) +{ + FILE *file; + int fp; + char *p, *command_buf; + int cmd_size = 128; + int okay; + + if ( (fp = openat(pidfd, "cmdline", O_RDONLY, 0)) < 0) + return -1; + + if (!(file = fdopen (fp, "r"))) + { + close(fp); + return -1; + } + + if ( (command_buf = (char *)malloc (cmd_size)) == NULL) + exit(1); + + while (1) + { + /* look for actual command so we skip over initial "sh" if any */ + + /* 'cmdline' has arguments separated by nulls */ + for (p=command_buf; ; p++) + { + int c; + if (p == (command_buf + cmd_size)) + { + char *new_command_buf; + int cur_size = cmd_size; + cmd_size *= 2; + new_command_buf = (char *)realloc(command_buf, cmd_size); + if (!new_command_buf) { + if (command_buf) + free(command_buf); + exit (1); + } + command_buf = new_command_buf; + p = command_buf + cur_size; + } + c = fgetc(file); + if (c == EOF || c == '\0') + { + *p = '\0'; + break; + } else { + *p = c; + } + } + if (strlen(command_buf) == 0) { + okay = 0; + break; + } + p = strrchr(command_buf,'/'); + p = p ? p+1 : command_buf; + if (strncmp(p, comm, check_comm_length) == 0) { + okay = 1; + if (!(*command = strdup(p))) { + free(command_buf); + exit(1); + } + break; + } + } + (void) close(fp); + free(command_buf); + command_buf = NULL; + + if (exact && !okay) + { + if (verbose) + fprintf (stderr, _("killall: skipping partial match %s(%d)\n"), + comm, pid); + *got_long = okay; + return -1; + } + *got_long = okay; + return 0; +} + +static pid_t * +create_pid_table(int *max_pids, int *pids) +{ + pid_t self, *pid_table, *realloc_pid_table; + int pid; + DIR *dir; + struct dirent *de; + + self = getpid (); + if (!(dir = opendir (PROC_BASE))) + { + perror (PROC_BASE); + exit (1); + } + *max_pids = 256; + pid_table = malloc (*max_pids * sizeof (pid_t)); + if (!pid_table) + { + perror ("malloc"); + exit (1); + } + *pids = 0; + while ( (de = readdir (dir)) != NULL) + { + if (!(pid = (pid_t) atoi (de->d_name)) || pid == self) + continue; + if (*pids == *max_pids) + { + if (!(realloc_pid_table = realloc (pid_table, 2 * *pids * sizeof (pid_t)))) + { + perror ("realloc"); + free(pid_table); + exit (1); + } + pid_table = realloc_pid_table; + *max_pids *= 2; + } + pid_table[(*pids)++] = pid; + } + (void) closedir (dir); + return pid_table; +} + +#define strcmp2(A,B,I) (I? strcasecmp((A),(B)):strcmp((A),(B))) +#define strncmp2(A,B,L,I) (I? strncasecmp((A),(B),(L)):strncmp((A),(B),(L))) +static int match_process_name( + const char *proc_comm, + const int comm_len, + const char *proc_cmdline, + const char *match_name, + const int match_len, + const int got_long + ) +{ + /* process is old length but matching longer */ + if (comm_len == OLD_COMM_LEN - 1 && match_len >= OLD_COMM_LEN - 1) + { + if (got_long) + { + return (0 == strcmp2 (match_name, proc_cmdline, ignore_case)); + } else { + return (0 == strncmp2 (match_name, proc_comm, OLD_COMM_LEN - 1, + ignore_case)); + } + } + + if (comm_len == COMM_LEN - 1 && match_len >= COMM_LEN - 1) + { + if (got_long) + { + return (0 == strcmp2 (match_name, proc_cmdline, ignore_case)); + } else { + return (0 == strncmp2 (match_name, proc_comm, COMM_LEN - 1, + ignore_case)); + } + } + /* Not old new COMM_LEN so we match all of it */ + if (got_long) + { + return (0 == strcmp2 (match_name, proc_cmdline, ignore_case)); + } + return (0 == strcmp2 (match_name, proc_comm, ignore_case)); +} + +static int +kill_all(int signal, int name_count, char **namelist, struct passwd *pwent, + regex_t *scontext ) +{ + struct stat st; + NAMEINFO *name_info = NULL; + char comm[COMM_LEN]; + char *command = NULL; + pid_t *pid_table, *pid_killed; + pid_t *pgids = NULL; + int i, j, length, got_long, error; + int pids, max_pids, pids_killed; + int pidfd = 0; + unsigned long found; + regex_t *reglist = NULL; + long ns_ino = 0; + + if (opt_ns_pid) + ns_ino = get_ns(opt_ns_pid, PIDNS); + + if (name_count && reg) + reglist = build_regexp_list(name_count, namelist); + else + if ( (name_info = build_nameinfo(name_count, namelist)) == NULL) + exit(1); + + pid_table = create_pid_table(&max_pids, &pids); + found = 0; + pids_killed = 0; + pid_killed = malloc (max_pids * sizeof (pid_t)); + if (!pid_killed) + { + perror ("malloc"); + exit (1); + } + if (process_group) + { + pgids = calloc (pids, sizeof (pid_t)); + if (!pgids) + { + perror ("malloc"); + exit (1); + } + } + got_long = 0; + for (i = 0; i < pids; i++) + { + pid_t id; + int found_name = -1; + double process_age_sec = 0; + char pidpath[256]; + + /* Open PID directory */ + if (pidfd > 0) + close(pidfd); + snprintf (pidpath, sizeof pidpath, PROC_BASE "/%d", pid_table[i]); + if ( (pidfd = open(pidpath, O_RDONLY|O_DIRECTORY)) < 0) + continue; + /* match by UID */ + if (pwent && match_process_uid(pidfd, pwent->pw_uid)==0) + continue; + if (opt_ns_pid && ns_ino && ns_ino != get_ns(pid_table[i], PIDNS)) + continue; + + if (scontext && match_process_context(pid_table[i], scontext) == 0) + continue; + + length = load_process_name_and_age(comm, &process_age_sec, pidfd, (younger_than||older_than)); + if (length < 0) + continue; + + /* test for process age, if required */ + if ( younger_than && (process_age_sec > younger_than ) ) + continue; + if ( older_than && (process_age_sec < older_than ) ) + continue; + + got_long = 0; + if (command) { + free(command); + command = NULL; + } + + if (length == COMM_LEN - 1 || length == OLD_COMM_LEN - 1) + if (load_proc_cmdline(pidfd, pid_table[i], comm, length, &command, &got_long) < 0) + continue; + + /* match by process name */ + for (j = 0; j < name_count; j++) + { + if (reg) + { + if (regexec (®list[j], got_long ? command : comm, 0, NULL, 0) != 0) + continue; + } + else /* non-regex */ + { + if (!name_info[j].st.st_dev) + { + if (!match_process_name(comm, length, command, namelist[j], + name_info[j].name_length, got_long)) + continue; + + } else { + int ok = 1; + if (fstatat(pidfd, "exe", &st, 0) < 0) + ok = 0; + else if (name_info[j].st.st_dev != st.st_dev || + name_info[j].st.st_ino != st.st_ino) + { + /* maybe the binary has been modified and std[j].st_ino + * is not reliable anymore. We need to compare paths. + */ + size_t len = strlen(namelist[j]); + char *linkbuf = malloc(len + 1); + + if (!linkbuf || + readlinkat(pidfd, "exe", linkbuf, len + 1) != (ssize_t)len || + memcmp(namelist[j], linkbuf, len)) + ok = 0; + free(linkbuf); + } + if (!ok) + continue; + } + } /* non-regex */ + found_name = j; + break; + } + if (name_count && found_name==-1) + continue; /* match by process name faild */ + + /* check for process group */ + if (!process_group) + id = pid_table[i]; + else + { + int j; + + id = getpgid (pid_table[i]); + pgids[i] = id; + if (id < 0) + { + fprintf (stderr, "killall: getpgid(%d): %s\n", + pid_table[i], strerror (errno)); + } + for (j = 0; j < i; j++) + if (pgids[j] == id) + break; + if (j < i) + continue; + } + if (interactive && !ask (comm, id, signal)) + continue; + if (my_send_signal (pidfd, process_group ? -id : id, signal) >= 0) + { + if (verbose) + fprintf (stderr, _("Killed %s(%s%d) with signal %d\n"), got_long ? command : + comm, process_group ? "pgid " : "", id, signal); + if (found_name >= 0) + /* mark item of namelist */ + found |= 1UL << found_name; + pid_killed[pids_killed++] = id; + } + else if (errno != ESRCH || interactive) + fprintf (stderr, "%s(%d): %s\n", got_long ? command : + comm, id, strerror (errno)); + } + if (command) + free(command); + if (reglist) + free_regexp_list(reglist, name_count); + free(pgids); + if (pidfd > 0) + close(pidfd); + if (!quiet) + for (i = 0; i < name_count; i++) + if (!(found & (1UL << i))) + fprintf (stderr, _("%s: no process found\n"), namelist[i]); + if (name_count) + /* killall returns a zero return code if at least one process has + * been killed for each listed command. */ + error = found == ((1UL << (name_count - 1)) | ((1UL << (name_count - 1)) - 1)) ? 0 : 1; + else + /* in nameless mode killall returns a zero return code if at least + * one process has killed */ + error = pids_killed ? 0 : 1; + /* + * We scan all (supposedly) killed processes every second to detect dead + * processes as soon as possible in order to limit problems of race with + * PID re-use. + */ + while (pids_killed && wait_until_dead) + { + for (i = 0; i < pids_killed;) + { + if (kill (process_group ? -pid_killed[i] : pid_killed[i], 0) < 0 && + errno == ESRCH) + { + pid_killed[i] = pid_killed[--pids_killed]; + continue; + } + i++; + } + sleep (1); /* wait a bit longer */ + } + free(pid_killed); + free(pid_table); + free(name_info); + return error; +} + + +static void +usage (const char *msg) +{ + if (msg != NULL) + fprintf(stderr, "%s\n", msg); + fprintf(stderr, _( + "Usage: killall [OPTION]... [--] NAME...\n")); + fprintf(stderr, _( + " killall -l, --list\n" + " killall -V, --version\n\n" + " -e,--exact require exact match for very long names\n" + " -I,--ignore-case case insensitive process name match\n" + " -g,--process-group kill process group instead of process\n" + " -y,--younger-than kill processes younger than TIME\n" + " -o,--older-than kill processes older than TIME\n" + " -i,--interactive ask for confirmation before killing\n" + " -l,--list list all known signal names\n" + " -q,--quiet don't print complaints\n" + " -r,--regexp interpret NAME as an extended regular expression\n" + " -s,--signal SIGNAL send this signal instead of SIGTERM\n" + " -u,--user USER kill only process(es) running as USER\n" + " -v,--verbose report if the signal was successfully sent\n" + " -V,--version display version information\n" + " -w,--wait wait for processes to die\n" + " -n,--ns PID match processes that belong to the same namespaces\n" + " as PID\n")); + + fprintf(stderr, _( + " -Z,--context REGEXP kill only process(es) having context\n" + " (must precede other arguments)\n")); + fputc('\n', stderr); + exit(1); +} + + +void print_version() +{ + fprintf(stderr, "killall (PSmisc) %s\n", VERSION); + fprintf(stderr, _( + "Copyright (C) 1993-2024 Werner Almesberger and Craig Small\n\n")); + fprintf(stderr, _( + "PSmisc comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under\n" + "the terms of the GNU General Public License.\n" + "For more information about these matters, see the files named COPYING.\n")); +} + +static int +have_proc_self_stat (void) +{ + char filename[128]; + struct stat isproc; + pid_t pid = getpid(); + + snprintf(filename, sizeof(filename), PROC_BASE"/%d/stat", (int) pid); + return stat(filename, &isproc) == 0; +} + +int +main (int argc, char **argv) +{ + char *name; + int sig_num; + int optc; + int myoptind; + int skip_error=0; + struct passwd *pwent = NULL; + char yt[COMM_LEN]; + char ot[COMM_LEN]; + + //int optsig = 0; + + struct option options[] = { + {"exact", 0, NULL, 'e'}, + {"ignore-case", 0, NULL, 'I'}, + {"process-group", 0, NULL, 'g'}, + {"younger-than", 1, NULL, 'y'}, + {"older-than", 1, NULL, 'o'}, + {"interactive", 0, NULL, 'i'}, + {"list-signals", 0, NULL, 'l'}, + {"quiet", 0, NULL, 'q'}, + {"regexp", 0, NULL, 'r'}, + {"signal", 1, NULL, 's'}, + {"user", 1, NULL, 'u'}, + {"verbose", 0, NULL, 'v'}, + {"wait", 0, NULL, 'w'}, + {"ns", 1, NULL, 'n' }, + {"context", 1, NULL, 'Z'}, + {"version", 0, NULL, 'V'}, + {0,0,0,0 }}; + + + /* Setup the i18n */ +#ifdef ENABLE_NLS + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + char *scontext = NULL; + regex_t scontext_reg; + + if ( argc < 2 ) usage(NULL); /* do the obvious thing... */ + + name = strrchr (*argv, '/'); + if (name) + name++; + else + name = *argv; + sig_num = SIGTERM; + + + opterr = 0; + while ( (optc = getopt_long_only(argc,argv,"egy:o:ilqrs:u:vwZ:VIn:",options,NULL)) != -1) { + switch (optc) { + case 'e': + exact = 1; + break; + case 'g': + process_group = 1; + break; + case 'y': + strncpy(yt, optarg, sizeof yt -1); + yt[sizeof yt -1] = '\0'; + if ( 0 >= (younger_than = parse_time_units(yt) ) ) + usage(_("Invalid time format")); + break; + case 'o': + strncpy(ot, optarg, sizeof ot - 1); + ot[sizeof ot -1] = '\0'; + if ( 0 >= (older_than = parse_time_units(ot) ) ) + usage(_("Invalid time format")); + break; + case 'i': + interactive = 1; + break; + case 'l': + list_signals(); + return 0; + break; + case 'q': + quiet = 1; + break; + case 'r': + reg = 1; + break; + case 's': + sig_num = get_signal (optarg, "killall"); + break; + case 'u': + if (!(pwent = getpwnam(optarg))) { + fprintf (stderr, _("Cannot find user %s\n"), optarg); + exit (1); + } + break; + case 'v': + verbose = 1; + break; + case 'w': + wait_until_dead = 1; + break; + case 'I': + /* option check is optind-1 but sig name is optind */ + if (strcmp(argv[optind-1],"-I") == 0 || strncmp(argv[optind-1],"--",2) == 0) { + ignore_case = 1; + } else { + sig_num = get_signal (argv[optind]+1, "killall"); + skip_error=optind; + } + break; + case 'V': + /* option check is optind-1 but sig name is optind */ + if (strcmp(argv[optind-1],"-V") == 0 || strncmp(argv[optind-1],"--",2) == 0) { + print_version(); + return 0; + } else { + sig_num = get_signal (argv[optind]+1, "killall"); + skip_error=optind; + } + break; + case 'n': { + long num; + char *end = NULL; + errno = 0; + num = strtol(optarg, &end, 10); + if (errno != 0 || optarg == end || end == NULL) + usage(_("Invalid namespace PID")); + opt_ns_pid = (pid_t) num; + } + break; + case 'Z': + scontext=optarg; + if (regcomp(&scontext_reg, scontext, REG_EXTENDED|REG_NOSUB) != 0) { + fprintf(stderr, _("Bad regular expression: %s\n"), scontext); + exit (1); + } + break; + case '?': + if (skip_error == optind) + break; + /* Sigh, this is a hack because -ve could be -version or + * -verbose */ + if (strncmp(argv[optind-1], "-ve", 3) == 0) { + verbose=1; + exact=1; + break; + } + /* Signal names are in uppercase, so check to see if the argv + * is upper case */ + if (argv[optind-1][1] >= 'A' && argv[optind-1][1] <= 'Z') { + sig_num = get_signal (argv[optind-1]+1, "killall"); + } else { + /* Might also be a -## signal too */ + if (argv[optind-1][1] >= '0' && argv[optind-1][1] <= '9') { + sig_num = atoi(argv[optind-1]+1); + } else { + usage(NULL); + } + } + break; + } + } + myoptind = optind; + if ((argc - myoptind < 1) && pwent==NULL && scontext==NULL) + usage(NULL); + + if (argc - myoptind > MAX_NAMES) { + fprintf (stderr, _("killall: Maximum number of names is %d\n"), + MAX_NAMES); + exit (1); + } + if (!have_proc_self_stat()) { + fprintf (stderr, _("killall: %s lacks process entries (not mounted ?)\n"), + PROC_BASE); + exit (1); + } + argv = argv + myoptind; + return kill_all(sig_num,argc - myoptind, argv, pwent, + scontext ? &scontext_reg : NULL); + } diff --git a/src/lists.h b/src/lists.h new file mode 100644 index 0000000..bd371a4 --- /dev/null +++ b/src/lists.h @@ -0,0 +1,373 @@ +/* + * lists.h Simple doubly linked list implementation, based on + * <linux/list.h>, <linux/prefetch.h>, and lib/list_sort.c + * + * Version: 0.2 11-Dec-2012 Fink + * + * Copyright 2011,2012 Werner Fink, 2005,2012 SUSE LINUX Products GmbH, Germany. + * + * 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. + * + * Author: Werner Fink <werner@suse.de>, 2011 + */ + +#ifndef _LISTS_H +#define _LISTS_H + +#include <stddef.h> +#include <sys/types.h> + +typedef enum _boolean {false, true} boolean; +typedef unsigned char uchar; +#ifndef __USE_MISC +typedef unsigned short ushort; +typedef unsigned int uint; +#endif + +#ifndef __OPTIMIZE__ +# warning This will not compile without -O at least +#endif +#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) +# ifndef inline +# define inline __inline__ +# endif +# ifndef restrict +# define restrict __restrict__ +# endif +# ifndef volatile +# define volatile __volatile__ +# endif +# ifndef asm +# define asm __asm__ +# endif +# ifndef extension +# define extension __extension__ +# endif +#endif +#ifndef attribute +# define attribute(attr) __attribute__(attr) +#endif + +/* + * This is lent from the kernel by e.g. using + * + * echo '#include <asm-i386/processor.h>\nint main () { prefetch(); return 0; }' | \ + * gcc -I/usr/src/linux/include -D__KERNEL__ -x c -E -P - | \ + * sed -rn '/void[[:blank:]]+prefetch[[:blank:]]*\(/,/^}/p' + * + * on the appropriate architecture (here on i686 for i586). + */ +extern inline void attribute((used,__gnu_inline__,always_inline,__artificial__)) prefetch(const void *restrict x) +{ +#if defined(__x86_64__) + asm volatile ("prefetcht0 %0" :: "m" (*(unsigned long *)x)) +#elif defined(__ia64__) + asm volatile ("lfetch [%0]" :: "r" (x)) +#elif defined(__powerpc64__) + asm volatile ("dcbt 0,%0" :: "r" (x)) +#elif !defined(__CYGWIN__) && !defined(__PIC__) && defined(__i386__) + asm volatile ("661:\n\t" + ".byte 0x8d,0x74,0x26,0x00\n" + "\n662:\n" + ".section .altinstructions,\"a\"\n" + " .align 4\n" + " .long 661b\n" + " .long 663f\n" + " .byte %c0\n" + " .byte 662b-661b\n" + " .byte 664f-663f\n" + ".previous\n" + ".section .altinstr_replacement,\"ax\"\n" + " 663:\n\t" + " prefetchnta (%1)" + " \n664:\n" + ".previous" + :: "i" ((0*32+25)), "r" (x)) +#else + __builtin_prefetch ((x), 0, 1); +#endif + ; +} + +#if defined(DEBUG) && (DEBUG > 0) +# define __align attribute((packed)) +#else +# define __align attribute((aligned(sizeof(struct list_struct*)))) +#endif +#define __packed attribute((packed)) + +#define alignof(type) ((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1)) +#define strsize(string) ((strlen(string)+1)*sizeof(char)) + +typedef struct list_struct { + struct list_struct * next, * prev; +} __align list_t; + +/* + * Linked list handling + * ==================== + * The structures which will be linked into such lists have to be of the + * same type. The structures may have alway a list identifier of the type + * `list_t' as very first element. With this the macro list_entry() can + * be used to cast the memory address of a list member to the corresponding + * allocated structure. + */ + +/* + * Insert new entry as next member. + */ +static inline void _insert(list_t *restrict new, list_t *restrict here) attribute((always_inline,nonnull(1,2))); +static inline void _insert(list_t *restrict new, list_t *restrict here) +{ + list_t * prev = here; + list_t * next = here->next; + + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +#define insert(new, list) _insert(&((new)->this), (&(list))); +#define append(new, list) _insert(&((new)->this), (&(list))->prev); + +/* + * Set head + */ +static inline void initial(list_t *restrict head) attribute((always_inline,nonnull(1))); +static inline void initial(list_t *restrict head) +{ + head->prev = head->next = head; +} + +/* + * Remove entries, note that the pointer its self remains. + */ +static inline void delete(list_t *restrict entry) attribute((always_inline,nonnull(1))); +static inline void delete(list_t *restrict entry) +{ + list_t * prev = entry->prev; + list_t * next = entry->next; + + next->prev = prev; + prev->next = next; + + initial(entry); +} + +/* + * Replace an entry by a new one. + */ +static inline void replace(list_t *restrict old, list_t *restrict new) attribute((always_inline,nonnull(1,2))); +static inline void replace(list_t *restrict old, list_t *restrict new) +{ + new->next = old->next; + new->next->prev = new; + new->prev = old->prev; + new->prev->next = new; +} + +static inline void join(list_t *restrict list, list_t *restrict head) attribute((always_inline,nonnull(1,2))); +static inline void join(list_t *restrict list, list_t *restrict head) +{ + list_t * first = list->next; + + if (first != list) { + list_t * last = list->prev; + list_t * at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +static inline boolean list_empty(const list_t *restrict const head) attribute((always_inline,nonnull(1))); +static inline boolean list_empty(const list_t *restrict const head) +{ + return head->next == head; +} + +static inline void move_head(list_t *restrict entry, list_t *restrict head) attribute((always_inline,nonnull(1,2))); +static inline void move_head(list_t *restrict entry, list_t *restrict head) +{ + list_t * prev = entry->prev; + list_t * next = entry->next; + + next->prev = prev; /* remove entry from old list */ + prev->next = next; + + prev = head; + next = head->next; + + next->prev = entry; /* and add it at head of new list */ + entry->next = next; + entry->prev = prev; + prev->next = entry; +} + +static inline void move_tail(list_t *restrict entry, list_t *restrict head) attribute((always_inline,nonnull(1,2))); +static inline void move_tail(list_t *restrict entry, list_t *restrict head) +{ + list_t * prev = entry->prev; + list_t * next = entry->next; + + next->prev = prev; /* remove entry from old list */ + prev->next = next; + + prev = head->prev; + next = head; + + next->prev = entry; /* and add it at tail of new list */ + entry->next = next; + entry->prev = prev; + prev->next = entry; +} + +/* + * The handle of the list is named `this' + */ +#define list_entry(ptr, type) (__extension__ ({ \ + const typeof( ((type *)0)->this ) *__mptr = (ptr); \ + ((type *)( (char *)(__mptr) - offsetof(type,this) )); })) +#define list_for_each(pos, head) \ + for (pos = (head)->next; prefetch(pos->next), pos != (head); pos = pos->next) +#define np_list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) +#define list_for_each_safe(pos, safe, head) \ + for (pos = (head)->next, safe = pos->next; pos != (head); pos = safe, safe = pos->next) +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; prefetch(pos->prev), pos != (head); pos = pos->prev) +#define np_list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); pos = pos->prev) + +#define MAX_LIST_LENGTH_BITS 20 + +/* + * Returns a list organized in an intermediate format suited + * to chaining of merge() calls: null-terminated, no reserved or + * sentinel head node, "prev" links not maintained. + */ +static inline list_t *merge(int (*cmp)(list_t *a, list_t *b), list_t *a, list_t *b) +{ + list_t head, *tail = &head; + while (a && b) { + /* if equal, take 'a' -- important for sort stability */ + if ((*cmp)(a, b) <= 0) { + tail->next = a; + a = a->next; + } else { + tail->next = b; + b = b->next; + } + tail = tail->next; + } + tail->next = a ? a : b; + return head.next; +} + +/* + * Combine final list merge with restoration of standard doubly-linked + * list structure. This approach duplicates code from merge(), but + * runs faster than the tidier alternatives of either a separate final + * prev-link restoration pass, or maintaining the prev links + * throughout. + */ +static inline void merge_and_restore_back_links(int (*cmp)(list_t *a, list_t *b), list_t *head, list_t *a, list_t *b) +{ + list_t *tail = head; + + while (a && b) { + /* if equal, take 'a' -- important for sort stability */ + if ((*cmp)(a, b) <= 0) { + tail->next = a; + a->prev = tail; + a = a->next; + } else { + tail->next = b; + b->prev = tail; + b = b->next; + } + tail = tail->next; + } + tail->next = a ? a : b; + + do { + /* + * In worst cases this loop may run many iterations. + * Continue callbacks to the client even though no + * element comparison is needed, so the client's cmp() + * routine can invoke cond_resched() periodically. + */ + (*cmp)(tail->next, tail->next); + + tail->next->prev = tail; + tail = tail->next; + } while (tail->next); + + tail->next = head; + head->prev = tail; +} + + +/** + * list_sort - sort a list + * @head: the list to sort + * @cmp: the elements comparison function + * + * This function implements "merge sort", which has O(nlog(n)) + * complexity. + * + * The comparison function @cmp must return a negative value if @a + * should sort before @b, and a positive value if @a should sort after + * @b. If @a and @b are equivalent, and their original relative + * ordering is to be preserved, @cmp must return 0. + */ +static inline void list_sort(list_t *head, int (*cmp)(list_t *a, list_t *b)) +{ + list_t *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists + -- last slot is a sentinel */ + size_t lev; /* index into part[] */ + size_t max_lev = 0; + list_t *list; + + if (list_empty(head)) + return; + + memset(part, 0, sizeof(part)); + + head->prev->next = NULL; + list = head->next; + + while (list) { + list_t *cur = list; + list = list->next; + cur->next = NULL; + + for (lev = 0; part[lev]; lev++) { + cur = merge(cmp, part[lev], cur); + part[lev] = NULL; + } + if (lev > max_lev) { + /* list passed to list_sort() too long for efficiency */ + if (lev >= MAX_LIST_LENGTH_BITS) + lev--; + max_lev = lev; + } + part[lev] = cur; + } + + for (lev = 0; lev < max_lev; lev++) { + if (part[lev]) + list = merge(cmp, part[lev], list); + } + + merge_and_restore_back_links(cmp, head, part[max_lev], list); +} + +#endif /* _LISTS_H */ diff --git a/src/peekfd.c b/src/peekfd.c new file mode 100644 index 0000000..36dff04 --- /dev/null +++ b/src/peekfd.c @@ -0,0 +1,466 @@ +/* + * peekfd.c - Intercept file descriptor read and writes + * + * Copyright (C) 2007 Trent Waddington <trent.waddington@gmail.com> + * + * 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 + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <stdio.h> +#include <sys/ptrace.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/syscall.h> +#include <asm/ptrace.h> +#include <byteswap.h> +#include <endian.h> +#include <sys/user.h> +#include <stdlib.h> +#include <getopt.h> +#include <ctype.h> +#include <dirent.h> +#include <stdint.h> +#include <errno.h> +#include <string.h> + +#include "i18n.h" + +#ifdef ARM64 +#include <sys/uio.h> +#include <linux/elf.h> +#endif + +#ifdef I386 + #define REG_ORIG_ACCUM orig_eax + #define REG_ACCUM eax + #define REG_PARAM1 ebx + #define REG_PARAM2 ecx + #define REG_PARAM3 edx +#elif X86_64 + #define REG_ORIG_ACCUM orig_rax + #define REG_ACCUM rax + #define REG_PARAM1 rdi + #define REG_PARAM2 rsi + #define REG_PARAM3 rdx +#elif PPC + #if !defined(__WORDSIZE) + #include <bits/reg.h> + #endif + + #define REG_ORIG_ACCUM gpr[0] + #define REG_ACCUM gpr[3] + #define REG_PARAM1 orig_gpr3 + #define REG_PARAM2 gpr[4] + #define REG_PARAM3 gpr[5] +#ifndef PT_ORIG_R3 + #define PT_ORIG_R3 34 +#endif +#elif defined(ARM) +#ifndef __ARM_EABI__ +#error arm oabi not supported +#endif + #define REG_ORIG_ACCUM ARM_r7 + #define REG_ACCUM ARM_r0 + #define REG_PARAM1 ARM_ORIG_r0 + #define REG_PARAM2 ARM_r1 + #define REG_PARAM3 ARM_r2 + +#elif defined(ARM64) + #define REG_ORIG_ACCUM regs[8] + #define REG_ACCUM regs[0] + #define REG_PARAM1 regs[0] + #define REG_PARAM2 regs[1] + #define REG_PARAM3 regs[2] + + + +#elif defined(MIPS) +#ifndef MIPSEL +#error only little endian supported +#endif + #define REG_ORIG_ACCUM regs[3] + #define REG_ACCUM regs[2] + #define REG_PARAM1 regs[4] + #define REG_PARAM2 regs[5] + #define REG_PARAM3 regs[6] +#elif defined(M68K) + #define REG_ORIG_ACCUM orig_d0 + #define REG_ACCUM d0 + #define REG_PARAM1 d1 + #define REG_PARAM2 d2 + #define REG_PARAM3 d3 +#endif + +#define MAX_ATTACHED_PIDS 1024 +int num_attached_pids = 0; +pid_t attached_pids[MAX_ATTACHED_PIDS]; +int *fds = NULL; + +#ifdef ARM64 +struct user_pt_regs_node { + struct user_pt_regs regs; + struct user_pt_regs_node *user_pt_regs_next; +}; + +void user_pt_regs_insert(struct user_pt_regs_node** user_pt_regs_head, struct user_pt_regs *regs) +{ + struct user_pt_regs_node* new_node = + (struct user_pt_regs_node*) malloc(sizeof(struct user_pt_regs_node)); + + memcpy(&new_node->regs, regs, sizeof(struct user_pt_regs)); + new_node->user_pt_regs_next = (*user_pt_regs_head); + (*user_pt_regs_head) = new_node; +} + +struct user_pt_regs * user_pt_regs_search(struct user_pt_regs_node** user_pt_regs_head, struct user_pt_regs *regs) +{ + struct user_pt_regs_node* current = *user_pt_regs_head; + while (current != NULL) + { + if ((current->regs.REG_ORIG_ACCUM == regs->REG_ORIG_ACCUM) && (current->regs.REG_PARAM2 == regs->REG_PARAM2)) + return ¤t->regs; + current = current->user_pt_regs_next; + } + return NULL; +} + + +int user_pt_regs_delete(struct user_pt_regs_node** user_pt_regs_head, struct user_pt_regs *regs) +{ + struct user_pt_regs_node* temp = *user_pt_regs_head, *prev; + + if (temp != NULL && (&temp->regs == regs)) + { + *user_pt_regs_head = temp->user_pt_regs_next; + free(temp); + return 0; + } + + while (temp != NULL && (&temp->regs != regs)) + { + prev = temp; + temp = temp->user_pt_regs_next; + } + + if (temp == NULL) return -1; + prev->user_pt_regs_next = temp->user_pt_regs_next; + free(temp); + return 0; +} +#endif + +void detach(int signum) { + int i; + for (i = 0; i < num_attached_pids; i++) + ptrace(PTRACE_DETACH, attached_pids[i], 0, 0); + if (fds) + free(fds); + signal(SIGINT, SIG_DFL); + raise(SIGINT); +} + +void attach(pid_t pid) { + if (num_attached_pids >= MAX_ATTACHED_PIDS) + return; + attached_pids[num_attached_pids] = pid; + if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) { + fprintf(stderr, _("Error attaching to pid %i\n"), pid); + return; + } + num_attached_pids++; +} + +void print_version() +{ + fprintf(stderr, _("peekfd (PSmisc) %s\n"), VERSION); + fprintf(stderr, _( + "Copyright (C) 2007 Trent Waddington\n\n")); + fprintf(stderr, _( + "PSmisc comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under\n" + "the terms of the GNU General Public License.\n" + "For more information about these matters, see the files named COPYING.\n")); +} + +void usage() { + fprintf(stderr, _( + "Usage: peekfd [-8] [-n] [-c] [-d] [-V] [-h] <pid> [<fd> ..]\n" + " -8, --eight-bit-clean output 8 bit clean streams.\n" + " -n, --no-headers don't display read/write from fd headers.\n" + " -c, --follow peek at any new child processes too.\n" + " -t, --tgid peek at all threads where tgid equals <pid>.\n" + " -d, --duplicates-removed remove duplicate read/writes from the output.\n" + " -V, --version prints version info.\n" + " -h, --help prints this help.\n" + "\n" + " Press CTRL-C to end output.\n")); +} + +int bufdiff(pid_t pid, unsigned char *lastbuf, unsigned long addr, unsigned long len) { + unsigned long i; + for (i = 0; i < len; i++) + if (lastbuf[i] != (ptrace(PTRACE_PEEKTEXT, pid, addr + i, 0) & 0xff)) + return 1; + return 0; +} + +int main(int argc, char **argv) +{ + int eight_bit_clean = 0; + int no_headers = 0; + int follow_forks = 0; + int follow_clones = 0; + int tgid = 0; + int remove_duplicates = 0; + int optc; + int target_pid = 0; + int numfds = 0; + int i; + unsigned long j; + + struct option options[] = { + {"eight-bit-clean", 0, NULL, '8'}, + {"no-headers", 0, NULL, 'n'}, + {"follow", 0, NULL, 'c'}, + {"tgid", 0, NULL, 't'}, + {"duplicates-removed", 0, NULL, 'd'}, + {"help", 0, NULL, 'h'}, + {"version", 0, NULL, 'V'}, + }; + + /* Setup the i18n */ +#ifdef ENABLE_NLS + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + if (argc < 2) { + usage(); + return 1; + } + + while ((optc = getopt_long(argc, argv, "8nctdhV", options, NULL)) != -1) { + switch(optc) { + case '8': + eight_bit_clean = 1; + break; + case 'n': + no_headers = 1; + break; + case 'c': + follow_forks = 1; + follow_clones = 1; + break; + case 't': + tgid = 1; + follow_clones = 1; + break; + case 'd': + remove_duplicates = 1; + break; + case 'V': + print_version(); + return 1; + case 'h': + case '?': + usage(); + return 1; + } + } + /* First arg off the options is the PID to see */ + if (optind >= argc) { + usage(); + return -1; + } + target_pid = atoi(argv[optind++]); + + if (optind < argc) { + numfds = argc - optind; + fds = malloc(sizeof(int) * numfds); + for (i = 0; i < numfds; i++) + fds[i] = atoi(argv[optind + i]); + } + + attach(target_pid); + + if (tgid) { + DIR *taskdir; + struct dirent *dt; + char taskpath[24]; + + snprintf(taskpath, 24, "/proc/%d/task", target_pid); + + if ((taskdir = opendir(taskpath)) != 0) { + while ((dt = readdir(taskdir)) != NULL) { + int thread = atoi(dt->d_name); + if ((thread != 0) && (thread != target_pid)) + attach(thread); + } + closedir(taskdir); + } + } + + if (num_attached_pids == 0) + return 1; + + signal(SIGINT, detach); + + for (i = 0; i < num_attached_pids; i++) + ptrace(PTRACE_SYSCALL, attached_pids[i], 0, 0); + + /*int count = 0;*/ + int lastfd = numfds > 0 ? fds[0] : 0; + int lastdir = 3; + unsigned char *lastbuf = NULL; + unsigned long last_buf_size = -1; + +#ifdef ARM64 + struct user_pt_regs_node* user_pt_regs_head = NULL; +#endif + + for(;;) { + int status; + pid_t pid = wait(&status); + if (WIFSTOPPED(status)) { +#ifdef PPC + struct pt_regs regs; + regs.gpr[0] = ptrace(PTRACE_PEEKUSER, pid, __WORDSIZE/8 * PT_R0, 0); + regs.gpr[3] = ptrace(PTRACE_PEEKUSER, pid, __WORDSIZE/8 * PT_R3, 0); + regs.gpr[4] = ptrace(PTRACE_PEEKUSER, pid, __WORDSIZE/8 * PT_R4, 0); + regs.gpr[5] = ptrace(PTRACE_PEEKUSER, pid, __WORDSIZE/8 * PT_R5, 0); + regs.orig_gpr3 = ptrace(PTRACE_PEEKUSER, pid, __WORDSIZE/8 * PT_ORIG_R3, 0); +#elif defined(ARM) + struct pt_regs regs; + ptrace(PTRACE_GETREGS, pid, 0, ®s); + +#elif defined(ARM64) + struct user_pt_regs regs, *old_regs; + struct iovec io; + io.iov_base = ®s; + io.iov_len = sizeof(regs); + + if (ptrace(PTRACE_GETREGSET, pid, (void*) NT_PRSTATUS, (void*) &io) == -1) { + printf("ARM64: PTRACE_GETREGSET: %s\n", strerror(errno)); + return errno; + } + +#elif defined(MIPS) + struct pt_regs regs; + long pc = ptrace(PTRACE_PEEKUSER, pid, 64, 0); + regs.regs[2] = ptrace(PTRACE_PEEKUSER,pid,2,0); + regs.regs[3] = ptrace(PTRACE_PEEKTEXT, pid, pc - 8, 0) & 0xffff; + regs.regs[4] = ptrace(PTRACE_PEEKUSER,pid,4,0); + regs.regs[5] = ptrace(PTRACE_PEEKUSER,pid,5,0); + regs.regs[6] = ptrace(PTRACE_PEEKUSER,pid,6,0); +#else + struct user_regs_struct regs; + ptrace(PTRACE_GETREGS, pid, 0, ®s); +#endif + /*unsigned int b = ptrace(PTRACE_PEEKTEXT, pid, regs.eip, 0);*/ + +#if defined(ARM64) + if (follow_forks && regs.REG_ORIG_ACCUM == SYS_clone) { +#else + if ((follow_forks && regs.REG_ORIG_ACCUM == SYS_fork) + || (follow_clones && regs.REG_ORIG_ACCUM == SYS_clone)) { +#endif + if (regs.REG_ACCUM > 0) + attach(regs.REG_ACCUM); + } + if ((regs.REG_ORIG_ACCUM == SYS_read || regs.REG_ORIG_ACCUM == SYS_write) && (regs.REG_PARAM3 == regs.REG_ACCUM)) { +#ifdef ARM64 + /* ARM64 doesn't expose orig_x0 to user space, + so retrive orig_x0 from older user pt regs */ + old_regs = user_pt_regs_search(&user_pt_regs_head, ®s); + if (old_regs != NULL) { + regs.REG_PARAM1 = old_regs->REG_PARAM1; + user_pt_regs_delete(&user_pt_regs_head, old_regs); + } +#endif + for (i = 0; i < numfds; i++) + if (fds[i] == (int)regs.REG_PARAM1) + break; + if (i != numfds || numfds == 0) { + if ((int)regs.REG_PARAM1 != lastfd || (int)regs.REG_ORIG_ACCUM != lastdir) { + lastfd = regs.REG_PARAM1; + lastdir = regs.REG_ORIG_ACCUM; + if (!no_headers) { + printf("\n%sing fd %i", regs.REG_ORIG_ACCUM == SYS_read ? "read" : "writ", lastfd); + if (tgid) + printf(" (thread %d)", pid); + printf(":\n"); + } + } + if (!remove_duplicates || lastbuf == NULL + || last_buf_size != regs.REG_PARAM3 || + bufdiff(pid, lastbuf, regs.REG_PARAM2, regs.REG_PARAM3)) { + + if (remove_duplicates) { + if (lastbuf) + free(lastbuf); + if ( NULL == (lastbuf = malloc(regs.REG_PARAM3))) { + perror("lastbuf malloc"); + exit(1); + } + last_buf_size = regs.REG_PARAM3; + } + + for (j = 0; j < regs.REG_PARAM3; j++) { +#if BYTE_ORDER == BIG_ENDIAN +#if __WORDSIZE == 64 + unsigned int a = bswap_64(ptrace(PTRACE_PEEKTEXT, pid, regs.REG_PARAM2 + j, 0)); +#else + unsigned int a = bswap_32(ptrace(PTRACE_PEEKTEXT, pid, regs.REG_PARAM2 + j, 0)); +#endif +#else + unsigned int a = ptrace(PTRACE_PEEKTEXT, pid, regs.REG_PARAM2 + j, 0); +#endif + if (remove_duplicates) + lastbuf[j] = a & 0xff; + + if (eight_bit_clean) + putchar(a & 0xff); + else { + if (isprint(a & 0xff) || (a & 0xff) == '\n') + printf("%c", a & 0xff); + else if ((a & 0xff) == 0x0d) + printf("\n"); + else if ((a & 0xff) == 0x7f) + printf("\b"); + else if (a & 0xff) + printf(" [%02x] ", a & 0xff); + } + } + } + fflush(stdout); + } + } +#ifdef ARM64 + else if (regs.REG_ORIG_ACCUM == SYS_read || regs.REG_ORIG_ACCUM == SYS_write) + { + user_pt_regs_insert(&user_pt_regs_head,®s); + } +#endif + ptrace(PTRACE_SYSCALL, pid, 0, 0); + } + } + + return 0; +} diff --git a/src/prtstat.c b/src/prtstat.c new file mode 100644 index 0000000..d14c659 --- /dev/null +++ b/src/prtstat.c @@ -0,0 +1,341 @@ +/* + * prtstat.c - Print a processes stat file + * + * Copyright (C) 2009-2024 Craig Small + * Based upon a shell script pstat by martin f. krafft <madduck@madduck.net> + * + * 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 + +#include <sys/types.h> +#include <sys/stat.h> +#include <assert.h> +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "i18n.h" +#include "prtstat.h" + +#define NORETURN __attribute__((__noreturn__)) + +static long sc_clk_tck; + +static void usage(const char *errormsg) NORETURN; + +static void usage(const char *errormsg) +{ + if (errormsg != NULL) + fprintf(stderr, "%s\n", errormsg); + fprintf(stderr, + _ + ("Usage: prtstat [options] PID ...\n" + " prtstat -V\n" + "Print information about a process\n" + " -r,--raw Raw display of information\n" + " -V,--version Display version information and exit\n" + )); + exit(1); +} + +static void print_version(void) +{ + fprintf(stderr, _("prtstat (PSmisc) %s\n"), VERSION); + fprintf(stderr, _( "Copyright (C) 2009-2024 Craig Small\n\n")); + fprintf(stderr, _( + "PSmisc comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under\n" + "the terms of the GNU General Public License.\n" + "For more information about these matters, see the files named COPYING.\n")); +} + +static char *print_state(const char state) +{ + switch(state) { + case 'R': + return _("running"); + case 'S': + return _("sleeping"); + case 'D': + return _("disk sleep"); + case 'Z': + return _("zombie"); + case 'T': + return _("traced"); + case 'W': + return _("paging"); + } + return _("unknown"); +} + +#define RAW_STAT(afmt,aname, aval, bfmt, bname, bval) \ + printf("%12.11s: %-15"afmt"\t%22.21s: %"bfmt"\n",(aname),(aval),(bname),(bval)) + +static double convert_time(const unsigned long ticks) +{ + assert(sc_clk_tck > 0); + return (float)ticks / (float)sc_clk_tck; +} + +static void convert_bytes(char *buf, unsigned long bytes) +{ + if (bytes > (10000000)) + sprintf(buf, "%lu MB",bytes/1000000L); + else if (bytes > (10000)) + sprintf(buf, "%lu kB", bytes/1000L); + else + sprintf(buf, "%lu B", bytes); +} + +/* comes from SCHED_* from linux/sched.h */ +static char *convert_policy(const unsigned int policy) +{ + static char *policy_names[] = { "normal", "fifo","rr", "batch", "iso", "idle" }; + if (policy < 6) + return policy_names[policy]; + return "unknown"; +} + +/* minor is bits 31-20 and 7-0, major is 15-8 */ +static char *convert_tty(int tty_nr) +{ + static char buf[20]; + sprintf(buf, "%d:%d",(tty_nr & 0xff00)>>8,(tty_nr & 0xff)|((tty_nr & 0xfff00000)>>20)); + return buf; +} + + +static void print_raw_stat(const int pid,struct proc_info *pr) +{ + RAW_STAT("d","pid",pid,"s","comm",pr->comm); + RAW_STAT("c","state",pr->state, "d","ppid",pr->ppid); + RAW_STAT("d","pgrp",pr->pgrp, "d","session",pr->session); + RAW_STAT("d","tty_nr",pr->tty_nr, "d","tpgid",pr->tp_gid); + RAW_STAT("x","flags",pr->flags, "lu","minflt",pr->minflt); + RAW_STAT("lu","cminflt",pr->cminflt, "lu","majflt",pr->majflt); + RAW_STAT("lu","cmajflt",pr->cmajflt, "lu","utime",pr->utime); + RAW_STAT("lu","stime",pr->stime, "ld","cutime",pr->cutime); + RAW_STAT("ld","cstime",pr->cstime, "ld","priority",pr->priority); + RAW_STAT("ld","nice",pr->nice, "ld","num_threads",pr->num_threads); + RAW_STAT("ld","itrealvalue",pr->itrealvalue, "llu","starttime",pr->starttime); + RAW_STAT("lu","vsize",pr->vsize, "ld","rss",pr->rss); + RAW_STAT("lu","rsslim",pr->rsslim, "lu","startcode",pr->startcode); + RAW_STAT("lu","endcode",pr->endcode, "lu","startstack",pr->startstack); + RAW_STAT("lX","kstkesp",pr->kstesp, "lX","kstkeip",pr->ksteip); + RAW_STAT("lu","wchan",pr->wchan, "lu","nswap",pr->nswap); + RAW_STAT("lu","cnswap",pr->wchan, "d","exit_signal",pr->exit_signal); + RAW_STAT("d","processor",pr->processor, "u","rt_priority",pr->rt_priority); + RAW_STAT("u","policy",pr->policy, "llu","delayaccr_blkio_ticks",pr->blkio); + RAW_STAT("lu","guest_time",pr->guest_time, "ld","cguest_time",pr->cguest_time); +} +static void print_formated_stat(const int pid,struct proc_info *pr) +{ + char buf_vsize[100]; + char buf_rss[100]; + char buf_rsslim[100]; + long page_size; + + page_size = sysconf(_SC_PAGESIZE); + assert(page_size>1); + + printf(_( + "Process: %-14s\t\tState: %c (%s)\n" + " CPU#: %-3d\t\tTTY: %s\tThreads: %ld\n"), + pr->comm, pr->state, print_state(pr->state), + pr->processor, convert_tty(pr->tty_nr), pr->num_threads); + printf(_( + "Process, Group and Session IDs\n" + " Process ID: %d\t\t Parent ID: %d\n" + " Group ID: %d\t\t Session ID: %d\n" + " T Group ID: %d\n\n"), + pid, pr->ppid, pr->pgrp, pr->session, pr->tp_gid); + printf(_( + "Page Faults\n" + " This Process (minor major): %8lu %8lu\n" + " Child Processes (minor major): %8lu %8lu\n"), + pr->minflt, pr->majflt, pr->cminflt, pr->cmajflt); + printf(_( + "CPU Times\n" + " This Process (user system guest blkio): %6.2f %6.2f %6.2f %6.2f\n" + " Child processes (user system guest): %6.2f %6.2f %6.2f\n"), + convert_time(pr->utime), convert_time(pr->stime), convert_time(pr->guest_time), convert_time(pr->blkio), + convert_time(pr->cutime), convert_time(pr->cstime), convert_time(pr->cguest_time)); + convert_bytes(buf_vsize, pr->vsize); + convert_bytes(buf_rss, pr->rss*page_size); + convert_bytes(buf_rsslim, pr->rsslim); + printf(_( + "Memory\n" + " Vsize: %-10s\n" + " RSS: %-10s \t\t RSS Limit: %s\n" + " Code Start: %#-10lx\t\t Code Stop: %#-10lx\n" + " Stack Start: %#-10lx\n" + " Stack Pointer (ESP): %#10lx\t Inst Pointer (EIP): %#10lx\n"), + buf_vsize, buf_rss, buf_rsslim, + pr->startcode, pr->endcode, + pr->startstack, pr->kstesp, pr->ksteip); + printf(_( + "Scheduling\n" + " Policy: %s\n" + " Nice: %ld \t\t RT Priority: %ld %s\n"), + convert_policy(pr->policy), + pr->nice, (pr->priority>0?pr->priority-20:1-pr->priority), + (pr->priority>0?"(non RT)":"")); + + + + +} +static void print_stat(const int pid, const opt_type options) +{ + char *pathname; + char buf[BUFSIZ]; + char *bptr; + FILE *fp; + + struct proc_info *pr = NULL; + + if ( (asprintf(&pathname, "/proc/%d/stat",(int)pid)) < 0) { + perror(_("asprintf in print_stat failed.\n")); + exit(1); + } + if ( (fp = fopen(pathname,"r")) == NULL) { + if (errno == ENOENT) + fprintf(stderr, _("Process with pid %d does not exist.\n"), pid); + else + fprintf(stderr, _("Unable to open stat file for pid %d (%s)\n"),(int)pid,strerror(errno)); + free(pathname); + free(pr); + return; + } + free(pathname); + + if (fgets(buf,BUFSIZ,fp) == NULL) { + fclose(fp); + return; + } + fclose(fp); + bptr = strchr(buf, '('); + if (bptr == NULL) return; + bptr++; + if ((pr = malloc(sizeof(struct proc_info))) == NULL) { + fprintf(stderr, _("Unable to allocate memory for proc_info\n")); + return; + } + pr->comm = NULL; + if (sscanf(bptr, + "%m[^)]) " + "%c " + "%d %d %d %d %d %d" + "%lu %lu %lu %lu " /*flts*/ + "%lu %lu %lu %lu " /*times */ + "%ld %ld %ld %ld " /* nice, priority, threads, itreal*/ + "%llu " /*startime*/ + "%lu %ld %lu " /* vsize, rss, rslim */ + "%lu %lu %lu " /* startcode endcode startstack */ + "%lu %lu " /* stack and ip */ + "%*s %*s %*s %*s " /* signals - ignore as they are obsolete */ + "%lu %lu %lu " /* wchan nswap cnswap */ + "%d %d %u" + "%u %llu " /* policy blkio */ + "%lu %lu ", /* guest time cguest time */ + &pr->comm, + &pr->state, + &pr->ppid, &pr->pgrp, &pr->session, &pr->tty_nr, &pr->tp_gid, &pr->flags, + &pr->minflt, &pr->cminflt, &pr->majflt, &pr->cmajflt, + &pr->utime, &pr->stime, &pr->cutime, &pr->cstime, + &pr->priority, &pr->nice, &pr->num_threads, &pr->itrealvalue, + &pr->starttime, + &pr->vsize, &pr->rss, &pr->rsslim, + &pr->startcode, &pr->endcode, &pr->startstack, + &pr->kstesp, &pr->ksteip, + &pr->wchan, &pr->nswap, &pr->cnswap, + &pr->exit_signal, &pr->processor, &pr->rt_priority, + &pr->policy, &pr->blkio, + &pr->guest_time, &pr->cguest_time + ) == 39) { + if (options & OPT_RAW) + print_raw_stat(pid, pr); + else + print_formated_stat(pid, pr); + } else + fprintf(stderr, _("Unable to scan stat file")); + free(pr->comm); + free(pr); +} + +int main(int argc, char *argv[]) +{ + int optc; + struct stat st; + int pptr; + int pid; + opt_type opt_flags = 0; + + struct option options[] = { + {"raw" ,0, NULL, 'r' }, + {"version", 0, NULL, 'V'}, + { 0, 0, 0, 0} + }; + +#ifdef ENABLE_NLS + /* Set up the i18n */ + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + while ((optc = getopt_long(argc, argv, "rV", options, NULL)) != -1) { + switch(optc) { + case 'r': + opt_flags |= OPT_RAW; + break; + case 'V': + print_version(); + return 0; + case '?': + usage(_("Invalid option")); + break; + } + } /* while */ + if (argc <= optind) + usage(_("You must provide at least one PID.")); + + if (stat("/proc/self/stat", &st) == -1) + { + fprintf(stderr, _("/proc is not mounted, cannot stat /proc/self/stat.\n")); + exit(1); + } + sc_clk_tck = sysconf(_SC_CLK_TCK); + for(pptr = optind; pptr < argc; pptr++) + { + pid = atoi(argv[pptr]); + print_stat(pid, opt_flags); + } + + return 0; +} + + + diff --git a/src/prtstat.h b/src/prtstat.h new file mode 100644 index 0000000..64c724f --- /dev/null +++ b/src/prtstat.h @@ -0,0 +1,21 @@ + +typedef unsigned char opt_type; +#define OPT_RAW 1 + +struct proc_info +{ + char *comm; + char state; + int ppid, pgrp, session, tty_nr, tp_gid, + exit_signal, processor; + unsigned int flags, rt_priority, policy; + unsigned long minflt, cminflt, majflt, cmajflt, + utime, stime, vsize, rsslim, + startcode, endcode, startstack, + kstesp, ksteip, + wchan, nswap, cnswap, guest_time; + long cutime, cstime, priority, nice, num_threads, + itrealvalue, rss, cguest_time; + unsigned long long starttime, blkio; +}; + diff --git a/src/pslog.c b/src/pslog.c new file mode 100644 index 0000000..6da6f9b --- /dev/null +++ b/src/pslog.c @@ -0,0 +1,175 @@ +/* + * pslog.c - print process log paths. + * + * Copyright (C) 2015-2017 Vito Mule' + * Copyright (C) 2024 Craig Small <csmall@dropbear.xyz> + * + * + * 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 + +#include <dirent.h> +#include <errno.h> +#include <limits.h> +#include <regex.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> + +#include "i18n.h" + + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif /* PATH_MAX */ + +static int +usage () +{ + fprintf(stderr, + "Usage: pslog PID...\n" + " pslog -V, --version\n\n" + + " -V,--version display version information\n\n"); + exit(255); +} + +void +print_version() +{ + fprintf(stderr, "pslog (PSmisc) %s\n", VERSION); + fprintf(stderr, + "Copyright (C) 2015-2017 Vito Mule'.\n\n"); + fprintf(stderr, + "PSmisc comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under\n" + "the terms of the GNU General Public License.\n" + "For more information about these matters, see the files named COPYING.\n"); +} + +int +main(int argc, char const *argv[]) +{ + regex_t re_log; + regex_t re_pid; + char *fullpath = NULL; + + if (argc < 2) { + usage(); + } + + /* + * Allowed on the command line: + * --version + * -V + * /proc/nnnn + * nnnn + * where nnnn is any number that doesn't begin with 0. + * If --version or -V are present, further arguments are ignored + * completely. + */ + + regcomp(&re_pid, "^((/proc/+)?[1-9][0-9]*|-V|--version)$", + REG_EXTENDED|REG_NOSUB); + + if (regexec(&re_pid, argv[1], 0, NULL, 0) != 0) { + fprintf(stderr, "pslog: invalid process id: %s\n\n", argv[1]); + usage(); + } + else if (!strcmp("-V", argv[1]) || !strcmp("--version", argv[1])) { + print_version(); + return 0; + } + + regfree(&re_pid); + regcomp(&re_log, "^(.*log)$",REG_EXTENDED|REG_NOSUB); + + /* + * At this point, all arguments are in the form /proc/nnnn + * or nnnn, so a simple check based on the first char is + * possible. + */ + + struct dirent *namelist; + + char* linkpath = (char*) malloc(PATH_MAX+1); + if (!linkpath) { + perror ("malloc"); + return 1; + } + + ssize_t linkname_size; + char buf[PATH_MAX+1]; + DIR *pid_dir; + + if (argv[1][0] != '/') { + if (asprintf(&fullpath, "/proc/%s/fd/", argv[1]) < 0) { + perror ("asprintf"); + free(linkpath); + return 1; + } + } else { + if (asprintf(&fullpath, "%s/fd/", argv[1]) < 0) { + perror("asprintf"); + free(linkpath); + return 1; + } + } + + pid_dir = opendir(fullpath); + if (!pid_dir) { + perror("opendir"); + free(linkpath); + free(fullpath); + return 1; + } + + fprintf(stdout, "Pid no %s:\n", argv[1]); + + while((namelist = readdir(pid_dir))) { + strncpy(linkpath, fullpath, PATH_MAX); + strncat(linkpath, namelist->d_name, PATH_MAX - strlen(linkpath)); + linkname_size = readlink(linkpath, buf, PATH_MAX -1); + buf[linkname_size+1] = '\0'; + + if (regexec(&re_log, buf, 0, NULL, 0) == 0) { + fprintf(stdout, "Log path: %s\n", buf); + } + memset(&linkpath[0], 0, sizeof(*linkpath)); + memset(&buf[0], 0, sizeof(buf)); + } + + free(linkpath); + free(fullpath); + regfree(&re_log); + + if (closedir(pid_dir)) { + perror ("closedir"); + return 1; + } + + return 0; +} diff --git a/src/pstree.c b/src/pstree.c new file mode 100644 index 0000000..39265d1 --- /dev/null +++ b/src/pstree.c @@ -0,0 +1,1568 @@ +/* + * pstree.c - display process tree + * + * Copyright (C) 1993-2002 Werner Almesberger + * Copyright (C) 2002-2024 Craig Small <csmall@dropbear.xyz> + * + * 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 + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> +#include <getopt.h> +#include <pwd.h> +#include <dirent.h> +#include <errno.h> +#include <curses.h> +#include <term.h> +#include <termios.h> +#include <langinfo.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <limits.h> +#include <locale.h> + +#include "i18n.h" +#include "comm.h" + +#ifdef WITH_SELINUX +#include <dlfcn.h> +#include <selinux/selinux.h> +#endif /*WITH_SELINUX */ + +#ifdef WITH_APPARMOR +#include <dlfcn.h> +#include <sys/apparmor.h> +#endif /* WITH_APPARMOR */ + +#if !defined(WITH_SELINUX) && !defined(WITH_APPARMOR) +typedef void* security_context_t; /* DUMMY to remove most ifdefs */ +#endif /* !WITH_SELINUX && !WITH_APPARMOR */ + +extern const char *__progname; + +#define PROC_BASE "/proc" + +#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__) +#define DEFAULT_ROOT_PID 0 +#else +#define DEFAULT_ROOT_PID 1 +#endif /* __FreeBSD__ */ + +/* UTF-8 defines by Johan Myreen, updated by Ben Winslow */ +#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */ +#define UTF_VR "\342\224\234" /* U+251C, Vertical and right */ +#define UTF_H "\342\224\200" /* U+2500, Horizontal */ +#define UTF_UR "\342\224\224" /* U+2514, Up and right */ +#define UTF_HD "\342\224\254" /* U+252C, Horizontal and down */ + +#define VT_BEG "\033(0\017" /* use graphic chars */ +#define VT_END "\033(B" /* back to normal char set */ +#define VT_V "x" /* see UTF definitions above */ +#define VT_VR "t" +#define VT_H "q" +#define VT_UR "m" +#define VT_HD "w" + +#define THREAD_FORMAT "{%.*s}" /* Format for thread names */ + +enum ns_type { + CGROUPNS = 0, + IPCNS, + MNTNS, + NETNS, + PIDNS, + USERNS, + UTSNS, + TIMENS, + NUM_NS +}; + +enum color_type { + COLOR_NONE = 0, + COLOR_AGE, + NUM_COLOUR +}; + +static const char *ns_names[] = { + [CGROUPNS] = "cgroup", + [IPCNS] = "ipc", + [MNTNS] = "mnt", + [NETNS] = "net", + [PIDNS] = "pid", + [USERNS] = "user", + [UTSNS] = "uts", + [TIMENS] = "time", +}; + +typedef struct _proc { + char comm[COMM_LEN + 2 + 1]; /* add another 2 for thread brackets */ + char **argv; /* only used : argv[0] is 1st arg; undef if argc < 1 */ + int argc; /* with -a : number of arguments, -1 if swapped */ + pid_t pid; + pid_t pgid; + uid_t uid; + ino_t ns[NUM_NS]; + char flags; + double age; + struct _child *children; + struct _proc *parent; + struct _proc *next; +} PROC; + +/* For flags above */ +#define PFLAG_HILIGHT 0x01 +#define PFLAG_THREAD 0x02 + +typedef struct _child { + PROC *child; + struct _child *next; +} CHILD; + +struct ns_entry { + ino_t number; + CHILD *children; + struct ns_entry *next; +}; + +static struct { + const char *empty_2; /* */ + const char *branch_2; /* |- */ + const char *vert_2; /* | */ + const char *last_2; /* `- */ + const char *single_3; /* --- */ + const char *first_3; /* -+- */ +} sym_ascii = { +" ", "|-", "| ", "`-", "---", "-+-"} + +, sym_utf = { +" ", + UTF_VR UTF_H, + UTF_V " ", + UTF_UR UTF_H, UTF_H UTF_H UTF_H, UTF_H UTF_HD UTF_H}, sym_vt100 = { +" ", + VT_BEG VT_VR VT_H VT_END, + VT_BEG VT_V VT_END " ", + VT_BEG VT_UR VT_H VT_END, + VT_BEG VT_H VT_H VT_H VT_END, VT_BEG VT_H VT_HD VT_H VT_END} + +, *sym = &sym_ascii; + +static PROC *list = NULL; + +struct age_to_color { + unsigned int age_seconds; + char *color; +}; + +struct age_to_color age_to_color[] = { + { 60, "\033[32m"}, + {3600, "\033[33m"}, + {0, "\033[31m"} + }; + +/* The buffers will be dynamically increased in size as needed. */ +static int capacity = 0; +static int *width = NULL; +static int *more = NULL; + +static int print_args = 0, compact = 1, user_change = 0, pids = 0, pgids = 0, + show_parents = 0, by_pid = 0, trunc = 1, wait_end = 0, ns_change = 0, + thread_names = 0, hide_threads = 0; +static int show_scontext = 0; +static int output_width = 132; +static int cur_x = 1; +static char last_char = 0; +static int dumped = 0; /* used by dump_by_user */ +static int charlen = 0; /* length of character */ +static enum color_type color_highlight = COLOR_NONE; + +/* + * Find the root PID. + * Check to see if PID 0 exists, such as in LXC + * Otherwise return 0 for BSD, 1 for others + */ +static pid_t find_root_pid(void) +{ + struct stat s; + + if (stat(PROC_BASE "/0", &s) == 0) + return 0; + return DEFAULT_ROOT_PID; +} + +const char *get_ns_name(enum ns_type id) { + if (id >= NUM_NS) + return NULL; + return ns_names[id]; +} + +static enum ns_type get_ns_id(const char *name) { + int i; + + for (i = 0; i < NUM_NS; i++) + if (!strcmp(ns_names[i], name)) + return i; + return NUM_NS; +} + +static int verify_ns(enum ns_type id) +{ + char filename[50]; + struct stat s; + + snprintf(filename, 50, "/proc/%i/ns/%s", getpid(), get_ns_name(id)); + + return stat(filename, &s); +} + +static inline void new_proc_ns(PROC *ns_task) +{ + struct stat st; + char buff[50]; + pid_t pid = ns_task->pid; + int i; + + for (i = 0; i < NUM_NS; i++) { + snprintf(buff, sizeof(buff), "/proc/%i/ns/%s", pid, + get_ns_name(i)); + if (stat(buff, &st)) { + ns_task->ns[i] = 0; + continue; + } + ns_task->ns[i] = st.st_ino; + } +} + +static void find_ns_and_add(struct ns_entry **root, PROC *r, enum ns_type id) +{ + struct ns_entry *ptr, *last = NULL; + CHILD *tmp_child, **c; + + for (ptr = *root; ptr; ptr = ptr->next) { + if (ptr->number == r->ns[id]) + break; + last = ptr; + } + + if (!ptr) { + + if (!(ptr = malloc(sizeof(*ptr)))) { + perror("malloc"); + exit(1); + } + + memset(ptr, 0, sizeof(*ptr)); + ptr->number = r->ns[id]; + if (*root == NULL) + *root = ptr; + else + last->next = ptr; + } + + /* move the child to under the namespace's umbrella */ + for (c = &ptr->children; *c; c = &(*c)->next) + ; + + if (!(*c = malloc(sizeof(CHILD)))) { + perror("malloc"); + exit(1); + } + + (*c)->child = r; + (*c)->next = NULL; + + /* detaching from parent */ + if (r->parent) { + for (c = &r->parent->children; *c; c = &(*c)->next) { + if ((*c)->child == r) { + tmp_child = (*c)->next; + free(*c); + *c = tmp_child; + break; + } + } + r->parent = NULL; + } + +} + +static PROC *find_proc(pid_t pid); +static void sort_by_namespace(PROC *r, enum ns_type id, struct ns_entry **root) +{ + CHILD *walk, *next; + + /* first run, find the first process */ + if (!r) { + r = find_proc(1); + if (!r) + return; + } + + if (r->parent == NULL || r->parent->ns[id] != r->ns[id]) + find_ns_and_add(root, r, id); + + walk = r->children; + while (walk) { + next = walk->next; + sort_by_namespace(walk->child, id, root); + walk = next; + } +} + +static void fix_orphans(const pid_t root_pid); + +/* + * Determine the correct output width, what we use is: + */ +static int get_output_width(void) +{ + char *ep, *env_columns; + struct winsize winsz; + + env_columns = getenv("COLUMNS"); + if (env_columns && *env_columns) { + long t; + t = strtol(env_columns, &ep, 0); + if (!*ep && (t > 0) && (t < 0x7fffffffL)) + return (int)t; + } + if (ioctl(1, TIOCGWINSZ, &winsz) >= 0) + if (winsz.ws_col) + return winsz.ws_col; + return 132; + +} + +/* + * Allocates additional buffer space for width and more as needed. + * The first call will allocate the first buffer. + * + * index the index that will be used after the call + * to this function. + */ +static void ensure_buffer_capacity(int index) +{ + if (index >= capacity) { + if (capacity == 0) + capacity = 100; + else + capacity *= 2; + if (!(width = realloc(width, capacity * sizeof(int)))) { + perror("realloc"); + exit(1); + } + if (!(more = realloc(more, capacity * sizeof(int)))) { + perror("realloc"); + exit(1); + } + } +} + +/* + * Frees any buffers allocated by ensure_buffer_capacity. + */ +static void free_buffers() +{ + if (width != NULL) { + free(width); + width = NULL; + } + if (more != NULL) { + free(more); + more = NULL; + } + capacity = 0; +} + +static void free_children(CHILD *children) +{ + CHILD *walk, *next; + + walk = children; + while (walk != NULL) { + next = walk->next; + free(walk); + walk = next; + } +} + +static void free_proc() +{ + PROC *walk, *next; + walk = list; + while (walk != NULL) { + next = walk->next; + free_children(walk->children); + if (walk->argv) { + free(walk->argv[0]); + free(walk->argv); + } + free(walk); + walk = next; + } + list = NULL; +} + +static void free_namespace(struct ns_entry **nsroot) +{ + struct ns_entry *walk, *next; + walk = *nsroot; + while (walk != NULL) { + next = walk->next; + free_children(walk->children); + free(walk); + walk = next; + } + *nsroot = NULL; +} + +static void out_char(char c) +{ + if (charlen == 0) { /* "new" character */ + if ((c & 0x80) == 0) { + charlen = 1; /* ASCII */ + } else if ((c & 0xe0) == 0xc0) { /* 110.. 2 bytes */ + charlen = 2; + } else if ((c & 0xf0) == 0xe0) { /* 1110.. 3 bytes */ + charlen = 3; + } else if ((c & 0xf8) == 0xf0) { /* 11110.. 4 bytes */ + charlen = 4; + } else { + charlen = 1; + } + cur_x++; /* count first byte of whatever it is only */ + } + charlen--; + if (!trunc || cur_x <= output_width) + putchar(c); + else { + if (trunc && (cur_x == output_width + 1)) + putchar('+'); + } +} + + +static void out_string(const char *str) +{ + while (*str) + out_char(*str++); +} + + +static int out_int(int x) +{ /* non-negative integers only */ + int digits, div; + + digits = 0; + for (div = 1; x / div; div *= 10) + digits++; + if (!digits) + digits = 1; + for (div /= 10; div; div /= 10) + out_char('0' + (x / div) % 10); + return digits; +} + +#ifdef WITH_SELINUX +static bool out_selinux_context(const PROC *current) +{ + static void (*my_freecon)(char*) = 0; + static int (*my_getpidcon)(pid_t pid, char **context) = 0; + static int (*my_is_selinux_enabled)(void) = 0; + static int selinux_enabled = 0; + static int tried_load = 0; + bool ret = false; + char *context; + + if (!my_getpidcon && !tried_load) { + void *handle = dlopen("libselinux.so.1", RTLD_NOW); + if (handle) { + my_freecon = dlsym(handle, "freecon"); + if (dlerror()) + my_freecon = 0; + dlerror(); + my_getpidcon = dlsym(handle, "getpidcon"); + if (dlerror()) + my_getpidcon = 0; + my_is_selinux_enabled = dlsym(handle, "is_selinux_enabled"); + if (dlerror()) + my_is_selinux_enabled = 0; + else + selinux_enabled = my_is_selinux_enabled(); + } + tried_load++; + } + if (my_getpidcon && selinux_enabled && !my_getpidcon(current->pid, &context)) { + out_string(context); + my_freecon(context); + ret = true; + } + return ret; +} +#endif /* WITH_SELINUX */ + +#ifdef WITH_APPARMOR +static bool out_apparmor_context(const PROC *current) +{ + static int (*my_aa_gettaskcon)(pid_t pid, char **context, char **mode) = 0; + static int (*my_aa_is_enabled)(void) = 0; + static int apparmor_enabled = 0; + static int tried_load = 0; + bool ret = false; + char *context; + + if (!my_aa_gettaskcon && !tried_load) { + void *handle = dlopen("libapparmor.so.1", RTLD_NOW); + if (handle) { + my_aa_gettaskcon = dlsym(handle, "aa_gettaskcon"); + if (dlerror()) + my_aa_gettaskcon = 0; + my_aa_is_enabled = dlsym(handle, "aa_is_enabled"); + if (dlerror()) + my_aa_is_enabled = 0; + else + apparmor_enabled = my_aa_is_enabled(); + } + tried_load++; + } + if (my_aa_gettaskcon && apparmor_enabled && my_aa_gettaskcon(current->pid, &context, NULL) >= 0) { + out_string(context); + free(context); + ret = true; + } + return ret; +} +#endif /* WITH_APPARMOR */ + +/* + * Print the security context of the current process. This is largely lifted + * from pr_context from procps ps/output.c + */ +static void out_scontext(const PROC *current) +{ + bool success = false; + out_string("`"); + +#ifdef WITH_SELINUX + success = out_selinux_context(current); +#endif /* WITH_SELINUX */ + +#ifdef WITH_APPARMOR + success |= out_apparmor_context(current); +#endif /* WITH_APPARMOR */ + + if (!success) { + FILE *file; + char path[50]; + char readbuf[BUFSIZ+1]; + int num_read; + snprintf(path, sizeof path, "/proc/%d/attr/current", current->pid); + if ( (file = fopen(path, "r")) != NULL) { + if (fgets(readbuf, BUFSIZ, file) != NULL) { + num_read = strlen(readbuf); + readbuf[num_read-1] = '\0'; + out_string(readbuf); + } + fclose(file); + } + } + out_string("'"); +} + +static void out_newline(void) +{ + if (last_char && cur_x == output_width) + putchar(last_char); + last_char = 0; + putchar('\n'); + cur_x = 1; +} + +static void reset_color(void) +{ + if (color_highlight != COLOR_NONE) { + char *str = "\033[0m"; + while (*str) putchar(*str++); + } +} + +static void print_proc_color(const int process_age) +{ + struct age_to_color *p; + switch(color_highlight) { + case COLOR_AGE: + for(p=age_to_color; p->age_seconds != 0; p++) + if (process_age < p->age_seconds) break; + + char *str = p->color; + while (*str) putchar(*str++); + break; + default: + break; + } +} + +static PROC *find_proc(pid_t pid) +{ + PROC *walk; + + for (walk = list; walk; walk = walk->next) { + if (walk->pid == pid) + return walk; + } + return NULL; +} + +static PROC *new_proc(const char *comm, pid_t pid, uid_t uid) +{ + PROC *new; + + if (!(new = malloc(sizeof(PROC)))) { + perror("malloc"); + exit(1); + } + + strncpy(new->comm, comm, COMM_LEN+2); + new->comm[COMM_LEN+1] = '\0'; /* make sure nul terminated*/ + new->pid = pid; + new->uid = uid; + new->flags = 0; + new->argc = 0; + new->argv = NULL; + new->children = NULL; + new->parent = NULL; + new->next = list; + new_proc_ns(new); + return list = new; +} + + +static void add_child(PROC * parent, PROC * child) +{ + CHILD *new, **walk; + int cmp; + + if (!(new = malloc(sizeof(CHILD)))) { + perror("malloc"); + exit(1); + } + new->child = child; + for (walk = &parent->children; *walk; walk = &(*walk)->next) + if (by_pid) { + if ((*walk)->child->pid > child->pid) + break; + } else if ((cmp = strcmp((*walk)->child->comm, child->comm)) > 0) { + break; } + else if (!cmp && (*walk)->child->uid > child->uid) + break; + new->next = *walk; + *walk = new; +} + + +static void set_args(PROC * this, const char *args, int size) +{ + char *start; + int i; + + if (!size) { + this->argc = -1; + return; + } + this->argc = 0; + for (i = 0; i < size - 1; i++) + if (!args[i]) { + this->argc++; + /* now skip consecutive NUL */ + while(!args[i] && (i < size -1 )) + i++; + } + if (!this->argc) + return; + if (!(this->argv = malloc(sizeof(char *) * this->argc))) { + perror("malloc"); + exit(1); + } + start = strchr(args, 0) + 1; + size -= start - args; + if (!(this->argv[0] = malloc((size_t) size))) { + perror("malloc"); + exit(1); + } + start = memcpy(this->argv[0], start, (size_t) size); + for (i = 1; i < this->argc; i++) + this->argv[i] = start = strchr(start, 0) + 1; +} + +static void +rename_proc(PROC *this, const char *comm, uid_t uid) +{ + PROC *tmp_child, *parent; + CHILD **walk; + + strncpy(this->comm, comm, COMM_LEN+2); + this->comm[COMM_LEN+1] = '\0'; + this->uid = uid; + + /* Re-sort children in parent, now we have a name */ + if (!by_pid && this->parent) { + parent = this->parent; + for (walk = &parent->children; *walk; walk = &(*walk)->next) { + if ( + ((*walk)->next != NULL) && + strcmp((*walk)->child->comm, (*walk)->next->child->comm) > 0 ) { + tmp_child = (*walk)->child; + (*walk)->child = (*walk)->next->child; + (*walk)->next->child = tmp_child; + } + } + } +} + +static void +add_proc(const char *comm, pid_t pid, pid_t ppid, pid_t pgid, uid_t uid, + const char *args, int size, char isthread, double process_age_sec) +{ + PROC *this, *parent; + + if (!(this = find_proc(pid))) + this = new_proc(comm, pid, uid); + else { + rename_proc(this, comm, uid); + } + if (args) + set_args(this, args, size); + if (pid == ppid) + ppid = 0; + this->pgid = pgid; + this->age = process_age_sec; + if (isthread) + this->flags |= PFLAG_THREAD; + if (!(parent = find_proc(ppid))) { + parent = new_proc("?", ppid, 0); + } + if (pid != 0) { + add_child(parent, this); + this->parent = parent; + } +} + + +static int tree_equal(const PROC * a, const PROC * b) +{ + const CHILD *walk_a, *walk_b; + int i; + + if (strcmp(a->comm, b->comm)) + return 0; + if (user_change && a->uid != b->uid) + return 0; + if (ns_change) { + for (i = 0; i < NUM_NS; i++) + if (a->ns[i] != b->ns[i]) + return 0; + } + for (walk_a = a->children, walk_b = b->children; walk_a && walk_b; + walk_a = walk_a->next, walk_b = walk_b->next) + if (!tree_equal(walk_a->child, walk_b->child)) + return 0; + return !(walk_a || walk_b); +} + +static int +out_args(char *mystr) +{ + char *here; + int strcount=0; + char tmpstr[5]; + + for (here = mystr; *here; here++) { + if (*here == '\\') { + out_string("\\\\"); + strcount += 2; + } else if (*here >= ' ' && *here <= '~') { + out_char(*here); + strcount++; + } else { + sprintf(tmpstr, "\\%03o", (unsigned char) *here); + out_string(tmpstr); + strcount += 4; + } + } /* for */ + return strcount; +} + +static void +dump_tree(PROC * current, int level, int rep, int leaf, int last, + uid_t prev_uid, int closing) +{ + CHILD *walk, *next, *tmp_child, **scan; + const struct passwd *pw; + int lvl, i, add, offset, len, swapped, info, count, comm_len, first; + const char *tmp, *here; + + assert(closing >= 0); + if (!current) + return; + if (!leaf) + for (lvl = 0; lvl < level; lvl++) { + for (i = width[lvl] + 1; i; i--) + out_char(' '); + out_string(lvl == + level - + 1 ? last ? sym->last_2 : sym->branch_2 : more[lvl + + 1] ? + sym->vert_2 : sym->empty_2); + } + + if (rep < 2) + add = 0; + else { + add = out_int(rep) + 2; + out_string("*["); + } + print_proc_color(current->age); + if ((current->flags & PFLAG_HILIGHT) && (tmp = tgetstr("md", NULL))) + tputs(tmp, 1, putchar); + swapped = info = print_args; + if (swapped && current->argc < 0) + out_char('('); + comm_len = out_args(current->comm); + offset = cur_x; + if (pids) { + out_char(info++ ? ',' : '('); + (void) out_int(current->pid); + } + if (pgids) { + out_char(info++ ? ',' : '('); + (void) out_int(current->pgid); + } + if (user_change && prev_uid != current->uid) { + out_char(info++ ? ',' : '('); + if ((pw = getpwuid(current->uid))) + out_string(pw->pw_name); + else + (void) out_int(current->uid); + } + if (ns_change && current->parent) { + for (i = 0; i < NUM_NS; i++) { + if (current->ns[i] == 0 || current->parent->ns[i] == 0) + continue; + if (current->ns[i] != current->parent->ns[i]) { + out_char(info++ ? ',' : '('); + out_string(get_ns_name(i)); + } + } + } + if (show_scontext) { + out_char(info++ ? ',' : '('); + out_scontext(current); + } + if ((swapped && print_args && current->argc < 0) || (!swapped && info)) + out_char(')'); + if ((current->flags & PFLAG_HILIGHT) && (tmp = tgetstr("me", NULL))) + tputs(tmp, 1, putchar); + if (print_args) { + for (i = 0; i < current->argc; i++) { + if (i < current->argc - 1) /* Space between words but not at the end of last */ + out_char(' '); + len = 0; + for (here = current->argv[i]; *here; here++) + len += *here >= ' ' && *here <= '~' ? 1 : 4; + if (cur_x + len <= + output_width - (i == current->argc - 1 ? 0 : 4) || !trunc) + out_args(current->argv[i]); + else { + out_string("..."); + break; + } + } + } + reset_color(); + if (show_scontext || print_args || !current->children) + { + while (closing--) + out_char(']'); + out_newline(); + } + ensure_buffer_capacity(level); + more[level] = !last; + + if (show_scontext || print_args) + { + width[level] = swapped + (comm_len > 1 ? 0 : -1); + count=0; + first=1; + for (walk = current->children; walk; walk = next) { + next = walk->next; + count=0; + if (compact && (walk->child->flags & PFLAG_THREAD)) { + scan = &walk->next; + while (*scan) { + if (!tree_equal(walk->child, (*scan)->child)) { + scan = &(*scan)->next; + } else { + if (next == *scan) + next = (*scan)->next; + count++; + tmp_child = (*scan)->next; + free(*scan); + *scan = tmp_child; + } + } + dump_tree(walk->child, level + 1, count + 1, + 0, !next, current->uid, closing+ (count ? 2 : 1)); + //closing + (count ? 1 : 0)); + } else { + dump_tree(walk->child, level + 1, 1, 0, !walk->next, + current->uid, 0); + } + } + return; + } + width[level] = comm_len + cur_x - offset + add; + if (cur_x >= output_width && trunc) { + out_string(sym->first_3); + out_string("+"); + out_newline(); + return; + } + first = 1; + for (walk = current->children; walk; walk = next) { + count = 0; + next = walk->next; + if (compact) { + scan = &walk->next; + while (*scan) + if (!tree_equal(walk->child, (*scan)->child)) + scan = &(*scan)->next; + else { + if (next == *scan) + next = (*scan)->next; + count++; + tmp_child = (*scan)->next; + free(*scan); + *scan = tmp_child; + } + } + if (first) { + out_string(next ? sym->first_3 : sym->single_3); + first = 0; + } + dump_tree(walk->child, level + 1, count + 1, + walk == current->children, !next, current->uid, + closing + (count ? 1 : 0)); + } +} + + +static void dump_by_user(PROC * current, uid_t uid) +{ + const CHILD *walk; + + if (!current) + return; + + if (current->uid == uid) { + if (dumped) + putchar('\n'); + dump_tree(current, 0, 1, 1, 1, uid, 0); + dumped = 1; + return; + } + for (walk = current->children; walk; walk = walk->next) + dump_by_user(walk->child, uid); +} + +static void dump_by_namespace(struct ns_entry *root) +{ + struct ns_entry *ptr = root; + CHILD *c; + char buff[14]; + + for ( ; ptr; ptr = ptr->next) { + snprintf(buff, sizeof(buff), "[%li]\n", (long int)ptr->number); + out_string(buff); + for (c = ptr->children; c; c = c->next) + dump_tree(c->child, 0, 1, 1, 1, 0, 0); + } +} + +static void trim_tree_by_parent(PROC * current) +{ + if (!current) + return; + + PROC * parent = current->parent; + + if (!parent) + return; + + free_children(parent->children); + parent->children = NULL; + add_child(parent, current); + trim_tree_by_parent(parent); +} + +static double +uptime() +{ + char * savelocale; + char buf[2048]; + FILE* file; + if (!(file=fopen( PROC_BASE "/uptime", "r"))) { + fprintf(stderr, "pstree: error opening uptime file\n"); + exit(1); + } + savelocale = setlocale(LC_NUMERIC,"C"); + if (fscanf(file, "%2047s", buf) == EOF) perror("uptime"); + fclose(file); + setlocale(LC_NUMERIC,savelocale); + return atof(buf); +} + +/* process age from jiffies to seconds via uptime */ +static double process_age(const unsigned long long jf) +{ + double age; + double sc_clk_tck = sysconf(_SC_CLK_TCK); + assert(sc_clk_tck > 0); + age = uptime() - jf / sc_clk_tck; + if (age < 0L) + return 0L; + return age; +} + +static char* get_threadname(const pid_t pid, const int tid, const char *comm) +{ + FILE *file; + char *thread_comm, *endcomm, *threadname; + char *path = NULL; + int len, nbytes; + char readbuf[BUFSIZ + 1]; + + if (! (threadname = malloc(COMM_LEN + 2 + 1))) { + exit(2); + } + if (!thread_names) { + sprintf(threadname, THREAD_FORMAT, COMM_LEN, comm); + return threadname; + } + len = snprintf(NULL, 0, "%s/%d/task/%d/stat", PROC_BASE, pid, tid); + if (len < 0) + exit(2); + len++; + path = malloc(len); + if (path == NULL) + exit(2); + nbytes = snprintf(path, len, "%s/%d/task/%d/stat", PROC_BASE, pid, tid); + if (nbytes < 0 || nbytes >= len) + perror("get_threadname: snprintf"); + if ( (file = fopen(path, "r")) != NULL) { + if (fgets(readbuf, BUFSIZ, file) != NULL) { + if ((thread_comm = strchr(readbuf, '(')) + && (endcomm = strrchr(thread_comm, ')'))) { + ++thread_comm; + *endcomm = '\0'; + sprintf(threadname, THREAD_FORMAT, COMM_LEN, thread_comm); + (void) fclose(file); + free(path); + return threadname; + } + } + fclose(file); + } + free(path); + + /* Fall back to old method */ + sprintf(threadname, THREAD_FORMAT, COMM_LEN, comm); + return threadname; +} + +/* + * read_proc now uses a similar method as procps for finding the process + * name in the /proc filesystem. My thanks to Albert and procps authors. + */ +static void read_proc(const pid_t root_pid) +{ + DIR *dir; + struct dirent *de; + FILE *file; + struct stat st; + char *path, *comm; + char *buffer; + size_t buffer_size; + char readbuf[BUFSIZ + 1]; + char *tmpptr, *endptr; + pid_t pid, ppid, pgid; + int fd, size; + int empty; + unsigned long long proc_stt_jf = 0; + double process_age_sec = 0; + + if (trunc) + buffer_size = output_width + 1; + else + buffer_size = BUFSIZ + 1; + + if (!print_args) + buffer = NULL; + else if (!(buffer = malloc(buffer_size))) { + perror("malloc"); + exit(1); + } + if (!(dir = opendir(PROC_BASE))) { + perror(PROC_BASE); + exit(1); + } + empty = 1; + while ((de = readdir(dir)) != NULL) { + pid = (pid_t) strtol(de->d_name, &endptr, 10); + if (endptr != de->d_name && endptr[0] == '\0') { + if (! (path = malloc(strlen(PROC_BASE) + strlen(de->d_name) + 10))) + exit(2); + sprintf(path, "%s/%d/stat", PROC_BASE, pid); + if ((file = fopen(path, "r")) != NULL) { + empty = 0; + sprintf(path, "%s/%d", PROC_BASE, pid); + if (stat(path, &st) < 0) { + (void) fclose(file); + free(path); + continue; + } + size = fread(readbuf, 1, BUFSIZ, file); + if (ferror(file) == 0) { + readbuf[size] = 0; + /* commands may have spaces or ) in them. + * so don't trust anything from the ( to the last ) */ + if ((comm = strchr(readbuf, '(')) + && (tmpptr = strrchr(comm, ')'))) { + ++comm; + *tmpptr = 0; + /* We now have readbuf with pid and cmd, and tmpptr+2 + * with the rest */ + /*printf("tmpptr: %s\n", tmpptr+2); */ + if (sscanf(tmpptr + 2, "%*c %d %d %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %Lu", + &ppid, &pgid, &proc_stt_jf) == 3) { + DIR *taskdir; + struct dirent *dt; + char *taskpath; + int thread; + + process_age_sec = process_age(proc_stt_jf); + /* handle process threads */ + if (! hide_threads) { + if (! (taskpath = malloc(strlen(path) + 10))) + exit(2); + sprintf(taskpath, "%s/task", path); + + if ((taskdir = opendir(taskpath)) != 0) { + /* if we have this dir, we're on 2.6 */ + while ((dt = readdir(taskdir)) != NULL) { + if ((thread = atoi(dt->d_name)) != 0) { + if (thread != pid) { + char *threadname; + threadname = get_threadname(pid, thread, comm); + if (print_args) + add_proc(threadname, thread, pid, pgid, st.st_uid, + threadname, strlen (threadname) + 1, 1, + process_age_sec); + else + add_proc(threadname, thread, pid, pgid, st.st_uid, + NULL, 0, 1, + process_age_sec); + free(threadname); + } + } + } + (void) closedir(taskdir); + } + free(taskpath); + } + + /* handle process */ + if (!print_args) + add_proc(comm, pid, ppid, pgid, st.st_uid, NULL, 0, 0, + process_age_sec); + else { + sprintf(path, "%s/%d/cmdline", PROC_BASE, pid); + if ((fd = open(path, O_RDONLY)) < 0) { + /* If this fails then the process is gone. If a PID + * was specified on the command-line then we might + * not even be interested in the current process. + * There's no sensible way of dealing with this race + * so we might as well behave as if the current + * process did not exist. */ + (void) fclose(file); + free(path); + continue; + } + if ((size = read(fd, buffer, buffer_size)) < 0) { + /* As above. */ + close(fd); + (void) fclose(file); + free(path); + continue; + } + (void) close(fd); + /* If we have read the maximum screen length of args, + * bring it back by one to stop overflow */ + if (size >= (int)buffer_size) + size--; + if (size) + buffer[size++] = 0; + add_proc(comm, pid, ppid, pgid, st.st_uid, + buffer, size, 0, process_age_sec); + } + } + } + } + (void) fclose(file); + } + free(path); + } + } + (void) closedir(dir); + fix_orphans(root_pid); + if (print_args) + free(buffer); + if (empty) { + fprintf(stderr, _("%s is empty (not mounted ?)\n"), PROC_BASE); + exit(1); + } +} + + +/* When using kernel 3.3 with hidepid feature enabled on /proc + * then we need fake root pid and gather all the orphan processes + * that is, processes with no known parent + * As we cannot be sure if it is just the root pid or others missing + * we gather the lot + */ +static void fix_orphans(const pid_t root_pid) +{ + PROC *root, *walk; + + if (!(root = find_proc(root_pid))) { + root = new_proc("?", root_pid, 0); + } + for (walk = list; walk; walk = walk->next) { + if (walk->pid == 1 || walk->pid == 0) + continue; + if (walk->parent == NULL) { + add_child(root, walk); + walk->parent = root; + } + } +} + + +static void usage(void) +{ + fprintf(stderr, _( + "Usage: pstree [-acglpsStTuZ] [ -h | -H PID ] [ -n | -N type ]\n" + " [ -A | -G | -U ] [ PID | USER ]\n" + " or: pstree -V\n")); + fprintf(stderr, _( + "\n" + "Display a tree of processes.\n\n")); + fprintf(stderr, _( + " -a, --arguments show command line arguments\n" + " -A, --ascii use ASCII line drawing characters\n" + " -c, --compact-not don't compact identical subtrees\n")); + fprintf(stderr, _( + " -C, --color=TYPE color process by attribute\n" + " (age)\n")); + fprintf(stderr, _( + " -g, --show-pgids show process group ids; implies -c\n" + " -G, --vt100 use VT100 line drawing characters\n")); + fprintf(stderr, _( + " -h, --highlight-all highlight current process and its ancestors\n" + " -H PID, --highlight-pid=PID\n" + " highlight this process and its ancestors\n" + " -l, --long don't truncate long lines\n")); + fprintf(stderr, _( + " -n, --numeric-sort sort output by PID\n" + " -N TYPE, --ns-sort=TYPE\n" + " sort output by this namespace type\n" + " (cgroup, ipc, mnt, net, pid, time, user, uts)\n" + " -p, --show-pids show PIDs; implies -c\n")); + fprintf(stderr, _( + " -s, --show-parents show parents of the selected process\n" + " -S, --ns-changes show namespace transitions\n" + " -t, --thread-names show full thread names\n" + " -T, --hide-threads hide threads, show only processes\n")); + fprintf(stderr, _( + " -u, --uid-changes show uid transitions\n" + " -U, --unicode use UTF-8 (Unicode) line drawing characters\n" + " -V, --version display version information\n")); + fprintf(stderr, _( + " -Z, --security-context\n" + " show security attributes\n")); + fprintf(stderr, _("\n" + " PID start at this PID; default is 1 (init)\n" + " USER show only trees rooted at processes of this user\n\n")); + exit(1); +} + +void print_version() +{ + fprintf(stderr, _("pstree (PSmisc) %s\n"), VERSION); + fprintf(stderr, + _ + ("Copyright (C) 1993-2024 Werner Almesberger and Craig Small\n\n")); + fprintf(stderr, + _("PSmisc comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under\n" + "the terms of the GNU General Public License.\n" + "For more information about these matters, see the files named COPYING.\n")); +} + + +int main(int argc, char **argv) +{ + PROC *current; + const struct passwd *pw; + struct ns_entry *nsroot = NULL; + pid_t pid, highlight, root_pid; + char termcap_area[1024]; + char *termname, *endptr; + int c, pid_set = 0; + enum ns_type nsid = NUM_NS; + + struct option options[] = { + {"arguments", 0, NULL, 'a'}, + {"ascii", 0, NULL, 'A'}, + {"compact-not", 0, NULL, 'c'}, + {"color", 1, NULL, 'C'}, + {"vt100", 0, NULL, 'G'}, + {"highlight-all", 0, NULL, 'h'}, + {"highlight-pid", 1, NULL, 'H'}, + {"long", 0, NULL, 'l'}, + {"numeric-sort", 0, NULL, 'n'}, + {"ns-sort", 1, NULL, 'N' }, + {"show-pids", 0, NULL, 'p'}, + {"show-pgids", 0, NULL, 'g'}, + {"show-parents", 0, NULL, 's'}, + {"ns-changes", 0, NULL, 'S' }, + {"thread-names", 0, NULL, 't'}, + {"hide-threads", 0, NULL, 'T'}, + {"uid-changes", 0, NULL, 'u'}, + {"unicode", 0, NULL, 'U'}, + {"version", 0, NULL, 'V'}, + {"security-context", 0, NULL, 'Z'}, + { 0, 0, 0, 0 } + }; + + output_width = get_output_width(); + root_pid = find_root_pid(); + pid = root_pid; + highlight = 0; + pw = NULL; + +#ifdef ENABLE_NLS + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + if (!strcmp(__progname, "pstree.x11")) + wait_end = 1; + + /* + * Attempt to figure out a good default symbol set. Will be overriden by + * command-line options, if given. + */ + + if (isatty(1) && !strcmp(nl_langinfo(CODESET), "UTF-8")) { + /* Use UTF-8 symbols if the locale's character set is UTF-8. */ + sym = &sym_utf; + } else if (isatty(1) && (termname = getenv("TERM")) && + (strlen(termname) > 0) && + (setupterm(NULL, 1 /* stdout */ , NULL) == OK) && + (tigetstr("acsc") != NULL) && (tigetstr("acsc") != (char *)-1)) { + /* + * Failing that, if TERM is defined, a non-null value, and the terminal + * has the VT100 graphics charset, use it. + */ + /* problems with VT100 on some terminals, making this ascci + * for now + */ + sym = &sym_ascii; + } else { + /* Otherwise, fall back to ASCII. */ + sym = &sym_ascii; + } + + while ((c = + getopt_long(argc, argv, "aAcC:GhH:nN:pglsStTuUVZ", options, + NULL)) != -1) + switch (c) { + case 'a': + print_args = 1; + break; + case 'A': + sym = &sym_ascii; + break; + case 'c': + compact = 0; + break; + case 'C': + if (strcasecmp("age", optarg) == 0) { + color_highlight = COLOR_AGE; + } else { + usage(); + } + break; + case 'G': + sym = &sym_vt100; + break; + case 'h': + if (highlight) + usage(); + if (getenv("TERM") + && tgetent(termcap_area, getenv("TERM")) > 0) + highlight = getpid(); + break; + case 'H': + if (highlight) + usage(); + if (!getenv("TERM")) { + fprintf(stderr, _("TERM is not set\n")); + return 1; + } + if (tgetent(termcap_area, getenv("TERM")) <= 0) { + fprintf(stderr, _("Can't get terminal capabilities\n")); + return 1; + } + if (!(highlight = atoi(optarg))) + usage(); + break; + case 'l': + trunc = 0; + break; + case 'n': + by_pid = 1; + break; + case 'N': + nsid = get_ns_id(optarg); + if (nsid == NUM_NS) + usage(); + if (verify_ns(nsid)) { + fprintf(stderr, + _("procfs file for %s namespace not available\n"), + optarg); + return 1; + } + break; + case 'p': + pids = 1; + compact = 0; + break; + case 'g': + pgids = 1; + break; + case 's': + show_parents = 1; + break; + case 'S': + ns_change = 1; + break; + case 't': + thread_names = 1; + break; + case 'T': + hide_threads = 1; + break; + case 'u': + user_change = 1; + break; + case 'U': + sym = &sym_utf; + break; + case 'V': + print_version(); + return 0; + case 'Z': + show_scontext = 1; + break; + default: + usage(); + } + if (optind == argc - 1) { + if (isdigit(*argv[optind])) { + pid = (pid_t) strtol(argv[optind++], &endptr, 10); + pid_set = 1; + if (endptr[0] != '\0') + usage(); + } else if (!(pw = getpwnam(argv[optind++]))) { + fprintf(stderr, _("No such user name: %s\n"), + argv[optind - 1]); + return 1; + } + } + if (optind != argc) + usage(); + read_proc(root_pid); + for (current = find_proc(highlight); current; + current = current->parent) + current->flags |= PFLAG_HILIGHT; + + if(show_parents && pid_set == 1) { + PROC *child_proc; + + if ( (child_proc = find_proc(pid)) == NULL) { + fprintf(stderr, _("Process %d not found.\n"), pid); + return 1; + } + trim_tree_by_parent(child_proc); + + pid = root_pid; + } + + if (nsid != NUM_NS) { + sort_by_namespace(NULL, nsid, &nsroot); + dump_by_namespace(nsroot); + } else if (!pw) + dump_tree(find_proc(pid), 0, 1, 1, 1, 0, 0); + else { + dump_by_user(find_proc(root_pid), pw->pw_uid); + if (!dumped) { + fprintf(stderr, _("No processes found.\n")); + return 1; + } + } + free_buffers(); + free_proc(); + free_namespace(&nsroot); + if (wait_end == 1) { + fprintf(stderr, _("Press return to close\n")); + (void) getchar(); + } + + return 0; +} diff --git a/src/signals.c b/src/signals.c new file mode 100644 index 0000000..c140dba --- /dev/null +++ b/src/signals.c @@ -0,0 +1,86 @@ +/* + * signals.c - signal name handling + * + * Copyright (C) 1993-2002 Werner Almesberger + * Copyright (C) 2002-2024 Craig Small + * + * 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 + */ + + + + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <signal.h> + +#include "i18n.h" +#include "signals.h" + + +typedef struct +{ + int number; + const char *name; +} +SIGNAME; + + +static SIGNAME signals[] = { +#include "signames.h" + {0, NULL} +}; + + +void +list_signals (void) +{ + SIGNAME *walk; + int col; + + col = 0; + for (walk = signals; walk->name; walk++) + { + if (col + strlen (walk->name) + 1 > 80) + { + putchar ('\n'); + col = 0; + } + printf ("%s%s", col ? " " : "", walk->name); + col += strlen (walk->name) + 1; + } + putchar ('\n'); +} + + +int +get_signal (char *name, const char *cmd) +{ + SIGNAME *walk; + + if (isdigit (*name)) + return atoi (name); + if (!strncmp("SIG", name, 3)) + name += 3; + for (walk = signals; walk->name; walk++) + if (!strcmp (walk->name, name)) + break; + if (walk->name) + return walk->number; + fprintf (stderr, _("%s: unknown signal; %s -l lists signals.\n"), name, cmd); + exit (1); +} diff --git a/src/signals.h b/src/signals.h new file mode 100644 index 0000000..b936c46 --- /dev/null +++ b/src/signals.h @@ -0,0 +1,19 @@ +/* signals.h - signal name handling */ + +/* Copyright 1993-1995 Werner Almesberger. See file COPYING for details. */ + + +#ifndef SIGNALS_H +#define SIGNALS_H + +void list_signals (void); + +/* Lists all known signal names on standard output. */ + +int get_signal (char *name, const char *cmd); + +/* Returns the signal number of NAME. If no such signal exists, an error + message is displayed and the program is terminated. CMD is the name of the + application. */ + +#endif diff --git a/src/signames.c b/src/signames.c new file mode 100644 index 0000000..fd8d455 --- /dev/null +++ b/src/signames.c @@ -0,0 +1,5 @@ +/* + * signames.c: Dummy file to better generate signames.h + */ + +#include <signal.h> diff --git a/src/socket_test.c b/src/socket_test.c new file mode 100644 index 0000000..ac6ebec --- /dev/null +++ b/src/socket_test.c @@ -0,0 +1,55 @@ +/* + * Creates named and anonymous sockets to test fuser + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/un.h> + +int main(int argc, char *argv[]) +{ + int skt; + struct sockaddr_un name; + + if (argc < 2) + { + fprintf(stderr, "Usage:\n%s <socketpath>\n", argv[0]); + exit(EXIT_FAILURE); + } + + if ( (skt = socket(AF_UNIX, SOCK_SEQPACKET, 0)) < 0) + { + perror("socket"); + exit(EXIT_FAILURE); + } + + memset(&name, 0, sizeof(name)); + + name.sun_family = AF_UNIX; + strncpy(name.sun_path, argv[1], sizeof(name.sun_path) -1); + + if ( bind(skt, (const struct sockaddr *) &name, sizeof name) < 0) + { + perror("bind"); + exit(EXIT_FAILURE); + } + + if ( listen(skt, 5) < 0) + { + perror("listen"); + exit(EXIT_FAILURE); + } + + if ( accept(skt, NULL, NULL) < 0) + { + perror("accept"); + exit(EXIT_FAILURE); + } + +// sleep(100); + exit(EXIT_SUCCESS); +} + diff --git a/src/statx.c b/src/statx.c new file mode 100644 index 0000000..d4c8d0b --- /dev/null +++ b/src/statx.c @@ -0,0 +1,159 @@ +/* + * 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 + +#include <sys/sysmacros.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> /* Definition of AT_* constants */ + +int stat_flags = AT_NO_AUTOMOUNT|AT_STATX_DONT_SYNC; +#ifdef WITH_STATX + +#include <errno.h> +#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 <sys/stat.h> + +#ifndef HAVE_STATX +# ifndef STATX_TYPE +# include <linux/stat.h> /* Provides 'struct statx' and STATX_* ! */ +# endif +#endif + + +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; + } else if (errno==ENOSYS || errno==EINVAL) + return stat(pathname, st); + 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; + } else if (errno==ENOSYS || errno==EINVAL) + return fstat(fd, st); + 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; + } else if (errno==ENOSYS || errno==EINVAL) + return lstat(pathname, st); + return ret; +} +#endif /* WITH_STATX */ diff --git a/src/statx.h b/src/statx.h new file mode 100644 index 0000000..b28cf9d --- /dev/null +++ b/src/statx.h @@ -0,0 +1,73 @@ +/* + * 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; +#ifdef WITH_STATX +# 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 /* WITH_STATX */ + +#if !defined(HAVE_DECL_STATX_TYPE) || HAVE_DECL_STATX_TYPE == 0 +#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 /* HAVE_DECL_STATX_TYPE */ + +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); +} +#endif /* WITH_STATX */ + +#endif |