Index: trunk/src/kmk/Makefile.kmk
===================================================================
--- trunk/src/kmk/Makefile.kmk	(revision 3351)
+++ trunk/src/kmk/Makefile.kmk	(revision 3352)
@@ -340,4 +340,6 @@
        \
 	kmkbuiltin/err.c
+kmk_SOURCES.win += \
+	kmkbuiltin/kill.c
 
 
@@ -374,5 +376,7 @@
 	kmk_touch \
 	kDepIDB \
-	kDepObj \
+	kDepObj
+PROGRAMS.win += \
+	kmk_kill
 
 kmk_append_TEMPLATE = BIN-KMK-BUILTIN
@@ -411,4 +415,8 @@
 kmk_install_SOURCES = \
 	kmkbuiltin/install.c
+
+kmk_kill_TEMPLATE = BIN-KMK-BUILTIN
+kmk_kill_SOURCES = \
+	kmkbuiltin/kill.c
 
 kmk_ln_TEMPLATE = BIN-KMK-BUILTIN
Index: trunk/src/kmk/kmkbuiltin.c
===================================================================
--- trunk/src/kmk/kmkbuiltin.c	(revision 3351)
+++ trunk/src/kmk/kmkbuiltin.c	(revision 3352)
@@ -265,4 +265,5 @@
 #ifdef KBUILD_OS_WINDOWS
     BUILTIN_ENTRY(kmk_builtin_kSubmit,  "kSubmit",      FN_SIG_MAIN_SPAWNS,     0, 1),
+    BUILTIN_ENTRY(kmk_builtin_kill,     "kill",         FN_SIG_MAIN,            0, 0),
 #endif
     BUILTIN_ENTRY(kmk_builtin_mkdir,    "mkdir",        FN_SIG_MAIN,            0, 0),
Index: trunk/src/kmk/kmkbuiltin.h
===================================================================
--- trunk/src/kmk/kmkbuiltin.h	(revision 3351)
+++ trunk/src/kmk/kmkbuiltin.h	(revision 3352)
@@ -126,5 +126,5 @@
     size_t      uFnSignature : 8;
     size_t      fMtSafe : 1;            /**< Safe for multi threaded execution. */
-    size_t      fNeedEnv : 1;           /**< Needs the (target) enviornment. */
+    size_t      fNeedEnv : 1;           /**< Needs the (target) environment. */
 } KMKBUILTINENTRY;
 /** Pointer to kmk built-in command entry. */
@@ -156,4 +156,5 @@
 extern int kSubmitSubProcKill(intptr_t pvUser, int iSignal);
 extern void kSubmitSubProcCleanup(intptr_t pvUser);
+extern int kmk_builtin_kill(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
 #endif
 extern int kmk_builtin_kDepIDB(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
Index: trunk/src/kmk/kmkbuiltin/kill.c
===================================================================
--- trunk/src/kmk/kmkbuiltin/kill.c	(revision 3352)
+++ trunk/src/kmk/kmkbuiltin/kill.c	(revision 3352)
@@ -0,0 +1,653 @@
+/* $Id$ */
+/** @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
+
