summaryrefslogtreecommitdiffhomepage
path: root/procps/pgrep.c
blob: a3ca9e295686495f0bf328cf8c255e46de363661 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/* vi: set sw=4 ts=4: */
/*
 * Mini pgrep/pkill implementation for busybox
 *
 * Copyright (C) 2007 Loic Grenie <loic.grenie@gmail.com>
 *
 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
 */
//config:config PGREP
//config:	bool "pgrep (6.8 kb)"
//config:	default y
//config:	help
//config:	Look for processes by name.
//config:
//config:config PKILL
//config:	bool "pkill (7.6 kb)"
//config:	default y
//config:	help
//config:	Send signals to processes by name.

//applet:IF_PGREP(APPLET(pgrep, BB_DIR_USR_BIN, BB_SUID_DROP))
//                APPLET_ODDNAME:name   main   location        suid_type     help
//applet:IF_PKILL(APPLET_ODDNAME(pkill, pgrep, BB_DIR_USR_BIN, BB_SUID_DROP, pkill))

//kbuild:lib-$(CONFIG_PGREP) += pgrep.o
//kbuild:lib-$(CONFIG_PKILL) += pgrep.o

//usage:#define pgrep_trivial_usage
//usage:       "[-flanovx] [-s SID|-P PPID|PATTERN]"
//usage:#define pgrep_full_usage "\n\n"
//usage:       "Display process(es) selected by regex PATTERN\n"
//usage:     "\n	-l	Show command name too"
//usage:     "\n	-a	Show command line too"
//usage:     "\n	-f	Match against entire command line"
//usage:     "\n	-n	Show the newest process only"
//usage:     "\n	-o	Show the oldest process only"
//usage:     "\n	-v	Negate the match"
//usage:     "\n	-x	Match whole name (not substring)"
//usage:     "\n	-s	Match session ID (0 for current)"
//usage:     "\n	-P	Match parent process ID"
//usage:
//usage:#define pkill_trivial_usage
//usage:       "[-l|-SIGNAL] [-fnovx] [-s SID|-P PPID|PATTERN]"
//usage:#define pkill_full_usage "\n\n"
//usage:       "Send a signal to process(es) selected by regex PATTERN\n"
//usage:     "\n	-l	List all signals"
//usage:     "\n	-f	Match against entire command line"
//usage:     "\n	-n	Signal the newest process only"
//usage:     "\n	-o	Signal the oldest process only"
//usage:     "\n	-v	Negate the match"
//usage:     "\n	-x	Match whole name (not substring)"
//usage:     "\n	-s	Match session ID (0 for current)"
//usage:     "\n	-P	Match parent process ID"

#include "libbb.h"
#include "xregex.h"

/* Idea taken from kill.c */
#define pgrep (ENABLE_PGREP && (!ENABLE_PKILL || applet_name[1] == 'g'))
#define pkill (ENABLE_PKILL && (!ENABLE_PGREP || applet_name[1] == 'k'))

enum {
	/* "vlafxons:+P:+" */
	OPTBIT_V = 0, /* must be first, we need OPT_INVERT = 0/1 */
	OPTBIT_L,
	OPTBIT_A,
	OPTBIT_F,
	OPTBIT_X,
	OPTBIT_O,
	OPTBIT_N,
	OPTBIT_S,
	OPTBIT_P,
};

#define OPT_INVERT	(opt & (1 << OPTBIT_V))
#define OPT_LIST	(opt & (1 << OPTBIT_L))
#define OPT_LISTFULL	(opt & (1 << OPTBIT_A))
#define OPT_FULL	(opt & (1 << OPTBIT_F))
#define OPT_ANCHOR	(opt & (1 << OPTBIT_X))
#define OPT_FIRST	(opt & (1 << OPTBIT_O))
#define OPT_LAST	(opt & (1 << OPTBIT_N))
#define OPT_SID		(opt & (1 << OPTBIT_S))
#define OPT_PPID	(opt & (1 << OPTBIT_P))

static void act(unsigned pid, char *cmd, int signo)
{
	if (pgrep) {
		if (option_mask32 & ((1 << OPTBIT_L)|(1 << OPTBIT_A))) /* -l or -a */
			printf("%u %s\n", pid, cmd);
		else
			printf("%u\n", pid);
	} else
		kill(pid, signo);
}

int pgrep_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int pgrep_main(int argc UNUSED_PARAM, char **argv)
{
	unsigned pid;
	int signo;
	unsigned opt;
	int scan_mask;
	int matched_pid;
	int sid2match, ppid2match;
	char *cmd_last;
	procps_status_t *proc;
	/* These are initialized to 0 */
	struct {
		regex_t re_buffer;
		regmatch_t re_match[1];
	} Z;
#define re_buffer (Z.re_buffer)
#define re_match  (Z.re_match )

	memset(&Z, 0, sizeof(Z));

	/* Parse -SIGNAL for pkill. Must be first option, if present */
	signo = SIGTERM;
	if (pkill && argv[1] && argv[1][0] == '-') {
		int temp = get_signum(argv[1]+1);
		if (temp != -1) {
			signo = temp;
			argv++;
		}
	}

	/* Parse remaining options */
	ppid2match = -1;
	sid2match = -1;
	opt = getopt32(argv, "vlafxons:+P:+", &sid2match, &ppid2match);
	argv += optind;

	if (pkill && OPT_LIST) { /* -l: print the whole signal list */
		print_signames();
		return 0;
	}

	pid = getpid();
	if (sid2match == 0)
		sid2match = getsid(pid);

	scan_mask = PSSCAN_COMM | PSSCAN_ARGV0;
	if (OPT_FULL)
		scan_mask |= PSSCAN_ARGVN;

	/* One pattern is required, if no -s and no -P */
	if ((sid2match & ppid2match) < 0 && (!argv[0] || argv[1]))
		bb_show_usage();

	if (argv[0])
		xregcomp(&re_buffer, argv[0], OPT_ANCHOR ? REG_EXTENDED : (REG_EXTENDED|REG_NOSUB));

	matched_pid = 0;
	cmd_last = NULL;
	proc = NULL;
	while ((proc = procps_scan(proc, scan_mask)) != NULL) {
		char *cmd;
		int cmdlen, match;

		if (proc->pid == pid)
			continue;

		if (!OPT_INVERT) {
			/* Quickly reject -sN -PN mismatches... unless -v */
			if (ppid2match >= 0 && ppid2match != proc->ppid)
				continue;
			if (sid2match >= 0 && sid2match != proc->sid)
				continue;
		}

		cmdlen = -1;
		cmd = proc->argv0;
		if (!cmd) {
			cmd = proc->comm;
		} else {
			int i = proc->argv_len;

			if (!OPT_LISTFULL)
				cmdlen = strlen(cmd); /* not -a: find first NUL */
			/*
			 * "sleep 11" looks like "sleep""\0""11""\0" in argv0.
			 * Make sure last "\0" does not get converted to " ":
			 */
			if (i && cmd[i-1] == '\0')
				i--;
			while (--i >= 0) {
				if ((unsigned char)cmd[i] < ' ')
					cmd[i] = ' ';
			}
		}

		if (OPT_INVERT) {
			/* "pgrep -v -P1 firefox" means "not (ppid=1 AND name=firefox)"
			 * or equivalently "ppid!=1 OR name!=firefox".
			 * Check the first condition and if true, skip matching.
			 */
			if (ppid2match >= 0 && ppid2match != proc->ppid)
				goto got_it;
			if (sid2match >= 0 && sid2match != proc->sid)
				goto got_it;
		}

		match = !argv[0]; /* if no PATTERN, then it's a match, else... */
		if (!match) {
 again:
			match = (regexec(&re_buffer, cmd, 1, re_match, 0) == 0);
			if (!match && cmd != proc->comm) {
				/* if argv[] did not match, try comm */
				cmdlen = -1;
				cmd = proc->comm;
				goto again;
			}
			if (match && OPT_ANCHOR) {
				/* -x requires full string match */
				match = (re_match[0].rm_so == 0 && cmd[re_match[0].rm_eo] == '\0');
			}
		}

		/* NB: OPT_INVERT is always 0 or 1 */
		if (match ^ OPT_INVERT) {
 got_it:
			matched_pid = proc->pid;
			if (OPT_LAST) {
				free(cmd_last);
				cmd_last = xstrdup(cmd);
				continue;
			}
			if (cmdlen >= 0)
				cmd[cmdlen] = '\0';
			act(proc->pid, cmd, signo);
			if (OPT_FIRST)
				break;
		}
	}

	if (cmd_last) {
		act(matched_pid, cmd_last, signo);
		if (ENABLE_FEATURE_CLEAN_UP)
			free(cmd_last);
	}
	return matched_pid == 0; /* return 1 if no processes listed/signaled */
}