1 /*
2   Copyright 2021 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 #include <cf3.defs.h>
26 
27 #include <mon.h>
28 #include <dir.h>
29 #include <item_lib.h>
30 #include <files_interfaces.h>
31 #include <pipes.h>
32 
33 /* Globals */
34 
35 static bool ACPI       = false;
36 static bool SYSTHERMAL = false;
37 static bool LMSENSORS  = false;
38 
39 /* Prototypes */
40 
41 #if defined(__linux__)
42 static bool GetAcpi(double *cf_this);
43 static bool GetSysThermal(double *cf_this);
44 static bool GetLMSensors(double *cf_this);
45 #endif
46 
47 /* Implementation */
48 
49 /******************************************************************************
50  * Motherboard sensors - how to standardize this somehow
51  * We're mainly interested in temperature and power consumption, but only the
52  * temperature is generally available. Several temperatures exist too ...
53  ******************************************************************************/
54 
55 #if defined(__linux__)
56 
MonTempGatherData(double * cf_this)57 void MonTempGatherData(double *cf_this)
58 {
59     if (ACPI && GetAcpi(cf_this))
60     {
61         return;
62     }
63 
64     if (SYSTHERMAL && GetSysThermal(cf_this))
65     {
66         return;
67     }
68 
69     if (LMSENSORS && GetLMSensors(cf_this))
70     {
71         return;
72     }
73 }
74 
75 #else
76 
MonTempGatherData(ARG_UNUSED double * cf_this)77 void MonTempGatherData(ARG_UNUSED double *cf_this)
78 {
79 }
80 
81 #endif
82 
83 /******************************************************************************/
84 
MonTempInit(void)85 void MonTempInit(void)
86 {
87     struct stat statbuf;
88 
89     for (int i = 0; i < 4; i++)
90     {
91         char s[128];
92         xsnprintf(s, sizeof(s),
93                   "/sys/devices/virtual/thermal/thermal_zone%d", i);
94 
95         if (stat(s, &statbuf) != -1)
96         {
97             Log(LOG_LEVEL_DEBUG, "Found a thermal device in /sys");
98             SYSTHERMAL = true;
99         }
100     }
101 
102     if (stat("/proc/acpi/thermal_zone", &statbuf) != -1)
103     {
104         Log(LOG_LEVEL_DEBUG, "Found an acpi service");
105         ACPI = true;
106     }
107 
108     if (stat("/usr/bin/sensors", &statbuf) != -1)
109     {
110         if (statbuf.st_mode & 0111)
111         {
112             Log(LOG_LEVEL_DEBUG, "Found an lmsensor system");
113             LMSENSORS = true;
114         }
115     }
116 }
117 
118 /******************************************************************************/
119 
120 #if defined(__linux__)
GetAcpi(double * cf_this)121 static bool GetAcpi(double *cf_this)
122 {
123     Dir *dirh;
124     FILE *fp;
125     const struct dirent *dirp;
126     int count;
127     char path[CF_BUFSIZE], buf[CF_BUFSIZE], index[4];
128     double temp;
129 
130     if ((dirh = DirOpen("/proc/acpi/thermal_zone")) == NULL)
131     {
132         Log(LOG_LEVEL_VERBOSE, "Can't open directory '%s'. (opendir: %s)", path, GetErrorStr());
133         return false;
134     }
135 
136     for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
137     {
138         if (!strcmp(dirp->d_name, ".") || !strcmp(dirp->d_name, ".."))
139         {
140             continue;
141         }
142 
143         snprintf(path, CF_BUFSIZE, "/proc/acpi/thermal_zone/%s/temperature", dirp->d_name);
144 
145         if ((fp = fopen(path, "r")) == NULL)
146         {
147             Log(LOG_LEVEL_ERR, "Couldn't open '%s' to gather temperature data (fopen: %s)", path, GetErrorStr());
148             continue;
149         }
150 
151         if (fgets(buf, sizeof(buf), fp) == NULL)
152         {
153             Log(LOG_LEVEL_ERR, "Failed to read line from stream '%s'", path);
154             fclose(fp);
155             continue;
156         }
157 
158         temp = 0.0;
159         sscanf(buf, "%*s %lf", &temp);
160 
161         for (count = 0; count < 4; count++)
162         {
163             snprintf(index, 2, "%d", count);
164 
165             if (strstr(dirp->d_name, index))
166             {
167                 switch (count)
168                 {
169                 case 0:
170                     cf_this[ob_temp0] = temp;
171                     break;
172                 case 1:
173                     cf_this[ob_temp1] = temp;
174                     break;
175                 case 2:
176                     cf_this[ob_temp2] = temp;
177                     break;
178                 case 3:
179                     cf_this[ob_temp3] = temp;
180                     break;
181                 }
182 
183                 Log(LOG_LEVEL_DEBUG, "Set temp%d to %lf", count, temp);
184             }
185         }
186         fclose(fp);
187     }
188 
189     DirClose(dirh);
190     return true;
191 }
192 
193 /******************************************************************************/
194 
GetSysThermal(double * cf_this)195 static bool GetSysThermal(double *cf_this)
196 {
197     FILE *fp;
198     int count;
199     bool retval = false;
200 
201     for (count = 0; count < 4; count++)
202     {
203         double temp = 0;
204 
205         char path[128];
206         xsnprintf(path, sizeof(path),
207                   "/sys/devices/virtual/thermal/thermal_zone%d/temp", count);
208 
209         if ((fp = fopen(path, "r")) == NULL)
210         {
211             Log(LOG_LEVEL_INFO, "Couldn't open '%s'", path);
212             continue;
213         }
214 
215         char buf[128];
216         if (fgets(buf, sizeof(buf), fp) == NULL)
217         {
218             Log(LOG_LEVEL_INFO, "Failed to read line from stream '%s'", path);
219             fclose(fp);
220             continue;
221         }
222 
223         int ret = sscanf(buf, "%lf", &temp);
224         if (ret == 1)
225         {
226             switch (count)
227             {
228             case 0:
229                 cf_this[ob_temp0] = temp;
230                 break;
231             case 1:
232                 cf_this[ob_temp1] = temp;
233                 break;
234             case 2:
235                 cf_this[ob_temp2] = temp;
236                 break;
237             case 3:
238                 cf_this[ob_temp3] = temp;
239                 break;
240             }
241 
242             Log(LOG_LEVEL_DEBUG, "Set temp%d to %lf", count, temp);
243             retval = true;
244         }
245         else
246         {
247             Log(LOG_LEVEL_INFO, "Failed to read number from: %s", path);
248         }
249 
250         fclose(fp);
251     }
252 
253     return retval;
254 }
255 
256 /******************************************************************************/
257 
GetLMSensors(double * cf_this)258 static bool GetLMSensors(double *cf_this)
259 {
260     FILE *pp;
261     Item *ip, *list = NULL;
262     double temp = 0;
263     char name[CF_BUFSIZE];
264     int count;
265 
266     cf_this[ob_temp0] = 0.0;
267     cf_this[ob_temp1] = 0.0;
268     cf_this[ob_temp2] = 0.0;
269     cf_this[ob_temp3] = 0.0;
270 
271     if ((pp = cf_popen("/usr/bin/sensors", "r", true)) == NULL)
272     {
273         LMSENSORS = false;      /* Broken */
274         return false;
275     }
276 
277     {
278         size_t vbuff_size = CF_BUFSIZE;
279         char *vbuff = xmalloc(vbuff_size);
280 
281         ssize_t res = CfReadLine(&vbuff, &vbuff_size, pp);
282         if (res <= 0)
283         {
284             /* FIXME: do we need to log anything here? */
285             cf_pclose(pp);
286             free(vbuff);
287             return false;
288         }
289 
290         for (;;)
291         {
292             ssize_t res = CfReadLine(&vbuff, &vbuff_size, pp);
293             if (res == -1)
294             {
295                 if (!feof(pp))
296                 {
297                     /* FIXME: Do we need to log anything here? */
298                     cf_pclose(pp);
299                     free(vbuff);
300                     return false;
301                 }
302                 else
303                 {
304                     break;
305                 }
306             }
307 
308             if (strstr(vbuff, "Temp") || strstr(vbuff, "temp"))
309             {
310                 PrependItem(&list, vbuff, NULL);
311             }
312         }
313 
314         cf_pclose(pp);
315         free(vbuff);
316     }
317 
318     if (ListLen(list) > 0)
319     {
320         Log(LOG_LEVEL_DEBUG, "LM Sensors seemed to return ok data");
321     }
322     else
323     {
324         return false;
325     }
326 
327 /* lmsensor names are hopelessly inconsistent - so try a few things */
328 
329     for (ip = list; ip != NULL; ip = ip->next)
330     {
331         for (count = 0; count < 4; count++)
332         {
333             snprintf(name, 16, "CPU%d Temp:", count);
334 
335             if (strncmp(ip->name, name, strlen(name)) == 0)
336             {
337                 sscanf(ip->name, "%*[^:]: %lf", &temp);
338 
339                 switch (count)
340                 {
341                 case 0:
342                     cf_this[ob_temp0] = temp;
343                     break;
344                 case 1:
345                     cf_this[ob_temp1] = temp;
346                     break;
347                 case 2:
348                     cf_this[ob_temp2] = temp;
349                     break;
350                 case 3:
351                     cf_this[ob_temp3] = temp;
352                     break;
353                 }
354 
355                 Log(LOG_LEVEL_DEBUG, "Set temp%d to %lf from what looks like cpu temperature", count, temp);
356             }
357         }
358     }
359 
360     if (cf_this[ob_temp0] != 0)
361     {
362         /* We got something plausible */
363         return true;
364     }
365 
366 /* Alternative name Core x: */
367 
368     for (ip = list; ip != NULL; ip = ip->next)
369     {
370         for (count = 0; count < 4; count++)
371         {
372             snprintf(name, 16, "Core %d:", count);
373 
374             if (strncmp(ip->name, name, strlen(name)) == 0)
375             {
376                 sscanf(ip->name, "%*[^:]: %lf", &temp);
377 
378                 switch (count)
379                 {
380                 case 0:
381                     cf_this[ob_temp0] = temp;
382                     break;
383                 case 1:
384                     cf_this[ob_temp1] = temp;
385                     break;
386                 case 2:
387                     cf_this[ob_temp2] = temp;
388                     break;
389                 case 3:
390                     cf_this[ob_temp3] = temp;
391                     break;
392                 }
393 
394                 Log(LOG_LEVEL_DEBUG, "Set temp%d to %lf from what looks like core temperatures", count, temp);
395             }
396         }
397     }
398 
399     if (cf_this[ob_temp0] != 0)
400     {
401         /* We got something plausible */
402         return true;
403     }
404 
405     for (ip = list; ip != NULL; ip = ip->next)
406     {
407         if (strncmp(ip->name, "CPU Temp:", strlen("CPU Temp:")) == 0)
408         {
409             sscanf(ip->name, "%*[^:]: %lf", &temp);
410             Log(LOG_LEVEL_DEBUG, "Setting temp0 to CPU Temp");
411             cf_this[ob_temp0] = temp;
412         }
413 
414         if (strncmp(ip->name, "M/B Temp:", strlen("M/B Temp:")) == 0)
415         {
416             sscanf(ip->name, "%*[^:]: %lf", &temp);
417             Log(LOG_LEVEL_DEBUG, "Setting temp0 to M/B Temp");
418             cf_this[ob_temp1] = temp;
419         }
420 
421         if (strncmp(ip->name, "Sys Temp:", strlen("Sys Temp:")) == 0)
422         {
423             sscanf(ip->name, "%*[^:]: %lf", &temp);
424             Log(LOG_LEVEL_DEBUG, "Setting temp0 to Sys Temp");
425             cf_this[ob_temp2] = temp;
426         }
427 
428         if (strncmp(ip->name, "AUX Temp:", strlen("AUX Temp:")) == 0)
429         {
430             sscanf(ip->name, "%*[^:]: %lf", &temp);
431             Log(LOG_LEVEL_DEBUG, "Setting temp0 to AUX Temp");
432             cf_this[ob_temp3] = temp;
433         }
434     }
435 
436     if (cf_this[ob_temp0] != 0)
437     {
438         /* We got something plausible */
439         return true;
440     }
441 
442 /* Alternative name Core x: */
443 
444     for (ip = list; ip != NULL; ip = ip->next)
445     {
446         for (count = 0; count < 4; count++)
447         {
448             snprintf(name, 16, "temp%d:", count);
449 
450             if (strncmp(ip->name, name, strlen(name)) == 0)
451             {
452                 sscanf(ip->name, "%*[^:]: %lf", &temp);
453 
454                 switch (count)
455                 {
456                 case 0:
457                     cf_this[ob_temp0] = temp;
458                     break;
459                 case 1:
460                     cf_this[ob_temp1] = temp;
461                     break;
462                 case 2:
463                     cf_this[ob_temp2] = temp;
464                     break;
465                 case 3:
466                     cf_this[ob_temp3] = temp;
467                     break;
468                 }
469 
470                 Log(LOG_LEVEL_DEBUG, "Set temp%d to %lf", count, temp);
471             }
472         }
473     }
474 
475 /* Give up? */
476     DeleteItemList(list);
477     return true;
478 }
479 
480 #endif
481