/* * 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-2019, npyl * * 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 . * */ /* *********************************************************************************************** * * 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. */ #include "conky.h" // for struct info #include "darwin.h" #include #include // statfs #include #include #include #include #include #include #include #include // update_total_processes #include // get_top_info #include // get_top_info #include "top.h" // get_top_info #include // update_net_stats #include "net_stat.h" // update_net_stats #include "darwin_sip.h" // sip status #include #ifdef BUILD_IPGFREQ #include #endif #ifdef BUILD_WLAN #import #endif /* clock_gettime includes */ #ifndef HAVE_CLOCK_GETTIME #include #include #include #include #include #endif /* debugging defines */ #define DEBUG_MODE /* (E)nhanced printf */ #ifdef DEBUG_MODE #include 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 top_cpu_separate("top_cpu_separate", false, true); /* * used by update_cpu_usage() */ struct cpusample *sample_handle = nullptr; 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(&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; machHost = mach_host_self(); kern_return_t err = host_processor_info( machHost, PROCESSOR_CPU_LOAD_INFO, &processorCount, reinterpret_cast(&processorTickInfo), &processorInfoCount); if (err != KERN_SUCCESS) { printf("host_statistics: %s\n", mach_error_string(err)); return; } /* * start from samples[1] because samples[0] is overall CPU usage */ for (natural_t i = 1; i < processorCount + 1; i++) { (*sample)[i].totalSystemTime = processorTickInfo[i - 1].cpu_ticks[CPU_STATE_SYSTEM], (*sample)[i].totalUserTime = processorTickInfo[i - 1].cpu_ticks[CPU_STATE_USER], (*sample)[i].totalIdleTime = processorTickInfo[i - 1].cpu_ticks[CPU_STATE_IDLE]; } /* * sum up all totals */ for (natural_t i = 1; i < processorCount + 1; i++) { (*sample)[0].totalSystemTime += (*sample)[i].totalSystemTime; (*sample)[0].totalUserTime += (*sample)[i].totalUserTime; (*sample)[0].totalIdleTime += (*sample)[i].totalIdleTime; } /* * Dealloc */ vm_deallocate(mach_task_self(), (vm_address_t)processorTickInfo, static_cast(processorInfoCount * sizeof(natural_t))); } void allocate_cpu_sample(struct cpusample **sample) { if (*sample != nullptr) return; /* * allocate ncpus+1 cpusample structs (one foreach CPU) * ** sample_handle[0] is overal CPU usage */ *sample = reinterpret_cast(malloc(sizeof(cpusample) * (info.cpu_count + 1))); memset(*sample, 0, sizeof(cpusample) * (info.cpu_count + 1)); sample_handle = *sample; /* use a public handle for deallocating */ } void free_cpu(struct text_object *) { if (sample_handle != nullptr) { free(sample_handle); sample_handle = nullptr; } } /* * 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(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 . */ 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 . */ 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(&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( 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 See #34 */ 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 * Occasionally 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; } #ifdef BUILD_WLAN void update_wlan_stats(struct net_stat *ns) { CWWiFiClient *client = [CWWiFiClient sharedWiFiClient]; CWInterface *interface = [client interfaceWithName:[NSString stringWithUTF8String:ns->dev]]; if (!interface) return; const char *essid = (ns->up) ? [interface ssid].UTF8String : "off/any"; const char *freq = "Unknown"; const char *bitrate = [NSString stringWithFormat:@"%f", [interface transmitRate]].UTF8String; const char *mode = "Unknown"; const char *ap = [interface hardwareAddress].UTF8String; if (essid == nullptr || bitrate == nullptr || ap == nullptr) return; /* * Freq */ switch ([interface wlanChannel].channelBand) { case kCWChannelBand2GHz: freq = "2 GHz"; break; case kCWChannelBand5GHz: freq = "5 GHz"; break; case kCWChannelBandUnknown: default: break; } /* * Mode */ switch ([interface interfaceMode]) { case kCWInterfaceModeStation: mode = "Managed"; break; case kCWInterfaceModeIBSS: mode = "Ad-Hoc"; break; case kCWInterfaceModeHostAP: mode = "Master"; break; case kCWInterfaceModeNone: default: break; } /* * Setup */ memcpy(ns->essid, essid, sizeof(char)*strlen(essid)); ns->channel = interface.wlanChannel.channelNumber; memcpy(ns->freq, freq, sizeof(char)*strlen(freq)); memcpy(ns->bitrate, bitrate, sizeof(char)*strlen(bitrate)); memcpy(ns->mode, mode, sizeof(char)*strlen(mode)); ns->link_qual = 0; ns->link_qual_max = 0; memcpy(ns->ap, ap, sizeof(char)*strlen(ap)); } #endif /* BUILD_WLAN */ 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; #ifdef BUILD_WLAN update_wlan_stats(ns); #endif 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(ifa->ifa_data); r = ifd->ifi_ibytes; t = ifd->ifi_obytes; if (r < ns->last_read_recv) { ns->recv += (static_cast(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(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 accordingly. */ 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(); return 0; /* * 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; } if (info.cpu_usage == nullptr) { /* * Allocate ncpus+1 slots because cpu_usage[0] is overall usage. */ info.cpu_usage = static_cast(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() { static bool cpu_setup = 0; long used, total; static struct cpu_info *cpu = nullptr; unsigned int malloc_cpu_size = 0; extern void *global_cpu; static struct cpusample *sample = nullptr; 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(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(malloc(malloc_cpu_size)); memset(cpu, 0, malloc_cpu_size); global_cpu = cpu; } allocate_cpu_sample(&sample); get_cpu_sample(&sample); /* * Setup conky's structs for-each core */ for (unsigned int i = 1; i < info.cpu_count + 1; i++) { unsigned int j = i - 1; total = sample[i].totalUserTime + sample[i].totalIdleTime + sample[i].totalSystemTime; used = total - sample[i].totalIdleTime; if ((total - cpu[j].oldtotal) != 0) { info.cpu_usage[j] = (static_cast(used - cpu[j].oldused)) / static_cast(total - cpu[j].oldtotal); } else { info.cpu_usage[j] = 0; } cpu[j].oldused = used; cpu[j].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) { (void)cpu; if ((p_client_buffer == nullptr) || client_buffer_size <= 0 || (p_format == nullptr) || divisor <= 0) { return 0; } #ifdef BUILD_IPGFREQ /* * Our data is always the same for every core, so ignore |cpu| argument. */ static bool initialised = false; if (!initialised) { IntelEnergyLibInitialize(); initialised = true; } int freq = 0; GetIAFrequency(cpu, &freq); snprintf(p_client_buffer, client_buffer_size, p_format, static_cast(freq) / divisor); #else /* * We get the factory cpu frequency, not **current** cpu frequency * (Also, it is always the same for every core, so ignore |cpu| argument) * Enable BUILD_IPGFREQ for getting current frequency. */ int mib[2]; unsigned int freq; size_t len; 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(freq) / divisor); } else { snprintf(p_client_buffer, client_buffer_size, p_format, 0.0f); return 0; } #endif return 1; } 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(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 */ struct cpusample *sample = nullptr; allocate_cpu_sample(&sample); get_cpu_sample(&sample); current_total = sample[0].totalUserTime + sample[0].totalIdleTime + sample[0].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 multiple 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)) { usleep(500); } /* 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; /* * See #16 */ 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 --> apple internal * 1 --> forbid untrusted kexts * 2 --> forbid task-for-pid * 3 --> restrict filesystem * 4 --> forbid kernel-debugger * 5 --> restrict dtrace * 6 --> restrict nvram * 7 --> forbid device-configuration * 8 --> forbid any-recovery-os * 9 --> forbid 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'. * * 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, unsigned 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