VirtualBox

source: kBuild/trunk/src/kmk/kmkbuiltin/kill.c@ 3387

Last change on this file since 3387 was 3352, checked in by bird, 4 years ago

Adding kmk_kill for windows.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 21.1 KB
Line 
1/* $Id: kill.c 3352 2020-06-05 00:31:50Z bird $ */
2/** @file
3 * kmk kill - Process killer.
4 */
5
6/*
7 * Copyright (c) 2007-2020 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25
26/*******************************************************************************
27* Header Files *
28*******************************************************************************/
29#include <assert.h>
30#include <stdlib.h>
31#include <stddef.h>
32#include <string.h>
33#ifdef WINDOWS32
34# include <process.h>
35# include <Windows.h>
36# include <tlhelp32.h>
37#endif
38
39#ifdef HAVE_CONFIG_H
40# include "config.h"
41#endif
42#include "err.h"
43#include "kmkbuiltin.h"
44
45
46/*********************************************************************************************************************************
47* Defined Constants And Macros *
48*********************************************************************************************************************************/
49/** @name kmkKillMatchName flags
50 * @{ */
51#define KMKKILL_FN_EXE_SUFF 1
52#define KMKKILL_FN_WILDCARD 2
53#define KMKKILL_FN_WITH_PATH 4
54#define KMKKILL_FN_ROOT_SLASH 8
55/** @} */
56
57
58/*********************************************************************************************************************************
59* Structures and Typedefs *
60*********************************************************************************************************************************/
61typedef struct KMKKILLGLOBALS
62{
63 PKMKBUILTINCTX pCtx;
64 int cVerbosity;
65 const char *pszCur;
66} KMKKILLGLOBALS;
67
68
69
70/**
71 * Kill one process by it's PID.
72 *
73 * The name is optional and only for messaging.
74 */
75static int kmkKillProcessByPid(KMKKILLGLOBALS *pThis, pid_t pid, const char *pszName)
76{
77 int rcExit;
78 HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE /*bInherit*/, pid);
79 if (!hProcess)
80 rcExit = errx(pThis->pCtx, 1, "Failed to open pid %u (%#x%s%s): %u",
81 pid, pid, pszName ? ", " : "", pszName ? pszName : "", GetLastError());
82 else
83 {
84 if (!TerminateProcess(hProcess, DBG_TERMINATE_PROCESS))
85 rcExit = errx(pThis->pCtx, 1, "TerminateProcess failed on pid %u (%#x%s%s): %u",
86 pid, pid, pszName ? ", " : "", pszName ? pszName : "", GetLastError());
87 else if (pThis->cVerbosity > 0)
88 kmk_builtin_ctx_printf(pThis->pCtx, 0, "Terminated %u (%#x)%s%s\n",
89 pid, pid, pszName ? " - " : "", pszName ? pszName : "");
90 CloseHandle(hProcess);
91 }
92 return rcExit;
93}
94
95
96/**
97 * Does simple wilcard filename matching.
98 *
99 * @returns 1 on match, 0 on mismatch.
100 * @param pszPattern The pattern.
101 * @param cchPattern The length of the pattern.
102 * @param pchFilename The filename to match. This does not have to be
103 * terminated at @a cchFilename.
104 * @param cchFilename The length of the filename that we should match.
105 * @param cDepth The recursion depth.
106 */
107static int kmkKillMatchWildcard(const char *pszPattern, size_t cchPattern,
108 const char *pchFilename, size_t cchFilename, unsigned cDepth)
109{
110 while (cchPattern > 0 && cchFilename > 0)
111 {
112 char chPat = *pszPattern++;
113 cchPattern--;
114 if (chPat != '*')
115 {
116 if (chPat != '?')
117 {
118 char chExe = *pchFilename;
119 if ( chExe != chPat
120 && tolower(chExe) != tolower(chPat))
121 return 0;
122 }
123 pchFilename++;
124 cchFilename--;
125 }
126 else
127 {
128 size_t off, cchExeMin;
129
130 while (cchPattern > 0 && *pszPattern == '*')
131 {
132 pszPattern++;
133 cchPattern--;
134 }
135
136 /* Trailing '*'? */
137 if (!cchPattern)
138 return 1;
139
140 /* Final wildcard? Match the tail. */
141 if (memchr(pszPattern, '*', cchPattern) == NULL)
142 {
143 if (memchr(pszPattern, '?', cchPattern) == NULL)
144 return cchFilename >= cchPattern
145 && strnicmp(&pchFilename[cchFilename - cchPattern], pszPattern, cchPattern) == 0;
146
147 /* Only '?', so we know the exact length of this '*' match and can do a single tail matching. */
148 return cchFilename >= cchPattern
149 && kmkKillMatchWildcard(pszPattern, cchPattern, &pchFilename[cchFilename - cchPattern], cchPattern, cDepth + 1);
150 }
151
152 /*
153 * More wildcards ('*'), so we need to need to try out all matching
154 * length for this one. We start by counting non-asterisks chars
155 * in the remaining pattern so we know when to stop trying.
156 * This must be at least 1 char.
157 */
158 if (cDepth >= 32)
159 return 0;
160
161 for (off = cchExeMin = 0; off < cchPattern; off++)
162 cchExeMin += pszPattern[off] != '*';
163 assert(cchExeMin > 0);
164
165 while (cchFilename >= cchExeMin)
166 {
167 if (kmkKillMatchWildcard(pszPattern, cchPattern, pchFilename, cchFilename, cDepth + 1))
168 return 1;
169 /* next */
170 pchFilename++;
171 cchFilename--;
172 }
173 return 0;
174 }
175 }
176
177 /* If there is more filename left, we won't have a match. */
178 if (cchFilename != 0)
179 return 0;
180
181 /* If there is pattern left, we still have a match if it's all asterisks. */
182 while (cchPattern > 0 && *pszPattern == '*')
183 {
184 pszPattern++;
185 cchPattern--;
186 }
187 return cchPattern == 0;
188}
189
190
191/**
192 * Matches a process name against the given pattern.
193 *
194 * @returns 1 if it matches, 0 if it doesn't.
195 * @param pszPattern The pattern to match against.
196 * @param cchPattern The pattern length.
197 * @param fPattern Pattern characteristics.
198 * @param pszExeFile The EXE filename to match (includes path).
199 * @param cchExeFile The length of the EXE filename.
200 */
201static int kmkKillMatchName(const char *pszPattern, size_t cchPattern, unsigned fPattern,
202 const char *pszExeFile, size_t cchExeFile)
203{
204 /*
205 * Automatically match the exe suffix if not present in the pattern.
206 */
207 if ( !(fPattern & KMKKILL_FN_EXE_SUFF)
208 && cchExeFile > 4
209 && stricmp(&pszExeFile[cchExeFile - 4], ".exe") == 0)
210 cchExeFile -= sizeof(".exe") - 1;
211
212 /*
213 * If no path in the pattern, move pszExeFile up to the filename part.
214 */
215 if (!(fPattern & KMKKILL_FN_WITH_PATH)
216 && ( memchr(pszExeFile, '\\', cchExeFile) != NULL
217 || memchr(pszExeFile, '/', cchExeFile) != NULL
218 || memchr(pszExeFile, ':', cchExeFile) != NULL))
219 {
220 size_t offFilename = cchExeFile;
221 char ch;
222 while ( offFilename > 0
223 && (ch = pszExeFile[offFilename - 1]) != '\\'
224 && ch != '//'
225 && ch != ':')
226 offFilename--;
227 cchExeFile -= offFilename;
228 pszExeFile += offFilename;
229 }
230
231 /*
232 * Wildcard? This only works for the filename part.
233 */
234 if (fPattern & KMKKILL_FN_WILDCARD)
235 return kmkKillMatchWildcard(pszPattern, cchPattern, pszExeFile, cchExeFile, 0);
236
237 /*
238 * No-wildcard pattern.
239 */
240 if (cchExeFile >= cchPattern)
241 {
242 if (strnicmp(&pszExeFile[cchExeFile - cchPattern], pszPattern, cchPattern) == 0)
243 return cchExeFile == cchPattern
244 || (fPattern & KMKKILL_FN_ROOT_SLASH)
245 || pszExeFile[cchExeFile - cchPattern - 1] == '\\'
246 || pszExeFile[cchExeFile - cchPattern - 1] == '/'
247 || pszExeFile[cchExeFile - cchPattern - 1] == ':';
248
249 /*
250 * Might be slash directions or multiple consequtive slashes
251 * making a difference here, so compare char-by-char from the end.
252 */
253 if (fPattern & KMKKILL_FN_WITH_PATH)
254 {
255 while (cchPattern > 0 && cchExeFile > 0)
256 {
257 char chPat = pszPattern[--cchPattern];
258 char chExe = pszExeFile[--cchExeFile];
259 if (chPat == chExe)
260 {
261 if (chPat != '\\' && chPat != '/')
262 {
263 if (chPat != ':' || cchPattern > 0)
264 continue;
265 return 1;
266 }
267 }
268 else
269 {
270 chPat = tolower(chPat);
271 chExe = tolower(chExe);
272 if (chPat == chExe)
273 continue;
274 if (chPat == '/')
275 chPat = '\\';
276 if (chExe == '/')
277 chExe = '\\';
278 if (chPat != chExe)
279 return 0;
280 }
281
282 while (cchPattern > 0 && ((chPat = pszPattern[cchPattern - 1] == '\\') || chPat == '/'))
283 cchPattern--;
284 if (!cchPattern)
285 return 1;
286
287 while (cchExeFile > 0 && ((chExe = pszExeFile[cchExeFile - 1] == '\\') || chExe == '/'))
288 cchExeFile--;
289 }
290
291 if ( cchExeFile == 0
292 || pszExeFile[cchExeFile - 1] == '\\'
293 || pszExeFile[cchExeFile - 1] == '/'
294 || pszExeFile[cchExeFile - 1] == ':')
295 return 1;
296 }
297 }
298 return 0;
299}
300
301
302/**
303 * Analyzes pattern for kmkKillMatchName().
304 *
305 * @returns Pattern characteristics.
306 * @param pszPattern The pattern.
307 * @param cchPattern The pattern length.
308 */
309static unsigned kmkKillAnalyzePattern(const char *pszPattern, size_t cchPattern)
310{
311 unsigned fPattern = 0;
312 if (cchPattern > 4 && stricmp(&pszPattern[cchPattern - 4], ".exe") == 0)
313 fPattern |= KMKKILL_FN_EXE_SUFF;
314 if (memchr(pszPattern, '*', cchPattern) != NULL)
315 fPattern |= KMKKILL_FN_WILDCARD;
316 if (memchr(pszPattern, '?', cchPattern) != NULL)
317 fPattern |= KMKKILL_FN_WILDCARD;
318 if (memchr(pszPattern, '/', cchPattern) != NULL)
319 fPattern |= KMKKILL_FN_WITH_PATH;
320 if (memchr(pszPattern, '\\', cchPattern) != NULL)
321 fPattern |= KMKKILL_FN_WITH_PATH;
322 if (*pszPattern == '\\' || *pszPattern == '/')
323 fPattern |= KMKKILL_FN_ROOT_SLASH;
324 return fPattern;
325}
326
327
328/**
329 * Enumerate processes and kill the ones matching the pattern.
330 */
331static int kmkKillProcessByName(KMKKILLGLOBALS *pThis, const char *pszPattern)
332{
333 HANDLE hSnapshot;
334 int rcExit = 0;
335 size_t const cchPattern = strlen(pszPattern);
336 unsigned const fPattern = kmkKillAnalyzePattern(pszPattern, cchPattern);
337 if (cchPattern == 0)
338 return errx(pThis->pCtx, 1, "Empty process name!");
339 if ((fPattern & (KMKKILL_FN_WILDCARD | KMKKILL_FN_WITH_PATH)) == (KMKKILL_FN_WILDCARD | KMKKILL_FN_WITH_PATH))
340 return errx(pThis->pCtx, 1, "Wildcard and paths cannot be mixed: %s", pszPattern);
341
342 hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
343 if (!hSnapshot)
344 rcExit = errx(pThis->pCtx, 1, "CreateToolhelp32Snapshot failed: %d", GetLastError());
345 else
346 {
347 union
348 {
349 PROCESSENTRY32 Entry;
350 char achBuf[8192];
351 } u;
352
353 memset(&u, 0, sizeof(u));
354 u.Entry.dwSize = sizeof(u) - sizeof(".exe");
355 SetLastError(NO_ERROR);
356 if (Process32First(hSnapshot, &u.Entry))
357 {
358 for (;;)
359 {
360 /* Kill it if it matches. */
361 u.achBuf[sizeof(u.achBuf) - sizeof(".exe")] = '\0';
362 if ( u.Entry.szExeFile[0] != '\0'
363 && kmkKillMatchName(pszPattern, cchPattern, fPattern, u.Entry.szExeFile, strlen(u.Entry.szExeFile)))
364 {
365 int rcExit2 = kmkKillProcessByPid(pThis, u.Entry.th32ProcessID, u.Entry.szExeFile);
366 if (rcExit2 != 0 && rcExit == 0)
367 rcExit = rcExit2;
368 }
369
370 /* next */
371 u.Entry.dwSize = sizeof(u) - sizeof(".exe");
372 SetLastError(NO_ERROR);
373 if (!Process32Next(hSnapshot, &u.Entry))
374 {
375 if (GetLastError() != ERROR_NO_MORE_FILES)
376 rcExit = errx(pThis->pCtx, 1, "Process32Next failed: %d", GetLastError());
377 break;
378 }
379 }
380 }
381 else
382 rcExit = errx(pThis->pCtx, 1, "Process32First failed: %d", GetLastError());
383 CloseHandle(hSnapshot);
384 }
385 return rcExit;
386
387}
388
389
390/**
391 * Worker for handling one command line process target.
392 *
393 * @returns 0 on success, non-zero exit code on failure.
394 */
395static int kmkKillProcess(KMKKILLGLOBALS *pThis, const char *pszTarget)
396{
397 /*
398 * Try treat the target as a pid first, then a name pattern.
399 */
400 char *pszNext = NULL;
401 long pid;
402 errno = 0;
403 pid = strtol(pszTarget, &pszNext, 0);
404 if (pszNext && *pszNext == '\0' && errno == 0)
405 return kmkKillProcessByPid(pThis, pid, NULL);
406 return kmkKillProcessByName(pThis, pszTarget);
407}
408
409
410/**
411 * Worker for handling one command line job target.
412 *
413 * @returns 0 on success, non-zero exit code on failure.
414 */
415static int kmkKillJob(KMKKILLGLOBALS *pThis, const char *pszTarget)
416{
417 int rcExit = 0;
418 HANDLE hTempJob = NULL;
419 BOOL fIsInJob = FALSE;
420
421 /*
422 * Open the job object.
423 */
424 DWORD fRights = JOB_OBJECT_QUERY | JOB_OBJECT_TERMINATE | JOB_OBJECT_ASSIGN_PROCESS;
425 HANDLE hJob = OpenJobObjectA(fRights, FALSE /*bInheritHandle*/, pszTarget);
426 if (!hJob)
427 {
428 fRights &= ~JOB_OBJECT_ASSIGN_PROCESS;
429 hJob = OpenJobObjectA(fRights, FALSE /*bInheritHandle*/, pszTarget);
430 if (!hJob)
431 return errx(pThis->pCtx, 1, "Failed to open job '%s': %u", pszTarget, GetLastError());
432 }
433
434 /*
435 * Are we a member of this job? If so, temporarily move
436 * to a different object so we don't kill ourselves.
437 */
438 if (IsProcessInJob(hJob, GetCurrentProcess(), &fIsInJob))
439 {
440 if (fIsInJob)
441 {
442 /** @todo this probably isn't working. */
443 if (pThis->cVerbosity >= 1)
444 kmk_builtin_ctx_printf(pThis->pCtx, 0,
445 "kmk_kill: Is myself (%u) a member of target job (%s)", getpid(), pszTarget);
446 if (!(fRights & JOB_OBJECT_ASSIGN_PROCESS))
447 rcExit = errx(pThis->pCtx, 1,
448 "Is myself member of the target job and don't have the JOB_OBJECT_ASSIGN_PROCESS right.");
449 else
450 {
451 hTempJob = CreateJobObjectA(NULL, NULL);
452 if (hTempJob)
453 {
454 if (!(AssignProcessToJobObject(hTempJob, GetCurrentProcess())))
455 rcExit = errx(pThis->pCtx, 1, "AssignProcessToJobObject(temp, me) failed: %u", GetLastError());
456 }
457 }
458 }
459 }
460 else
461 rcExit = errx(pThis->pCtx, 1, "IsProcessInJob(%s, me) failed: %u", pszTarget, GetLastError());
462
463 /*
464 * Do the killing.
465 */
466 if (rcExit == 0)
467 {
468 if (!TerminateJobObject(hJob, DBG_TERMINATE_PROCESS))
469 rcExit = errx(pThis->pCtx, 1, "TerminateJobObject(%s) failed: %u", pszTarget, GetLastError());
470 }
471
472 /*
473 * Cleanup.
474 */
475 if (hTempJob)
476 {
477 if (!(AssignProcessToJobObject(hJob, GetCurrentProcess())))
478 rcExit = errx(pThis->pCtx, 1, "AssignProcessToJobObject(%s, me) failed: %u", pszTarget, GetLastError());
479 CloseHandle(hTempJob);
480 }
481 CloseHandle(hJob);
482
483 return rcExit;
484}
485
486
487/**
488 * Show usage.
489 */
490static void kmkKillUsage(PKMKBUILTINCTX pCtx, int fIsErr)
491{
492 kmk_builtin_ctx_printf(pCtx, fIsErr,
493 "usage: %s [options] <job-name|process-name|pid> [options] [...]\n"
494 " or: %s --help\n"
495 " or: %s --version\n"
496 "\n"
497 "Options that decide what to kill next:\n"
498 " -p|--process Processes, either by name or by PID. (default)\n"
499 " -j|--job Windows jobs.\n"
500 "\n"
501 "Other options:\n"
502 " -q|--quiet Quiet operation. Only real errors are displayed.\n"
503 " -v|--verbose Increase verbosity.\n"
504 ,
505 pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
506}
507
508
509int kmk_builtin_kill(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
510{
511 int rcExit = 0;
512 int i;
513 KMKKILLGLOBALS This;
514 enum { kTargetJobs, kTargetProcesses } enmTarget = kTargetProcesses;
515
516 /* Globals. */
517 This.pCtx = pCtx;
518 This.cVerbosity = 1;
519
520 /*
521 * Parse arguments.
522 */
523 if (argc <= 1)
524 {
525 kmkKillUsage(pCtx, 0);
526 return 1;
527 }
528 for (i = 1; i < argc; i++)
529 {
530 if (argv[i][0] == '-')
531 {
532 const char *pszValue;
533 const char *psz = &argv[i][1];
534 char chOpt;
535 chOpt = *psz++;
536 if (chOpt == '-')
537 {
538 /* Convert long to short option. */
539 if (!strcmp(psz, "job"))
540 chOpt = 'j';
541 else if (!strcmp(psz, "process"))
542 chOpt = 'p';
543 else if (!strcmp(psz, "quiet"))
544 chOpt = 'q';
545 else if (!strcmp(psz, "verbose"))
546 chOpt = 'v';
547 else if (!strcmp(psz, "help"))
548 chOpt = '?';
549 else if (!strcmp(psz, "version"))
550 chOpt = 'V';
551 else
552 {
553 errx(pCtx, 2, "Invalid argument '%s'.", argv[i]);
554 kmkKillUsage(pCtx, 1);
555 return 2;
556 }
557 psz = "";
558 }
559
560 /*
561 * Requires value?
562 */
563 switch (chOpt)
564 {
565 /*case '':
566 if (*psz)
567 pszValue = psz;
568 else if (++i < argc)
569 pszValue = argv[i];
570 else
571 return errx(pCtx, 2, "The '-%c' option takes a value.", chOpt);
572 break;*/
573
574 default:
575 pszValue = NULL;
576 break;
577 }
578
579
580 switch (chOpt)
581 {
582 /*
583 * What to kill
584 */
585 case 'j':
586 enmTarget = kTargetJobs;
587 break;
588
589 case 'p':
590 enmTarget = kTargetProcesses;
591 break;
592
593 /*
594 * How to kill processes...
595 */
596
597
598 /*
599 * Noise level.
600 */
601 case 'q':
602 This.cVerbosity = 0;
603 break;
604
605 case 'v':
606 This.cVerbosity += 1;
607 break;
608
609 /*
610 * The mandatory version & help.
611 */
612 case '?':
613 kmkKillUsage(pCtx, 0);
614 return rcExit;
615 case 'V':
616 return kbuild_version(argv[0]);
617
618 /*
619 * Invalid argument.
620 */
621 default:
622 errx(pCtx, 2, "Invalid argument '%s'.", argv[i]);
623 kmkKillUsage(pCtx, 1);
624 return 2;
625 }
626 }
627 else
628 {
629 /*
630 * Kill something.
631 */
632 int rcExitOne;
633 This.pszCur = argv[i];
634 if (enmTarget == kTargetJobs)
635 rcExitOne = kmkKillJob(&This, argv[i]);
636 else
637 rcExitOne = kmkKillProcess(&This, argv[i]);
638 if (rcExitOne != 0 && rcExit == 0)
639 rcExit = rcExitOne;
640 }
641 }
642
643 return rcExit;
644}
645
646#ifdef KMK_BUILTIN_STANDALONE
647int main(int argc, char **argv, char **envp)
648{
649 KMKBUILTINCTX Ctx = { "kmk_kill", NULL };
650 return kmk_builtin_kill(argc, argv, envp, &Ctx);
651}
652#endif
653
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use