From 10eea2ab1bae2a8ec159d81c0446fd8061b33e2b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 21:58:07 +0200 Subject: Adding upstream version 3.3.0. Signed-off-by: Daniel Baumann --- openbsd/OpenBSDMachine.c | 290 ++++++++++++++++++++++++++++++ openbsd/OpenBSDMachine.h | 53 ++++++ openbsd/OpenBSDProcess.c | 269 ++++++++++++++++++++++++++++ openbsd/OpenBSDProcess.h | 33 ++++ openbsd/OpenBSDProcessTable.c | 245 ++++++++++++++++++++++++++ openbsd/OpenBSDProcessTable.h | 21 +++ openbsd/Platform.c | 400 ++++++++++++++++++++++++++++++++++++++++++ openbsd/Platform.h | 132 ++++++++++++++ openbsd/ProcessField.h | 15 ++ 9 files changed, 1458 insertions(+) create mode 100644 openbsd/OpenBSDMachine.c create mode 100644 openbsd/OpenBSDMachine.h create mode 100644 openbsd/OpenBSDProcess.c create mode 100644 openbsd/OpenBSDProcess.h create mode 100644 openbsd/OpenBSDProcessTable.c create mode 100644 openbsd/OpenBSDProcessTable.h create mode 100644 openbsd/Platform.c create mode 100644 openbsd/Platform.h create mode 100644 openbsd/ProcessField.h (limited to 'openbsd') diff --git a/openbsd/OpenBSDMachine.c b/openbsd/OpenBSDMachine.c new file mode 100644 index 0000000..0ff893b --- /dev/null +++ b/openbsd/OpenBSDMachine.c @@ -0,0 +1,290 @@ +/* +htop - OpenBSDMachine.c +(C) 2014 Hisham H. Muhammad +(C) 2015 Michael McConville +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "openbsd/OpenBSDMachine.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CRT.h" +#include "Macros.h" +#include "Object.h" +#include "Settings.h" +#include "XUtils.h" + + +static void OpenBSDMachine_updateCPUcount(OpenBSDMachine* this) { + Machine* super = &this->super; + const int nmib[] = { CTL_HW, HW_NCPU }; + const int mib[] = { CTL_HW, HW_NCPUONLINE }; + int r; + unsigned int value; + size_t size; + bool change = false; + + size = sizeof(value); + r = sysctl(mib, 2, &value, &size, NULL, 0); + if (r < 0 || value < 1) { + value = 1; + } + + if (value != super->activeCPUs) { + super->activeCPUs = value; + change = true; + } + + size = sizeof(value); + r = sysctl(nmib, 2, &value, &size, NULL, 0); + if (r < 0 || value < 1) { + value = super->activeCPUs; + } + + if (value != super->existingCPUs) { + this->cpuData = xReallocArray(this->cpuData, value + 1, sizeof(CPUData)); + super->existingCPUs = value; + change = true; + } + + if (change) { + CPUData* dAvg = &this->cpuData[0]; + memset(dAvg, '\0', sizeof(CPUData)); + dAvg->totalTime = 1; + dAvg->totalPeriod = 1; + dAvg->online = true; + + for (unsigned int i = 0; i < super->existingCPUs; i++) { + CPUData* d = &this->cpuData[i + 1]; + memset(d, '\0', sizeof(CPUData)); + d->totalTime = 1; + d->totalPeriod = 1; + + const int ncmib[] = { CTL_KERN, KERN_CPUSTATS, i }; + struct cpustats cpu_stats; + + size = sizeof(cpu_stats); + if (sysctl(ncmib, 3, &cpu_stats, &size, NULL, 0) < 0) { + CRT_fatalError("ncmib sysctl call failed"); + } + d->online = (cpu_stats.cs_flags & CPUSTATS_ONLINE); + } + } +} + +Machine* Machine_new(UsersTable* usersTable, uid_t userId) { + const int fmib[] = { CTL_KERN, KERN_FSCALE }; + size_t size; + char errbuf[_POSIX2_LINE_MAX]; + + OpenBSDMachine* this = xCalloc(1, sizeof(OpenBSDMachine)); + Machine* super = &this->super; + + Machine_init(super, usersTable, userId); + + OpenBSDMachine_updateCPUcount(this); + + size = sizeof(this->fscale); + if (sysctl(fmib, 2, &this->fscale, &size, NULL, 0) < 0 || this->fscale <= 0) { + CRT_fatalError("fscale sysctl call failed"); + } + + if ((this->pageSize = sysconf(_SC_PAGESIZE)) == -1) + CRT_fatalError("pagesize sysconf call failed"); + this->pageSizeKB = this->pageSize / ONE_K; + + this->kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + if (this->kd == NULL) { + CRT_fatalError("kvm_openfiles() failed"); + } + + this->cpuSpeed = -1; + + return super; +} + +void Machine_delete(Machine* super) { + OpenBSDMachine* this = (OpenBSDMachine*) super; + + if (this->kd) { + kvm_close(this->kd); + } + free(this->cpuData); + Machine_done(super); + free(this); +} + +static void OpenBSDMachine_scanMemoryInfo(OpenBSDMachine* this) { + Machine* super = &this->super; + const int uvmexp_mib[] = { CTL_VM, VM_UVMEXP }; + struct uvmexp uvmexp; + size_t size_uvmexp = sizeof(uvmexp); + + if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) { + CRT_fatalError("uvmexp sysctl call failed"); + } + + super->totalMem = uvmexp.npages * this->pageSizeKB; + super->usedMem = (uvmexp.npages - uvmexp.free - uvmexp.paging) * this->pageSizeKB; + + // Taken from OpenBSD systat/iostat.c, top/machine.c and uvm_sysctl(9) + const int bcache_mib[] = { CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT }; + struct bcachestats bcstats; + size_t size_bcstats = sizeof(bcstats); + + if (sysctl(bcache_mib, 3, &bcstats, &size_bcstats, NULL, 0) < 0) { + CRT_fatalError("cannot get vfs.bcachestat"); + } + + super->cachedMem = bcstats.numbufpages * this->pageSizeKB; + + /* + * Copyright (c) 1994 Thorsten Lockert + * All rights reserved. + * + * Taken almost directly from OpenBSD's top(1) + * + * Originally released under a BSD-3 license + * Modified through htop developers applying GPL-2 + */ + int nswap = swapctl(SWAP_NSWAP, 0, 0); + if (nswap > 0) { + struct swapent* swdev = xMallocArray(nswap, sizeof(struct swapent)); + int rnswap = swapctl(SWAP_STATS, swdev, nswap); + + /* Total things up */ + unsigned long long int total = 0, used = 0; + for (int i = 0; i < rnswap; i++) { + if (swdev[i].se_flags & SWF_ENABLE) { + used += (swdev[i].se_inuse / (1024 / DEV_BSIZE)); + total += (swdev[i].se_nblks / (1024 / DEV_BSIZE)); + } + } + + super->totalSwap = total; + super->usedSwap = used; + + free(swdev); + } else { + super->totalSwap = super->usedSwap = 0; + } +} + +static void getKernelCPUTimes(unsigned int cpuId, u_int64_t* times) { + const int mib[] = { CTL_KERN, KERN_CPTIME2, cpuId }; + size_t length = sizeof(*times) * CPUSTATES; + if (sysctl(mib, 3, times, &length, NULL, 0) == -1 || length != sizeof(*times) * CPUSTATES) { + CRT_fatalError("sysctl kern.cp_time2 failed"); + } +} + +static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) { + unsigned long long totalTime = 0; + for (int i = 0; i < CPUSTATES; i++) { + totalTime += times[i]; + } + + unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS]; + + // XXX Not sure if CP_SPIN should be added to sysAllTime. + // See https://github.com/openbsd/src/commit/531d8034253fb82282f0f353c086e9ad827e031c + #ifdef CP_SPIN + sysAllTime += times[CP_SPIN]; + #endif + + cpu->totalPeriod = saturatingSub(totalTime, cpu->totalTime); + cpu->userPeriod = saturatingSub(times[CP_USER], cpu->userTime); + cpu->nicePeriod = saturatingSub(times[CP_NICE], cpu->niceTime); + cpu->sysPeriod = saturatingSub(times[CP_SYS], cpu->sysTime); + cpu->sysAllPeriod = saturatingSub(sysAllTime, cpu->sysAllTime); + #ifdef CP_SPIN + cpu->spinPeriod = saturatingSub(times[CP_SPIN], cpu->spinTime); + #endif + cpu->intrPeriod = saturatingSub(times[CP_INTR], cpu->intrTime); + cpu->idlePeriod = saturatingSub(times[CP_IDLE], cpu->idleTime); + + cpu->totalTime = totalTime; + cpu->userTime = times[CP_USER]; + cpu->niceTime = times[CP_NICE]; + cpu->sysTime = times[CP_SYS]; + cpu->sysAllTime = sysAllTime; + #ifdef CP_SPIN + cpu->spinTime = times[CP_SPIN]; + #endif + cpu->intrTime = times[CP_INTR]; + cpu->idleTime = times[CP_IDLE]; +} + +static void OpenBSDMachine_scanCPUTime(OpenBSDMachine* this) { + Machine* super = &this->super; + u_int64_t kernelTimes[CPUSTATES] = {0}; + u_int64_t avg[CPUSTATES] = {0}; + + for (unsigned int i = 0; i < super->existingCPUs; i++) { + CPUData* cpu = &this->cpuData[i + 1]; + + if (!cpu->online) { + continue; + } + + getKernelCPUTimes(i, kernelTimes); + kernelCPUTimesToHtop(kernelTimes, cpu); + + avg[CP_USER] += cpu->userTime; + avg[CP_NICE] += cpu->niceTime; + avg[CP_SYS] += cpu->sysTime; + #ifdef CP_SPIN + avg[CP_SPIN] += cpu->spinTime; + #endif + avg[CP_INTR] += cpu->intrTime; + avg[CP_IDLE] += cpu->idleTime; + } + + for (int i = 0; i < CPUSTATES; i++) { + avg[i] /= super->activeCPUs; + } + + kernelCPUTimesToHtop(avg, &this->cpuData[0]); + + { + const int mib[] = { CTL_HW, HW_CPUSPEED }; + int cpuSpeed; + size_t size = sizeof(cpuSpeed); + if (sysctl(mib, 2, &cpuSpeed, &size, NULL, 0) == -1) { + this->cpuSpeed = -1; + } else { + this->cpuSpeed = cpuSpeed; + } + } +} + +void Machine_scan(Machine* super) { + OpenBSDMachine* this = (OpenBSDMachine*) super; + + OpenBSDMachine_updateCPUcount(this); + OpenBSDMachine_scanMemoryInfo(this); + OpenBSDMachine_scanCPUTime(this); +} + +bool Machine_isCPUonline(const Machine* super, unsigned int id) { + assert(id < super->existingCPUs); + + const OpenBSDMachine* this = (const OpenBSDMachine*) super; + return this->cpuData[id + 1].online; +} diff --git a/openbsd/OpenBSDMachine.h b/openbsd/OpenBSDMachine.h new file mode 100644 index 0000000..51d6a75 --- /dev/null +++ b/openbsd/OpenBSDMachine.h @@ -0,0 +1,53 @@ +#ifndef HEADER_OpenBSDMachine +#define HEADER_OpenBSDMachine +/* +htop - OpenBSDMachine.h +(C) 2014 Hisham H. Muhammad +(C) 2015 Michael McConville +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include +#include + +#include "Machine.h" + + +typedef struct CPUData_ { + unsigned long long int totalTime; + unsigned long long int userTime; + unsigned long long int niceTime; + unsigned long long int sysTime; + unsigned long long int sysAllTime; + unsigned long long int spinTime; + unsigned long long int intrTime; + unsigned long long int idleTime; + + unsigned long long int totalPeriod; + unsigned long long int userPeriod; + unsigned long long int nicePeriod; + unsigned long long int sysPeriod; + unsigned long long int sysAllPeriod; + unsigned long long int spinPeriod; + unsigned long long int intrPeriod; + unsigned long long int idlePeriod; + + bool online; +} CPUData; + +typedef struct OpenBSDMachine_ { + Machine super; + kvm_t* kd; + + CPUData* cpuData; + + long fscale; + int cpuSpeed; + int pageSize; + int pageSizeKB; + +} OpenBSDMachine; + +#endif diff --git a/openbsd/OpenBSDProcess.c b/openbsd/OpenBSDProcess.c new file mode 100644 index 0000000..681d516 --- /dev/null +++ b/openbsd/OpenBSDProcess.c @@ -0,0 +1,269 @@ +/* +htop - OpenBSDProcess.c +(C) 2015 Hisham H. Muhammad +(C) 2015 Michael McConville +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "openbsd/OpenBSDProcess.h" + +#include + +#include "CRT.h" +#include "Process.h" +#include "RichString.h" +#include "XUtils.h" + + +const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { + [0] = { + .name = "", + .title = NULL, + .description = NULL, + .flags = 0, + }, + [PID] = { + .name = "PID", + .title = "PID", + .description = "Process/thread ID", + .flags = 0, + .pidColumn = true, + }, + [COMM] = { + .name = "Command", + .title = "Command ", + .description = "Command line", + .flags = 0, + }, + [STATE] = { + .name = "STATE", + .title = "S ", + .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)", + .flags = 0, + }, + [PPID] = { + .name = "PPID", + .title = "PPID", + .description = "Parent process ID", + .flags = 0, + .pidColumn = true, + }, + [PGRP] = { + .name = "PGRP", + .title = "PGRP", + .description = "Process group ID", + .flags = 0, + .pidColumn = true, + }, + [SESSION] = { + .name = "SESSION", + .title = "SESN", + .description = "Process's session ID", + .flags = 0, + .pidColumn = true, + }, + [TTY] = { + .name = "TTY", + .title = "TTY ", + .description = "Controlling terminal", + .flags = 0, + }, + [TPGID] = { + .name = "TPGID", + .title = "TPGID", + .description = "Process ID of the fg process group of the controlling terminal", + .flags = 0, + .pidColumn = true, + }, + [MINFLT] = { + .name = "MINFLT", + .title = " MINFLT ", + .description = "Number of minor faults which have not required loading a memory page from disk", + .flags = 0, + .defaultSortDesc = true, + }, + [MAJFLT] = { + .name = "MAJFLT", + .title = " MAJFLT ", + .description = "Number of major faults which have required loading a memory page from disk", + .flags = 0, + .defaultSortDesc = true, + }, + [PRIORITY] = { + .name = "PRIORITY", + .title = "PRI ", + .description = "Kernel's internal priority for the process", + .flags = 0, + }, + [NICE] = { + .name = "NICE", + .title = " NI ", + .description = "Nice value (the higher the value, the more it lets other processes take priority)", + .flags = 0, + }, + [STARTTIME] = { + .name = "STARTTIME", + .title = "START ", + .description = "Time the process was started", + .flags = 0, + }, + [ELAPSED] = { + .name = "ELAPSED", + .title = "ELAPSED ", + .description = "Time since the process was started", + .flags = 0, + }, + [PROCESSOR] = { + .name = "PROCESSOR", + .title = "CPU ", + .description = "Id of the CPU the process last executed on", + .flags = 0, + }, + [M_VIRT] = { + .name = "M_VIRT", + .title = " VIRT ", + .description = "Total program size in virtual memory", + .flags = 0, + .defaultSortDesc = true, + }, + [M_RESIDENT] = { + .name = "M_RESIDENT", + .title = " RES ", + .description = "Resident set size, size of the text and data sections, plus stack usage", + .flags = 0, + .defaultSortDesc = true, + }, + [ST_UID] = { + .name = "ST_UID", + .title = "UID", + .description = "User ID of the process owner", + .flags = 0, + }, + [PERCENT_CPU] = { + .name = "PERCENT_CPU", + .title = " CPU%", + .description = "Percentage of the CPU time the process used in the last sampling", + .flags = 0, + .defaultSortDesc = true, + .autoWidth = true, + }, + [PERCENT_NORM_CPU] = { + .name = "PERCENT_NORM_CPU", + .title = "NCPU%", + .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", + .flags = 0, + .defaultSortDesc = true, + .autoWidth = true, + }, + [PERCENT_MEM] = { + .name = "PERCENT_MEM", + .title = "MEM% ", + .description = "Percentage of the memory the process is using, based on resident memory size", + .flags = 0, + .defaultSortDesc = true, + }, + [USER] = { + .name = "USER", + .title = "USER ", + .description = "Username of the process owner (or user ID if name cannot be determined)", + .flags = 0, + }, + [TIME] = { + .name = "TIME", + .title = " TIME+ ", + .description = "Total time the process has spent in user and system time", + .flags = 0, + .defaultSortDesc = true, + }, + [NLWP] = { + .name = "NLWP", + .title = "NLWP ", + .description = "Number of threads in the process", + .flags = 0, + }, + [TGID] = { + .name = "TGID", + .title = "TGID", + .description = "Thread group ID (i.e. process ID)", + .flags = 0, + .pidColumn = true, + }, + [PROC_COMM] = { + .name = "COMM", + .title = "COMM ", + .description = "comm string of the process", + .flags = 0, + }, + [CWD] = { + .name = "CWD", + .title = "CWD ", + .description = "The current working directory of the process", + .flags = PROCESS_FLAG_CWD, + }, + +}; + +Process* OpenBSDProcess_new(const Machine* host) { + OpenBSDProcess* this = xCalloc(1, sizeof(OpenBSDProcess)); + Object_setClass(this, Class(OpenBSDProcess)); + Process_init(&this->super, host); + return &this->super; +} + +void Process_delete(Object* cast) { + OpenBSDProcess* this = (OpenBSDProcess*) cast; + Process_done((Process*)cast); + free(this); +} + +static void OpenBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const OpenBSDProcess* op = (const OpenBSDProcess*) super; + + char buffer[256]; buffer[255] = '\0'; + int attr = CRT_colors[DEFAULT_COLOR]; + //size_t n = sizeof(buffer) - 1; + + switch (field) { + // add OpenBSD-specific fields here + default: + Process_writeField(&op->super, str, field); + return; + } + + RichString_appendWide(str, attr, buffer); +} + +static int OpenBSDProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) { + const OpenBSDProcess* p1 = (const OpenBSDProcess*)v1; + const OpenBSDProcess* p2 = (const OpenBSDProcess*)v2; + + // remove if actually used + (void)p1; (void)p2; + + switch (key) { + // add OpenBSD-specific fields here + default: + return Process_compareByKey_Base(v1, v2, key); + } +} + +const ProcessClass OpenBSDProcess_class = { + .super = { + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = OpenBSDProcess_rowWriteField + }, + .compareByKey = OpenBSDProcess_compareByKey +}; diff --git a/openbsd/OpenBSDProcess.h b/openbsd/OpenBSDProcess.h new file mode 100644 index 0000000..aac4b31 --- /dev/null +++ b/openbsd/OpenBSDProcess.h @@ -0,0 +1,33 @@ +#ifndef HEADER_OpenBSDProcess +#define HEADER_OpenBSDProcess +/* +htop - OpenBSDProcess.h +(C) 2015 Hisham H. Muhammad +(C) 2015 Michael McConville +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include + +#include "Machine.h" +#include "Object.h" +#include "Process.h" + + +typedef struct OpenBSDProcess_ { + Process super; + + /* 'Kernel virtual addr of u-area' to detect main threads */ + uint64_t addr; +} OpenBSDProcess; + +extern const ProcessClass OpenBSDProcess_class; + +extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; + +Process* OpenBSDProcess_new(const Machine* host); + +void Process_delete(Object* cast); + +#endif diff --git a/openbsd/OpenBSDProcessTable.c b/openbsd/OpenBSDProcessTable.c new file mode 100644 index 0000000..f2980a4 --- /dev/null +++ b/openbsd/OpenBSDProcessTable.c @@ -0,0 +1,245 @@ +/* +htop - OpenBSDProcessTable.c +(C) 2014 Hisham H. Muhammad +(C) 2015 Michael McConville +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "openbsd/OpenBSDProcessTable.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CRT.h" +#include "Macros.h" +#include "Object.h" +#include "Process.h" +#include "ProcessTable.h" +#include "Settings.h" +#include "XUtils.h" +#include "openbsd/OpenBSDMachine.h" +#include "openbsd/OpenBSDProcess.h" + + +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + OpenBSDProcessTable* this = xCalloc(1, sizeof(OpenBSDProcessTable)); + Object_setClass(this, Class(ProcessTable)); + + ProcessTable* super = &this->super; + ProcessTable_init(super, Class(OpenBSDProcess), host, pidMatchList); + + return this; +} + +void ProcessTable_delete(Object* cast) { + OpenBSDProcessTable* this = (OpenBSDProcessTable*) cast; + ProcessTable_done(&this->super); + free(this); +} + +static void OpenBSDProcessTable_updateCwd(const struct kinfo_proc* kproc, Process* proc) { + const int mib[] = { CTL_KERN, KERN_PROC_CWD, kproc->p_pid }; + char buffer[2048]; + size_t size = sizeof(buffer); + if (sysctl(mib, 3, buffer, &size, NULL, 0) != 0) { + free(proc->procCwd); + proc->procCwd = NULL; + return; + } + + /* Kernel threads return an empty buffer */ + if (buffer[0] == '\0') { + free(proc->procCwd); + proc->procCwd = NULL; + return; + } + + free_and_xStrdup(&proc->procCwd, buffer); +} + +static void OpenBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) { + Process_updateComm(proc, kproc->p_comm); + + /* + * Like OpenBSD's top(1), we try to fall back to the command name + * (argv[0]) if we fail to construct the full command. + */ + char** arg = kvm_getargv(kd, kproc, 500); + if (arg == NULL || *arg == NULL) { + Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm)); + return; + } + + size_t len = 0; + for (int i = 0; arg[i] != NULL; i++) { + len += strlen(arg[i]) + 1; /* room for arg and trailing space or NUL */ + } + + /* don't use xMalloc here - we want to handle huge argv's gracefully */ + char* s; + if ((s = malloc(len)) == NULL) { + Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm)); + return; + } + + *s = '\0'; + + int start = 0; + int end = 0; + for (int i = 0; arg[i] != NULL; i++) { + size_t n = strlcat(s, arg[i], len); + if (i == 0) { + end = MINIMUM(n, len - 1); + /* check if cmdline ended earlier, e.g 'kdeinit5: Running...' */ + for (int j = end; j > 0; j--) { + if (arg[0][j] == ' ' && arg[0][j - 1] != '\\') { + end = (arg[0][j - 1] == ':') ? (j - 1) : j; + } + } + } + /* the trailing space should get truncated anyway */ + strlcat(s, " ", len); + } + + Process_updateCmdline(proc, s, start, end); + + free(s); +} + +/* + * Taken from OpenBSD's ps(1). + */ +static double getpcpu(const OpenBSDMachine* ohost, const struct kinfo_proc* kp) { + if (ohost->fscale == 0) + return 0.0; + + return 100.0 * (double)kp->p_pctcpu / ohost->fscale; +} + +static void OpenBSDProcessTable_scanProcs(OpenBSDProcessTable* this) { + Machine* host = this->super.super.host; + OpenBSDMachine* ohost = (OpenBSDMachine*) host; + const Settings* settings = host->settings; + const bool hideKernelThreads = settings->hideKernelThreads; + const bool hideUserlandThreads = settings->hideUserlandThreads; + int count = 0; + + const struct kinfo_proc* kprocs = kvm_getprocs(ohost->kd, KERN_PROC_KTHREAD | KERN_PROC_SHOW_THREADS, 0, sizeof(struct kinfo_proc), &count); + + for (int i = 0; i < count; i++) { + const struct kinfo_proc* kproc = &kprocs[i]; + + /* Ignore main threads */ + if (kproc->p_tid != -1) { + Process* containingProcess = ProcessTable_findProcess(&this->super, kproc->p_pid); + if (containingProcess) { + if (((OpenBSDProcess*)containingProcess)->addr == kproc->p_addr) + continue; + + containingProcess->nlwp++; + } + } + + bool preExisting = false; + Process* proc = ProcessTable_getProcess(&this->super, (kproc->p_tid == -1) ? kproc->p_pid : kproc->p_tid, &preExisting, OpenBSDProcess_new); + OpenBSDProcess* op = (OpenBSDProcess*) proc; + + if (!preExisting) { + Process_setParent(proc, kproc->p_ppid); + Process_setThreadGroup(proc, kproc->p_pid); + proc->tpgid = kproc->p_tpgid; + proc->session = kproc->p_sid; + proc->pgrp = kproc->p__pgid; + proc->isKernelThread = proc->pgrp == 0; + proc->isUserlandThread = kproc->p_tid != -1; + proc->starttime_ctime = kproc->p_ustart_sec; + Process_fillStarttimeBuffer(proc); + ProcessTable_add(&this->super, proc); + + OpenBSDProcessTable_updateProcessName(ohost->kd, kproc, proc); + + if (settings->ss->flags & PROCESS_FLAG_CWD) { + OpenBSDProcessTable_updateCwd(kproc, proc); + } + + proc->tty_nr = kproc->p_tdev; + const char* name = ((dev_t)kproc->p_tdev != NODEV) ? devname(kproc->p_tdev, S_IFCHR) : NULL; + if (!name || String_eq(name, "??")) { + free(proc->tty_name); + proc->tty_name = NULL; + } else { + free_and_xStrdup(&proc->tty_name, name); + } + } else { + if (settings->updateProcessNames) { + OpenBSDProcessTable_updateProcessName(ohost->kd, kproc, proc); + } + } + + op->addr = kproc->p_addr; + proc->m_virt = kproc->p_vm_dsize * ohost->pageSizeKB; + proc->m_resident = kproc->p_vm_rssize * ohost->pageSizeKB; + + proc->percent_mem = proc->m_resident / (float)host->totalMem * 100.0F; + proc->percent_cpu = CLAMP(getpcpu(ohost, kproc), 0.0F, host->activeCPUs * 100.0F); + Process_updateCPUFieldWidths(proc->percent_cpu); + + proc->nice = kproc->p_nice - 20; + proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000)); + proc->priority = kproc->p_priority - PZERO; + proc->processor = kproc->p_cpuid; + proc->minflt = kproc->p_uru_minflt; + proc->majflt = kproc->p_uru_majflt; + proc->nlwp = 1; + + if (proc->st_uid != kproc->p_uid) { + proc->st_uid = kproc->p_uid; + proc->user = UsersTable_getRef(host->usersTable, proc->st_uid); + } + + /* Taken from: https://github.com/openbsd/src/blob/6a38af0976a6870911f4b2de19d8da28723a5778/sys/sys/proc.h#L420 */ + switch (kproc->p_stat) { + case SIDL: proc->state = IDLE; break; + case SRUN: proc->state = RUNNABLE; break; + case SSLEEP: proc->state = SLEEPING; break; + case SSTOP: proc->state = STOPPED; break; + case SZOMB: proc->state = ZOMBIE; break; + case SDEAD: proc->state = DEFUNCT; break; + case SONPROC: proc->state = RUNNING; break; + default: proc->state = UNKNOWN; + } + + if (Process_isKernelThread(proc)) { + this->super.kernelThreads++; + } else if (Process_isUserlandThread(proc)) { + this->super.userlandThreads++; + } + + this->super.totalTasks++; + if (proc->state == RUNNING) { + this->super.runningTasks++; + } + + proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc))); + proc->super.updated = true; + } +} + +void ProcessTable_goThroughEntries(ProcessTable* super) { + OpenBSDProcessTable* this = (OpenBSDProcessTable*) super; + + OpenBSDProcessTable_scanProcs(this); +} diff --git a/openbsd/OpenBSDProcessTable.h b/openbsd/OpenBSDProcessTable.h new file mode 100644 index 0000000..f911a10 --- /dev/null +++ b/openbsd/OpenBSDProcessTable.h @@ -0,0 +1,21 @@ +#ifndef HEADER_OpenBSDProcessTable +#define HEADER_OpenBSDProcessTable +/* +htop - OpenBSDProcessTable.h +(C) 2014 Hisham H. Muhammad +(C) 2015 Michael McConville +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include + +#include "ProcessTable.h" + + +typedef struct OpenBSDProcessTable_ { + ProcessTable super; +} OpenBSDProcessTable; + +#endif diff --git a/openbsd/Platform.c b/openbsd/Platform.c new file mode 100644 index 0000000..a8b5d21 --- /dev/null +++ b/openbsd/Platform.c @@ -0,0 +1,400 @@ +/* +htop - openbsd/Platform.c +(C) 2014 Hisham H. Muhammad +(C) 2015 Michael McConville +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "openbsd/Platform.h" + +#include +#include +#include +#include +#include +#include +#include +#include // needs to be included before for 'struct sigaltstack' +#include +#include +#include +#include +#include +#include +#include + +#include "CPUMeter.h" +#include "ClockMeter.h" +#include "DateMeter.h" +#include "DateTimeMeter.h" +#include "FileDescriptorMeter.h" +#include "HostnameMeter.h" +#include "LoadAverageMeter.h" +#include "Macros.h" +#include "MemoryMeter.h" +#include "MemorySwapMeter.h" +#include "Meter.h" +#include "Settings.h" +#include "SignalsPanel.h" +#include "SwapMeter.h" +#include "SysArchMeter.h" +#include "TasksMeter.h" +#include "UptimeMeter.h" +#include "XUtils.h" +#include "openbsd/OpenBSDMachine.h" +#include "openbsd/OpenBSDProcess.h" + + +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, +}; + +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); + +/* + * See /usr/include/sys/signal.h + */ +const SignalItem Platform_signals[] = { + { .name = " 0 Cancel", .number = 0 }, + { .name = " 1 SIGHUP", .number = 1 }, + { .name = " 2 SIGINT", .number = 2 }, + { .name = " 3 SIGQUIT", .number = 3 }, + { .name = " 4 SIGILL", .number = 4 }, + { .name = " 5 SIGTRAP", .number = 5 }, + { .name = " 6 SIGABRT", .number = 6 }, + { .name = " 6 SIGIOT", .number = 6 }, + { .name = " 7 SIGEMT", .number = 7 }, + { .name = " 8 SIGFPE", .number = 8 }, + { .name = " 9 SIGKILL", .number = 9 }, + { .name = "10 SIGBUS", .number = 10 }, + { .name = "11 SIGSEGV", .number = 11 }, + { .name = "12 SIGSYS", .number = 12 }, + { .name = "13 SIGPIPE", .number = 13 }, + { .name = "14 SIGALRM", .number = 14 }, + { .name = "15 SIGTERM", .number = 15 }, + { .name = "16 SIGURG", .number = 16 }, + { .name = "17 SIGSTOP", .number = 17 }, + { .name = "18 SIGTSTP", .number = 18 }, + { .name = "19 SIGCONT", .number = 19 }, + { .name = "20 SIGCHLD", .number = 20 }, + { .name = "21 SIGTTIN", .number = 21 }, + { .name = "22 SIGTTOU", .number = 22 }, + { .name = "23 SIGIO", .number = 23 }, + { .name = "24 SIGXCPU", .number = 24 }, + { .name = "25 SIGXFSZ", .number = 25 }, + { .name = "26 SIGVTALRM", .number = 26 }, + { .name = "27 SIGPROF", .number = 27 }, + { .name = "28 SIGWINCH", .number = 28 }, + { .name = "29 SIGINFO", .number = 29 }, + { .name = "30 SIGUSR1", .number = 30 }, + { .name = "31 SIGUSR2", .number = 31 }, + { .name = "32 SIGTHR", .number = 32 }, +}; + +const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals); + +const MeterClass* const Platform_meterTypes[] = { + &CPUMeter_class, + &ClockMeter_class, + &DateMeter_class, + &DateTimeMeter_class, + &LoadAverageMeter_class, + &LoadMeter_class, + &MemoryMeter_class, + &SwapMeter_class, + &MemorySwapMeter_class, + &TasksMeter_class, + &UptimeMeter_class, + &BatteryMeter_class, + &HostnameMeter_class, + &SysArchMeter_class, + &AllCPUsMeter_class, + &AllCPUs2Meter_class, + &AllCPUs4Meter_class, + &AllCPUs8Meter_class, + &LeftCPUsMeter_class, + &RightCPUsMeter_class, + &LeftCPUs2Meter_class, + &RightCPUs2Meter_class, + &LeftCPUs4Meter_class, + &RightCPUs4Meter_class, + &LeftCPUs8Meter_class, + &RightCPUs8Meter_class, + &FileDescriptorMeter_class, + &BlankMeter_class, + NULL +}; + +bool Platform_init(void) { + /* no platform-specific setup needed */ + return true; +} + +void Platform_done(void) { + /* no platform-specific cleanup needed */ +} + +void Platform_setBindings(Htop_Action* keys) { + /* no platform-specific key bindings */ + (void) keys; +} + +int Platform_getUptime(void) { + struct timeval bootTime, currTime; + const int mib[2] = { CTL_KERN, KERN_BOOTTIME }; + size_t size = sizeof(bootTime); + + int err = sysctl(mib, 2, &bootTime, &size, NULL, 0); + if (err) { + return -1; + } + gettimeofday(&currTime, NULL); + + return (int) difftime(currTime.tv_sec, bootTime.tv_sec); +} + +void Platform_getLoadAverage(double* one, double* five, double* fifteen) { + struct loadavg loadAverage; + const int mib[2] = { CTL_VM, VM_LOADAVG }; + size_t size = sizeof(loadAverage); + + int err = sysctl(mib, 2, &loadAverage, &size, NULL, 0); + if (err) { + *one = 0; + *five = 0; + *fifteen = 0; + return; + } + *one = (double) loadAverage.ldavg[0] / loadAverage.fscale; + *five = (double) loadAverage.ldavg[1] / loadAverage.fscale; + *fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale; +} + +pid_t Platform_getMaxPid(void) { + return 2 * THREAD_PID_OFFSET; +} + +double Platform_setCPUValues(Meter* this, unsigned int cpu) { + const Machine* host = this->host; + const OpenBSDMachine* ohost = (const OpenBSDMachine*) host; + const CPUData* cpuData = &ohost->cpuData[cpu]; + double total; + double totalPercent; + double* v = this->values; + + if (!cpuData->online) { + this->curItems = 0; + return NAN; + } + + total = cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod; + + v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0; + v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0; + if (host->settings->detailedCPUTime) { + v[CPU_METER_KERNEL] = cpuData->sysPeriod / total * 100.0; + v[CPU_METER_IRQ] = cpuData->intrPeriod / total * 100.0; + v[CPU_METER_SOFTIRQ] = 0.0; + v[CPU_METER_STEAL] = 0.0; + v[CPU_METER_GUEST] = 0.0; + v[CPU_METER_IOWAIT] = 0.0; + v[CPU_METER_FREQUENCY] = NAN; + this->curItems = 8; + } else { + v[CPU_METER_KERNEL] = cpuData->sysAllPeriod / total * 100.0; + v[CPU_METER_IRQ] = 0.0; // No steal nor guest on OpenBSD + this->curItems = 4; + } + totalPercent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ]; + + totalPercent = CLAMP(totalPercent, 0.0, 100.0); + + v[CPU_METER_TEMPERATURE] = NAN; + + v[CPU_METER_FREQUENCY] = (ohost->cpuSpeed != -1) ? ohost->cpuSpeed : NAN; + + return totalPercent; +} + +void Platform_setMemoryValues(Meter* this) { + const Machine* host = this->host; + long int usedMem = host->usedMem; + long int buffersMem = host->buffersMem; + long int cachedMem = host->cachedMem; + usedMem -= buffersMem + cachedMem; + this->total = host->totalMem; + this->values[MEMORY_METER_USED] = usedMem; + // this->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm" + // this->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux" + this->values[MEMORY_METER_BUFFERS] = buffersMem; + this->values[MEMORY_METER_CACHE] = cachedMem; + // this->values[MEMORY_METER_AVAILABLE] = "available memory" +} + +void Platform_setSwapValues(Meter* this) { + const Machine* host = this->host; + this->total = host->totalSwap; + this->values[SWAP_METER_USED] = host->usedSwap; + // this->values[SWAP_METER_CACHE] = "pages that are both in swap and RAM, like SwapCached on linux" + // this->values[SWAP_METER_FRONTSWAP] = "pages that are accounted to swap but stored elsewhere, like frontswap on linux" +} + +char* Platform_getProcessEnv(pid_t pid) { + char errbuf[_POSIX2_LINE_MAX]; + char* env; + char** ptr; + int count; + kvm_t* kt; + struct kinfo_proc* kproc; + size_t capacity = 4096, size = 0; + + if ((kt = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf)) == NULL) { + return NULL; + } + + if ((kproc = kvm_getprocs(kt, KERN_PROC_PID, pid, + sizeof(struct kinfo_proc), &count)) == NULL) { + (void) kvm_close(kt); + return NULL; + } + + if ((ptr = kvm_getenvv(kt, kproc, 0)) == NULL) { + (void) kvm_close(kt); + return NULL; + } + + env = xMalloc(capacity); + for (char** p = ptr; *p; p++) { + size_t len = strlen(*p) + 1; + + while (size + len > capacity) { + if (capacity > (SIZE_MAX / 2)) { + free(env); + env = NULL; + goto end; + } + + capacity *= 2; + env = xRealloc(env, capacity); + } + + strlcpy(env + size, *p, len); + size += len; + } + + if (size < 2 || env[size - 1] || env[size - 2]) { + if (size + 2 < capacity) + env = xRealloc(env, capacity + 2); + env[size] = 0; + env[size + 1] = 0; + } + +end: + (void) kvm_close(kt); + return env; +} + +FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) { + (void)pid; + return NULL; +} + +void Platform_getFileDescriptors(double* used, double* max) { + static const int mib_kern_maxfile[] = { CTL_KERN, KERN_MAXFILES }; + int sysctl_maxfile = 0; + size_t size_maxfile = sizeof(int); + if (sysctl(mib_kern_maxfile, ARRAYSIZE(mib_kern_maxfile), &sysctl_maxfile, &size_maxfile, NULL, 0) < 0) { + *max = NAN; + } else if (size_maxfile != sizeof(int) || sysctl_maxfile < 1) { + *max = NAN; + } else { + *max = sysctl_maxfile; + } + + static const int mib_kern_nfiles[] = { CTL_KERN, KERN_NFILES }; + int sysctl_nfiles = 0; + size_t size_nfiles = sizeof(int); + if (sysctl(mib_kern_nfiles, ARRAYSIZE(mib_kern_nfiles), &sysctl_nfiles, &size_nfiles, NULL, 0) < 0) { + *used = NAN; + } else if (size_nfiles != sizeof(int) || sysctl_nfiles < 0) { + *used = NAN; + } else { + *used = sysctl_nfiles; + } +} + +bool Platform_getDiskIO(DiskIOData* data) { + // TODO + (void)data; + return false; +} + +bool Platform_getNetworkIO(NetworkIOData* data) { + // TODO + (void)data; + return false; +} + +static bool findDevice(const char* name, int* mib, struct sensordev* snsrdev, size_t* sdlen) { + for (int devn = 0;; devn++) { + mib[2] = devn; + if (sysctl(mib, 3, snsrdev, sdlen, NULL, 0) == -1) { + if (errno == ENXIO) + continue; + if (errno == ENOENT) + return false; + } + if (String_eq(name, snsrdev->xname)) { + return true; + } + } +} + +void Platform_getBattery(double* percent, ACPresence* isOnAC) { + int mib[] = {CTL_HW, HW_SENSORS, 0, 0, 0}; + struct sensor s; + size_t slen = sizeof(struct sensor); + struct sensordev snsrdev; + size_t sdlen = sizeof(struct sensordev); + + bool found = findDevice("acpibat0", mib, &snsrdev, &sdlen); + + *percent = NAN; + if (found) { + /* last full capacity */ + mib[3] = 7; + mib[4] = 0; + double last_full_capacity = 0; + if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1) + last_full_capacity = s.value; + if (last_full_capacity > 0) { + /* remaining capacity */ + mib[3] = 7; + mib[4] = 3; + if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1) { + double charge = s.value; + *percent = 100 * (charge / last_full_capacity); + if (charge >= last_full_capacity) { + *percent = 100; + } + } + } + } + + found = findDevice("acpiac0", mib, &snsrdev, &sdlen); + + *isOnAC = AC_ERROR; + if (found) { + mib[3] = 9; + mib[4] = 0; + if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1) + *isOnAC = s.value; + } +} diff --git a/openbsd/Platform.h b/openbsd/Platform.h new file mode 100644 index 0000000..339616c --- /dev/null +++ b/openbsd/Platform.h @@ -0,0 +1,132 @@ +#ifndef HEADER_Platform +#define HEADER_Platform +/* +htop - openbsd/Platform.h +(C) 2014 Hisham H. Muhammad +(C) 2015 Michael McConville +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include +#include + +#include "Action.h" +#include "BatteryMeter.h" +#include "DiskIOMeter.h" +#include "Hashtable.h" +#include "Meter.h" +#include "NetworkIOMeter.h" +#include "Process.h" +#include "ProcessLocksScreen.h" +#include "SignalsPanel.h" +#include "CommandLine.h" +#include "generic/gettime.h" +#include "generic/hostname.h" +#include "generic/uname.h" + + +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; + +/* see /usr/include/sys/signal.h */ +extern const SignalItem Platform_signals[]; + +extern const unsigned int Platform_numberOfSignals; + +extern const MeterClass* const Platform_meterTypes[]; + +bool Platform_init(void); + +void Platform_done(void); + +void Platform_setBindings(Htop_Action* keys); + +int Platform_getUptime(void); + +void Platform_getLoadAverage(double* one, double* five, double* fifteen); + +pid_t Platform_getMaxPid(void); + +double Platform_setCPUValues(Meter* this, unsigned int cpu); + +void Platform_setMemoryValues(Meter* this); + +void Platform_setSwapValues(Meter* this); + +char* Platform_getProcessEnv(pid_t pid); + +FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid); + +void Platform_getFileDescriptors(double* used, double* max); + +bool Platform_getDiskIO(DiskIOData* data); + +bool Platform_getNetworkIO(NetworkIOData* data); + +void Platform_getBattery(double* percent, ACPresence* isOnAC); + +static inline void Platform_getHostname(char* buffer, size_t size) { + Generic_hostname(buffer, size); +} + +static inline void Platform_getRelease(char** string) { + *string = Generic_uname(); +} + +#define PLATFORM_LONG_OPTIONS + +static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { } + +static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { + return STATUS_ERROR_EXIT; +} + +static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { + Generic_gettime_realtime(tv, msec); +} + +static inline void Platform_gettime_monotonic(uint64_t* msec) { + Generic_gettime_monotonic(msec); +} + +static inline Hashtable* Platform_dynamicMeters(void) { + return NULL; +} + +static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { } + +static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } + +static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { } + +static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } + +static inline Hashtable* Platform_dynamicColumns(void) { + return NULL; +} + +static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } + +static inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) { + return NULL; +} + +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { + return false; +} + +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + +#endif diff --git a/openbsd/ProcessField.h b/openbsd/ProcessField.h new file mode 100644 index 0000000..b8e8d6b --- /dev/null +++ b/openbsd/ProcessField.h @@ -0,0 +1,15 @@ +#ifndef HEADER_OpenBSDProcessField +#define HEADER_OpenBSDProcessField +/* +htop - openbsd/ProcessField.h +(C) 2020 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + + +#define PLATFORM_PROCESS_FIELDS \ + // End of list + + +#endif /* HEADER_OpenBSDProcessField */ -- cgit v1.2.3