/*******************************************************************************
 * Copyright (c) 2007, 2015 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 * The Eclipse Public License is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 * You may elect to redistribute this code under either of these licenses.
 *
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/

#include <tcf/config.h>

#if SERVICE_SysMonitor

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <tcf/framework/mdep-fs.h>
#include <tcf/framework/protocol.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/json.h>
#include <tcf/framework/context.h>
#include <tcf/framework/errors.h>
#include <tcf/services/sysmon.h>

static const char SYS_MON[] = "SysMonitor";

#if defined(_WRS_KERNEL)

#  error "SysMonitor service is not supported for VxWorks"

#elif defined(__FreeBSD__) || defined(__NetBSD__)

#  error "SysMonitor service is not supported for BSD"

#elif defined(__APPLE__)

#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <stdbool.h>
#include <sys/sysctl.h>
#include <mach/mach.h>
#include <mach/task_info.h>
#include <mach/thread_info.h>

typedef struct kinfo_proc kinfo_proc;

static void write_string_array(OutputStream * out, char **ap, int len) {
    int cnt;

    write_stream(out, '[');
    for (cnt = 0; cnt < len; cnt++) {
        if (cnt > 0) write_stream(out, ',');
        json_write_string(out, ap[cnt]);
    }
    write_stream(out, ']');
}

static void free_array(char **ap, int len) {
    int c;
    for (c = 0; c < len; c++) {
        free(*ap++);
    }
    free(ap);
}

/*
 * Get kernel process information for all processes.
 */
static int get_allprocesses(kinfo_proc **kprocs, int *nprocs)
{
    size_t          len;
    kinfo_proc *    kp;
    int             mib_name[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL};
    int             mib_len = 3;

    if (sysctl(mib_name, mib_len, NULL, &len, NULL, 0) < 0) {
        return -1;
    }

    kp = (struct kinfo_proc *)malloc(len);

    if (sysctl(mib_name, mib_len, kp, &len, NULL, 0) < 0) {
        free(kp);
        return -1;
    }

    *kprocs = kp;
    *nprocs = len / sizeof(kinfo_proc);
    return 0;
}

/*
 * Get kernel process information for a specified pid.
 */
static kinfo_proc *get_process(pid_t pid)
{
    kinfo_proc *        kp;
    int                 mib_name[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, 0};
    int                 mib_len = 4;
    size_t              len = sizeof(kinfo_proc);

    mib_name[3] = pid;

    kp = malloc(len);
    if (kp == NULL) {
        return NULL;
    }

    if (sysctl(mib_name, mib_len, kp, &len, NULL, 0) < 0) {
        free(kp);
        return NULL;
    }

    return kp;
}

static void write_context(OutputStream * out, char * id, char * parent_id, kinfo_proc * p) {
    struct passwd * pwd;
    struct group *  grp;

    write_stream(out, '{');

    json_write_string(out, "UID");
    write_stream(out, ':');
    json_write_long(out, p->kp_eproc.e_ucred.cr_uid);
    write_stream(out, ',');

    json_write_string(out, "UGID");
    write_stream(out, ':');
    json_write_long(out, p->kp_eproc.e_pcred.p_rgid);
    write_stream(out, ',');

    pwd = getpwuid(p->kp_eproc.e_ucred.cr_uid);
    if (pwd != NULL) {
        json_write_string(out, "UserName");
        write_stream(out, ':');
        json_write_string(out, pwd->pw_name);
        write_stream(out, ',');
    }

    grp = getgrgid(p->kp_eproc.e_pcred.p_rgid);
    if (grp != NULL) {
        json_write_string(out, "GroupName");
        write_stream(out, ':');
        json_write_string(out, grp->gr_name);
        write_stream(out, ',');
    }

    json_write_string(out, "File");
    write_stream(out, ':');
    json_write_string(out, p->kp_proc.p_comm);
    write_stream(out, ',');

    json_write_string(out, "PID");
    write_stream(out, ':');
    json_write_long(out, p->kp_proc.p_pid);
    write_stream(out, ',');

    json_write_string(out, "State");
    write_stream(out, ':');
    write_stream(out, '"');
    json_write_char(out, p->kp_proc.p_stat);
    write_stream(out, '"');
    write_stream(out, ',');

    if (p->kp_eproc.e_ppid > 0) {
        json_write_string(out, "PPID");
        write_stream(out, ':');
        json_write_long(out, p->kp_eproc.e_ppid);
        write_stream(out, ',');
    }

    json_write_string(out, "PGRP");
    write_stream(out, ':');
    json_write_long(out, p->kp_eproc.e_pgid);
    write_stream(out, ',');

    if (p->kp_eproc.e_tpgid > 0) {
        json_write_string(out, "TGID");
        write_stream(out, ':');
        json_write_long(out, p->kp_eproc.e_tpgid);
        write_stream(out, ',');
    }

    json_write_string(out, "Flags");
    write_stream(out, ':');
    json_write_long(out, p->kp_proc.p_flag);
    write_stream(out, ',');

    json_write_string(out, "UTime");
    write_stream(out, ':');
    json_write_uint64(out, p->kp_proc.p_uticks);
    write_stream(out, ',');

    json_write_string(out, "STime");
    write_stream(out, ':');
    json_write_uint64(out, p->kp_proc.p_sticks);
    write_stream(out, ',');

    json_write_string(out, "Priority");
    write_stream(out, ':');
    json_write_long(out, (long)p->kp_proc.p_priority);
    write_stream(out, ',');

    if (p->kp_proc.p_nice != 0) {
        json_write_string(out, "Nice");
        write_stream(out, ':');
        json_write_long(out, (long)p->kp_proc.p_nice);
        write_stream(out, ',');
    }

    if (parent_id != NULL && parent_id[0] != 0) {
        json_write_string(out, "ParentID");
        write_stream(out, ':');
        json_write_string(out, parent_id);
        write_stream(out, ',');
    }

    json_write_string(out, "ID");
    write_stream(out, ':');
    json_write_string(out, id);

    write_stream(out, '}');
}

static void command_get_context(char * token, Channel * c) {
    char            id[256];
    pid_t           pid = 0;
    pid_t           parent = 0;
    int             err = 0;
    kinfo_proc *    p;

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    pid = id2pid(id, &parent);
    p = get_process(pid);
    if (p == NULL) err = errno;

    write_errno(&c->out, err);

    if (err == 0 && pid != 0) {
        char *parent_id;
        asprintf(&parent_id, "%d", parent);
        write_context(&c->out, id, parent == 0 ? NULL : parent_id, p);
        write_stream(&c->out, 0);
        free(parent_id);
    }
    else {
        write_stringz(&c->out, "null");
    }

    write_stream(&c->out, MARKER_EOM);
}

static void command_get_children(char * token, Channel * c) {
    int     err = 0;
    char    id[256];
    pid_t   pid = 0;
    pid_t   parent = 0;

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    pid = id2pid(id, &parent);

    if (parent != 0) {
        write_errno(&c->out, 0);
        write_stringz(&c->out, "null");
    }
    else {
        if (pid == 0) {
            int             np;
            int             i;
            int             n;
            kinfo_proc *    p;

            if (get_allprocesses(&p, &np) < 0) {
                write_errno(&c->out, errno);
                write_stringz(&c->out, "null");
            }
            else {
                write_errno(&c->out, 0);
                write_stream(&c->out, '[');
                for (n = 0, i = 0; i < np; i++) {
                    if (p->kp_proc.p_pid != 0) {
                        if (n > 0) write_stream(&c->out, ',');
                        json_write_string(&c->out, pid2id(p->kp_proc.p_pid, 0));
                        n++;
                    }
                    p++;
                }
                write_stream(&c->out, ']');
                write_stream(&c->out, 0);
            }
        }
        else {
            kinfo_proc *    p;

            p = get_process(pid);
            if (p == NULL) {
                write_errno(&c->out, errno);
                write_stringz(&c->out, "null");
            }
            else {
                task_port_t task;

                if (task_for_pid(mach_task_self(), pid, &task) != KERN_SUCCESS) {
                    /*
                     * User is probably not in procmod group
                     */
                    write_errno(&c->out, 0);
                    write_stringz(&c->out, "[]");
                }
                else {
                    unsigned int        thread_count;
                    thread_port_array_t thread_list;

                    if (task_threads(task, &thread_list, &thread_count) != KERN_SUCCESS) {
                        write_errno(&c->out, errno);
                        write_stringz(&c->out, "null");
                    }
                    else {
                        int cnt;
                        write_errno(&c->out, 0);
                        write_stream(&c->out, '[');
                        for (cnt = 0; cnt < thread_count; cnt++) {
                            if (cnt > 0) write_stream(&c->out, ',');
                            json_write_string(&c->out, pid2id(thread_list[cnt], pid));
                        }
                        write_stream(&c->out, ']');
                        write_stream(&c->out, 0);
                    }
                }
            }
        }
    }

    write_stream(&c->out, MARKER_EOM);
}

static void command_get_command_line(char * token, Channel * c) {
    int             err;
    char            id[256];
    pid_t           pid;
    pid_t           parent;
    kinfo_proc *    p;

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    pid = id2pid(id, &parent);
    if (pid != 0 && parent == 0) {
        p = get_process(pid);
        if (p == NULL) err = errno;
    }
    else {
        err = ERR_INV_CONTEXT;
    }

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    write_errno(&c->out, err);

    if (err != 0) {
        write_stringz(&c->out, "null");
    }
    else {
        write_stringz(&c->out, p->kp_proc.p_comm);
    }

    write_stream(&c->out, MARKER_EOM);
}

static void command_get_environment(char * token, Channel * c) {
    char            id[256];

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);
    write_errno(&c->out, 0);
    write_stringz(&c->out, "[]");

    write_stream(&c->out, MARKER_EOM);
}

#elif defined(_WIN32) || defined(__CYGWIN__)

#include <windows.h>
#include <wchar.h>

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING;

typedef enum _PROCESSINFOCLASS {
    ProcessBasicInformation = 0,
    ProcessWow64Information = 26
} PROCESSINFOCLASS;

typedef struct _RTL_DRIVE_LETTER_CURDIR {
    USHORT                  Flags;
    USHORT                  Length;
    ULONG                   TimeStamp;
    UNICODE_STRING          DosPath;
} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR;

typedef struct _RTL_USER_PROCESS_PARAMETERS {
    ULONG                   MaximumLength;
    ULONG                   Length;
    ULONG                   Flags;
    ULONG                   DebugFlags;
    PVOID                   ConsoleHandle;
    ULONG                   ConsoleFlags;
    HANDLE                  StdInputHandle;
    HANDLE                  StdOutputHandle;
    HANDLE                  StdErrorHandle;
    UNICODE_STRING          CurrentDirectoryPath;
    HANDLE                  CurrentDirectoryHandle;
    UNICODE_STRING          DllPath;
    UNICODE_STRING          ImagePathName;
    UNICODE_STRING          CommandLine;
    PVOID                   Environment;
    ULONG                   StartingPositionLeft;
    ULONG                   StartingPositionTop;
    ULONG                   Width;
    ULONG                   Height;
    ULONG                   CharWidth;
    ULONG                   CharHeight;
    ULONG                   ConsoleTextAttributes;
    ULONG                   WindowFlags;
    ULONG                   ShowWindowFlags;
    UNICODE_STRING          WindowTitle;
    UNICODE_STRING          DesktopName;
    UNICODE_STRING          ShellInfo;
    UNICODE_STRING          RuntimeData;
    RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20];
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

typedef struct _PEB_LDR_DATA * PPEB_LDR_DATA;
typedef struct _PEBLOCKROUTINE * PPEBLOCKROUTINE;
typedef struct _PEB_FREE_BLOCK * PPEB_FREE_BLOCK;
typedef PVOID * PPVOID;

typedef struct _PROCESS_ENVIRONMENT_BLOCK {
    BOOLEAN                 InheritedAddressSpace;
    BOOLEAN                 ReadImageFileExecOptions;
    BOOLEAN                 BeingDebugged;
    BOOLEAN                 Spare;
    HANDLE                  Mutant;
    PVOID                   ImageBaseAddress;
    PPEB_LDR_DATA           LoaderData;
    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
    PVOID                   SubSystemData;
    PVOID                   ProcessHeap;
    PVOID                   FastPebLock;
    PPEBLOCKROUTINE         FastPebLockRoutine;
    PPEBLOCKROUTINE         FastPebUnlockRoutine;
    ULONG                   EnvironmentUpdateCount;
    PPVOID                  KernelCallbackTable;
    PVOID                   EventLogSection;
    PVOID                   EventLog;
    PPEB_FREE_BLOCK         FreeList;
    ULONG                   TlsExpansionCounter;
    PVOID                   TlsBitmap;
    ULONG                   TlsBitmapBits[0x2];
    PVOID                   ReadOnlySharedMemoryBase;
    PVOID                   ReadOnlySharedMemoryHeap;
    PPVOID                  ReadOnlyStaticServerData;
    PVOID                   AnsiCodePageData;
    PVOID                   OemCodePageData;
    PVOID                   UnicodeCaseTableData;
    ULONG                   NumberOfProcessors;
    ULONG                   NtGlobalFlag;
    BYTE                    Spare2[0x4];
    LARGE_INTEGER           CriticalSectionTimeout;
    ULONG                   HeapSegmentReserve;
    ULONG                   HeapSegmentCommit;
    ULONG                   HeapDeCommitTotalFreeThreshold;
    ULONG                   HeapDeCommitFreeBlockThreshold;
    ULONG                   NumberOfHeaps;
    ULONG                   MaximumNumberOfHeaps;
    PPVOID                  *ProcessHeaps;
    PVOID                   GdiSharedHandleTable;
    PVOID                   ProcessStarterHelper;
    PVOID                   GdiDCAttributeList;
    PVOID                   LoaderLock;
    ULONG                   OSMajorVersion;
    ULONG                   OSMinorVersion;
    ULONG                   OSBuildNumber;
    ULONG                   OSPlatformId;
    ULONG                   ImageSubSystem;
    ULONG                   ImageSubSystemMajorVersion;
    ULONG                   ImageSubSystemMinorVersion;
    ULONG                   GdiHandleBuffer[0x22];
    ULONG                   PostProcessInitRoutine;
    ULONG                   TlsExpansionBitmap;
    BYTE                    TlsExpansionBitmapBits[0x80];
    ULONG                   SessionId;
} loc_PEB, *loc_PPEB;

typedef struct loc_PROCESS_BASIC_INFORMATION {
    LONG                    ExitStatus;
    loc_PPEB                PebBaseAddress;
    ULONG_PTR               AffinityMask;
    LONG                    BasePriority;
    ULONG_PTR               UniqueProcessId;
    ULONG_PTR               InheritedFromUniqueProcessId;
} PBI;

static PBI pbi;
static loc_PEB peb;
static RTL_USER_PROCESS_PARAMETERS upa;

static int get_process_info(HANDLE prs) {
    typedef LONG (NTAPI * QueryInformationProcessProcType)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
    static QueryInformationProcessProcType QueryInformationProcessProc;

    memset(&pbi, 0, sizeof(pbi));
    memset(&peb, 0, sizeof(peb));
    memset(&upa, 0, sizeof(upa));

    if (QueryInformationProcessProc == NULL) {
        QueryInformationProcessProc = (QueryInformationProcessProcType)GetProcAddress(
            GetModuleHandle("NTDLL.DLL"), "ZwQueryInformationProcess");
        if (QueryInformationProcessProc == NULL) {
            set_win32_errno(GetLastError());
            return -1;
        }
    }
    if (QueryInformationProcessProc(prs, ProcessBasicInformation, &pbi, sizeof(pbi), NULL) < 0) {
        set_win32_errno(GetLastError());
        return -1;
    }

    if (pbi.PebBaseAddress != NULL) {
        if (ReadProcessMemory(prs, (LPCVOID)pbi.PebBaseAddress, &peb, sizeof(peb), NULL) == 0) {
            set_win32_errno(GetLastError());
            return -1;
        }

        if (peb.ProcessParameters != NULL) {
            if (ReadProcessMemory(prs, (LPCVOID)peb.ProcessParameters, &upa, sizeof(upa), NULL) == 0) {
                set_win32_errno(GetLastError());
                return -1;
            }
        }
    }

    return 0;
}

static int write_unicode_string(OutputStream * out, HANDLE prs, UNICODE_STRING str, const char * name) {
    if (str.Buffer != NULL) {
        wchar_t w_fnm[FILE_PATH_SIZE];
        SIZE_T buff_size = str.Length;
        SIZE_T read_size = 0;
        memset(w_fnm, 0, sizeof(w_fnm));
        if (buff_size > sizeof(w_fnm)) buff_size = sizeof(w_fnm);
        if (ReadProcessMemory(prs, (LPCVOID)str.Buffer, w_fnm, buff_size, &read_size)) {
            char a_fnm[FILE_PATH_SIZE * 4];
            size_t k = wcslen(w_fnm);
            int n = WideCharToMultiByte(CP_UTF8, 0, w_fnm, (int)k, a_fnm, sizeof(a_fnm), NULL, NULL);
            a_fnm[n] = 0;
            write_stream(out, ',');
            json_write_string(out, name);
            write_stream(out, ':');
            json_write_string(out, a_fnm);
            return 1;
        }
    }
    return 0;
}

static void write_time(OutputStream * out, FILETIME tm, int64_t base, const char * name) {
    int64_t n = (((int64_t)tm.dwLowDateTime | ((int64_t)tm.dwHighDateTime << 32)) - base) / 10000;

    write_stream(out, ',');

    json_write_string(out, name);
    write_stream(out, ':');
    if (n < 0) n = 0;
    json_write_int64(out, n);
}

static void write_process_context(OutputStream * out, char * id, pid_t pid, HANDLE prs) {
    write_stream(out, '{');

    json_write_string(out, "ID");
    write_stream(out, ':');
    json_write_string(out, id);

    write_stream(out, ',');

    json_write_string(out, "PID");
    write_stream(out, ':');
    json_write_ulong(out, pid);

    write_unicode_string(out, prs, upa.ImagePathName, "File");
    write_unicode_string(out, prs, upa.CurrentDirectoryPath, "CWD");

    {
        FILETIME c_time, e_time, k_time, u_time;
        if (GetProcessTimes(prs, &c_time, &e_time, &k_time, &u_time)) {
            static int64_t system_start_time = 0; /* In FILETIME format: 100-nanosecond intervals since January 1, 1601 (UTC). */
            if (system_start_time == 0) {
                HKEY key;
                if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                        L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management",
                        0, KEY_READ, &key) == ERROR_SUCCESS) {
                    wchar_t buf[FILE_PATH_SIZE];
                    DWORD size = sizeof(buf);
                    memset(buf, 0, sizeof(buf));
                    if (RegQueryValueExW(key,
                            L"PagingFiles",
                            NULL, NULL, (LPBYTE)buf, &size) == ERROR_SUCCESS) {
                        WIN32_FIND_DATAW data;
                        HANDLE h = INVALID_HANDLE_VALUE;
                        int n = 0;
                        while (n < FILE_PATH_SIZE && buf[n] != 0) n++;
                        while (n > 0 && buf[n - 1] != ' ') n--;
                        while (n > 0 && buf[n - 1] == ' ') n--;
                        while (n > 0 && buf[n - 1] != ' ') n--;
                        while (n > 0 && buf[n - 1] == ' ') n--;
                        buf[n] = 0;
                        h = FindFirstFileW(buf, &data);
                        if (h != INVALID_HANDLE_VALUE) {
                            system_start_time = (int64_t)data.ftLastWriteTime.dwLowDateTime | ((int64_t)data.ftLastWriteTime.dwHighDateTime << 32);
                            FindClose(h);
                        }
                    }
                    RegCloseKey(key);
                }
            }
            if (system_start_time == 0) {
                SYSTEMTIME st;
                FILETIME ft;
                GetSystemTime(&st);
                if (SystemTimeToFileTime(&st, &ft)) {
                    system_start_time = (int64_t)ft.dwLowDateTime | ((int64_t)ft.dwHighDateTime << 32);
                    system_start_time -= (int64_t)GetTickCount() * 10000; /* Note: GetTickCount() is valid only first 49 days */
                }
            }
            if (system_start_time != 0) {
                write_time(out, c_time, system_start_time, "StartTime");
            }
            write_time(out, k_time, 0, "STime");
            write_time(out, u_time, 0, "UTime");
        }
    }

    write_stream(out, '}');
}

static void command_get_context(char * token, Channel * c) {
    char id[256];
    pid_t pid = 0;
    pid_t parent = 0;
    int err = 0;

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    pid = id2pid(id, &parent);
    if (parent != 0) {
        write_errno(&c->out, err);
        write_stringz(&c->out, "null");
    }
    else if (pid != 0) {
        HANDLE prs = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
        if (prs == NULL) err = set_win32_errno(GetLastError());
        if (err == 0 && get_process_info(prs) < 0) err = errno;
        write_errno(&c->out, err);
        if (err == 0) {
            write_process_context(&c->out, id, pid, prs);
            write_stream(&c->out, 0);
        }
        else {
            write_stringz(&c->out, "null");
        }
        if (prs != NULL) CloseHandle(prs);
    }
    else {
        write_errno(&c->out, err);
        write_stringz(&c->out, "null");
    }

    write_stream(&c->out, MARKER_EOM);
}

static void command_get_children(char * token, Channel * c) {
    char id[256];
    pid_t pid = 0;
    pid_t parent = 0;
    int err = 0;

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    pid = id2pid(id, &parent);

    if (parent != 0) {
        /* Children of a thread: none */
        write_errno(&c->out, 0);
        write_stringz(&c->out, "null");
    }
    else if (pid != 0) {
        /* Children of a process: threads */
        /* TODO: enumerate threads */
        write_errno(&c->out, 0);
        write_stringz(&c->out, "null");
    }
    else {
        /* Children of the root: processes */
        typedef BOOL (WINAPI * EnumProcessesProcType)(DWORD *, DWORD, DWORD *);
        static EnumProcessesProcType EnumProcessesProc;
        HANDLE heap = GetProcessHeap();
        DWORD * prs_ids = NULL;
        int prs_cnt = 0;
        if (EnumProcessesProc == NULL) {
            HINSTANCE psapi = LoadLibrary("PSAPI.DLL");
            if (psapi == NULL) {
                err = set_win32_errno(GetLastError());
            }
            else {
                EnumProcessesProc = (EnumProcessesProcType)GetProcAddress(psapi, "EnumProcesses");
                if (EnumProcessesProc == NULL) err = set_win32_errno(GetLastError());
            }
        }
        if (err == 0) {
            DWORD size_allocated = 128;
            DWORD size_returned = 0;
            do {
                size_allocated *= 2;
                if (prs_ids != NULL) HeapFree(heap, 0, prs_ids);
                prs_ids = (DWORD *)HeapAlloc(heap, 0, size_allocated);
                if (prs_ids == NULL) {
                    err = set_win32_errno(GetLastError());
                    break;
                }
                if (!EnumProcessesProc(prs_ids, size_allocated, &size_returned)) {
                    err = set_win32_errno(GetLastError());
                    break;
                }
            }
            while (size_returned == size_allocated);
            prs_cnt = size_returned / sizeof(DWORD);
        }
        write_errno(&c->out, err);
        if (err == 0) {
            int pos = 0;
            int cnt = 0;
            write_stream(&c->out, '[');
            for (pos = 0; pos < prs_cnt; pos++) {
                if (prs_ids[pos] == 0) continue;
                if (cnt > 0) write_stream(&c->out, ',');
                json_write_string(&c->out, pid2id(prs_ids[pos], 0));
                cnt++;
            }
            write_stream(&c->out, ']');
            write_stream(&c->out, 0);
        }
        else {
            write_stringz(&c->out, "null");
        }
        if (prs_ids != NULL) HeapFree(heap, 0, prs_ids);
    }

    write_stream(&c->out, MARKER_EOM);
}

static void command_get_command_line(char * token, Channel * c) {
    char id[256];
    pid_t pid = 0;
    pid_t parent = 0;
    int err = 0;
    HANDLE prs = NULL;
    wchar_t * cmd = NULL;

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    pid = id2pid(id, &parent);
    if (pid != 0 && parent == 0) {
        prs = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
        if (prs == NULL) err = set_win32_errno(GetLastError());
    }
    else {
        err = ERR_INV_CONTEXT;
    }
    if (err == 0 && get_process_info(prs) < 0) err = errno;
    if (err == 0 && upa.CommandLine.Buffer != NULL) {
        SIZE_T cmd_size = upa.CommandLine.Length;
        SIZE_T read_size = 0;
        cmd = (wchar_t *)loc_alloc(cmd_size);
        if (ReadProcessMemory(prs, (LPCVOID)upa.CommandLine.Buffer, cmd, cmd_size, &read_size) == 0) {
            err = set_win32_errno(GetLastError());
        }
    }
    if (prs != NULL) CloseHandle(prs);

    write_errno(&c->out, err);

    if (err == 0 && cmd != NULL) {
        wchar_t * p = cmd;
        wchar_t * e = cmd + upa.CommandLine.Length / sizeof(wchar_t);
        int cnt = 0;
        write_stream(&c->out, '[');
        while (p < e && *p) {
            int quotation = 0;
            if (*p == ' ') { p++; continue; }
            if (*p == '\t') { p++; continue; }
            if (cnt > 0) write_stream(&c->out, ',');
            write_stream(&c->out, '"');
            while (p < e && *p) {
                char buf[0x100];
                unsigned k = 0;
                while (p < e && *p && k < sizeof(buf) / 4) {
                    if (*p == '"' || *p == '\\' || *p == ' ' || *p == '\t') break;
                    p++;
                    k++;
                }
                if (k > 0) {
                    int i = 0;
                    int n = WideCharToMultiByte(CP_UTF8, 0, p - k, k, buf, sizeof(buf), NULL, NULL);
                    while (i < n) json_write_char(&c->out, buf[i++]);
                    if (p == e || *p == 0) break;
                }
                if (*p == '"') {
                    quotation = !quotation;
                    p++;
                }
                else if (*p == '\\') {
                    p++;
                    if (p == e) {
                        json_write_char(&c->out, '\\');
                    }
                    else if (*p == '"') {
                        json_write_char(&c->out, '"');
                        p++;
                    }
                    else if (*p == '\\') {
                        json_write_char(&c->out, '\\');
                        p++;
                    }
                    else {
                        json_write_char(&c->out, '\\');
                    }
                }
                else if (*p == ' ' || *p == '\t') {
                    p++;
                    if (!quotation) break;
                    json_write_char(&c->out, ' ');
                }
                else {
                    assert(k > 0);
                }
            }
            write_stream(&c->out, '"');
            cnt++;
        }
        write_stream(&c->out, ']');
        write_stream(&c->out, 0);
    }
    else {
        write_stringz(&c->out, "null");
    }

    write_stream(&c->out, MARKER_EOM);
    loc_free(cmd);
}

static void command_get_environment(char * token, Channel * c) {
    char id[256];
    pid_t pid = 0;
    pid_t parent = 0;
    int err = 0;
    HANDLE prs = NULL;
    wchar_t * env = NULL;

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    pid = id2pid(id, &parent);
    if (pid != 0 && parent == 0) {
        prs = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
        if (prs == NULL) err = set_win32_errno(GetLastError());
    }
    else {
        err = ERR_INV_CONTEXT;
    }
    if (err == 0 && get_process_info(prs) < 0) err = errno;
    if (err == 0 && upa.Environment != NULL) {
        wchar_t buf[0x100];
        SIZE_T buf_pos = 0;
        SIZE_T buf_len = 0;
        SIZE_T env_size = 0;
        int cnt = 0;

        for (;;) {
            if (buf_pos >= buf_len) {
                SIZE_T len = 0;
                if (ReadProcessMemory(prs, (LPCVOID)((SIZE_T)upa.Environment + env_size), buf, sizeof(buf), &len) == 0) {
                    err = set_win32_errno(GetLastError());
                    break;
                }
                buf_pos = 0;
                buf_len = len / sizeof(wchar_t);
            }
            env_size += sizeof(wchar_t);
            if (buf[buf_pos++] == 0) {
                cnt++;
                if (cnt == 2) break;
            }
            else {
                cnt = 0;
            }
        }

        if (err == 0) {
            env = (wchar_t *)loc_alloc(env_size);
            if (ReadProcessMemory(prs, (LPCVOID)upa.Environment, env, env_size, &buf_len) == 0) {
                err = set_win32_errno(GetLastError());
            }
        }
    }
    if (prs != NULL) CloseHandle(prs);

    write_errno(&c->out, err);

    if (err == 0 && env != NULL) {
        wchar_t * p = env;
        int cnt = 0;
        write_stream(&c->out, '[');
        while (*p) {
            if (cnt > 0) write_stream(&c->out, ',');
            write_stream(&c->out, '"');
            while (*p) {
                char buf[0x100];
                unsigned k = 0;
                int n = 0, i = 0;
                while (*p && k < sizeof(buf) / 4) { p++; k++; }
                n = WideCharToMultiByte(CP_UTF8, 0, p - k, k, buf, sizeof(buf), NULL, NULL);
                while (i < n) json_write_char(&c->out, buf[i++]);
            }
            p++;
            write_stream(&c->out, '"');
            cnt++;
        }
        write_stream(&c->out, ']');
        write_stream(&c->out, 0);
    }
    else {
        write_stringz(&c->out, "null");
    }

    write_stream(&c->out, MARKER_EOM);
    loc_free(env);
}

#else

#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <linux/param.h>

#define BUF_EOF (-1)

static char buf[1024];
static int buf_fd = -1;
static int buf_pos = 0;
static int buf_len = 0;
static int buf_ch = 0;

static void next_ch(void) {
    while (buf_len >= 0 && buf_pos >= buf_len) {
        buf_pos = 0;
        buf_len = read(buf_fd, buf, sizeof(buf));
        if (buf_len == 0) buf_len = -1;
    }
    if (buf_len < 0) {
        buf_ch = BUF_EOF;
    }
    else {
        buf_ch = (unsigned char)buf[buf_pos++];
    }
}

static void first_ch(int fd) {
    buf_fd = fd;
    buf_pos = 0;
    buf_len = 0;
    next_ch();
}

static void write_string_array(OutputStream * out, int f) {
    int cnt = 0;
    first_ch(f);
    write_stream(out, '[');
    while (buf_ch != BUF_EOF && buf_ch != 0) {
        if (cnt > 0) write_stream(out, ',');
        write_stream(out, '"');
        do {
            json_write_char(out, buf_ch);
            next_ch();
        }
        while (buf_ch != BUF_EOF && buf_ch != 0);
        next_ch();
        write_stream(out, '"');
        cnt++;
    }
    write_stream(out, ']');
}

static void write_context(OutputStream * out, char * id, char * parent_id, char * dir) {
    char fnm[FILE_PATH_SIZE + 1];
    int sz;
    int f;

    write_stream(out, '{');

    if (chdir(dir) >= 0) {
        if ((sz = readlink("cwd", fnm, FILE_PATH_SIZE)) > 0) {
            fnm[sz] = 0;
            json_write_string(out, "CWD");
            write_stream(out, ':');
            json_write_string(out, fnm);
            write_stream(out, ',');
        }

        if ((sz = readlink("root", fnm, FILE_PATH_SIZE)) > 0) {
            fnm[sz] = 0;
            json_write_string(out, "Root");
            write_stream(out, ':');
            json_write_string(out, fnm);
            write_stream(out, ',');
        }

        if ((sz = readlink("exe", fnm, FILE_PATH_SIZE)) > 0) {
            fnm[sz] = 0;
            json_write_string(out, "Exe");
            write_stream(out, ':');
            json_write_string(out, fnm);
            write_stream(out, ',');
            json_write_string(out, "ExeType");
            write_stream(out, ':');
            json_write_long(out, EXETYPE_USER);
            write_stream(out, ',');
        }
        else if (errno == ENOENT) {
            json_write_string(out, "ExeType");
            write_stream(out, ':');
            json_write_long(out, EXETYPE_KERNEL);
            write_stream(out, ',');
        }
        else if (errno == EACCES) {
            json_write_string(out, "ExeType");
            write_stream(out, ':');
            json_write_long(out, EXETYPE_ACCESS_DENIED);
            write_stream(out, ',');
        }

        f = open("stat", O_RDONLY);
        if (f >= 0) {
            struct stat st;
            if (fstat(f, &st) == 0) {
                struct passwd * pwd;
                struct group * grp;

                json_write_string(out, "UID");
                write_stream(out, ':');
                json_write_long(out, st.st_uid);
                write_stream(out, ',');

                json_write_string(out, "UGID");
                write_stream(out, ':');
                json_write_long(out, st.st_gid);
                write_stream(out, ',');

                pwd = getpwuid(st.st_uid);
                if (pwd != NULL) {
                    json_write_string(out, "UserName");
                    write_stream(out, ':');
                    json_write_string(out, pwd->pw_name);
                    write_stream(out, ',');
                }

                grp = getgrgid(st.st_gid);
                if (grp != NULL) {
                    json_write_string(out, "GroupName");
                    write_stream(out, ':');
                    json_write_string(out, grp->gr_name);
                    write_stream(out, ',');
                }
            }

            memset(buf, 0, sizeof(buf));
            if ((sz = read(f, buf, sizeof(buf) - 1)) > 0) {
                char * str = buf;
                int pid = 0;                /* The process ID. */
                char * comm = fnm;          /* The  filename  of  the  executable,  in parentheses.  This is visible */
                                            /* whether or not the executable is swapped out. */
                char state = 0;             /* One character from the string "RSDZTW"  where  R  is  running,  S  is */
                                            /* sleeping  in  an  interruptible wait, D is waiting in uninterruptible */
                                            /* disk sleep, Z is zombie, T is traced or stopped (on a signal), and  W */
                                            /* is paging. */
                int ppid = 0;               /* The PID of the parent. */
                int pgrp = 0;               /* The process group ID of the process. */
                int session = 0;            /* The session ID of the process. */
                int tty_nr = 0;             /* The tty the process uses. */
                int tpgid = 0;              /* The process group ID of the process which currently owns the tty that */
                                            /* the process is connected to. */
                unsigned long flags = 0;    /* The kernel flags word of the process. For bit meanings, see the  PF_* */
                                            /* defines in <linux/sched.h>.  Details depend on the kernel version. */
                unsigned long minflt = 0;   /* The  number  of  minor  faults  the  process  has made which have not */
                                            /* required loading a memory page from disk. */
                unsigned long cminflt = 0;  /* The number of minor faults that  the  process's  waited-for  children */
                                            /* have made. */
                unsigned long majflt = 0;   /* The  number  of major faults the process has made which have required */
                                            /* loading a memory page from disk. */
                unsigned long cmajflt = 0;  /* The number of major faults that  the  process's  waited-for  children */
                                            /* have made. */
                unsigned long utime = 0;    /* The  number  of  jiffies that this process has been scheduled in user */
                                            /* mode. */
                unsigned long stime = 0;    /* The number of jiffies that this process has been scheduled in  kernel */
                                            /* mode. */
                long cutime = 0;            /* The  number  of  jiffies that this process's waited-for children have */
                                            /* been scheduled in user mode. (See also times(2).) */
                long cstime = 0;            /* The number of jiffies that this process's  waited-for  children  have */
                                            /* been scheduled in kernel mode. */
                long priority = 0;          /* The  standard  nice value, plus fifteen.  The value is never negative */
                                            /* in the kernel. */
                long nice = 0;              /* The nice value ranges from 19 (nicest) to -19 (not nice to others). */
                long dummy = 0;             /* This value is hard coded to 0 as a placeholder for a removed field. */
                long itrealvalue = 0;       /* The time in jiffies before the next SIGALRM is sent  to  the  process */
                                            /* due to an interval timer. */
                unsigned long starttime = 0;/* The time in jiffies the process started after system boot. */
                unsigned long vsize = 0;    /* Virtual memory size in bytes. */
                long rss = 0;               /* Resident  Set  Size:  number of pages the process has in real memory, */
                                            /* minus 3 for administrative purposes. This is  just  the  pages  which */
                                            /* count  towards  text,  data,  or  stack space.  This does not include */
                                            /* pages which have not been demand-loaded in, or which are swapped out. */
                unsigned long rlim = 0;     /* Current  limit in bytes on the rss of the process (usually 4294967295 */
                                            /* on i386). */
                unsigned long startcode = 0;/* The address above which program text can run. */
                unsigned long endcode = 0;  /* The address below which program text can run. */
                unsigned long startstack =0;/* The address of the start of the stack. */
                unsigned long kstkesp = 0;  /* The current value of esp (stack pointer),  as  found  in  the  kernel */
                                            /* stack page for the process. */
                unsigned long kstkeip = 0;  /* The current EIP (instruction pointer). */
                unsigned long signal = 0;   /* The bitmap of pending signals. */
                unsigned long blocked = 0;  /* The bitmap of blocked signals. */
                unsigned long sigignore = 0;/* The bitmap of ignored signals. */
                unsigned long sigcatch = 0; /* The bitmap of caught signals. */
                unsigned long wchan = 0;    /* This  is  the  "channel"  in which the process is waiting.  It is the */
                                            /* address of a system call, and can be looked up in a namelist  if  you */
                                            /* need  a  textual  name.   (If you have an up-to-date /etc/psdatabase, */
                                            /* then try ps -l to see the WCHAN field in action.) */
                unsigned long nswap = 0;    /* Number of pages swapped (not maintained). */
                unsigned long cnswap = 0;   /* Cumulative nswap for child processes (not maintained). */
                int exit_signal = 0;        /* Signal to be sent to parent when we die. */
                int processor = 0;          /* CPU number last executed on. */
                unsigned long rt_priority=0;/* Real-time scheduling priority (see sched_setscheduler(2)). */
                unsigned long policy = 0;   /* Scheduling policy (see sched_setscheduler(2)). */

                pid = (int)strtol(str, &str, 10);
                while (*str == ' ') str++;

                if (*str == '(') str++;
                sz = strlen(str);
                while (sz > 0 && str[sz] != ')') sz--;
                memcpy(comm, str, sz);
                comm[sz] = 0;
                str += sz;
                if (*str == ')') str++;
                while (*str == ' ') str++;

                sscanf(str,
                    "%c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld %ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %lu",
                    &state, &ppid, &pgrp, &session, &tty_nr, &tpgid, &flags,
                    &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime, &cutime, &cstime,
                    &priority, &nice, &dummy, &itrealvalue, &starttime, &vsize, &rss, &rlim,
                    &startcode, &endcode, &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore, &sigcatch,
                    &wchan, &nswap, &cnswap, &exit_signal, &processor, &rt_priority, &policy);

                json_write_string(out, "PID");
                write_stream(out, ':');
                json_write_long(out, pid);
                write_stream(out, ',');

                json_write_string(out, "File");
                write_stream(out, ':');
                json_write_string(out, comm);
                write_stream(out, ',');

                json_write_string(out, "State");
                write_stream(out, ':');
                write_stream(out, '"');
                json_write_char(out, state);
                write_stream(out, '"');
                write_stream(out, ',');

                if (ppid > 0) {
                    json_write_string(out, "PPID");
                    write_stream(out, ':');
                    json_write_long(out, ppid);
                    write_stream(out, ',');
                }

                json_write_string(out, "PGRP");
                write_stream(out, ':');
                json_write_long(out, pgrp);
                write_stream(out, ',');

                json_write_string(out, "Session");
                write_stream(out, ':');
                json_write_long(out, session);
                write_stream(out, ',');

                if (tty_nr > 0) {
                    json_write_string(out, "TTY");
                    write_stream(out, ':');
                    json_write_long(out, tty_nr);
                    write_stream(out, ',');
                }

                if (tpgid > 0) {
                    json_write_string(out, "TGID");
                    write_stream(out, ':');
                    json_write_long(out, tpgid);
                    write_stream(out, ',');
                }

                json_write_string(out, "Flags");
                write_stream(out, ':');
                json_write_ulong(out, flags);
                write_stream(out, ',');

                json_write_string(out, "MinFlt");
                write_stream(out, ':');
                json_write_ulong(out, minflt);
                write_stream(out, ',');

                json_write_string(out, "CMinFlt");
                write_stream(out, ':');
                json_write_ulong(out, cminflt);
                write_stream(out, ',');

                json_write_string(out, "MajFlt");
                write_stream(out, ':');
                json_write_ulong(out, majflt);
                write_stream(out, ',');

                json_write_string(out, "CMajFlt");
                write_stream(out, ':');
                json_write_ulong(out, cmajflt);
                write_stream(out, ',');

                json_write_string(out, "UTime");
                write_stream(out, ':');
                json_write_uint64(out, (uint64_t)utime * 1000 / HZ);
                write_stream(out, ',');

                json_write_string(out, "STime");
                write_stream(out, ':');
                json_write_uint64(out, (uint64_t)stime * 1000 / HZ);
                write_stream(out, ',');

                json_write_string(out, "CUTime");
                write_stream(out, ':');
                json_write_uint64(out, (uint64_t)cutime * 1000 / HZ);
                write_stream(out, ',');

                json_write_string(out, "CSTime");
                write_stream(out, ':');
                json_write_uint64(out, (uint64_t)cstime * 1000 / HZ);
                write_stream(out, ',');

                json_write_string(out, "Priority");
                write_stream(out, ':');
                json_write_long(out, (long)priority - 15);
                write_stream(out, ',');

                if (nice != 0) {
                    json_write_string(out, "Nice");
                    write_stream(out, ':');
                    json_write_long(out, nice);
                    write_stream(out, ',');
                }

                if (itrealvalue != 0) {
                    json_write_string(out, "ITRealValue");
                    write_stream(out, ':');
                    json_write_int64(out, (int64_t)itrealvalue * 1000 / HZ);
                    write_stream(out, ',');
                }

                json_write_string(out, "StartTime");
                write_stream(out, ':');
                json_write_uint64(out, (uint64_t)starttime * 1000 / HZ);
                write_stream(out, ',');

                json_write_string(out, "VSize");
                write_stream(out, ':');
                json_write_ulong(out, vsize);
                write_stream(out, ',');

                json_write_string(out, "PSize");
                write_stream(out, ':');
                json_write_ulong(out, getpagesize());
                write_stream(out, ',');

                json_write_string(out, "RSS");
                write_stream(out, ':');
                json_write_long(out, rss);
                write_stream(out, ',');

                json_write_string(out, "RLimit");
                write_stream(out, ':');
                json_write_ulong(out, rlim);
                write_stream(out, ',');

                if (startcode != 0) {
                    json_write_string(out, "CodeStart");
                    write_stream(out, ':');
                    json_write_ulong(out, startcode);
                    write_stream(out, ',');
                }

                if (endcode != 0) {
                    json_write_string(out, "CodeEnd");
                    write_stream(out, ':');
                    json_write_ulong(out, endcode);
                    write_stream(out, ',');
                }

                if (startstack != 0) {
                    json_write_string(out, "StackStart");
                    write_stream(out, ':');
                    json_write_ulong(out, startstack);
                    write_stream(out, ',');
                }

                json_write_string(out, "Signals");
                write_stream(out, ':');
                json_write_ulong(out, signal);
                write_stream(out, ',');

                json_write_string(out, "SigBlock");
                write_stream(out, ':');
                json_write_ulong(out, blocked);
                write_stream(out, ',');

                json_write_string(out, "SigIgnore");
                write_stream(out, ':');
                json_write_ulong(out, sigignore);
                write_stream(out, ',');

                json_write_string(out, "SigCatch");
                write_stream(out, ':');
                json_write_ulong(out, sigcatch);
                write_stream(out, ',');

                if (wchan != 0) {
                    json_write_string(out, "WChan");
                    write_stream(out, ':');
                    json_write_ulong(out, wchan);
                    write_stream(out, ',');
                }

                json_write_string(out, "NSwap");
                write_stream(out, ':');
                json_write_ulong(out, nswap);
                write_stream(out, ',');

                json_write_string(out, "CNSwap");
                write_stream(out, ':');
                json_write_ulong(out, cnswap);
                write_stream(out, ',');

                json_write_string(out, "ExitSignal");
                write_stream(out, ':');
                json_write_long(out, exit_signal);
                write_stream(out, ',');

                json_write_string(out, "Processor");
                write_stream(out, ':');
                json_write_long(out, processor);
                write_stream(out, ',');

                json_write_string(out, "RTPriority");
                write_stream(out, ':');
                json_write_ulong(out, rt_priority);
                write_stream(out, ',');

                json_write_string(out, "Policy");
                write_stream(out, ':');
                json_write_ulong(out, policy);
                write_stream(out, ',');
            }
            close(f);
        }
    }

    if (parent_id != NULL && parent_id[0] != 0) {
        json_write_string(out, "ParentID");
        write_stream(out, ':');
        json_write_string(out, parent_id);
        write_stream(out, ',');
    }

    json_write_string(out, "ID");
    write_stream(out, ':');
    json_write_string(out, id);

    write_stream(out, '}');
}

static void command_get_context(char * token, Channel * c) {
    char id[256];
    pid_t pid = 0;
    pid_t parent = 0;
    int err = 0;
    char dir[FILE_PATH_SIZE];

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    pid = id2pid(id, &parent);
    if (pid != 0) {
        struct stat st;
        if (parent != 0) {
            snprintf(dir, sizeof(dir), "/proc/%d/task/%d", parent, pid);
        }
        else {
            snprintf(dir, sizeof(dir), "/proc/%d", pid);
        }
        if (lstat(dir, &st) < 0) err = errno == ENOENT ? ESRCH : errno;
        else if (!S_ISDIR(st.st_mode)) err = ERR_INV_CONTEXT;
    }

    write_errno(&c->out, err);

    if (err == 0 && pid != 0) {
        char bf[256];
        write_context(&c->out, id, parent == 0 ? NULL : strcpy(bf, pid2id(parent, 0)), dir);
        write_stream(&c->out, 0);
    }
    else {
        write_stringz(&c->out, "null");
    }

    write_stream(&c->out, MARKER_EOM);
}

static void command_get_children(char * token, Channel * c) {
    char id[256];
    pid_t pid = 0;
    pid_t parent = 0;

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    pid = id2pid(id, &parent);

    if (parent != 0) {
        write_errno(&c->out, 0);
        write_stringz(&c->out, "null");
    }
    else {
        DIR * proc = NULL;
        char dir[FILE_PATH_SIZE];
        if (pid == 0) strcpy(dir, "/proc");
        else snprintf(dir, sizeof(dir), "/proc/%d/task", pid);
        proc = opendir(dir);
        if (proc == NULL) {
            int err = errno;
            if (pid != 0 && err == ENOENT) {
                struct stat buf;
                snprintf(dir, sizeof(dir), "/proc/%d", pid);
                if (stat(dir, &buf) == 0) {
                    /* Zombie */
                    err = 0;
                }
                else {
                    err = ESRCH;
                }
            }
            write_errno(&c->out, err);
            write_stringz(&c->out, "null");
        }
        else {
            int cnt = 0;
            write_errno(&c->out, 0);
            write_stream(&c->out, '[');
            for (;;) {
                struct dirent * ent = readdir(proc);
                if (ent == NULL) break;
                if (ent->d_name[0] >= '1' && ent->d_name[0] <= '9') {
                    if (cnt > 0) write_stream(&c->out, ',');
                    json_write_string(&c->out, pid2id(atol(ent->d_name), pid));
                    cnt++;
                }
            }
            write_stream(&c->out, ']');
            write_stream(&c->out, 0);
            closedir(proc);
        }
    }

    write_stream(&c->out, MARKER_EOM);
}

static void command_get_command_line(char * token, Channel * c) {
    char id[256];
    pid_t pid = 0;
    pid_t parent = 0;
    int err = 0;
    char dir[FILE_PATH_SIZE];
    int f = -1;

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    pid = id2pid(id, &parent);
    if (pid != 0 && parent == 0) {
        struct stat st;
        snprintf(dir, sizeof(dir), "/proc/%d", pid);
        if (lstat(dir, &st) < 0) err = errno == ENOENT ? ESRCH : errno;
        else if (!S_ISDIR(st.st_mode)) err = ERR_INV_CONTEXT;
    }
    else {
        err = ERR_INV_CONTEXT;
    }

    if (err == 0 && chdir(dir) < 0) err = errno;
    if (err == 0 && (f = open("cmdline", O_RDONLY)) < 0) err = errno;

    write_errno(&c->out, err);

    if (err == 0) {
        write_string_array(&c->out, f);
        close(f);
        write_stream(&c->out, 0);
    }
    else {
        write_stringz(&c->out, "null");
    }

    write_stream(&c->out, MARKER_EOM);
}

static void command_get_environment(char * token, Channel * c) {
    char id[256];
    pid_t pid = 0;
    pid_t parent = 0;
    int err = 0;
    char dir[FILE_PATH_SIZE];
    int f = -1;

    json_read_string(&c->inp, id, sizeof(id));
    json_test_char(&c->inp, MARKER_EOA);
    json_test_char(&c->inp, MARKER_EOM);

    write_stringz(&c->out, "R");
    write_stringz(&c->out, token);

    pid = id2pid(id, &parent);
    if (pid != 0 && parent == 0) {
        struct stat st;
        snprintf(dir, sizeof(dir), "/proc/%d", pid);
        if (lstat(dir, &st) < 0) err = errno == ENOENT ? ESRCH : errno;
        else if (!S_ISDIR(st.st_mode)) err = ERR_INV_CONTEXT;
    }
    else {
        err = ERR_INV_CONTEXT;
    }

    if (err == 0 && chdir(dir) < 0) err = errno;
    if (err == 0 && (f = open("environ", O_RDONLY)) < 0) err = errno;

    write_errno(&c->out, err);

    if (err == 0) {
        write_string_array(&c->out, f);
        close(f);
        write_stream(&c->out, 0);
    }
    else {
        write_stringz(&c->out, "null");
    }

    write_stream(&c->out, MARKER_EOM);
}
#endif

extern void ini_sys_mon_service(Protocol * proto) {
    add_command_handler(proto, SYS_MON, "getContext", command_get_context);
    add_command_handler(proto, SYS_MON, "getChildren", command_get_children);
    add_command_handler(proto, SYS_MON, "getCommandLine", command_get_command_line);
    add_command_handler(proto, SYS_MON, "getEnvironment", command_get_environment);
}

#endif /* SERVICE_SysMonitor */
