/* * pgrep/pkill -- utilities to filter the process table * * Copyright © 2009-2023 Craig Small * Copyright © 2013-2023 Jim Warner * Copyright © 2011-2012 Sami Kerola * Copyright © 2012 Roberto Polli * Copyright © 2002-2007 Albert Cahalan * Copyright © 2000 Kjetil Torgrim Homme * * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_PIDWAIT #include #ifndef HAVE_PIDFD_OPEN #include #endif /* !HAVE_PIDFD_OPEN */ #endif /* EXIT_SUCCESS is 0 */ /* EXIT_FAILURE is 1 */ #define EXIT_USAGE 2 #define EXIT_FATAL 3 #define XALLOC_EXIT_CODE EXIT_FATAL #include "c.h" #include "fileutils.h" #include "nls.h" #include "signals.h" #include "xalloc.h" #include "misc.h" #include "pids.h" enum pids_item Items[] = { PIDS_ID_PID, PIDS_ID_PPID, PIDS_ID_PGRP, PIDS_ID_EUID, PIDS_ID_RUID, PIDS_ID_RGID, PIDS_ID_SESSION, PIDS_ID_TGID, PIDS_TICS_BEGAN, PIDS_TTY_NAME, PIDS_CMD, PIDS_CMDLINE, PIDS_STATE, PIDS_TIME_ELAPSED, PIDS_CGROUP_V, PIDS_SIGCATCH }; enum rel_items { EU_PID, EU_PPID, EU_PGRP, EU_EUID, EU_RUID, EU_RGID, EU_SESSION, EU_TGID, EU_STARTTIME, EU_TTYNAME, EU_CMD, EU_CMDLINE, EU_STA, EU_ELAPSED, EU_CGROUP, EU_SIGCATCH }; #define grow_size(x) do { \ if ((x) < 0 || (size_t)(x) >= INT_MAX / 5 / sizeof(struct el)) \ xerrx(EXIT_FAILURE, _("integer overflow")); \ (x) = (x) * 5 / 4 + 4; \ } while (0) static enum { PGREP = 0, PKILL, #ifdef ENABLE_PIDWAIT PIDWAIT, #endif } prog_mode; struct el { long num; char * str; }; /* User supplied arguments */ static int opt_full = 0; static int opt_long = 0; static int opt_longlong = 0; static int opt_oldest = 0; static int opt_older = 0; static int opt_newest = 0; static int opt_negate = 0; static int opt_exact = 0; static int opt_count = 0; static int opt_signal = SIGTERM; static int opt_lock = 0; static int opt_case = 0; static int opt_echo = 0; static int opt_threads = 0; static pid_t opt_ns_pid = 0; static bool use_sigqueue = false; static bool require_handler = false; static union sigval sigval = {0}; static const char *opt_delim = "\n"; static struct el *opt_pgrp = NULL; static struct el *opt_rgid = NULL; static struct el *opt_pid = NULL; static struct el *opt_ppid = NULL; static struct el *opt_ignore_ancestors = NULL; static struct el *opt_sid = NULL; static struct el *opt_term = NULL; static struct el *opt_euid = NULL; static struct el *opt_ruid = NULL; static struct el *opt_nslist = NULL; static struct el *opt_cgroup = NULL; static char *opt_pattern = NULL; static char *opt_pidfile = NULL; static char *opt_runstates = NULL; /* by default, all namespaces will be checked */ static int ns_flags = 0x3f; static int __attribute__ ((__noreturn__)) usage(int opt) { int err = (opt == '?'); FILE *fp = err ? stderr : stdout; fputs(USAGE_HEADER, fp); fprintf(fp, _(" %s [options] \n"), program_invocation_short_name); fputs(USAGE_OPTIONS, fp); switch (prog_mode) { case PGREP: fputs(_(" -d, --delimiter specify output delimiter\n"),fp); fputs(_(" -l, --list-name list PID and process name\n"),fp); fputs(_(" -a, --list-full list PID and full command line\n"),fp); fputs(_(" -v, --inverse negates the matching\n"),fp); fputs(_(" -w, --lightweight list all TID\n"), fp); break; case PKILL: fputs(_(" - signal to send (either number or name)\n"), fp); fputs(_(" -H, --require-handler match only if signal handler is present\n"), fp); fputs(_(" -q, --queue integer value to be sent with the signal\n"), fp); fputs(_(" -e, --echo display what is killed\n"), fp); break; #ifdef ENABLE_PIDWAIT case PIDWAIT: fputs(_(" -e, --echo display PIDs before waiting\n"), fp); break; #endif } fputs(_(" -c, --count count of matching processes\n"), fp); fputs(_(" -f, --full use full process name to match\n"), fp); fputs(_(" -g, --pgroup match listed process group IDs\n"), fp); fputs(_(" -G, --group match real group IDs\n"), fp); fputs(_(" -i, --ignore-case match case insensitively\n"), fp); fputs(_(" -n, --newest select most recently started\n"), fp); fputs(_(" -o, --oldest select least recently started\n"), fp); fputs(_(" -O, --older select where older than seconds\n"), fp); fputs(_(" -P, --parent match only child processes of the given parent\n"), fp); fputs(_(" -s, --session match session IDs\n"), fp); fputs(_(" --signal signal to send (either number or name)\n"), fp); fputs(_(" -t, --terminal match by controlling terminal\n"), fp); fputs(_(" -u, --euid match by effective IDs\n"), fp); fputs(_(" -U, --uid match by real IDs\n"), fp); fputs(_(" -x, --exact match exactly with the command name\n"), fp); fputs(_(" -F, --pidfile read PIDs from file\n"), fp); fputs(_(" -L, --logpidfile fail if PID file is not locked\n"), fp); fputs(_(" -r, --runstates match runstates [D,S,Z,...]\n"), fp); fputs(_(" -A, --ignore-ancestors exclude our ancestors from results\n"), fp); fputs(_(" --cgroup match by cgroup v2 names\n"), fp); fputs(_(" --ns match the processes that belong to the same\n" " namespace as \n"), fp); fputs(_(" --nslist list which namespaces will be considered for\n" " the --ns option.\n" " Available namespaces: ipc, mnt, net, pid, user, uts\n"), fp); fputs(USAGE_SEPARATOR, fp); fputs(USAGE_HELP, fp); fputs(USAGE_VERSION, fp); fprintf(fp, USAGE_MAN_TAIL("pgrep(1)")); exit(fp == stderr ? EXIT_USAGE : EXIT_SUCCESS); } static struct el *get_our_ancestors(void) { #define PIDS_GETINT(e) PIDS_VAL(EU_##e, s_int, stack, info) struct el *list = NULL; int i = 0; int size = 0; int done = 0; pid_t search_pid = getpid(); struct pids_stack *stack; while (!done) { struct pids_info *info = NULL; if (procps_pids_new(&info, Items, 16) < 0) xerrx(EXIT_FATAL, _("Unable to create pid info structure")); if (i == size) { grow_size(size); list = xrealloc(list, (1 + size) * sizeof(*list)); } done = 1; while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) { if (PIDS_GETINT(PID) == search_pid) { list[++i].num = PIDS_GETINT(PPID); search_pid = list[i].num; done = 0; break; } } procps_pids_unref(&info); } if (i == 0) { free(list); list = NULL; } else { list[0].num = i; } return list; #undef PIDS_GETINT } static struct el *split_list (const char *restrict str, int (*convert)(const char *, struct el *)) { char *copy; char *ptr; char *sep_pos; int i = 0; int size = 0; struct el *list = NULL; if (str[0] == '\0') return NULL; copy = xstrdup (str); ptr = copy; do { if (i == size) { grow_size(size); /* add 1 because slot zero is a count */ list = xrealloc (list, (1 + size) * sizeof *list); } sep_pos = strchr (ptr, ','); if (sep_pos) *sep_pos = 0; /* Use ++i instead of i++ because slot zero is a count */ if (list && !convert (ptr, &list[++i])) exit (EXIT_USAGE); if (sep_pos) ptr = sep_pos + 1; } while (sep_pos); free (copy); if (!i) { free (list); list = NULL; } else { list[0].num = i; } return list; } /* strict_atol returns a Boolean: TRUE if the input string * contains a plain number, FALSE if there are any non-digits. */ static int strict_atol (const char *restrict str, long *restrict value) { long res = 0; long sign = 1; if (*str == '+') ++str; else if (*str == '-') { ++str; sign = -1; } for ( ; *str; ++str) { if (! isdigit (*str)) return 0; if (res >= LONG_MAX / 10) return 0; res *= 10; if (res >= LONG_MAX - (*str - '0')) return 0; res += *str - '0'; } *value = sign * res; return 1; } #include /* We try a read lock. The daemon should have a write lock. * Seen using flock: FreeBSD code */ static int has_flock(int fd) { return flock(fd, LOCK_SH|LOCK_NB)==-1 && errno==EWOULDBLOCK; } /* We try a read lock. The daemon should have a write lock. * Seen using fcntl: libslack */ static int has_fcntl(int fd) { struct flock f; /* seriously, struct flock is for a fnctl lock! */ f.l_type = F_RDLCK; f.l_whence = SEEK_SET; f.l_start = 0; f.l_len = 0; return fcntl(fd,F_SETLK,&f)==-1 && (errno==EACCES || errno==EAGAIN); } static struct el *read_pidfile(void) { char buf[12]; int fd; struct stat sbuf; char *endp; int n, pid; struct el *list = NULL; fd = open(opt_pidfile, O_RDONLY|O_NOCTTY|O_NONBLOCK); if(fd<0) goto just_ret; if(fstat(fd,&sbuf) || !S_ISREG(sbuf.st_mode) || sbuf.st_size<1) goto out; /* type of lock, if any, is not standardized on Linux */ if(opt_lock && !has_flock(fd) && !has_fcntl(fd)) goto out; memset(buf,'\0',sizeof buf); n = read(fd,buf,sizeof buf-1); if (n<1) goto out; pid = strtoul(buf,&endp,10); if(endp<=buf || pid<1 ) goto out; if(*endp && !isspace(*endp)) goto out; list = xmalloc(2 * sizeof *list); list[0].num = 1; list[1].num = pid; out: close(fd); just_ret: return list; } static int conv_uid (const char *restrict name, struct el *restrict e) { struct passwd *pwd; if (strict_atol (name, &e->num)) return (1); pwd = getpwnam (name); if (pwd == NULL) { xwarnx(_("invalid user name: %s"), name); return 0; } e->num = pwd->pw_uid; return 1; } static int conv_gid (const char *restrict name, struct el *restrict e) { struct group *grp; if (strict_atol (name, &e->num)) return 1; grp = getgrnam (name); if (grp == NULL) { xwarnx(_("invalid group name: %s"), name); return 0; } e->num = grp->gr_gid; return 1; } static int conv_pgrp (const char *restrict name, struct el *restrict e) { if (! strict_atol (name, &e->num)) { xwarnx(_("invalid process group: %s"), name); return 0; } if (e->num == 0) e->num = getpgrp (); return 1; } static int conv_sid (const char *restrict name, struct el *restrict e) { if (! strict_atol (name, &e->num)) { xwarnx(_("invalid session id: %s"), name); return 0; } if (e->num == 0) e->num = getsid (0); return 1; } static int conv_num (const char *restrict name, struct el *restrict e) { if (! strict_atol (name, &e->num)) { xwarnx(_("not a number: %s"), name); return 0; } return 1; } static int conv_str (const char *restrict name, struct el *restrict e) { e->str = xstrdup (name); return 1; } static int conv_ns (const char *restrict name, struct el *restrict e) { int rc = conv_str(name, e); int id; ns_flags = 0; id = procps_ns_get_id(name); if (id < 0) return 0; ns_flags |= (1 << id); return rc; } static int match_numlist (long value, const struct el *restrict list) { int found = 0; if (list != NULL) { int i; for (i = list[0].num; i > 0; i--) { if (list[i].num == value) { found = 1; break; } } } return found; } static unsigned long long unhex (const char *restrict in) { unsigned long long ret; char *rem; errno = 0; ret = strtoull(in, &rem, 16); if (errno || *rem != '\0') { xwarnx(_("not a hex string: %s"), in); return 0; } return ret; } static int match_signal_handler (const char *restrict sigcgt, const int signal) { return sigcgt && (((1UL << (signal - 1)) & unhex(sigcgt)) != 0); } static int match_strlist (const char *restrict value, const struct el *restrict list) { int found = 0; if (list != NULL) { int i; for (i = list[0].num; i > 0; i--) { if (! strcmp (list[i].str, value)) { found = 1; break; } } } return found; } static int match_ns (const int pid, const struct procps_ns *match_ns) { struct procps_ns proc_ns; int found = 1; int i; if (procps_ns_read_pid(pid, &proc_ns) < 0) xerrx(EXIT_FATAL, _("Unable to read process namespace information")); for (i = 0; i < PROCPS_NS_COUNT; i++) { if (ns_flags & (1 << i)) { if (proc_ns.ns[i] != match_ns->ns[i]) { found = 0; break; } } } return found; } static int cgroup_cmp(const char *restrict cgroup, const char *restrict path) { if (cgroup == NULL || path == NULL) return 1; // Cgroup v2 have 0:: if (strncmp("0::", cgroup, 3) == 0) { return strcmp(cgroup+3, path); } //might try for cgroup v1 later return 1; } static int match_cgroup_list(char **values, const struct el *restrict list) { if (list != NULL && values != NULL) { int i, j; for (i = list[0].num; i > 0; i--) { for (j=0; values[j] && values[j][0]; j++) { if (! cgroup_cmp (values[j], list[i].str)) { return 1; } } } } return 0; } static void output_numlist (const struct el *restrict list, int num) { int i; const char *delim = opt_delim; for (i = 0; i < num; i++) { if(i+1==num) delim = "\n"; printf ("%ld%s", list[i].num, delim); } } static void output_strlist (const struct el *restrict list, int num) { /* FIXME: escape codes */ int i; const char *delim = opt_delim; for (i = 0; i < num; i++) { if(i+1==num) delim = "\n"; printf ("%lu %s%s", list[i].num, list[i].str, delim); } } static regex_t * do_regcomp (void) { regex_t *preg = NULL; if (opt_pattern) { char *re; char errbuf[256]; int re_err; preg = xmalloc (sizeof (regex_t)); if (opt_exact) { re = xmalloc (strlen (opt_pattern) + 5); sprintf (re, "^(%s)$", opt_pattern); } else { re = opt_pattern; } re_err = regcomp (preg, re, REG_EXTENDED | REG_NOSUB | opt_case); if (opt_exact) free(re); if (re_err) { regerror (re_err, preg, errbuf, sizeof(errbuf)); xerrx(EXIT_USAGE, _("regex error: %s"), errbuf); } } return preg; } /* * SC_ARG_MAX used to return the maximum size a command line can be * however changes to the kernel mean this can be bigger than we can * alloc. Clamp it to 128kB like xargs and friends do * Should also not be smaller than POSIX_ARG_MAX which is 4096 */ static size_t get_arg_max(void) { #define MIN_ARG_SIZE 4096u #define MAX_ARG_SIZE (128u * 1024u) size_t val = sysconf(_SC_ARG_MAX); if (val < MIN_ARG_SIZE) val = MIN_ARG_SIZE; if (val > MAX_ARG_SIZE) val = MAX_ARG_SIZE; return val; } static struct el * select_procs (int *num) { #define PIDS_GETINT(e) PIDS_VAL(EU_ ## e, s_int, stack, info) #define PIDS_GETUNT(e) PIDS_VAL(EU_ ## e, u_int, stack, info) #define PIDS_GETULL(e) PIDS_VAL(EU_ ## e, ull_int, stack, info) #define PIDS_GETSTR(e) PIDS_VAL(EU_ ## e, str, stack, info) #define PIDS_GETSCH(e) PIDS_VAL(EU_ ## e, s_ch, stack, info) #define PIDS_GETSTV(e) PIDS_VAL(EU_ ## e, strv, stack, info) #define PIDS_GETFLT(e) PIDS_VAL(EU_ ## e, real, stack, info) struct pids_info *info=NULL; struct procps_ns nsp; struct pids_stack *stack; unsigned long long saved_start_time; /* for new/old support */ int saved_pid = 0; /* for new/old support */ int matches = 0; int size = 0; regex_t *preg; pid_t myself = getpid(); struct el *list = NULL; long cmdlen = get_arg_max() * sizeof(char); char *cmdline = xmalloc(cmdlen); char *cmdsearch = xmalloc(cmdlen); char *cmdoutput = xmalloc(cmdlen); char *task_cmdline; enum pids_fetch_type which; preg = do_regcomp(); if (opt_newest) saved_start_time = 0ULL; else saved_start_time = ~0ULL; if (opt_newest) saved_pid = 0; if (opt_oldest) saved_pid = INT_MAX; if (opt_ns_pid && procps_ns_read_pid(opt_ns_pid, &nsp) < 0) { xerrx(EXIT_FATAL, _("Error reading reference namespace information\n")); } if (procps_pids_new(&info, Items, 16) < 0) xerrx(EXIT_FATAL, _("Unable to create pid info structure")); which = PIDS_FETCH_TASKS_ONLY; // pkill and pidwait don't support -w, but this is checked in getopt if (opt_threads) which = PIDS_FETCH_THREADS_TOO; while ((stack = procps_pids_get(info, which))) { int match = 1; if (PIDS_GETINT(PID) == myself) continue; else if (opt_ignore_ancestors && match_numlist(PIDS_GETINT(PID), opt_ignore_ancestors)) continue; else if (opt_newest && PIDS_GETULL(STARTTIME) < saved_start_time) match = 0; else if (opt_oldest && PIDS_GETULL(STARTTIME) > saved_start_time) match = 0; else if (opt_ppid && ! match_numlist(PIDS_GETINT(PPID), opt_ppid)) match = 0; else if (opt_pid && ! match_numlist (PIDS_GETINT(TGID), opt_pid)) match = 0; else if (opt_pgrp && ! match_numlist (PIDS_GETINT(PGRP), opt_pgrp)) match = 0; else if (opt_euid && ! match_numlist (PIDS_GETUNT(EUID), opt_euid)) match = 0; else if (opt_ruid && ! match_numlist (PIDS_GETUNT(RUID), opt_ruid)) match = 0; else if (opt_rgid && ! match_numlist (PIDS_GETUNT(RGID), opt_rgid)) match = 0; else if (opt_sid && ! match_numlist (PIDS_GETINT(SESSION), opt_sid)) match = 0; else if (opt_ns_pid && ! match_ns (PIDS_GETINT(PID), &nsp)) match = 0; else if (opt_older && (int)PIDS_GETFLT(ELAPSED) < opt_older) match = 0; else if (opt_term && ! match_strlist(PIDS_GETSTR(TTYNAME), opt_term)) match = 0; else if (opt_runstates && ! strchr(opt_runstates, PIDS_GETSCH(STA))) match = 0; else if (opt_cgroup && ! match_cgroup_list (PIDS_GETSTV(CGROUP), opt_cgroup)) match = 0; else if (require_handler && ! match_signal_handler (PIDS_GETSTR(SIGCATCH), opt_signal)) match = 0; task_cmdline = PIDS_GETSTR(CMDLINE); if (opt_long || opt_longlong || (match && opt_pattern)) { if (opt_longlong) strncpy (cmdoutput, task_cmdline, cmdlen -1); else strncpy (cmdoutput, PIDS_GETSTR(CMD), cmdlen -1); cmdoutput[cmdlen - 1] = '\0'; } if (match && opt_pattern) { if (opt_full) strncpy (cmdsearch, task_cmdline, cmdlen -1); else strncpy (cmdsearch, PIDS_GETSTR(CMD), cmdlen -1); cmdsearch[cmdlen - 1] = '\0'; if (regexec (preg, cmdsearch, 0, NULL, 0) != 0) match = 0; } if (match ^ opt_negate) { /* Exclusive OR is neat */ if (opt_newest) { if (saved_start_time == PIDS_GETULL(STARTTIME) && saved_pid > PIDS_GETINT(PID)) continue; saved_start_time = PIDS_GETULL(STARTTIME); saved_pid = PIDS_GETINT(PID); matches = 0; } if (opt_oldest) { if (saved_start_time == PIDS_GETULL(STARTTIME) && saved_pid < PIDS_GETINT(PID)) continue; saved_start_time = PIDS_GETULL(STARTTIME); saved_pid = PIDS_GETINT(PID); matches = 0; } if (matches == size) { grow_size(size); list = xrealloc(list, size * sizeof *list); } if (list && (opt_long || opt_longlong || opt_echo)) { list[matches].num = PIDS_GETINT(PID); list[matches++].str = xstrdup (cmdoutput); } else if (list) { list[matches++].num = PIDS_GETINT(PID); } else { xerrx(EXIT_FATAL, _("internal error")); } } } procps_pids_unref(&info); free(cmdline); free(cmdsearch); free(cmdoutput); if (preg) { regfree(preg); free(preg); } *num = matches; if ((!matches) && (!opt_full) && opt_pattern && (strlen(opt_pattern) > 15)) xwarnx(_("pattern that searches for process name longer than 15 characters will result in zero matches\n" "Try `%s -f' option to match against the complete command line."), program_invocation_short_name); return list; #undef PIDS_GETINT #undef PIDS_GETUNT #undef PIDS_GETULL #undef PIDS_GETSTR #undef PIDS_GETSTV } static int signal_option(int *argc, char **argv) { int sig; int i; for (i = 1; i < *argc; i++) { if (argv[i][0] == '-') { sig = signal_name_to_number(argv[i] + 1); if (-1 < sig) { memmove(argv + i, argv + i + 1, sizeof(char *) * (*argc - i)); (*argc)--; return sig; } } } return -1; } #if defined(ENABLE_PIDWAIT) && !defined(HAVE_PIDFD_OPEN) static int pidfd_open (pid_t pid, unsigned int flags) { return syscall(__NR_pidfd_open, pid, flags); } #endif static void parse_opts (int argc, char **argv) { char opts[64] = ""; int opt; int criteria_count = 0; enum { SIGNAL_OPTION = CHAR_MAX + 1, NS_OPTION, NSLIST_OPTION, CGROUP_OPTION, }; static const struct option longopts[] = { {"signal", required_argument, NULL, SIGNAL_OPTION}, {"ignore-ancestors", no_argument, NULL, 'A'}, {"require-handler", no_argument, NULL, 'H'}, {"count", no_argument, NULL, 'c'}, {"cgroup", required_argument, NULL, CGROUP_OPTION}, {"delimiter", required_argument, NULL, 'd'}, {"list-name", no_argument, NULL, 'l'}, {"list-full", no_argument, NULL, 'a'}, {"full", no_argument, NULL, 'f'}, {"pgroup", required_argument, NULL, 'g'}, {"group", required_argument, NULL, 'G'}, {"ignore-case", no_argument, NULL, 'i'}, {"newest", no_argument, NULL, 'n'}, {"oldest", no_argument, NULL, 'o'}, {"older", required_argument, NULL, 'O'}, {"parent", required_argument, NULL, 'P'}, {"session", required_argument, NULL, 's'}, {"terminal", required_argument, NULL, 't'}, {"euid", required_argument, NULL, 'u'}, {"uid", required_argument, NULL, 'U'}, {"inverse", no_argument, NULL, 'v'}, {"lightweight", no_argument, NULL, 'w'}, {"exact", no_argument, NULL, 'x'}, {"pidfile", required_argument, NULL, 'F'}, {"logpidfile", no_argument, NULL, 'L'}, {"echo", no_argument, NULL, 'e'}, {"ns", required_argument, NULL, NS_OPTION}, {"nslist", required_argument, NULL, NSLIST_OPTION}, {"queue", required_argument, NULL, 'q'}, {"runstates", required_argument, NULL, 'r'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0} }; #ifdef ENABLE_PIDWAIT if (strcmp (program_invocation_short_name, "pidwait") == 0 || strcmp (program_invocation_short_name, "lt-pidwait") == 0) { prog_mode = PIDWAIT; strcat (opts, "e"); } else #endif if (strcmp (program_invocation_short_name, "pkill") == 0 || strcmp (program_invocation_short_name, "lt-pkill") == 0) { int sig; prog_mode = PKILL; sig = signal_option(&argc, argv); if (-1 < sig) opt_signal = sig; strcat (opts, "eq:"); } else { strcat (opts, "lad:vw"); prog_mode = PGREP; } strcat (opts, "LF:cfinoxP:O:AHg:s:u:U:G:t:r:?Vh"); while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != -1) { switch (opt) { case SIGNAL_OPTION: opt_signal = signal_name_to_number (optarg); if (opt_signal == -1) { if (isdigit (optarg[0])) opt_signal = atoi (optarg); else { fprintf(stderr, _("Unknown signal \"%s\"."), optarg); usage('?'); } } break; case 'e': opt_echo = 1; break; /* case 'D': / * FreeBSD: print info about non-matches for debugging * / * break; */ case 'F': /* FreeBSD: the arg is a file containing a PID to match */ opt_pidfile = xstrdup (optarg); ++criteria_count; break; case 'G': /* Solaris: match rgid/rgroup */ opt_rgid = split_list (optarg, conv_gid); if (opt_rgid == NULL) usage ('?'); ++criteria_count; break; /* case 'I': / * FreeBSD: require confirmation before killing * / * break; */ /* case 'J': / * Solaris: match by project ID (name or number) * / * break; */ case 'L': /* FreeBSD: fail if pidfile (see -F) not locked */ opt_lock++; break; /* case 'M': / * FreeBSD: specify core (OS crash dump) file * / * break; */ /* case 'N': / * FreeBSD: specify alternate namelist file (for us, System.map -- but we don't need it) * / * break; */ case 'P': /* Solaris: match by PPID */ opt_ppid = split_list (optarg, conv_num); if (opt_ppid == NULL) usage ('?'); ++criteria_count; break; /* case 'S': / * FreeBSD: don't ignore the built-in kernel tasks * / * break; */ /* case 'T': / * Solaris: match by "task ID" (probably not a Linux task) * / * break; */ case 'U': /* Solaris: match by ruid/rgroup */ opt_ruid = split_list (optarg, conv_uid); if (opt_ruid == NULL) usage ('?'); ++criteria_count; break; case 'V': printf(PROCPS_NG_VERSION); exit(EXIT_SUCCESS); /* case 'c': / * Solaris: match by contract ID * / * break; */ case 'c': opt_count = 1; break; case 'd': /* Solaris: change the delimiter */ opt_delim = xstrdup (optarg); break; case 'f': /* Solaris: match full process name (as in "ps -f") */ opt_full = 1; break; case 'g': /* Solaris: match pgrp */ opt_pgrp = split_list (optarg, conv_pgrp); if (opt_pgrp == NULL) usage ('?'); ++criteria_count; break; case 'i': /* FreeBSD: ignore case. OpenBSD: withdrawn. See -I. This sucks. */ if (opt_case) usage (opt); opt_case = REG_ICASE; break; /* case 'j': / * FreeBSD: restricted to the given jail ID * / * break; */ case 'l': /* Solaris: long output format (pgrep only) Should require -f for beyond argv[0] maybe? */ opt_long = 1; break; case 'a': opt_longlong = 1; break; case 'A': opt_ignore_ancestors = get_our_ancestors(); break; case 'n': /* Solaris: match only the newest */ if (opt_oldest|opt_negate|opt_newest) usage ('?'); opt_newest = 1; ++criteria_count; break; case 'o': /* Solaris: match only the oldest */ if (opt_oldest|opt_negate|opt_newest) usage ('?'); opt_oldest = 1; ++criteria_count; break; case 'O': opt_older = atoi (optarg); ++criteria_count; break; case 's': /* Solaris: match by session ID -- zero means self */ opt_sid = split_list (optarg, conv_sid); if (opt_sid == NULL) usage ('?'); ++criteria_count; break; case 't': /* Solaris: match by tty */ opt_term = split_list (optarg, conv_str); if (opt_term == NULL) usage ('?'); ++criteria_count; break; case 'u': /* Solaris: match by euid/egroup */ opt_euid = split_list (optarg, conv_uid); if (opt_euid == NULL) usage ('?'); ++criteria_count; break; case 'v': /* Solaris: as in grep, invert the matching (uh... applied after selection I think) */ if (opt_oldest|opt_negate|opt_newest) usage ('?'); opt_negate = 1; break; case 'w': // Linux: show threads (lightweight process) too opt_threads = 1; break; /* OpenBSD -x, being broken, does a plain string */ case 'x': /* Solaris: use ^(regexp)$ in place of regexp (FreeBSD too) */ opt_exact = 1; break; /* case 'z': / * Solaris: match by zone ID * / * break; */ case NS_OPTION: opt_ns_pid = atoi(optarg); if (opt_ns_pid == 0) case 'r': /* match by runstate */ opt_runstates = xstrdup (optarg); ++criteria_count; break; case NSLIST_OPTION: opt_nslist = split_list (optarg, conv_ns); if (opt_nslist == NULL) usage ('?'); break; case 'q': sigval.sival_int = atoi(optarg); use_sigqueue = true; break; case CGROUP_OPTION: opt_cgroup = split_list (optarg, conv_str); if (opt_cgroup == NULL) usage ('?'); ++criteria_count; break; case 'H': require_handler = true; ++criteria_count; break; case 'h': case '?': usage (opt); break; } } if(opt_lock && !opt_pidfile) xerrx(EXIT_USAGE, _("-L without -F makes no sense\n" "Try `%s --help' for more information."), program_invocation_short_name); if(opt_pidfile){ opt_pid = read_pidfile(); if(!opt_pid) xerrx(EXIT_FAILURE, _("pidfile not valid\n" "Try `%s --help' for more information."), program_invocation_short_name); } if (argc - optind == 1) opt_pattern = argv[optind]; else if (argc - optind > 1) xerrx(EXIT_USAGE, _("only one pattern can be provided\n" "Try `%s --help' for more information."), program_invocation_short_name); else if (criteria_count == 0) xerrx(EXIT_USAGE, _("no matching criteria specified\n" "Try `%s --help' for more information."), program_invocation_short_name); } inline static int execute_kill(pid_t pid, int sig_num) { if (use_sigqueue) return sigqueue(pid, sig_num, sigval); else return kill(pid, sig_num); } int main (int argc, char **argv) { struct el *procs; int num; int i; int kill_count = 0; #ifdef ENABLE_PIDWAIT int poll_count = 0; int wait_count = 0; int epollfd = epoll_create(1); struct epoll_event ev, events[32]; #endif #ifdef HAVE_PROGRAM_INVOCATION_NAME program_invocation_name = program_invocation_short_name; #endif setlocale (LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); atexit(close_stdout); parse_opts (argc, argv); procs = select_procs (&num); switch (prog_mode) { case PGREP: if (opt_count) { fprintf(stdout, "%d\n", num); } else { if (opt_long || opt_longlong) output_strlist (procs,num); else output_numlist (procs,num); } return !num; case PKILL: for (i = 0; i < num; i++) { if (execute_kill (procs[i].num, opt_signal) != -1) { if (opt_echo) printf(_("%s killed (pid %lu)\n"), procs[i].str, procs[i].num); kill_count++; continue; } if (errno==ESRCH) /* gone now, which is OK */ continue; xwarn(_("killing pid %ld failed"), procs[i].num); } if (opt_count) fprintf(stdout, "%d\n", num); return !kill_count; #ifdef ENABLE_PIDWAIT case PIDWAIT: if (opt_count) fprintf(stdout, "%d\n", num); for (i = 0; i < num; i++) { if (opt_echo) printf(_("waiting for %s (pid %lu)\n"), procs[i].str, procs[i].num); int pidfd = pidfd_open(procs[i].num, 0); if (pidfd == -1) { if (errno == ENOSYS) xerrx(EXIT_FAILURE, _("pidfd_open() not implemented in Linux < 5.3")); /* ignore ESRCH, same as pkill */ if (errno != ESRCH) xwarn(_("opening pid %ld failed"), procs[i].num); continue; } ev.events = EPOLLIN | EPOLLET; ev.data.fd = pidfd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, pidfd, &ev) != -1) poll_count++; } while (wait_count < poll_count) { int ew = epoll_wait(epollfd, events, sizeof(events)/sizeof(events[0]), -1); if (ew == -1) { if (errno == EINTR) continue; xwarn(_("epoll_wait failed")); } wait_count += ew; } return !wait_count; #endif } /* Not sure if it is possible to get here */ return -1; }