summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/comm.h42
-rw-r--r--src/fuser.c2237
-rw-r--r--src/fuser.h113
-rw-r--r--src/i18n.h26
-rw-r--r--src/killall.c1069
-rw-r--r--src/lists.h373
-rw-r--r--src/peekfd.c466
-rw-r--r--src/prtstat.c341
-rw-r--r--src/prtstat.h21
-rw-r--r--src/pslog.c175
-rw-r--r--src/pstree.c1568
-rw-r--r--src/signals.c86
-rw-r--r--src/signals.h19
-rw-r--r--src/signames.c5
-rw-r--r--src/socket_test.c55
-rw-r--r--src/statx.c159
-rw-r--r--src/statx.h73
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(&reglist[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(&reglist[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 (&reglist[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 &current->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, &regs);
+
+#elif defined(ARM64)
+ struct user_pt_regs regs, *old_regs;
+ struct iovec io;
+ io.iov_base = &regs;
+ 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, &regs);
+#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, &regs);
+ 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,&regs);
+ }
+#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