1
0
mirror of https://github.com/Llewellynvdm/conky.git synced 2024-05-28 05:00:45 +00:00
conky/src/darwin.cc
2018-05-13 09:58:03 -04:00

1436 lines
41 KiB
C++

/*
* Conky, a system monitor, based on torsmo
*
* Any original torsmo code is licensed under the BSD license
*
* All code written since the fork of torsmo is licensed under the GPL
*
* Please see COPYING for details
*
* Copyright (c) 2018, npyl <n.pylarinos@hotmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
***********************************************************************************************
*
* darwin.cc
* Nickolas Pylarinos
*
* ~ To mrt and vggol ~
*
***********************************************************************************************
*
* Code for SIP taken from Pike R. Alpha's csrstat tool
*https://github.com/Piker-Alpha/csrstat csrstat version 1.8 ( works for OS up
*to High Sierra )
*
* My patches:
* made csr_get_active_config weak link and added check for finding if it
*is available. patched the _csr_check function to return the bool bit instead.
*/
/*
* Apologies for the code style...
* In my eyes it feels better to have
* different styles at some specific places... :)
*/
#include "conky.h" // for struct info
#include "darwin.h"
#include <AvailabilityMacros.h>
#include <sys/mount.h> // statfs
#include <sys/sysctl.h>
#include <cstdio>
#include <mach/mach_host.h>
#include <mach/mach_init.h>
#include <mach/mach_types.h>
#include <mach/machine.h>
#include <mach/vm_statistics.h>
#include <mach/mach.h> // update_total_processes
#include <dispatch/dispatch.h> // get_top_info
#include <libproc.h> // get_top_info
#include "top.h" // get_top_info
#include <ifaddrs.h> // update_net_stats
#include "net_stat.h" // update_net_stats
#include "darwin_sip.h" // sip status
/* clock_gettime includes */
#ifndef HAVE_CLOCK_GETTIME
#include <errno.h>
#include <mach/clock.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <time.h>
#endif
/* debugging defines */
#define DEBUG_MODE
/* (E)nhanced printf */
#ifdef DEBUG_MODE
#include <cstdarg>
void eprintf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
#else
#define eprintf(...) /* ... */
#endif
#define GETSYSCTL(name, var) getsysctl(name, &(var), sizeof(var))
/*
* used by calc_cpu_each() for get_top_info()
*/
static conky::simple_config_setting<bool> top_cpu_separate("top_cpu_separate",
false, true);
static int getsysctl(const char *name, void *ptr, size_t len) {
size_t nlen = len;
if (sysctlbyname(name, ptr, &nlen, nullptr, 0) == -1) { return -1; }
if (nlen != len && errno == ENOMEM) { return -1; }
return 0;
}
/*
* clock_gettime is not implemented on versions prior to Sierra!
* code taken from
* https://github.com/lorrden/darwin-posix-rt/blob/master/clock_gettime.c
*/
#ifndef HAVE_CLOCK_GETTIME
int clock_gettime(int clock_id, struct timespec *ts) {
mach_timespec_t mts;
static clock_serv_t rt_clock_serv = 0;
static clock_serv_t mono_clock_serv = 0;
switch (clock_id) {
case CLOCK_REALTIME:
if (rt_clock_serv == 0) {
(void)host_get_clock_service(mach_host_self(), CALENDAR_CLOCK,
&rt_clock_serv);
}
(void)clock_get_time(rt_clock_serv, &mts);
ts->tv_sec = mts.tv_sec;
ts->tv_nsec = mts.tv_nsec;
return 0;
case CLOCK_MONOTONIC:
if (mono_clock_serv == 0) {
(void)host_get_clock_service(mach_host_self(), SYSTEM_CLOCK,
&mono_clock_serv);
}
(void)clock_get_time(mono_clock_serv, &mts);
ts->tv_sec = mts.tv_sec;
ts->tv_nsec = mts.tv_nsec;
return 0;
default:
errno = EINVAL;
return -1;
}
}
#endif /* ifndef HAVE_CLOCK_GETTIME */
/*
* helper_update_threads_processes()
*
* Helper function for update_threads() and update_running_threads()
* Uses mach API to get load info ( task_count, thread_count )
*
*/
static void helper_update_threads_processes() {
static host_name_port_t machHost;
static processor_set_name_port_t processorSet = 0;
static bool machStuffInitialised = false;
/* Set up our mach host and default processor set for later calls */
if (!machStuffInitialised) {
machHost = mach_host_self();
processor_set_default(machHost, &processorSet);
/* set this to true so we don't ever initialise stuff again */
machStuffInitialised = true;
}
/* get load info */
struct processor_set_load_info loadInfo {};
mach_msg_type_number_t count = PROCESSOR_SET_LOAD_INFO_COUNT;
kern_return_t err = processor_set_statistics(
processorSet, PROCESSOR_SET_LOAD_INFO,
reinterpret_cast<processor_set_info_t>(&loadInfo), &count);
if (err != KERN_SUCCESS) { return; }
info.procs = loadInfo.task_count;
info.threads = loadInfo.thread_count;
}
/*
* useful info about the cpu used by functions such as update_cpu_usage() and
* get_top_info()
*/
struct cpusample {
uint64_t totalUserTime; /* ticks of CPU in userspace */
uint64_t totalSystemTime; /* ticks of CPU in kernelspace */
uint64_t totalIdleTime; /* ticks in idleness */
uint64_t total; /* delta of current and previous */
uint64_t current_total; /* total CPU ticks of current iteration */
uint64_t previous_total; /* total CPU tick of previous iteration */
};
/*
* Memory sample
*/
typedef struct memorysample {
vm_statistics64_data_t vm_stat; /* general VM information */
uint64_t pages_stolen; /* # of stolen pages */
vm_size_t pagesize; /* pagesize (in bytes) */
boolean_t purgeable_is_valid; /* check if we have data for purgeable memory */
} libtop_tsamp_t;
/*
* get_cpu_sample()
*
* Gets systemTime, userTime and idleTime for CPU
* MenuMeters has been great inspiration for this function
*/
static void get_cpu_sample(struct cpusample *sample) {
host_name_port_t machHost;
natural_t processorCount;
processor_cpu_load_info_t processorTickInfo;
mach_msg_type_number_t processorInfoCount;
struct cpusample *samples = nullptr;
machHost = mach_host_self();
kern_return_t err = host_processor_info(
machHost, PROCESSOR_CPU_LOAD_INFO, &processorCount,
reinterpret_cast<processor_info_array_t *>(&processorTickInfo),
&processorInfoCount);
if (err != KERN_SUCCESS) {
printf("host_statistics: %s\n", mach_error_string(err));
return;
}
/*
* allocate ncpus+1 cpusample structs (one foreach CPU)
* ** samples[0] is overal CPU usage
*/
samples = new struct cpusample[processorCount + 1];
memset(samples, 0, sizeof(cpusample) * (processorCount + 1));
/*
* start from samples[1] because samples[0] is overall CPU usage
*/
for (natural_t i = 1; i < processorCount + 1; i++) {
samples[i].totalSystemTime =
processorTickInfo[i - 1].cpu_ticks[CPU_STATE_SYSTEM],
samples[i].totalUserTime =
processorTickInfo[i - 1].cpu_ticks[CPU_STATE_USER],
samples[i].totalIdleTime =
processorTickInfo[i - 1].cpu_ticks[CPU_STATE_IDLE];
}
/*
* sum up all totals
*/
for (natural_t i = 1; i < processorCount + 1; i++) {
samples[0].totalSystemTime += samples[i].totalSystemTime;
samples[0].totalUserTime += samples[i].totalUserTime;
samples[0].totalIdleTime += samples[i].totalIdleTime;
}
/*
* set the sample pointer
*/
sample->totalSystemTime = samples[0].totalSystemTime;
sample->totalUserTime = samples[0].totalUserTime;
sample->totalIdleTime = samples[0].totalIdleTime;
/*
* Dealloc
*/
vm_deallocate(mach_task_self(), (vm_address_t)processorTickInfo,
static_cast<vm_size_t>(processorInfoCount * sizeof(natural_t)));
delete[] samples;
}
/*
* helper_get_proc_list()
*
* helper function that returns the count of processes
* and provides a list of kinfo_proc structs representing each.
*
* ERRORS: returns -1 if something failed
*
* ATTENTION: Do not forget to free the array once you are done with it,
* it is not freed automatically.
*/
static int helper_get_proc_list(struct kinfo_proc **p) {
int err = 0;
size_t length = 0;
static const int name[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
/* Call sysctl with a nullptr buffer to get proper length */
err = sysctl((int *)name, (sizeof(name) / sizeof(*name)) - 1, nullptr,
&length, nullptr, 0);
if (err != 0) {
perror(nullptr);
return (-1);
}
/* Allocate buffer */
*p = static_cast<kinfo_proc *>(malloc(length));
if (p == nullptr) {
perror(nullptr);
return (-1);
}
/* Get the actual process list */
err = sysctl((int *)name, (sizeof(name) / sizeof(*name)) - 1, *p, &length,
nullptr, 0);
if (err != 0) {
perror(nullptr);
return (-1);
}
int proc_count = length / sizeof(struct kinfo_proc);
return proc_count;
}
/*-------------------------------------------------------------------------------------------------------------------------------------------------------------------
* macOS Swapfiles Logic...
*
* o There is NO separate partition for swap storage ( unlike most Unix-based
*OSes ) --- Instead swap memory is stored in the currently used partition
*inside files
*
* o macOS can use ALL the available space on the used partition
*
* o Swap memory files are called swapfiles, stored inside /private/var/vm/
*
* o Every swapfile has index number eg. swapfile0, swapfile1, ...
*
* o Anyone can change the location of the swapfiles by editing the plist:
* /System/Library/LaunchDaemons/com.apple.dynamic_pager.plist ( Though it seems
*like this is not supported by the dynamic_pager application as can be observed
*from the code:
* https://github.com/practicalswift/osx/blob/master/src/system_cmds/dynamic_pager.tproj/dynamic_pager.c
*) o Every swapfile has size of 1GB
*
*-------------------------------------------------------------------------------------------------------------------------------------------------------------------
*/
static int swapmode(unsigned long *retavail, unsigned long *retfree) {
/*
* COMPATIBILITY: Tiger+
*/
int swapMIB[] = {CTL_VM, 5};
struct xsw_usage swapUsage {};
size_t swapUsageSize = sizeof(swapUsage);
memset(&swapUsage, 0, sizeof(swapUsage));
if (sysctl(swapMIB, 2, &swapUsage, &swapUsageSize, nullptr, 0) == 0) {
*retfree = swapUsage.xsu_avail / 1024;
*retavail = swapUsage.xsu_total / 1024;
} else {
perror("sysctl");
return (-1);
}
return 1;
}
void prepare_update() {
// in freebsd.cc this is empty so leaving it here too!
}
int update_uptime() {
int mib[2] = {CTL_KERN, KERN_BOOTTIME};
struct timeval boottime {};
time_t now;
size_t size = sizeof(boottime);
if ((sysctl(mib, 2, &boottime, &size, nullptr, 0) != -1) &&
(boottime.tv_sec != 0)) {
time(&now);
info.uptime = now - boottime.tv_sec;
} else {
fprintf(stderr, "could not get uptime\n");
info.uptime = 0;
}
return 0;
}
/*
* check_mount
*
* Notes on macOS implementation:
* 1. path mustn't contain a '/' at the end! ( eg. /Volumes/MacOS/ is not
* correct but this is correct: /Volumes/MacOS )
* 2. it works the same as the FreeBSD function
*/
int check_mount(struct text_object *obj) {
int num_mounts = 0;
struct statfs *mounts;
if (obj->data.s == nullptr) { return 0; }
num_mounts = getmntinfo(&mounts, MNT_WAIT);
if (num_mounts < 0) {
NORM_ERR("could not get mounts using getmntinfo");
return 0;
}
for (int i = 0; i < num_mounts; i++) {
if (strcmp(mounts[i].f_mntonname, obj->data.s) == 0) { return 1; }
}
return 0;
}
/*
* required by update_pages_stolen().
* Taken from apple's top.
* The code is intact.
*/
/* This is for <rdar://problem/6410098>. */
static uint64_t round_down_wired(uint64_t value) {
return (value & ~((128 * 1024 * 1024ULL) - 1));
}
/*
* must be called before libtop_tsamp_update_vm_stats()
* to calculate the pages_stolen variable.
* Taken from apple's top.
* The code is intact.
*/
/* This is for <rdar://problem/6410098>. */
static void update_pages_stolen(libtop_tsamp_t *tsamp) {
static int mib_reserved[CTL_MAXNAME];
static int mib_unusable[CTL_MAXNAME];
static int mib_other[CTL_MAXNAME];
static size_t mib_reserved_len = 0;
static size_t mib_unusable_len = 0;
static size_t mib_other_len = 0;
int r;
tsamp->pages_stolen = 0;
/* This can be used for testing: */
// tsamp->pages_stolen = (256 * 1024 * 1024ULL) / tsamp->pagesize;
if (0 == mib_reserved_len) {
mib_reserved_len = CTL_MAXNAME;
r = sysctlnametomib("machdep.memmap.Reserved", mib_reserved,
&mib_reserved_len);
if (-1 == r) {
mib_reserved_len = 0;
return;
}
mib_unusable_len = CTL_MAXNAME;
r = sysctlnametomib("machdep.memmap.Unusable", mib_unusable,
&mib_unusable_len);
if (-1 == r) {
mib_reserved_len = 0;
return;
}
mib_other_len = CTL_MAXNAME;
r = sysctlnametomib("machdep.memmap.Other", mib_other, &mib_other_len);
if (-1 == r) {
mib_reserved_len = 0;
return;
}
}
if (mib_reserved_len > 0 && mib_unusable_len > 0 && mib_other_len > 0) {
uint64_t reserved = 0, unusable = 0, other = 0;
size_t reserved_len;
size_t unusable_len;
size_t other_len;
reserved_len = sizeof(reserved);
unusable_len = sizeof(unusable);
other_len = sizeof(other);
/* These are all declared as QUAD/uint64_t sysctls in the kernel. */
if (-1 == sysctl(mib_reserved, mib_reserved_len, &reserved, &reserved_len,
nullptr, 0)) {
return;
}
if (-1 == sysctl(mib_unusable, mib_unusable_len, &unusable, &unusable_len,
nullptr, 0)) {
return;
}
if (-1 ==
sysctl(mib_other, mib_other_len, &other, &other_len, nullptr, 0)) {
return;
}
if (reserved_len == sizeof(reserved) && unusable_len == sizeof(unusable) &&
other_len == sizeof(other)) {
uint64_t stolen = reserved + unusable + other;
uint64_t mb128 = 128 * 1024 * 1024ULL;
if (stolen >= mb128) {
tsamp->pages_stolen = round_down_wired(stolen) / tsamp->pagesize;
}
}
}
}
/**
* libtop_tsamp_update_vm_stats
*
* taken from apple's top (libtop.c)
* Changes for conky:
* - remove references to p_* and b_* named variables
* - remove reference to seq variable
* - libtop_port replaced with mach_host_self()
*/
static int libtop_tsamp_update_vm_stats(libtop_tsamp_t *tsamp) {
kern_return_t kr;
mach_msg_type_number_t count = sizeof(tsamp->vm_stat) / sizeof(natural_t);
kr = host_statistics64(mach_host_self(), HOST_VM_INFO64,
reinterpret_cast<host_info64_t>(&tsamp->vm_stat),
&count);
if (kr != KERN_SUCCESS) { return kr; }
if (tsamp->pages_stolen > 0) {
tsamp->vm_stat.wire_count += tsamp->pages_stolen;
}
// Check whether we got purgeable memory statistics
tsamp->purgeable_is_valid = static_cast<boolean_t>(
count == (sizeof(tsamp->vm_stat) / sizeof(natural_t)));
if (tsamp->purgeable_is_valid == 0u) {
tsamp->vm_stat.purgeable_count = 0;
tsamp->vm_stat.purges = 0;
}
return kr;
}
/*
* helper function for update_meminfo()
* return physical memory in bytes
*/
uint64_t get_physical_memory() {
int mib[2] = {CTL_HW, HW_MEMSIZE};
int64_t physical_memory = 0;
size_t length = sizeof(int64_t);
if (sysctl(mib, 2, &physical_memory, &length, nullptr, 0) == -1) {
physical_memory = 0;
}
return physical_memory;
}
int update_meminfo() {
/* XXX implement remaining memory-related variables (see conky.h) */
/* XXX conky breaks the values ... :( probably some rounding problem...
Though we get the right values (based on top) */
/* XXX probably investigate the "probably apple keeps some info secret" */
vm_size_t page_size = getpagesize(); // get pagesize in bytes
unsigned long swap_avail, swap_free;
static libtop_tsamp_t *tsamp = nullptr;
if (tsamp == nullptr) {
tsamp = new libtop_tsamp_t;
if (tsamp == nullptr) { return 0; }
memset(tsamp, 0, sizeof(libtop_tsamp_t));
tsamp->pagesize = page_size;
}
/* get physical memory */
uint64_t physical_memory = get_physical_memory() / 1024;
info.memmax = physical_memory;
/*
* get general memory stats
* but first update pages stolen count
*/
update_pages_stolen(tsamp);
if (libtop_tsamp_update_vm_stats(tsamp) == KERN_FAILURE) { return 0; }
/*
* This is actually a tricky part.
*
* MenuMeters, Activity Monitor and top show different values.
* Our code uses top's implementation because:
* - it is apple's tool
* - professional projects such as osquery follow it
* - Activity Monitor seems to be hiding the whole truth (e.g. for being user
* friendly)
*
* STEPS:
* - get stolen pages count
* Occassionaly host_statistics64 doesn't return correct values (see
* https://stackoverflow.com/questions/14789672/why-does-host-statistics64-return-inconsistent-results)
* We need to get the count of stolen pages and add it to wired pages count.
* This is a known bug and apple has implemented the function
* update_pages_stolen().
*
* - use vm_stat.free_count instead of the sum of wired, active and inactive
* Based on
* https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process
* summing up wired, active and inactive is what we should do BUT, based on
* top, this is incorrect. Seems like apple keeps some info "secret"!
*/
info.mem = physical_memory - (tsamp->vm_stat.free_count * page_size / 1024);
/* rest memory related variables */
info.memwithbuffers = info.mem;
info.memeasyfree = info.memfree = info.memmax - info.mem;
if ((swapmode(&swap_avail, &swap_free)) >= 0) {
info.swapmax = swap_avail;
info.swap = (swap_avail - swap_free);
info.swapfree = swap_free;
} else {
info.swapmax = 0;
info.swap = 0;
info.swapfree = 0;
}
return 0;
}
int update_net_stats() {
struct net_stat *ns;
double delta;
long long r, t, last_recv, last_trans;
struct ifaddrs *ifap, *ifa;
struct if_data *ifd;
/* get delta */
delta = current_update_time - last_update_time;
if (delta <= 0.0001) { return 0; }
if (getifaddrs(&ifap) < 0) { return 0; }
for (ifa = ifap; ifa != nullptr; ifa = ifa->ifa_next) {
ns = get_net_stat((const char *)ifa->ifa_name, nullptr, nullptr);
if ((ifa->ifa_flags & IFF_UP) != 0u) {
struct ifaddrs *iftmp;
ns->up = 1;
last_recv = ns->recv;
last_trans = ns->trans;
if (ifa->ifa_addr->sa_family != AF_LINK) { continue; }
for (iftmp = ifa->ifa_next;
iftmp != nullptr && strcmp(ifa->ifa_name, iftmp->ifa_name) == 0;
iftmp = iftmp->ifa_next) {
if (iftmp->ifa_addr->sa_family == AF_INET) {
memcpy(&(ns->addr), iftmp->ifa_addr, iftmp->ifa_addr->sa_len);
}
}
ifd = static_cast<struct if_data *>(ifa->ifa_data);
r = ifd->ifi_ibytes;
t = ifd->ifi_obytes;
if (r < ns->last_read_recv) {
ns->recv +=
(static_cast<long long>(4294967295U) - ns->last_read_recv) + r;
} else {
ns->recv += (r - ns->last_read_recv);
}
ns->last_read_recv = r;
if (t < ns->last_read_trans) {
ns->trans +=
(static_cast<long long>(4294967295U) - ns->last_read_trans) + t;
} else {
ns->trans += (t - ns->last_read_trans);
}
ns->last_read_trans = t;
/* calculate speeds */
ns->recv_speed = (ns->recv - last_recv) / delta;
ns->trans_speed = (ns->trans - last_trans) / delta;
} else {
ns->up = 0;
}
}
freeifaddrs(ifap);
return 0;
}
int update_threads() {
helper_update_threads_processes();
return 0;
}
/*
* XXX This seems to be wrong... We must first find the threads (using
* thread_info() )
*
* Get list of all processes.
* Foreach process check its state.
* If it is RUNNING it means that it actually has some threads
* that are in TH_STATE_RUNNING.
* Foreach pid and foreach pid's threads check their state and increment
* the run_threads counter acordingly.
*/
int update_running_threads() {
struct kinfo_proc *p = nullptr;
int proc_count = 0;
int run_threads = 0;
proc_count = helper_get_proc_list(&p);
if (proc_count == -1) { return 0; }
for (int i = 0; i < proc_count; i++) {
if ((p[i].kp_proc.p_stat & SRUN) != 0) {
pid_t pid = 0;
struct proc_taskinfo pti {};
struct proc_threadinfo pthi {};
int num_threads = 0;
pid = p[i].kp_proc.p_pid;
/* get total number of threads this pid has */
if (sizeof(pti) ==
proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) {
num_threads = pti.pti_threadnum;
} else {
continue;
}
/* foreach thread check its state */
for (int i = 0; i < num_threads; i++) {
if (sizeof(pthi) ==
proc_pidinfo(pid, PROC_PIDTHREADINFO, i, &pthi, sizeof(pthi))) {
if (pthi.pth_run_state == TH_STATE_RUNNING) { run_threads++; }
} else {
continue;
}
}
}
}
free(p);
info.run_threads = run_threads;
return 0;
}
int update_total_processes() {
helper_update_threads_processes();
/*
* WARNING: You may stumble upon this implementation:
* https://stackoverflow.com/questions/8141913/is-there-a-lightweight-way-to-obtain-the-current-number-of-processes-in-linux
*
* This method DOESN'T find the correct number of tasks.
*
* This is probably (??) because on macOS there is no option for
* KERN_PROC_KTHREAD like there is in FreeBSD
*
* In FreeBSD's sysctl.h we can see the following:
*
* KERN_PROC_KTHREAD all processes (user-level plus kernel threads)
* KERN_PROC_ALL all user-level processes
* KERN_PROC_PID processes with process ID arg
* KERN_PROC_PGRP processes with process group arg
* KERN_PROC_SESSION processes with session arg
* KERN_PROC_TTY processes with tty(4) arg
* KERN_PROC_UID processes with effective user ID arg
* KERN_PROC_RUID processes with real user ID arg
*
* Though in macOS's sysctl.h there are only:
*
* KERN_PROC_ALL everything
* KERN_PROC_PID by process id
* KERN_PROC_PGRP by process group id
* KERN_PROC_SESSION by session of pid
* KERN_PROC_TTY by controlling tty
* KERN_PROC_UID by effective uid
* KERN_PROC_RUID by real uid
* KERN_PROC_LCID by login context id
*
* Probably by saying "everything" they mean that KERN_PROC_ALL gives all
* processes (user-level plus kernel threads) ( So basically this is the
* problem with the old implementation )
*/
}
int update_running_processes() {
struct kinfo_proc *p = nullptr;
int proc_count = 0;
int run_procs = 0;
proc_count = helper_get_proc_list(&p);
if (proc_count == -1) { return 0; }
for (int i = 0; i < proc_count; i++) {
int state = p[i].kp_proc.p_stat;
if (state == SRUN) { // XXX this check needs to be fixed...
run_procs++;
}
}
free(p);
info.run_procs = run_procs;
return 0;
}
/*
* get_cpu_count
*
* The macOS implementation gets the number of active cpus
* in compliance with linux implementation.
*/
void get_cpu_count() {
int cpu_count = 0;
if (GETSYSCTL("hw.activecpu", cpu_count) == 0) {
info.cpu_count = cpu_count;
} else {
fprintf(stderr, "Cannot get hw.activecpu\n");
info.cpu_count = 0;
}
/* XXX this can be moved to update_cpu_usage() but keep here to follow linux
* implementation */
if (info.cpu_usage == nullptr) {
/*
* Allocate ncpus+1 slots because cpu_usage[0] is overall usage.
*/
info.cpu_usage =
static_cast<float *>(malloc((info.cpu_count + 1) * sizeof(float)));
if (info.cpu_usage == nullptr) { CRIT_ERR(nullptr, nullptr, "malloc"); }
}
}
/*
* used by update_cpu_usage()
*/
struct cpu_info {
long oldtotal;
long oldused;
};
int update_cpu_usage() {
/* XXX add support for multiple cpus (see linux.cc) */
static bool cpu_setup = 0;
long used, total;
static struct cpu_info *cpu = nullptr;
unsigned int malloc_cpu_size = 0;
extern void *global_cpu;
struct cpusample sample {};
static pthread_mutex_t last_stat_update_mutex = PTHREAD_MUTEX_INITIALIZER;
static double last_stat_update = 0.0;
/* since we use wrappers for this function, the update machinery
* can't eliminate double invocations of this function. Check for
* them here, otherwise cpu_usage counters are freaking out. */
pthread_mutex_lock(&last_stat_update_mutex);
if (last_stat_update == current_update_time) {
pthread_mutex_unlock(&last_stat_update_mutex);
return 0;
}
last_stat_update = current_update_time;
pthread_mutex_unlock(&last_stat_update_mutex);
/* add check for !info.cpu_usage since that mem is freed on a SIGUSR1 */
if ((static_cast<int>(cpu_setup) == 0) || (info.cpu_usage == nullptr)) {
get_cpu_count();
cpu_setup = 1;
}
if (global_cpu == nullptr) {
/*
* Allocate ncpus+1 slots because cpu_usage[0] is overall usage.
*/
malloc_cpu_size = (info.cpu_count + 1) * sizeof(struct cpu_info);
cpu = static_cast<cpu_info *>(malloc(malloc_cpu_size));
memset(cpu, 0, malloc_cpu_size);
global_cpu = cpu;
}
get_cpu_sample(&sample);
total = sample.totalUserTime + sample.totalIdleTime + sample.totalSystemTime;
used = total - sample.totalIdleTime;
if ((total - cpu[0].oldtotal) != 0) {
info.cpu_usage[0] = (static_cast<double>(used - cpu[0].oldused)) /
static_cast<double>(total - cpu[0].oldtotal);
} else {
info.cpu_usage[0] = 0;
}
cpu[0].oldused = used;
cpu[0].oldtotal = total;
return 0;
}
int update_load_average() {
double v[3];
getloadavg(v, 3);
info.loadavg[0] = v[0];
info.loadavg[1] = v[1];
info.loadavg[2] = v[2];
return 0;
}
double get_acpi_temperature(int /*fd*/) {
printf("get_acpi_temperature: STUB\n");
return 0.0;
}
void get_battery_stuff(char * /*buf*/, unsigned int /*n*/, const char * /*bat*/,
int /*item*/) {
printf("get_battery_stuff: STUB\n");
}
int get_battery_perct(const char * /*bat*/) {
printf("get_battery_perct: STUB\n");
return 1;
}
double get_battery_perct_bar(struct text_object * /*obj*/) {
printf("get_battery_perct_bar: STUB\n");
return 0.0;
}
int open_acpi_temperature(const char *name) {
printf("open_acpi_temperature: STUB\n");
(void)name;
/* Not applicable for FreeBSD. */
return 0;
}
void get_acpi_ac_adapter(char * /*p_client_buffer*/,
size_t /*client_buffer_size*/,
const char * /*adapter*/) {
printf("get_acpi_ac_adapter: STUB\n");
}
void get_acpi_fan(char * /*p_client_buffer*/, size_t /*client_buffer_size*/) {
printf("get_acpi_fan: STUB\n");
}
/* void */
char get_freq(char *p_client_buffer, size_t client_buffer_size,
const char *p_format, int divisor, unsigned int /*cpu*/) {
/*
* For now, we get the factory cpu frequency, not **current** cpu frequency
* (Also, it is always the same for every core, so ignore |cpu| argument)
*/
// XXX Probably find a way to get **current** cpu frequency
int mib[2];
unsigned int freq;
size_t len;
if ((p_client_buffer == nullptr) || client_buffer_size <= 0 ||
(p_format == nullptr) || divisor <= 0) {
return 0;
}
mib[0] = CTL_HW;
mib[1] = HW_CPU_FREQ;
len = sizeof(freq);
if (sysctl(mib, 2, &freq, &len, nullptr, 0) == 0) {
/*
* convert to MHz
*/
divisor *= 1000000;
snprintf(p_client_buffer, client_buffer_size, p_format,
static_cast<float>(freq) / divisor);
} else {
snprintf(p_client_buffer, client_buffer_size, p_format, 0.0f);
return 0;
}
return 1;
}
#if 0
void update_wifi_stats(void)
{
printf("update_wifi_stats: STUB but also in #if 0\n");
}
#endif
int update_diskio() {
printf("update_diskio: STUB\n");
return 0;
}
void get_battery_short_status(char * /*buffer*/, unsigned int /*n*/,
const char * /*bat*/) {
printf("get_battery_short_status: STUB\n");
}
int get_entropy_avail(const unsigned int *val) {
(void)val;
return 1;
}
int get_entropy_poolsize(const unsigned int *val) {
(void)val;
return 1;
}
/******************************************
* get top info section *
******************************************/
/*
* Calculate a process' cpu usage percentage
*/
static void calc_cpu_usage_for_proc(struct process *proc, uint64_t total) {
float mul = 100.0;
if (top_cpu_separate.get(*state)) { mul *= info.cpu_count; }
proc->amount =
mul * (proc->user_time + proc->kernel_time) / static_cast<float>(total);
}
/*
* calculate total CPU usage based on total CPU usage
* of previous iteration stored inside |process| struct
*/
static void calc_cpu_total(struct process *proc, uint64_t *total) {
uint64_t current_total = 0; /* of current iteration */
// uint64_t total = 0; /* delta */
struct cpusample sample {};
get_cpu_sample(&sample);
current_total =
sample.totalUserTime + sample.totalIdleTime + sample.totalSystemTime;
*total = current_total - proc->previous_total_cpu_time;
proc->previous_total_cpu_time = current_total;
*total = ((*total / sysconf(_SC_CLK_TCK)) * 100) / info.cpu_count;
}
/*
* calc_cpu_time_for_proc
*
* calculates user_time and kernel_time and sets the contents of the |process|
* struct excessively based on process_parse_stat() from linux.cc
*/
static void calc_cpu_time_for_proc(struct process *process,
const struct proc_taskinfo *pti) {
unsigned long long user_time = 0;
unsigned long long kernel_time = 0;
process->user_time = pti->pti_total_user;
process->kernel_time = pti->pti_total_system;
/* user_time and kernel_time are in nanoseconds, total_cpu_time in
* centiseconds. Therefore we divide by 10^7 . */
process->user_time /= 10000000;
process->kernel_time /= 10000000;
process->total_cpu_time = process->user_time + process->kernel_time;
if (process->previous_user_time == ULONG_MAX) {
process->previous_user_time = process->user_time;
}
if (process->previous_kernel_time == ULONG_MAX) {
process->previous_kernel_time = process->kernel_time;
}
/* store the difference of the user_time */
user_time = process->user_time - process->previous_user_time;
kernel_time = process->kernel_time - process->previous_kernel_time;
/* backup the process->user_time for next time around */
process->previous_user_time = process->user_time;
process->previous_kernel_time = process->kernel_time;
/* store only the difference of the user_time here... */
process->user_time = user_time;
process->kernel_time = kernel_time;
}
/*
* finds top-information only for one process which is represented by a
* kinfo_proc struct this function is called mutliple types ( one foreach
* process ) to implement get_top_info()
*/
static void get_top_info_for_kinfo_proc(struct kinfo_proc *p) {
struct process *proc = nullptr;
struct proc_taskinfo pti {};
pid_t pid;
pid = p->kp_proc.p_pid;
proc = get_process(pid);
free_and_zero(proc->name);
free_and_zero(proc->basename);
proc->name = strndup(p->kp_proc.p_comm, text_buffer_size.get(*state));
proc->basename = strndup(p->kp_proc.p_comm, text_buffer_size.get(*state));
proc->uid = p->kp_eproc.e_pcred.p_ruid;
proc->time_stamp = g_time;
if (sizeof(pti) ==
proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) {
/* vsize, rss are in bytes thus we dont have to convert */
proc->vsize = pti.pti_virtual_size;
proc->rss = pti.pti_resident_size;
__block uint64_t t = 0;
__block bool calc_cpu_total_finished = false;
__block bool calc_proc_total_finished = false;
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/* calc CPU time for process */
calc_cpu_time_for_proc(proc, &pti);
calc_proc_total_finished = true;
});
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/* calc total CPU time (considering current process) */
calc_cpu_total(proc, &t);
calc_cpu_total_finished = true;
});
/*
* wait until done
*/
while (!(calc_cpu_total_finished && calc_proc_total_finished)) { ; }
/* calc the amount(%) of CPU the process used */
calc_cpu_usage_for_proc(proc, t);
}
}
/* While topless is obviously better, top is also not bad. */
void get_top_info() {
int proc_count = 0;
struct kinfo_proc *p = nullptr;
/*
* QUICKFIX for #16
* XXX if we run conky -t '${top_mem mem 1}' it will crash because
* info.cpu_count is not initialised.
*
* We can initialise it down here, but it seems like in the linux
* implementation of get_top_info() there is no call to the get_cpu_count()
* function. Neither is there in core.cc... If this is the case, when is
* info.cpu_count initialised???
*
* Find a proper better place for get_cpu_count() call. (for comformance with
* linux.cc)
*/
get_cpu_count();
/*
* get processes count
* and create the processes list
*/
proc_count = helper_get_proc_list(&p);
if (proc_count == -1) { return; }
/*
* get top info for-each process
*/
for (int i = 0; i < proc_count; i++) {
if ((((p[i].kp_proc.p_flag & P_SYSTEM)) == 0) &&
*p[i].kp_proc.p_comm != '\0') {
get_top_info_for_kinfo_proc(&p[i]);
}
}
free(p);
}
/*********************************************************************************************
* System Integrity Protection *
*********************************************************************************************/
#if (MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9)
/*
* Check if a flag is enabled based on the csr_config variable
* Also, flip the result on occasion
*/
bool _csr_check(int aMask, bool aFlipflag) {
bool bit = (info.csr_config & aMask) != 0u;
if (aFlipflag) { return !bit; }
return bit;
}
/*
* Extract info from the csr_config variable and set the flags struct
*/
void fill_csr_config_flags_struct() {
info.csr_config_flags.csr_allow_apple_internal =
_csr_check(CSR_ALLOW_APPLE_INTERNAL, 0);
info.csr_config_flags.csr_allow_untrusted_kexts =
_csr_check(CSR_ALLOW_UNTRUSTED_KEXTS, 1);
info.csr_config_flags.csr_allow_task_for_pid =
_csr_check(CSR_ALLOW_TASK_FOR_PID, 1);
info.csr_config_flags.csr_allow_unrestricted_fs =
_csr_check(CSR_ALLOW_UNRESTRICTED_FS, 1);
info.csr_config_flags.csr_allow_kernel_debugger =
_csr_check(CSR_ALLOW_KERNEL_DEBUGGER, 1);
info.csr_config_flags.csr_allow_unrestricted_dtrace =
_csr_check(CSR_ALLOW_UNRESTRICTED_DTRACE, 1);
info.csr_config_flags.csr_allow_unrestricted_nvram =
_csr_check(CSR_ALLOW_UNRESTRICTED_NVRAM, 1);
info.csr_config_flags.csr_allow_device_configuration =
_csr_check(CSR_ALLOW_DEVICE_CONFIGURATION, 0);
info.csr_config_flags.csr_allow_any_recovery_os =
_csr_check(CSR_ALLOW_ANY_RECOVERY_OS, 1);
info.csr_config_flags.csr_allow_user_approved_kexts =
_csr_check(CSR_ALLOW_UNAPPROVED_KEXTS, 1);
}
/*
* Get SIP configuration ( sets csr_config and csr_config_flags )
*/
int get_sip_status() {
if (csr_get_active_config ==
nullptr) /* check if weakly linked symbol exists */
{
NORM_ERR("$sip_status will not work on this version of macOS\n");
return 0;
}
csr_get_active_config(&info.csr_config);
fill_csr_config_flags_struct();
return 0;
}
/*
* Prints SIP status or a specific SIP feature status depending on the argument
* passed to $sip_status command
*
* Variables that can be passed to $sip_status command
*
* nothing --> print enabled / disabled
* 0 --> allow_apple_internal
* 1 --> allow_untrusted_kexts
* 2 --> allow_task_for_pid
* 3 --> allow_unrestricted_fs
* 4 --> allow_kernel_debugger
* 5 --> allow_unrestricted_dtrace
* 6 --> allow_unrestricted_nvram
* 7 --> allow_device_configuration
* 8 --> allow_any_recovery_os
* 9 --> allow_user_approved_kexts
* a --> check if unsupported configuration ---> this is not an apple SIP
* flag. This is for us.
*
* The print function is designed to show 'YES' if a specific protection
* measure is ENABLED. For example, if SIP is configured to disallow untrusted
* kexts, then our function will print 'YES'. Thus, it doesnt print 'YES' in the
* case SIP allows untrusted kexts.
*
* For this reason, your conkyrc should say for example: Untrusted Kexts
* Protection: ${sip_status 1} You should not write: "Allow Untrusted Kexts",
* this is wrong.
*/
void print_sip_status(struct text_object *obj, char *p, int p_max_size) {
if (csr_get_active_config ==
nullptr) /* check if weakly linked symbol exists */
{
snprintf(p, p_max_size, "%s", "unsupported");
NORM_ERR("$sip_status will not work on this version of macOS\n");
return;
}
/* conky window output */
(void)obj;
if (obj->data.s == nullptr) { return; }
if (strlen(obj->data.s) == 0) {
snprintf(p, p_max_size, "%s",
(info.csr_config == CSR_VALID_FLAGS) ? "disabled" : "enabled");
} else if (strlen(obj->data.s) == 1) {
switch (obj->data.s[0]) {
case '0':
snprintf(p, p_max_size, "%s",
info.csr_config_flags.csr_allow_apple_internal ? "YES" : "NO");
break;
case '1':
snprintf(
p, p_max_size, "%s",
info.csr_config_flags.csr_allow_untrusted_kexts ? "YES" : "NO");
break;
case '2':
snprintf(p, p_max_size, "%s",
info.csr_config_flags.csr_allow_task_for_pid ? "YES" : "NO");
break;
case '3':
snprintf(
p, p_max_size, "%s",
info.csr_config_flags.csr_allow_unrestricted_fs ? "YES" : "NO");
break;
case '4':
snprintf(
p, p_max_size, "%s",
info.csr_config_flags.csr_allow_kernel_debugger ? "YES" : "NO");
break;
case '5':
snprintf(
p, p_max_size, "%s",
info.csr_config_flags.csr_allow_unrestricted_dtrace ? "YES" : "NO");
break;
case '6':
snprintf(
p, p_max_size, "%s",
info.csr_config_flags.csr_allow_unrestricted_nvram ? "YES" : "NO");
break;
case '7':
snprintf(p, p_max_size, "%s",
info.csr_config_flags.csr_allow_device_configuration ? "YES"
: "NO");
break;
case '8':
snprintf(
p, p_max_size, "%s",
info.csr_config_flags.csr_allow_any_recovery_os ? "YES" : "NO");
break;
case '9':
snprintf(
p, p_max_size, "%s",
info.csr_config_flags.csr_allow_user_approved_kexts ? "YES" : "NO");
break;
case 'a':
snprintf(p, p_max_size, "%s",
((info.csr_config != 0u) &&
(info.csr_config != CSR_ALLOW_APPLE_INTERNAL))
? "unsupported configuration, beware!"
: "configuration is ok");
break;
default:
snprintf(p, p_max_size, "%s", "unsupported");
NORM_ERR(
"print_sip_status: unsupported argument passed to $sip_status");
break;
}
} else { /* bad argument */
snprintf(p, p_max_size, "%s", "unsupported");
NORM_ERR("print_sip_status: unsupported argument passed to $sip_status");
}
}
#else /* Mavericks and before */
/*
* Versions prior to Yosemite DONT EVEN DEFINE csr_get_active_config()
* function. Thus we must avoid calling this function!
*/
int get_sip_status(void) {
/* Does not do anything intentionally */
return 0;
}
void print_sip_status(struct text_object *obj, char *p, int p_max_size) {
/* conky window output */
(void)obj;
if (!obj->data.s) return;
if (strlen(obj->data.s) == 0) {
snprintf(p, p_max_size, "%s", "error unsupported");
} else if (strlen(obj->data.s) == 1) {
switch (obj->data.s[0]) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'a':
snprintf(p, p_max_size, "%s", "error unsupported");
break;
default:
snprintf(p, p_max_size, "%s", "unsupported");
NORM_ERR(
"print_sip_status: unsupported argument passed to $sip_status");
break;
}
} else { /* bad argument */
snprintf(p, p_max_size, "%s", "unsupported");
NORM_ERR("print_sip_status: unsupported argument passed to $sip_status");
}
}
#endif