master
c 580 lines 16.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "windows_plugin.h"
4 #include "windows-internals.h"
5
6 #include "driver/netdata_driver.h"
7
8 static const char *srv_name = "NetdataDriver";
9 static const char *drv_path = "%SystemRoot%\\system32\\drivers\\netdata_driver.sys";
10
11 struct cpu_data {
12 RRDDIM *rd_cpu_temp;
13
14 collected_number cpu_temp;
15 collected_number last_valid_temp;
16 int read_errors;
17 };
18
19 struct cpu_data *cpus = NULL;
20 size_t ncpus = 0;
21 static ND_THREAD *hardware_info_thread = NULL;
22 static collected_number (*temperature_fcnt)(MSR_REQUEST *) = NULL;
23 static CRITICAL_SECTION cpus_lock;
24 bool cpus_lock_initialized = false;
25 static HANDLE msr_device = INVALID_HANDLE_VALUE;
26 static CRITICAL_SECTION device_lock;
27 bool device_lock_initialized = false;
28 // Set by the worker immediately before exit so cleanup can distinguish
29 // "join failed but thread is done" from "join failed and thread may still run".
30 static volatile LONG hardware_info_thread_finished = 0;
31 static int consecutive_errors = 0;
32 static const int MAX_CONSECUTIVE_ERRORS = 5;
33 static const int IOCTL_RETRIES = 3;
34 static const int IOCTL_RETRY_DELAY_MS = 10;
35 static const int THREAD_JOIN_FALLBACK_WAIT_MS = 2000;
36 #define INVALID_TEMP ((collected_number)(-1))
37
38 static bool netdata_expand_driver_path(char *expanded_path, size_t expanded_path_size)
39 {
40 DWORD ret = ExpandEnvironmentStringsA(drv_path, expanded_path, (DWORD)expanded_path_size);
41 if (ret == 0) {
42 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot expand environment strings. Error= %lu \n", GetLastError());
43 return false;
44 }
45
46 if (ret > expanded_path_size) {
47 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Expanded driver path exceeds buffer size (%lu bytes needed)\n", ret);
48 return false;
49 }
50
51 return true;
52 }
53
54 static void netdata_stop_driver()
55 {
56 SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
57 if (scm == NULL) {
58 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open Service Manager. Error= %lu \n", GetLastError());
59 return;
60 }
61
62 SC_HANDLE service = OpenService(scm, srv_name, SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE);
63 if (service == NULL) {
64 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open the service. Error= %lu \n", GetLastError());
65 CloseServiceHandle(scm);
66 return;
67 }
68
69 SERVICE_STATUS ss_status = {};
70 if (ControlService(service, SERVICE_CONTROL_STOP, &ss_status) == 0) {
71 DWORD err = GetLastError();
72 if (err != ERROR_SERVICE_NOT_ACTIVE) {
73 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot stop the service. Error= %lu \n", err);
74 }
75 }
76
77 if (!DeleteService(service)) {
78 DWORD err = GetLastError();
79 if (err != ERROR_SERVICE_MARKED_FOR_DELETE) {
80 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot delete service. Error= %lu \n", err);
81 }
82 }
83
84 CloseServiceHandle(service);
85 CloseServiceHandle(scm);
86 }
87
88 int netdata_install_driver()
89 {
90 SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
91
92 if (unlikely(!scm)) {
93 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open Service Manager. Error= %lu \n", GetLastError());
94 return -1;
95 }
96
97 char expanded_path[MAX_PATH];
98 if (!netdata_expand_driver_path(expanded_path, sizeof(expanded_path))) {
99 CloseServiceHandle(scm);
100 return -1;
101 }
102
103 // Create the service entry for the driver
104 SC_HANDLE service = CreateServiceA(
105 scm,
106 srv_name,
107 srv_name,
108 SERVICE_START | SERVICE_STOP | DELETE,
109 SERVICE_KERNEL_DRIVER,
110 SERVICE_DEMAND_START,
111 SERVICE_ERROR_NORMAL,
112 expanded_path,
113 NULL,
114 NULL,
115 NULL,
116 NULL,
117 NULL);
118
119 if (unlikely(!service)) {
120 if (GetLastError() == ERROR_SERVICE_EXISTS) {
121 SC_HANDLE existing = OpenServiceA(scm, srv_name, SERVICE_CHANGE_CONFIG);
122 if (!existing) {
123 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open existing service. Error= %lu \n", GetLastError());
124 CloseServiceHandle(scm);
125 return -1;
126 }
127
128 if (!ChangeServiceConfigA(
129 existing,
130 SERVICE_KERNEL_DRIVER,
131 SERVICE_DEMAND_START,
132 SERVICE_ERROR_NORMAL,
133 expanded_path,
134 NULL,
135 NULL,
136 NULL,
137 NULL,
138 NULL,
139 NULL)) {
140 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot reconfigure existing service. Error= %lu \n", GetLastError());
141 CloseServiceHandle(existing);
142 CloseServiceHandle(scm);
143 return -1;
144 }
145
146 CloseServiceHandle(existing);
147 CloseServiceHandle(scm);
148 return 0;
149 }
150
151 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot create Service. Error= %lu \n", GetLastError());
152 CloseServiceHandle(scm);
153 return -1;
154 }
155
156 CloseServiceHandle(service);
157 CloseServiceHandle(scm);
158
159 return 0;
160 }
161
162 static inline void log_invalid_image_hash_error(void)
163 {
164 nd_log(
165 NDLS_COLLECTORS,
166 NDLP_ERR,
167 "Driver failed to start: ERROR_INVALID_IMAGE_HASH (577). "
168 "This usually indicates a driver signature verification failure. "
169 "The driver binary may be corrupted, unsigned, or signed with an untrusted certificate.\n");
170 }
171
172 int netdata_start_driver()
173 {
174 SC_HANDLE scm = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
175 if (unlikely(!scm)) {
176 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open Service Manager. Error= %lu \n", GetLastError());
177 return -1;
178 }
179
180 SC_HANDLE service = OpenServiceA(scm, srv_name, SERVICE_START | SERVICE_QUERY_STATUS);
181 if (unlikely(!service)) {
182 DWORD open_err = GetLastError();
183 CloseServiceHandle(scm);
184 scm = NULL;
185
186 // Service missing: attempt self-healing install then retry
187 if (open_err == ERROR_SERVICE_DOES_NOT_EXIST) {
188 nd_log(NDLS_COLLECTORS, NDLP_INFO, "Service not found, attempting to install driver and retry start\n");
189
190 if (netdata_install_driver() != 0) {
191 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Failed to install driver during self-healing\n");
192 return -1;
193 }
194
195 scm = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
196 if (unlikely(!scm)) {
197 nd_log(
198 NDLS_COLLECTORS,
199 NDLP_ERR,
200 "Cannot open Service Manager after install. Error= %lu \n",
201 GetLastError());
202 return -1;
203 }
204
205 service = OpenServiceA(scm, srv_name, SERVICE_START | SERVICE_QUERY_STATUS);
206 if (unlikely(!service)) {
207 nd_log(
208 NDLS_COLLECTORS,
209 NDLP_ERR,
210 "Cannot open Service after install. Error= %lu \n",
211 GetLastError());
212 CloseServiceHandle(scm);
213 return -1;
214 }
215 // fall through to StartServiceA with the newly opened handle
216 } else {
217 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open Service. Error= %lu \n", open_err);
218 return -1;
219 }
220 }
221
222 int ret = 0;
223 if (!StartServiceA(service, 0, NULL)) {
224 DWORD err = GetLastError();
225
226 if (err == ERROR_SERVICE_ALREADY_RUNNING) {
227 ret = 0;
228 } else if (err == ERROR_INVALID_IMAGE_HASH) {
229 log_invalid_image_hash_error();
230 ret = -1;
231 } else {
232 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot start Service. Error= %lu \n", err);
233 ret = -1;
234 }
235 }
236
237 if (service)
238 CloseServiceHandle(service);
239 if (scm)
240 CloseServiceHandle(scm);
241 return ret;
242 }
243
244 static inline HANDLE netdata_open_device()
245 {
246 HANDLE msr_h =
247 CreateFileA(MSR_USER_PATH, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
248 if (msr_h == INVALID_HANDLE_VALUE) {
249 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open device. GetLastError= %lu \n", GetLastError());
250 }
251 return msr_h;
252 }
253
254 static bool netdata_reopen_device_if_needed()
255 {
256 EnterCriticalSection(&device_lock);
257 if (msr_device != INVALID_HANDLE_VALUE) {
258 LeaveCriticalSection(&device_lock);
259 return true;
260 }
261
262 msr_device = netdata_open_device();
263 bool ok = (msr_device != INVALID_HANDLE_VALUE);
264 LeaveCriticalSection(&device_lock);
265 return ok;
266 }
267
268 static bool netdata_read_msr(MSR_REQUEST *req)
269 {
270 if (!req)
271 return false;
272
273 EnterCriticalSection(&device_lock);
274 if (msr_device == INVALID_HANDLE_VALUE) {
275 LeaveCriticalSection(&device_lock);
276 return false;
277 }
278
279 bool success = false;
280 for (int retry = 0; retry < IOCTL_RETRIES; retry++) {
281 DWORD bytes = 0;
282 if (DeviceIoControl(msr_device, IOCTL_MSR_READ, req, sizeof(*req), req, sizeof(*req), &bytes, NULL)) {
283 success = true;
284 break;
285 }
286
287 if (retry < IOCTL_RETRIES - 1) {
288 Sleep(IOCTL_RETRY_DELAY_MS);
289 }
290 }
291
292 LeaveCriticalSection(&device_lock);
293 return success;
294 }
295
296 static collected_number netdata_intel_cpu_temp(MSR_REQUEST *req)
297 {
298 if (!req)
299 return INVALID_TEMP;
300
301 const ULONG TJMAX = 100;
302 ULONG digital_readout = (req->low >> 16) & 0x7F; // bits [22:16]
303
304 collected_number temp = (collected_number)(TJMAX - digital_readout);
305
306 if (temp < 0 || temp > 150)
307 return INVALID_TEMP;
308
309 return temp;
310 }
311
312 static collected_number netdata_amd_cpu_temp(MSR_REQUEST *req)
313 {
314 if (!req)
315 return INVALID_TEMP;
316
317 ULONG amd_temp = (req->low >> 21) & 0x7FF;
318 collected_number temp = (collected_number)amd_temp / 8;
319
320 if (temp < 0 || temp > 150)
321 return INVALID_TEMP;
322
323 return temp;
324 }
325
326 void netdata_collect_cpu_chart()
327 {
328 if (!netdata_reopen_device_if_needed()) {
329 consecutive_errors++;
330 if (consecutive_errors >= MAX_CONSECUTIVE_ERRORS) {
331 nd_log(
332 NDLS_COLLECTORS, NDLP_ERR, "MSR device unavailable for %d consecutive attempts\n", consecutive_errors);
333 }
334 return;
335 }
336
337 consecutive_errors = 0;
338 const uint32_t MSR_THERM_STATUS = 0x19C;
339
340 EnterCriticalSection(&cpus_lock);
341 for (size_t cpu = 0; cpu < ncpus; cpu++) {
342 MSR_REQUEST req = {MSR_THERM_STATUS, (ULONG)cpu, 0, 0};
343
344 if (netdata_read_msr(&req)) {
345 collected_number temp = 0;
346 if (temperature_fcnt) {
347 temp = temperature_fcnt(&req);
348 }
349
350 if (temp != INVALID_TEMP) {
351 cpus[cpu].last_valid_temp = temp;
352 cpus[cpu].cpu_temp = temp;
353 cpus[cpu].read_errors = 0;
354 } else {
355 cpus[cpu].cpu_temp = cpus[cpu].last_valid_temp;
356 cpus[cpu].read_errors++;
357 }
358 } else {
359 cpus[cpu].cpu_temp = cpus[cpu].last_valid_temp;
360 cpus[cpu].read_errors++;
361 }
362 }
363 LeaveCriticalSection(&cpus_lock);
364 }
365
366 static void get_hardware_info_thread(void *ptr __maybe_unused)
367 {
368 heartbeat_t hb;
369 heartbeat_init(&hb, USEC_PER_SEC);
370
371 while (service_running(SERVICE_COLLECTORS)) {
372 (void)heartbeat_next(&hb);
373
374 netdata_collect_cpu_chart();
375 }
376
377 InterlockedExchange(&hardware_info_thread_finished, 1);
378 }
379
380 static void netdata_detect_cpu()
381 {
382 SYSTEM_INFO sysInfo;
383 GetSystemInfo(&sysInfo);
384
385 WORD test = sysInfo.wProcessorArchitecture;
386 if (test != PROCESSOR_ARCHITECTURE_AMD64 && test != PROCESSOR_ARCHITECTURE_IA64) {
387 return;
388 }
389
390 int cpuInfo[4];
391 __cpuid(cpuInfo, 0);
392
393 char vendorID[13];
394 memcpy(vendorID, &cpuInfo[1], 4);
395 memcpy(&vendorID[4], &cpuInfo[3], 4);
396 memcpy(&vendorID[8], &cpuInfo[2], 4);
397 vendorID[12] = '\0';
398
399 if (!strcmp(vendorID, "GenuineIntel"))
400 temperature_fcnt = netdata_intel_cpu_temp;
401 else if (!strcmp(vendorID, "AuthenticAMD"))
402 temperature_fcnt = netdata_amd_cpu_temp;
403 }
404
405 static int initialize()
406 {
407 char expanded_path[MAX_PATH];
408 if (!netdata_expand_driver_path(expanded_path, sizeof(expanded_path))) {
409 return -1;
410 }
411
412 if (GetFileAttributesA(expanded_path) == INVALID_FILE_ATTRIBUTES) {
413 nd_log(
414 NDLS_COLLECTORS,
415 NDLP_ERR,
416 "Driver not found at '%s'. Please ensure the driver is properly installed.\n",
417 expanded_path);
418 return -1;
419 }
420
421 InitializeCriticalSection(&cpus_lock);
422 cpus_lock_initialized = true;
423
424 InitializeCriticalSection(&device_lock);
425 device_lock_initialized = true;
426
427 netdata_detect_cpu();
428 if (!temperature_fcnt) {
429 return -1;
430 }
431
432 if (netdata_install_driver()) {
433 return -1;
434 }
435
436 if (netdata_start_driver()) {
437 return -1;
438 }
439
440 ncpus = os_get_system_cpus();
441 cpus = callocz(ncpus, sizeof(struct cpu_data));
442
443 for (size_t i = 0; i < ncpus; i++) {
444 cpus[i].cpu_temp = INVALID_TEMP;
445 cpus[i].last_valid_temp = INVALID_TEMP;
446 cpus[i].read_errors = 0;
447 }
448
449 InterlockedExchange(&hardware_info_thread_finished, 0);
450 hardware_info_thread =
451 nd_thread_create("hw_info_thread", NETDATA_THREAD_OPTION_DEFAULT, get_hardware_info_thread, NULL);
452
453 return 0;
454 }
455
456 static RRDSET *netdata_publish_cpu_chart(int update_every)
457 {
458 static RRDSET *st_cpu_temp = NULL;
459 if (!st_cpu_temp) {
460 st_cpu_temp = rrdset_create_localhost(
461 "cpu",
462 "temperature",
463 NULL,
464 "temperature",
465 "cpu.temperature",
466 "Core temperature",
467 "Celsius",
468 PLUGIN_WINDOWS_NAME,
469 "GetHardwareInfo",
470 NETDATA_CHART_PRIO_CPU_TEMPERATURE,
471 update_every,
472 RRDSET_TYPE_LINE);
473 }
474
475 return st_cpu_temp;
476 }
477
478 static void netdata_loop_cpu_chart(int update_every)
479 {
480 if (unlikely(!cpus_lock_initialized || !cpus))
481 return;
482
483 RRDSET *chart = netdata_publish_cpu_chart(update_every);
484
485 EnterCriticalSection(&cpus_lock);
486 for (int i = 0; i < (int)ncpus; i++) {
487 struct cpu_data *lcpu = &cpus[i];
488 if (!lcpu->rd_cpu_temp) {
489 char id[RRD_ID_LENGTH_MAX + 1];
490 snprintfz(id, RRD_ID_LENGTH_MAX, "cpu%d.temp", i);
491 lcpu->rd_cpu_temp = rrddim_add(chart, id, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
492 }
493
494 if (lcpu->cpu_temp != INVALID_TEMP) {
495 rrddim_set_by_pointer(chart, lcpu->rd_cpu_temp, lcpu->cpu_temp);
496 } else {
497 rrddim_set_by_pointer(chart, lcpu->rd_cpu_temp, 0);
498 }
499 }
500 LeaveCriticalSection(&cpus_lock);
501
502 rrdset_done(chart);
503 }
504
505 int do_GetHardwareInfo(int update_every, usec_t dt __maybe_unused)
506 {
507 static bool initialized = false;
508 static bool init_failed = false;
509
510 if (unlikely(init_failed))
511 return -1;
512
513 if (unlikely(!initialized)) {
514 if (initialize()) {
515 init_failed = true;
516 return -1;
517 }
518 initialized = true;
519 }
520
521 netdata_loop_cpu_chart(update_every);
522
523 return 0;
524 }
525
526 void do_GetHardwareInfo_cleanup()
527 {
528 if (hardware_info_thread) {
529 if (nd_thread_join(hardware_info_thread)) {
530 // nd_thread_join() frees the ND_THREAD object even on failure,
531 // so we cannot retry. The Windows/MSYS2 UV_EINVAL fast-exit case
532 // is already handled inside nd_thread_join(). For any other error,
533 // wait for up to one heartbeat interval plus slack for the worker
534 // to report completion before tearing down local resources it may
535 // still be touching. If it never does, abort cleanup: leaking here
536 // is safer than racing a live worker or hanging plugin shutdown
537 // indefinitely.
538 nd_log_daemon(NDLP_ERR, "Failed to join Get Hardware Info thread");
539
540 size_t retries = 0;
541 while (!InterlockedCompareExchange(&hardware_info_thread_finished, 1, 1) &&
542 retries < (size_t)THREAD_JOIN_FALLBACK_WAIT_MS) {
543 Sleep(1);
544 retries++;
545 }
546
547 if (!InterlockedCompareExchange(&hardware_info_thread_finished, 1, 1)) {
548 hardware_info_thread = NULL;
549 return;
550 }
551 }
552 hardware_info_thread = NULL;
553 }
554
555 if (device_lock_initialized) {
556 EnterCriticalSection(&device_lock);
557 if (msr_device != INVALID_HANDLE_VALUE) {
558 CloseHandle(msr_device);
559 msr_device = INVALID_HANDLE_VALUE;
560 }
561 LeaveCriticalSection(&device_lock);
562 } else if (msr_device != INVALID_HANDLE_VALUE) {
563 CloseHandle(msr_device);
564 msr_device = INVALID_HANDLE_VALUE;
565 }
566
567 netdata_stop_driver();
568
569 if (cpus_lock_initialized)
570 DeleteCriticalSection(&cpus_lock);
571
572 if (device_lock_initialized)
573 DeleteCriticalSection(&device_lock);
574
575 if (cpus) {
576 freez(cpus);
577 cpus = NULL;
578 ncpus = 0;
579 }
580 }