diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:21:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:21:29 +0000 |
commit | 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc (patch) | |
tree | 63ef546b10a81d461e5cf5ed9e98a68cd7dee1aa /src/kmk/kmkbuiltin/kill.c | |
parent | Initial commit. (diff) | |
download | kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.tar.xz kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.zip |
Adding upstream version 1:0.1.9998svn3589+dfsg.upstream/1%0.1.9998svn3589+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/kmk/kmkbuiltin/kill.c')
-rw-r--r-- | src/kmk/kmkbuiltin/kill.c | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/src/kmk/kmkbuiltin/kill.c b/src/kmk/kmkbuiltin/kill.c new file mode 100644 index 0000000..7ba6f17 --- /dev/null +++ b/src/kmk/kmkbuiltin/kill.c @@ -0,0 +1,653 @@ +/* $Id: kill.c 3352 2020-06-05 00:31:50Z bird $ */ +/** @file + * kmk kill - Process killer. + */ + +/* + * Copyright (c) 2007-2020 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * This file is part of kBuild. + * + * kBuild 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 3 of the License, or + * (at your option) any later version. + * + * kBuild 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 kBuild. If not, see <http://www.gnu.org/licenses/> + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include <assert.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#ifdef WINDOWS32 +# include <process.h> +# include <Windows.h> +# include <tlhelp32.h> +#endif + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "err.h" +#include "kmkbuiltin.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @name kmkKillMatchName flags + * @{ */ +#define KMKKILL_FN_EXE_SUFF 1 +#define KMKKILL_FN_WILDCARD 2 +#define KMKKILL_FN_WITH_PATH 4 +#define KMKKILL_FN_ROOT_SLASH 8 +/** @} */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct KMKKILLGLOBALS +{ + PKMKBUILTINCTX pCtx; + int cVerbosity; + const char *pszCur; +} KMKKILLGLOBALS; + + + +/** + * Kill one process by it's PID. + * + * The name is optional and only for messaging. + */ +static int kmkKillProcessByPid(KMKKILLGLOBALS *pThis, pid_t pid, const char *pszName) +{ + int rcExit; + HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE /*bInherit*/, pid); + if (!hProcess) + rcExit = errx(pThis->pCtx, 1, "Failed to open pid %u (%#x%s%s): %u", + pid, pid, pszName ? ", " : "", pszName ? pszName : "", GetLastError()); + else + { + if (!TerminateProcess(hProcess, DBG_TERMINATE_PROCESS)) + rcExit = errx(pThis->pCtx, 1, "TerminateProcess failed on pid %u (%#x%s%s): %u", + pid, pid, pszName ? ", " : "", pszName ? pszName : "", GetLastError()); + else if (pThis->cVerbosity > 0) + kmk_builtin_ctx_printf(pThis->pCtx, 0, "Terminated %u (%#x)%s%s\n", + pid, pid, pszName ? " - " : "", pszName ? pszName : ""); + CloseHandle(hProcess); + } + return rcExit; +} + + +/** + * Does simple wilcard filename matching. + * + * @returns 1 on match, 0 on mismatch. + * @param pszPattern The pattern. + * @param cchPattern The length of the pattern. + * @param pchFilename The filename to match. This does not have to be + * terminated at @a cchFilename. + * @param cchFilename The length of the filename that we should match. + * @param cDepth The recursion depth. + */ +static int kmkKillMatchWildcard(const char *pszPattern, size_t cchPattern, + const char *pchFilename, size_t cchFilename, unsigned cDepth) +{ + while (cchPattern > 0 && cchFilename > 0) + { + char chPat = *pszPattern++; + cchPattern--; + if (chPat != '*') + { + if (chPat != '?') + { + char chExe = *pchFilename; + if ( chExe != chPat + && tolower(chExe) != tolower(chPat)) + return 0; + } + pchFilename++; + cchFilename--; + } + else + { + size_t off, cchExeMin; + + while (cchPattern > 0 && *pszPattern == '*') + { + pszPattern++; + cchPattern--; + } + + /* Trailing '*'? */ + if (!cchPattern) + return 1; + + /* Final wildcard? Match the tail. */ + if (memchr(pszPattern, '*', cchPattern) == NULL) + { + if (memchr(pszPattern, '?', cchPattern) == NULL) + return cchFilename >= cchPattern + && strnicmp(&pchFilename[cchFilename - cchPattern], pszPattern, cchPattern) == 0; + + /* Only '?', so we know the exact length of this '*' match and can do a single tail matching. */ + return cchFilename >= cchPattern + && kmkKillMatchWildcard(pszPattern, cchPattern, &pchFilename[cchFilename - cchPattern], cchPattern, cDepth + 1); + } + + /* + * More wildcards ('*'), so we need to need to try out all matching + * length for this one. We start by counting non-asterisks chars + * in the remaining pattern so we know when to stop trying. + * This must be at least 1 char. + */ + if (cDepth >= 32) + return 0; + + for (off = cchExeMin = 0; off < cchPattern; off++) + cchExeMin += pszPattern[off] != '*'; + assert(cchExeMin > 0); + + while (cchFilename >= cchExeMin) + { + if (kmkKillMatchWildcard(pszPattern, cchPattern, pchFilename, cchFilename, cDepth + 1)) + return 1; + /* next */ + pchFilename++; + cchFilename--; + } + return 0; + } + } + + /* If there is more filename left, we won't have a match. */ + if (cchFilename != 0) + return 0; + + /* If there is pattern left, we still have a match if it's all asterisks. */ + while (cchPattern > 0 && *pszPattern == '*') + { + pszPattern++; + cchPattern--; + } + return cchPattern == 0; +} + + +/** + * Matches a process name against the given pattern. + * + * @returns 1 if it matches, 0 if it doesn't. + * @param pszPattern The pattern to match against. + * @param cchPattern The pattern length. + * @param fPattern Pattern characteristics. + * @param pszExeFile The EXE filename to match (includes path). + * @param cchExeFile The length of the EXE filename. + */ +static int kmkKillMatchName(const char *pszPattern, size_t cchPattern, unsigned fPattern, + const char *pszExeFile, size_t cchExeFile) +{ + /* + * Automatically match the exe suffix if not present in the pattern. + */ + if ( !(fPattern & KMKKILL_FN_EXE_SUFF) + && cchExeFile > 4 + && stricmp(&pszExeFile[cchExeFile - 4], ".exe") == 0) + cchExeFile -= sizeof(".exe") - 1; + + /* + * If no path in the pattern, move pszExeFile up to the filename part. + */ + if (!(fPattern & KMKKILL_FN_WITH_PATH) + && ( memchr(pszExeFile, '\\', cchExeFile) != NULL + || memchr(pszExeFile, '/', cchExeFile) != NULL + || memchr(pszExeFile, ':', cchExeFile) != NULL)) + { + size_t offFilename = cchExeFile; + char ch; + while ( offFilename > 0 + && (ch = pszExeFile[offFilename - 1]) != '\\' + && ch != '//' + && ch != ':') + offFilename--; + cchExeFile -= offFilename; + pszExeFile += offFilename; + } + + /* + * Wildcard? This only works for the filename part. + */ + if (fPattern & KMKKILL_FN_WILDCARD) + return kmkKillMatchWildcard(pszPattern, cchPattern, pszExeFile, cchExeFile, 0); + + /* + * No-wildcard pattern. + */ + if (cchExeFile >= cchPattern) + { + if (strnicmp(&pszExeFile[cchExeFile - cchPattern], pszPattern, cchPattern) == 0) + return cchExeFile == cchPattern + || (fPattern & KMKKILL_FN_ROOT_SLASH) + || pszExeFile[cchExeFile - cchPattern - 1] == '\\' + || pszExeFile[cchExeFile - cchPattern - 1] == '/' + || pszExeFile[cchExeFile - cchPattern - 1] == ':'; + + /* + * Might be slash directions or multiple consequtive slashes + * making a difference here, so compare char-by-char from the end. + */ + if (fPattern & KMKKILL_FN_WITH_PATH) + { + while (cchPattern > 0 && cchExeFile > 0) + { + char chPat = pszPattern[--cchPattern]; + char chExe = pszExeFile[--cchExeFile]; + if (chPat == chExe) + { + if (chPat != '\\' && chPat != '/') + { + if (chPat != ':' || cchPattern > 0) + continue; + return 1; + } + } + else + { + chPat = tolower(chPat); + chExe = tolower(chExe); + if (chPat == chExe) + continue; + if (chPat == '/') + chPat = '\\'; + if (chExe == '/') + chExe = '\\'; + if (chPat != chExe) + return 0; + } + + while (cchPattern > 0 && ((chPat = pszPattern[cchPattern - 1] == '\\') || chPat == '/')) + cchPattern--; + if (!cchPattern) + return 1; + + while (cchExeFile > 0 && ((chExe = pszExeFile[cchExeFile - 1] == '\\') || chExe == '/')) + cchExeFile--; + } + + if ( cchExeFile == 0 + || pszExeFile[cchExeFile - 1] == '\\' + || pszExeFile[cchExeFile - 1] == '/' + || pszExeFile[cchExeFile - 1] == ':') + return 1; + } + } + return 0; +} + + +/** + * Analyzes pattern for kmkKillMatchName(). + * + * @returns Pattern characteristics. + * @param pszPattern The pattern. + * @param cchPattern The pattern length. + */ +static unsigned kmkKillAnalyzePattern(const char *pszPattern, size_t cchPattern) +{ + unsigned fPattern = 0; + if (cchPattern > 4 && stricmp(&pszPattern[cchPattern - 4], ".exe") == 0) + fPattern |= KMKKILL_FN_EXE_SUFF; + if (memchr(pszPattern, '*', cchPattern) != NULL) + fPattern |= KMKKILL_FN_WILDCARD; + if (memchr(pszPattern, '?', cchPattern) != NULL) + fPattern |= KMKKILL_FN_WILDCARD; + if (memchr(pszPattern, '/', cchPattern) != NULL) + fPattern |= KMKKILL_FN_WITH_PATH; + if (memchr(pszPattern, '\\', cchPattern) != NULL) + fPattern |= KMKKILL_FN_WITH_PATH; + if (*pszPattern == '\\' || *pszPattern == '/') + fPattern |= KMKKILL_FN_ROOT_SLASH; + return fPattern; +} + + +/** + * Enumerate processes and kill the ones matching the pattern. + */ +static int kmkKillProcessByName(KMKKILLGLOBALS *pThis, const char *pszPattern) +{ + HANDLE hSnapshot; + int rcExit = 0; + size_t const cchPattern = strlen(pszPattern); + unsigned const fPattern = kmkKillAnalyzePattern(pszPattern, cchPattern); + if (cchPattern == 0) + return errx(pThis->pCtx, 1, "Empty process name!"); + if ((fPattern & (KMKKILL_FN_WILDCARD | KMKKILL_FN_WITH_PATH)) == (KMKKILL_FN_WILDCARD | KMKKILL_FN_WITH_PATH)) + return errx(pThis->pCtx, 1, "Wildcard and paths cannot be mixed: %s", pszPattern); + + hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (!hSnapshot) + rcExit = errx(pThis->pCtx, 1, "CreateToolhelp32Snapshot failed: %d", GetLastError()); + else + { + union + { + PROCESSENTRY32 Entry; + char achBuf[8192]; + } u; + + memset(&u, 0, sizeof(u)); + u.Entry.dwSize = sizeof(u) - sizeof(".exe"); + SetLastError(NO_ERROR); + if (Process32First(hSnapshot, &u.Entry)) + { + for (;;) + { + /* Kill it if it matches. */ + u.achBuf[sizeof(u.achBuf) - sizeof(".exe")] = '\0'; + if ( u.Entry.szExeFile[0] != '\0' + && kmkKillMatchName(pszPattern, cchPattern, fPattern, u.Entry.szExeFile, strlen(u.Entry.szExeFile))) + { + int rcExit2 = kmkKillProcessByPid(pThis, u.Entry.th32ProcessID, u.Entry.szExeFile); + if (rcExit2 != 0 && rcExit == 0) + rcExit = rcExit2; + } + + /* next */ + u.Entry.dwSize = sizeof(u) - sizeof(".exe"); + SetLastError(NO_ERROR); + if (!Process32Next(hSnapshot, &u.Entry)) + { + if (GetLastError() != ERROR_NO_MORE_FILES) + rcExit = errx(pThis->pCtx, 1, "Process32Next failed: %d", GetLastError()); + break; + } + } + } + else + rcExit = errx(pThis->pCtx, 1, "Process32First failed: %d", GetLastError()); + CloseHandle(hSnapshot); + } + return rcExit; + +} + + +/** + * Worker for handling one command line process target. + * + * @returns 0 on success, non-zero exit code on failure. + */ +static int kmkKillProcess(KMKKILLGLOBALS *pThis, const char *pszTarget) +{ + /* + * Try treat the target as a pid first, then a name pattern. + */ + char *pszNext = NULL; + long pid; + errno = 0; + pid = strtol(pszTarget, &pszNext, 0); + if (pszNext && *pszNext == '\0' && errno == 0) + return kmkKillProcessByPid(pThis, pid, NULL); + return kmkKillProcessByName(pThis, pszTarget); +} + + +/** + * Worker for handling one command line job target. + * + * @returns 0 on success, non-zero exit code on failure. + */ +static int kmkKillJob(KMKKILLGLOBALS *pThis, const char *pszTarget) +{ + int rcExit = 0; + HANDLE hTempJob = NULL; + BOOL fIsInJob = FALSE; + + /* + * Open the job object. + */ + DWORD fRights = JOB_OBJECT_QUERY | JOB_OBJECT_TERMINATE | JOB_OBJECT_ASSIGN_PROCESS; + HANDLE hJob = OpenJobObjectA(fRights, FALSE /*bInheritHandle*/, pszTarget); + if (!hJob) + { + fRights &= ~JOB_OBJECT_ASSIGN_PROCESS; + hJob = OpenJobObjectA(fRights, FALSE /*bInheritHandle*/, pszTarget); + if (!hJob) + return errx(pThis->pCtx, 1, "Failed to open job '%s': %u", pszTarget, GetLastError()); + } + + /* + * Are we a member of this job? If so, temporarily move + * to a different object so we don't kill ourselves. + */ + if (IsProcessInJob(hJob, GetCurrentProcess(), &fIsInJob)) + { + if (fIsInJob) + { + /** @todo this probably isn't working. */ + if (pThis->cVerbosity >= 1) + kmk_builtin_ctx_printf(pThis->pCtx, 0, + "kmk_kill: Is myself (%u) a member of target job (%s)", getpid(), pszTarget); + if (!(fRights & JOB_OBJECT_ASSIGN_PROCESS)) + rcExit = errx(pThis->pCtx, 1, + "Is myself member of the target job and don't have the JOB_OBJECT_ASSIGN_PROCESS right."); + else + { + hTempJob = CreateJobObjectA(NULL, NULL); + if (hTempJob) + { + if (!(AssignProcessToJobObject(hTempJob, GetCurrentProcess()))) + rcExit = errx(pThis->pCtx, 1, "AssignProcessToJobObject(temp, me) failed: %u", GetLastError()); + } + } + } + } + else + rcExit = errx(pThis->pCtx, 1, "IsProcessInJob(%s, me) failed: %u", pszTarget, GetLastError()); + + /* + * Do the killing. + */ + if (rcExit == 0) + { + if (!TerminateJobObject(hJob, DBG_TERMINATE_PROCESS)) + rcExit = errx(pThis->pCtx, 1, "TerminateJobObject(%s) failed: %u", pszTarget, GetLastError()); + } + + /* + * Cleanup. + */ + if (hTempJob) + { + if (!(AssignProcessToJobObject(hJob, GetCurrentProcess()))) + rcExit = errx(pThis->pCtx, 1, "AssignProcessToJobObject(%s, me) failed: %u", pszTarget, GetLastError()); + CloseHandle(hTempJob); + } + CloseHandle(hJob); + + return rcExit; +} + + +/** + * Show usage. + */ +static void kmkKillUsage(PKMKBUILTINCTX pCtx, int fIsErr) +{ + kmk_builtin_ctx_printf(pCtx, fIsErr, + "usage: %s [options] <job-name|process-name|pid> [options] [...]\n" + " or: %s --help\n" + " or: %s --version\n" + "\n" + "Options that decide what to kill next:\n" + " -p|--process Processes, either by name or by PID. (default)\n" + " -j|--job Windows jobs.\n" + "\n" + "Other options:\n" + " -q|--quiet Quiet operation. Only real errors are displayed.\n" + " -v|--verbose Increase verbosity.\n" + , + pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName); +} + + +int kmk_builtin_kill(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx) +{ + int rcExit = 0; + int i; + KMKKILLGLOBALS This; + enum { kTargetJobs, kTargetProcesses } enmTarget = kTargetProcesses; + + /* Globals. */ + This.pCtx = pCtx; + This.cVerbosity = 1; + + /* + * Parse arguments. + */ + if (argc <= 1) + { + kmkKillUsage(pCtx, 0); + return 1; + } + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + const char *pszValue; + const char *psz = &argv[i][1]; + char chOpt; + chOpt = *psz++; + if (chOpt == '-') + { + /* Convert long to short option. */ + if (!strcmp(psz, "job")) + chOpt = 'j'; + else if (!strcmp(psz, "process")) + chOpt = 'p'; + else if (!strcmp(psz, "quiet")) + chOpt = 'q'; + else if (!strcmp(psz, "verbose")) + chOpt = 'v'; + else if (!strcmp(psz, "help")) + chOpt = '?'; + else if (!strcmp(psz, "version")) + chOpt = 'V'; + else + { + errx(pCtx, 2, "Invalid argument '%s'.", argv[i]); + kmkKillUsage(pCtx, 1); + return 2; + } + psz = ""; + } + + /* + * Requires value? + */ + switch (chOpt) + { + /*case '': + if (*psz) + pszValue = psz; + else if (++i < argc) + pszValue = argv[i]; + else + return errx(pCtx, 2, "The '-%c' option takes a value.", chOpt); + break;*/ + + default: + pszValue = NULL; + break; + } + + + switch (chOpt) + { + /* + * What to kill + */ + case 'j': + enmTarget = kTargetJobs; + break; + + case 'p': + enmTarget = kTargetProcesses; + break; + + /* + * How to kill processes... + */ + + + /* + * Noise level. + */ + case 'q': + This.cVerbosity = 0; + break; + + case 'v': + This.cVerbosity += 1; + break; + + /* + * The mandatory version & help. + */ + case '?': + kmkKillUsage(pCtx, 0); + return rcExit; + case 'V': + return kbuild_version(argv[0]); + + /* + * Invalid argument. + */ + default: + errx(pCtx, 2, "Invalid argument '%s'.", argv[i]); + kmkKillUsage(pCtx, 1); + return 2; + } + } + else + { + /* + * Kill something. + */ + int rcExitOne; + This.pszCur = argv[i]; + if (enmTarget == kTargetJobs) + rcExitOne = kmkKillJob(&This, argv[i]); + else + rcExitOne = kmkKillProcess(&This, argv[i]); + if (rcExitOne != 0 && rcExit == 0) + rcExit = rcExitOne; + } + } + + return rcExit; +} + +#ifdef KMK_BUILTIN_STANDALONE +int main(int argc, char **argv, char **envp) +{ + KMKBUILTINCTX Ctx = { "kmk_kill", NULL }; + return kmk_builtin_kill(argc, argv, envp, &Ctx); +} +#endif + |