1 /*
2  * Copyright (C) Tildeslash Ltd. All rights reserved.
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Affero General Public License version 3.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU Affero General Public License
13  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
14  *
15  * In addition, as a special exception, the copyright holders give
16  * permission to link the code of portions of this program with the
17  * OpenSSL library under certain conditions as described in each
18  * individual source file, and distribute linked combinations
19  * including the two.
20  *
21  * You must obey the GNU Affero General Public License in all respects
22  * for all of the code used other than OpenSSL.
23  */
24 
25 
26 #include "config.h"
27 
28 #ifdef HAVE_ERRNO_H
29 #include <errno.h>
30 #endif
31 
32 #ifdef HAVE_STRING_H
33 #include <string.h>
34 #endif
35 
36 #ifdef HAVE_FCNTL_H
37 #include <fcntl.h>
38 #endif
39 
40 #ifdef HAVE_SYS_SYSCTL_H
41 #include <sys/sysctl.h>
42 #endif
43 
44 #ifdef HAVE_MACH_MACH_H
45 #include <mach/mach.h>
46 #endif
47 
48 #ifdef HAVE_LIBPROC_H
49 #include <libproc.h>
50 #endif
51 
52 #include "monit.h"
53 #include "ProcessTree.h"
54 #include "process_sysdep.h"
55 
56 // libmonit
57 #include "system/Time.h"
58 
59 
60 /**
61  *  System dependent resource data collecting code for MacOS X.
62  *
63  *  @file
64  */
65 
66 
67 #define ARGSSIZE 8192
68 
69 
70 /* ----------------------------------------------------------------- Private */
71 
72 
73 static int  pagesize;
74 static long total_old    = 0;
75 static long cpu_user_old = 0;
76 static long cpu_nice_old = 0;
77 static long cpu_syst_old = 0;
78 
79 
80 /* ------------------------------------------------------------------ Public */
81 
82 
init_process_info_sysdep(void)83 bool init_process_info_sysdep(void) {
84         size_t size = sizeof(systeminfo.cpu.count);
85         if (sysctlbyname("hw.logicalcpu", &systeminfo.cpu.count, &size, NULL, 0) == -1) {
86                 DEBUG("system statistics error -- sysctl hw.logicalcpu failed: %s\n", STRERROR);
87                 return false;
88         }
89 
90         size = sizeof(systeminfo.memory.size);
91         if (sysctlbyname("hw.memsize", &systeminfo.memory.size, &size, NULL, 0) == -1) {
92                 DEBUG("system statistics error -- sysctl hw.memsize failed: %s\n", STRERROR);
93                 return false;
94         }
95 
96         size = sizeof(pagesize);
97         if (sysctlbyname("hw.pagesize", &pagesize, &size, NULL, 0) == -1) {
98                 DEBUG("system statistics error -- sysctl hw.pagesize failed: %s\n", STRERROR);
99                 return false;
100         }
101 
102         size = sizeof(systeminfo.argmax);
103         if (sysctlbyname("kern.argmax", &systeminfo.argmax, &size, NULL, 0) == -1) {
104                 DEBUG("system statistics error -- sysctl kern.argmax failed: %s\n", STRERROR);
105                 return false;
106         }
107 
108         struct timeval booted;
109         size = sizeof(booted);
110         if (sysctlbyname("kern.boottime", &booted, &size, NULL, 0) == -1) {
111                 DEBUG("system statistics error -- sysctl kern.boottime failed: %s\n", STRERROR);
112                 return false;
113         } else {
114                 systeminfo.booted = booted.tv_sec;
115         }
116 
117         return true;
118 }
119 
120 
121 /**
122  * Read all processes to initialize the information tree.
123  * @param reference reference of ProcessTree
124  * @param pflags Process engine flags
125  * @return treesize > 0 if succeeded otherwise 0
126  */
initprocesstree_sysdep(ProcessTree_T ** reference,ProcessEngine_Flags pflags)127 int initprocesstree_sysdep(ProcessTree_T **reference, ProcessEngine_Flags pflags) {
128         size_t pinfo_size = 0;
129         int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
130         if (sysctl(mib, 4, NULL, &pinfo_size, NULL, 0) < 0) {
131                 Log_error("system statistic error -- sysctl failed: %s\n", STRERROR);
132                 return 0;
133         }
134         struct kinfo_proc *pinfo = CALLOC(1, pinfo_size);
135         if (sysctl(mib, 4, pinfo, &pinfo_size, NULL, 0)) {
136                 FREE(pinfo);
137                 Log_error("system statistic error -- sysctl failed: %s\n", STRERROR);
138                 return 0;
139         }
140         size_t treesize = pinfo_size / sizeof(struct kinfo_proc);
141         ProcessTree_T *pt = CALLOC(sizeof(ProcessTree_T), treesize);
142 
143         char *args = NULL;
144         StringBuffer_T cmdline = NULL;
145         if (pflags & ProcessEngine_CollectCommandLine) {
146                 cmdline = StringBuffer_create(64);
147                 args = CALLOC(1, systeminfo.argmax + 1);
148         }
149         for (size_t i = 0; i < treesize; i++) {
150                 pt[i].uptime    = systeminfo.time / 10. - pinfo[i].kp_proc.p_starttime.tv_sec;
151                 pt[i].zombie    = pinfo[i].kp_proc.p_stat == SZOMB ? true : false;
152                 pt[i].pid       = pinfo[i].kp_proc.p_pid;
153                 pt[i].ppid      = pinfo[i].kp_eproc.e_ppid;
154                 pt[i].cred.uid  = pinfo[i].kp_eproc.e_pcred.p_ruid;
155                 pt[i].cred.euid = pinfo[i].kp_eproc.e_ucred.cr_uid;
156                 pt[i].cred.gid  = pinfo[i].kp_eproc.e_pcred.p_rgid;
157                 if (pflags & ProcessEngine_CollectCommandLine) {
158                         size_t size = systeminfo.argmax;
159                         mib[0] = CTL_KERN;
160                         mib[1] = KERN_PROCARGS2;
161                         mib[2] = pt[i].pid;
162                         if (sysctl(mib, 3, args, &size, NULL, 0) != -1) {
163                                 /* KERN_PROCARGS2 sysctl() returns following pseudo structure:
164                                  *        struct {
165                                  *                int argc
166                                  *                char execname[];
167                                  *                char argv[argc][];
168                                  *                char env[][];
169                                  *        }
170                                  * The strings are terminated with '\0' and may have variable '\0' padding
171                                  */
172                                 int argc = *args;
173                                 char *p = args + sizeof(int); // arguments beginning
174                                 StringBuffer_clear(cmdline);
175                                 p += strlen(p); // skip exename
176                                 while (argc && p < args + systeminfo.argmax) {
177                                         if (*p == 0) { // skip terminating 0 and variable length 0 padding
178                                                 p++;
179                                                 continue;
180                                         }
181                                         StringBuffer_append(cmdline, argc-- ? "%s " : "%s", p);
182                                         p += strlen(p);
183                                 }
184                                 if (StringBuffer_length(cmdline))
185                                         pt[i].cmdline = Str_dup(StringBuffer_toString(StringBuffer_trim(cmdline)));
186                         }
187                         if (STR_UNDEF(pt[i].cmdline)) {
188                                 char cmdpath[PROC_PIDPATHINFO_MAXSIZE] = {};
189                                 FREE(pt[i].cmdline);
190                                 if (proc_pidpath(pt[i].pid, cmdpath, sizeof(cmdpath)) > 0) {
191                                         pt[i].cmdline = Str_dup(cmdpath);
192                                 } else {
193                                         pt[i].cmdline = Str_dup(pinfo[i].kp_proc.p_comm);
194                                 }
195                         }
196                 }
197                 if (! pt[i].zombie) {
198                         // CPU, memory, threads
199                         struct proc_taskinfo tinfo;
200                         int rv = proc_pidinfo(pt[i].pid, PROC_PIDTASKINFO, 0, &tinfo, sizeof(tinfo)); // If the process is zombie, skip this
201                         if (rv <= 0) {
202                                 if (errno != EPERM)
203                                         DEBUG("proc_pidinfo for pid %d failed -- %s\n", pt[i].pid, STRERROR);
204                         } else if ((unsigned long)rv < sizeof(tinfo)) {
205                                 Log_error("proc_pidinfo for pid %d -- invalid result size\n", pt[i].pid);
206                         } else {
207                                 pt[i].memory.usage = (unsigned long long)tinfo.pti_resident_size;
208                                 pt[i].cpu.time = (double)(tinfo.pti_total_user + tinfo.pti_total_system) / 100000000.; // The time is in nanoseconds, we store it as 1/10s
209                                 pt[i].threads.self = tinfo.pti_threadnum;
210                         }
211 #ifdef rusage_info_current
212                         // Disk IO
213                         rusage_info_current rusage;
214                         if (proc_pid_rusage(pt[i].pid, RUSAGE_INFO_CURRENT, (rusage_info_t *)&rusage) < 0) {
215                                 if (errno != EPERM)
216                                         DEBUG("proc_pid_rusage for pid %d failed -- %s\n", pt[i].pid, STRERROR);
217                         } else {
218                                 pt[i].read.time = pt[i].write.time = Time_milli();
219                                 pt[i].read.bytes = -1;
220                                 pt[i].read.bytesPhysical = rusage.ri_diskio_bytesread;
221                                 pt[i].read.operations = -1;
222                                 pt[i].write.bytes = -1;
223                                 pt[i].write.bytesPhysical = rusage.ri_diskio_byteswritten;
224                                 pt[i].write.operations = -1;
225                         }
226 #endif
227                 }
228         }
229         if (pflags & ProcessEngine_CollectCommandLine) {
230                 StringBuffer_free(&cmdline);
231                 FREE(args);
232         }
233         FREE(pinfo);
234 
235         *reference = pt;
236 
237         return (int)treesize;
238 }
239 
240 
241 /**
242  * This routine returns 'nelem' double precision floats containing
243  * the load averages in 'loadv'; at most 3 values will be returned.
244  * @param loadv destination of the load averages
245  * @param nelem number of averages
246  * @return: 0 if successful, -1 if failed (and all load averages are 0).
247  */
getloadavg_sysdep(double * loadv,int nelem)248 int getloadavg_sysdep (double *loadv, int nelem) {
249         return getloadavg(loadv, nelem);
250 }
251 
252 
253 /**
254  * This routine returns real memory in use.
255  * @return: true if successful, false if failed (or not available)
256  */
used_system_memory_sysdep(SystemInfo_T * si)257 bool used_system_memory_sysdep(SystemInfo_T *si) {
258         /* Memory */
259         vm_statistics_data_t page_info;
260         mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
261         kern_return_t kret = host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&page_info, &count);
262         if (kret != KERN_SUCCESS) {
263                 DEBUG("system statistic error -- cannot get memory usage\n");
264                 return false;
265         }
266         si->memory.usage.bytes = (unsigned long long)(page_info.wire_count + page_info.active_count) * (unsigned long long)pagesize;
267 
268         /* Swap */
269         int mib[2] = {CTL_VM, VM_SWAPUSAGE};
270         size_t len = sizeof(struct xsw_usage);
271         struct xsw_usage swap;
272         if (sysctl(mib, 2, &swap, &len, NULL, 0) == -1) {
273                 DEBUG("system statistic error -- cannot get swap usage: %s\n", STRERROR);
274                 si->swap.size = 0ULL;
275                 return false;
276         }
277         si->swap.size = (unsigned long long)swap.xsu_total;
278         si->swap.usage.bytes = (unsigned long long)swap.xsu_used;
279 
280         return true;
281 }
282 
283 
284 /**
285  * This routine returns system/user CPU time in use.
286  * @return: true if successful, false if failed
287  */
used_system_cpu_sysdep(SystemInfo_T * si)288 bool used_system_cpu_sysdep(SystemInfo_T *si) {
289         long                      total;
290         long                      total_new = 0;
291         kern_return_t             kret;
292         host_cpu_load_info_data_t cpu_info;
293         mach_msg_type_number_t    count;
294 
295         count = HOST_CPU_LOAD_INFO_COUNT;
296         kret  = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&cpu_info, &count);
297         if (kret == KERN_SUCCESS) {
298                 for (int i = 0; i < CPU_STATE_MAX; i++)
299                         total_new += cpu_info.cpu_ticks[i];
300                 total     = total_new - total_old;
301                 total_old = total_new;
302 
303                 si->cpu.usage.user = (total > 0) ? (100. * (double)(cpu_info.cpu_ticks[CPU_STATE_USER] - cpu_user_old) / total) : -1.;
304                 si->cpu.usage.nice = (total > 0) ? (100. * (double)(cpu_info.cpu_ticks[CPU_STATE_NICE] - cpu_nice_old) / total) : -1.;
305                 si->cpu.usage.system = (total > 0) ? (100. * (double)(cpu_info.cpu_ticks[CPU_STATE_SYSTEM] - cpu_syst_old) / total) : -1.;
306 
307                 cpu_user_old = cpu_info.cpu_ticks[CPU_STATE_USER];
308                 cpu_nice_old = cpu_info.cpu_ticks[CPU_STATE_NICE];
309                 cpu_syst_old = cpu_info.cpu_ticks[CPU_STATE_SYSTEM];
310 
311                 return true;
312         }
313         return false;
314 }
315 
316 
used_system_filedescriptors_sysdep(SystemInfo_T * si)317 bool used_system_filedescriptors_sysdep(__attribute__ ((unused)) SystemInfo_T *si) {
318         // Open files
319         size_t len = sizeof(si->filedescriptors.allocated);
320         if (sysctlbyname("kern.num_files", &si->filedescriptors.allocated, &len, NULL, 0) == -1) {
321                 DEBUG("system statistics error -- sysctl kern.openfiles failed: %s\n", STRERROR);
322                 return false;
323         }
324         // Max files
325         int mib[2] = {CTL_KERN, KERN_MAXFILES};
326         len = sizeof(si->filedescriptors.maximum);
327         if (sysctl(mib, 2, &si->filedescriptors.maximum, &len, NULL, 0) == -1) {
328                 DEBUG("system statistics error -- sysctl kern.maxfiles failed: %s\n", STRERROR);
329                 return false;
330         }
331         return true;
332 }
333 
334 
available_statistics(SystemInfo_T * si)335 bool available_statistics(SystemInfo_T *si) {
336         si->statisticsAvailable = Statistics_CpuUser | Statistics_CpuSystem | Statistics_CpuNice | Statistics_FiledescriptorsPerSystem;
337         return true;
338 }
339 
340