]> granicus.if.org Git - icinga2/blob - plugins/check_disk.cpp
Make UnameHelper() efficient
[icinga2] / plugins / check_disk.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2018 Icinga Development Team (https://icinga.com/)      *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
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 Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "plugins/thresholds.hpp"
21 #include <boost/program_options.hpp>
22 #include <vector>
23 #include <windows.h>
24 #include <set>
25 #include <iostream>
26 #include <functional>
27 #include <shlwapi.h>
28 #include <math.h>
29
30 using namespace std::placeholders;
31
32 #define VERSION 1.1
33
34 namespace po = boost::program_options;
35
36 struct drive
37 {
38         std::wstring name;
39         double cap;
40         double free;
41         double used;
42
43         drive(std::wstring p)
44                 : name(p)
45         { }
46 };
47
48 struct printInfoStruct
49 {
50         threshold warn;
51         threshold crit;
52         std::vector<std::wstring> drives;
53         std::vector<std::wstring> exclude_drives;
54         Bunit unit;
55         bool showUsed{false};
56 };
57
58 static bool l_Debug;
59
60 static int check_drives(std::vector<drive>& vDrives, std::vector<std::wstring>& vExclude_Drives)
61 {
62         DWORD dwResult, dwSize = 0, dwVolumePathNamesLen = MAX_PATH + 1;
63         WCHAR szLogicalDrives[1024], szVolumeName[MAX_PATH], *szVolumePathNames = NULL;
64         HANDLE hVolume = NULL;
65         std::wstring wsLogicalDrives;
66         size_t volumeNameEnd = 0;
67
68         std::set<std::wstring> sDrives;
69
70         if (l_Debug)
71                 std::wcout << "Getting logic drive string (includes network drives)\n";
72
73         dwResult = GetLogicalDriveStrings(MAX_PATH, szLogicalDrives);
74         if (dwResult > MAX_PATH)
75                 goto die;
76         if (l_Debug)
77                 std::wcout << "Splitting string into single drive names\n";
78
79         LPTSTR szSingleDrive = szLogicalDrives;
80         while (*szSingleDrive) {
81                 std::wstring drname = szSingleDrive;
82                 sDrives.insert(drname);
83                 szSingleDrive += wcslen(szSingleDrive) + 1;
84                 if (l_Debug)
85                         std::wcout << "Got: " << drname << '\n';
86         }
87
88         if (l_Debug)
89                 std::wcout << "Getting volume mountpoints (includes NTFS folders)\n"
90                 << "Getting first volume\n";
91
92         hVolume = FindFirstVolume(szVolumeName, MAX_PATH);
93         if (hVolume == INVALID_HANDLE_VALUE)
94                 goto die;
95
96         if (l_Debug)
97                 std::wcout << "Traversing through list of drives\n";
98
99         while (GetLastError() != ERROR_NO_MORE_FILES) {
100                 if (l_Debug)
101                         std::wcout << "Path name for " << szVolumeName << "= \"";
102                 volumeNameEnd = wcslen(szVolumeName) - 1;
103                 szVolumePathNames = new WCHAR[dwVolumePathNamesLen];
104
105                 while (!GetVolumePathNamesForVolumeName(szVolumeName, szVolumePathNames, dwVolumePathNamesLen,
106                         &dwVolumePathNamesLen)) {
107                         if (GetLastError() != ERROR_MORE_DATA)
108                                 break;
109                         delete[] szVolumePathNames;
110                         szVolumePathNames = new WCHAR[dwVolumePathNamesLen];
111
112                 }
113                 if (l_Debug)
114                         std::wcout << szVolumePathNames << "\"\n";
115
116                 sDrives.insert(std::wstring(szVolumePathNames));
117                 FindNextVolume(hVolume, szVolumeName, MAX_PATH);
118         }
119         if (l_Debug)
120                 std::wcout << "Creating vector from found volumes, ignoring CD drives etc.:\n";
121         for (const auto& driveName : sDrives) {
122                 unsigned int type = GetDriveType(driveName.c_str());
123                 if (type == DRIVE_FIXED || type == DRIVE_REMOTE) {
124                         if (l_Debug)
125                                 std::wcout << "\t" << driveName << '\n';
126                         vDrives.push_back(drive(driveName));
127                 }
128         }
129
130         FindVolumeClose(hVolume);
131         if (szVolumePathNames)
132                 delete[] szVolumePathNames;
133
134         if (l_Debug)
135                 std::wcout << "Removing excluded drives\n";
136
137         for (const auto& driveName : vExclude_Drives) {
138                 vDrives.erase(std::remove_if(vDrives.begin(), vDrives.end(),
139                         [&driveName](const drive& d) { return d.name == driveName + L'\\'; }),
140                         vDrives.end());
141         }
142
143         return -1;
144
145 die:
146         if (hVolume)
147                 FindVolumeClose(hVolume);
148         printErrorInfo();
149         return 3;
150 }
151
152 static int check_drives(std::vector<drive>& vDrives, printInfoStruct& printInfo)
153 {
154         if (l_Debug)
155                 std::wcout << "Removing excluded drives from user input drives\n";
156
157         for (const auto& driveName : printInfo.exclude_drives) {
158                 printInfo.drives.erase(std::remove(printInfo.drives.begin(), printInfo.drives.end(), driveName),
159                         printInfo.drives.end());
160         }
161
162         if (l_Debug)
163                 std::wcout << "Parsing user input drive names\n";
164
165         for (auto& driveName : printInfo.drives) {
166                 if (driveName.at(driveName.length() - 1) != *L"\\")
167                         driveName.append(L"\\");
168
169                 if (std::wstring::npos == driveName.find(L":\\")) {
170                         std::wcout << "A \":\" is required after the drive name of " << driveName << '\n';
171                         return 3;
172                 }
173
174                 if (l_Debug)
175                         std::wcout << "Added " << driveName << '\n';
176
177                 vDrives.emplace_back(driveName);
178         }
179
180         return -1;
181 }
182
183 static bool getDriveSpaceValues(drive& drive, const Bunit& unit)
184 {
185         if (l_Debug)
186                 std::wcout << "Getting free and used disk space for drive " << drive.name << '\n';
187
188         ULARGE_INTEGER tempFree, tempTotal;
189         if (!GetDiskFreeSpaceEx(drive.name.c_str(), NULL, &tempTotal, &tempFree))
190                 return false;
191
192         ULARGE_INTEGER tempUsed;
193         tempUsed.QuadPart = tempTotal.QuadPart - tempFree.QuadPart;
194
195         if (l_Debug)
196                 std::wcout << "\tcap: " << tempFree.QuadPart << '\n';
197
198         drive.cap = round((tempTotal.QuadPart / pow(1024.0, unit)));
199
200         if (l_Debug)
201                 std::wcout << "\tAfter conversion: " << drive.cap << '\n'
202                 << "\tfree: " << tempFree.QuadPart << '\n';
203
204         drive.free = round((tempFree.QuadPart / pow(1024.0, unit)));
205
206         if (l_Debug)
207                 std::wcout << "\tAfter conversion: " << drive.free << '\n'
208                 << "\tused: " << tempUsed.QuadPart << '\n';
209
210         drive.used = round((tempUsed.QuadPart / pow(1024.0, unit)));
211
212         if (l_Debug)
213                 std::wcout << "\tAfter conversion: " << drive.used << '\n' << '\n';
214
215         return true;
216 }
217
218 static int parseArguments(int ac, WCHAR **av, po::variables_map& vm, printInfoStruct& printInfo)
219 {
220         WCHAR namePath[MAX_PATH];
221         GetModuleFileName(NULL, namePath, MAX_PATH);
222         WCHAR *progName = PathFindFileName(namePath);
223
224         po::options_description desc("Options");
225
226         desc.add_options()
227                 ("help,h", "Print usage message and exit")
228                 ("version,V", "Print version and exit")
229                 ("debug,d", "Verbose/Debug output")
230                 ("warning,w", po::wvalue<std::wstring>(), "Warning threshold")
231                 ("critical,c", po::wvalue<std::wstring>(), "Critical threshold")
232                 ("path,p", po::wvalue<std::vector<std::wstring>>()->multitoken(), "Declare explicitly which drives to check (default checks all)")
233                 ("exclude_device,x", po::wvalue<std::vector<std::wstring>>()->multitoken(), "Exclude these drives from check")
234                 ("exclude-type,X", po::wvalue<std::vector<std::wstring>>()->multitoken(), "Exclude partition types (ignored)")
235                 ("iwarning,W", po::wvalue<std::wstring>(), "Warning threshold for inodes (ignored)")
236                 ("icritical,K", po::wvalue<std::wstring>(), "Critical threshold for inodes (ignored)")
237                 ("unit,u", po::wvalue<std::wstring>(), "Assign unit possible are: B, kB, MB, GB, TB")
238                 ("show-used,U", "Show used space instead of the free space")
239                 ("megabytes,m", "use megabytes, overridden by -unit")
240                 ;
241
242         po::wcommand_line_parser parser(ac, av);
243
244         try {
245                 po::store(
246                         parser
247                         .options(desc)
248                         .style(
249                                 po::command_line_style::unix_style |
250                                 po::command_line_style::allow_long_disguise)
251                         .run(),
252                         vm);
253                 vm.notify();
254         } catch (const std::exception& e) {
255                 std::cout << e.what() << '\n' << desc << '\n';
256                 return 3;
257         }
258
259         if (vm.count("help")) {
260                 std::wcout << progName << " Help\n\tVersion: " << VERSION << '\n';
261                 wprintf(
262                         L"%s is a simple program to check a machines disk space usage.\n"
263                         L"You can use the following options to define its behaviour:\n\n", progName);
264                 std::cout << desc;
265                 wprintf(
266                         L"\nIt will then output a string looking something like this:\n\n"
267                         L"\tDISK WARNING 29GB | disk=29GB;50%%;5;0;120\n\n"
268                         L"\"DISK\" being the type of the check, \"WARNING\" the returned status\n"
269                         L"and \"23.8304%%\" is the returned value.\n"
270                         L"The performance data is found behind the \"|\", in order:\n"
271                         L"returned value, warning threshold, critical threshold, minimal value and,\n"
272                         L"if applicable, the maximal value.\n"
273                         L"This program will also print out additional performance data disk by disk\n\n"
274                         L"%s' exit codes denote the following:\n\n"
275                         L" 0\tOK,\n\tNo Thresholds were broken or the programs check part was not executed\n"
276                         L" 1\tWARNING,\n\tThe warning, but not the critical threshold was broken\n"
277                         L" 2\tCRITICAL,\n\tThe critical threshold was broken\n"
278                         L" 3\tUNKNOWN, \n\tThe program experienced an internal or input error\n\n"
279                         L"Threshold syntax:\n\n"
280                         L"-w THRESHOLD\n"
281                         L"warn if threshold is broken, which means VALUE < THRESHOLD\n\n"
282                         L"-w !THRESHOLD\n"
283                         L"inverts threshold check, VALUE > THRESHOLD (analogous to above)\n\n"
284                         L"-w [THR1-THR2]\n"
285                         L"warn is VALUE is inside the range spanned by THR1 and THR2\n\n"
286                         L"-w ![THR1-THR2]\n"
287                         L"warn if VALUE is outside the range spanned by THR1 and THR2\n\n"
288                         L"-w THRESHOLD%%\n"
289                         L"if the plugin accepts percentage based thresholds those will be used.\n"
290                         L"Does nothing if the plugin does not accept percentages, or only uses\n"
291                         L"percentage thresholds. Ranges can be used with \"%%\", but both range values need\n"
292                         L"to end with a percentage sign.\n\n"
293                         L"All of these options work with the critical threshold \"-c\" too."
294                         , progName);
295                 std::cout << '\n';
296                 return 0;
297         }
298
299         if (vm.count("version"))
300                 std::cout << "Version: " << VERSION << '\n';
301
302         if (vm.count("warning")) {
303                 try {
304                         printInfo.warn = threshold(vm["warning"].as<std::wstring>());
305                 } catch (const std::invalid_argument& e) {
306                         std::cout << e.what() << '\n';
307                         return 3;
308                 }
309         }
310         if (vm.count("critical")) {
311                 try {
312                         printInfo.crit = threshold(vm["critical"].as<std::wstring>());
313                 } catch (const std::invalid_argument& e) {
314                         std::cout << e.what() << '\n';
315                         return 3;
316                 }
317         }
318
319         if (vm.count("path"))
320                 printInfo.drives = vm["path"].as<std::vector<std::wstring>>();
321
322         if (vm.count("exclude_device"))
323                 printInfo.exclude_drives = vm["exclude_device"].as<std::vector<std::wstring>>();
324
325         if (vm.count("unit")) {
326                 try {
327                         printInfo.unit = parseBUnit(vm["unit"].as<std::wstring>());
328                 } catch (const std::invalid_argument&) {
329                         std::wcout << "Unknown unit Type " << vm["unit"].as<std::wstring>() << '\n';
330                         return 3;
331                 }
332         } else {
333                 if (vm.count("megabytes"))
334                         printInfo.unit = BunitMB;
335                 else
336                         printInfo.unit = BunitB;
337         }
338
339         printInfo.showUsed = vm.count("show-used") > 0;
340
341         l_Debug = vm.count("debug") > 0;
342
343         return -1;
344 }
345
346 static int printOutput(printInfoStruct& printInfo, std::vector<drive>& vDrives)
347 {
348         if (l_Debug)
349                 std::wcout << "Constructing output string\n";
350
351         std::vector<std::wstring> wsDrives, wsPerf;
352         std::wstring unit = BunitStr(printInfo.unit);
353
354         state state = OK;
355
356         std::wstring output = L"DISK OK - free space:";
357
358         if (printInfo.showUsed)
359                 output = L"DISK OK - used space:";
360
361         double tCap = 0, tFree = 0, tUsed = 0;
362
363         for (std::vector<drive>::iterator it = vDrives.begin(); it != vDrives.end(); it++) {
364                 tCap += it->cap;
365                 tFree += it->free;
366                 tUsed += it->used;
367
368                 if (printInfo.showUsed) {
369                         wsDrives.push_back(it->name + L" " + removeZero(it->used) + L" " + unit + L" (" +
370                                 removeZero(std::round(it->used / it->cap * 100.0)) + L"%); ");
371
372                         wsPerf.push_back(L" " + it->name + L"=" + removeZero(it->used) + unit + L";" +
373                                 printInfo.warn.pString(it->cap) + L";" + printInfo.crit.pString(it->cap) + L";0;"
374                                 + removeZero(it->cap));
375
376                         if (printInfo.crit.set && !printInfo.crit.rend(it->used, it->cap))
377                                 state = CRITICAL;
378
379                         if (state == OK && printInfo.warn.set && !printInfo.warn.rend(it->used, it->cap))
380                                 state = WARNING;
381                 } else {
382                         wsDrives.push_back(it->name + L" " + removeZero(it->free) + L" " + unit + L" (" +
383                                 removeZero(std::round(it->free / it->cap * 100.0)) + L"%); ");
384
385                         wsPerf.push_back(L" '" + it->name + L"'=" + removeZero(it->free) + unit + L";" +
386                                 printInfo.warn.pString(it->cap) + L";" + printInfo.crit.pString(it->cap) + L";0;"
387                                 + removeZero(it->cap));
388
389                         if (printInfo.crit.rend(it->free, it->cap))
390                                 state = CRITICAL;
391
392                         if (state == OK && printInfo.warn.rend(it->free, it->cap))
393                                 state = WARNING;
394                 }
395         }
396
397         if (state == WARNING) {
398                 output = L"DISK WARNING - free space:";
399
400                 if (printInfo.showUsed)
401                         output = L"DISK WARNING - used space:";
402         }
403
404         if (state == CRITICAL) {
405                 output = L"DISK CRITICAL - free space:";
406
407                 if (printInfo.showUsed)
408                         output = L"DISK CRITICAL - used space:";
409         }
410
411         std::wcout << output;
412
413         if (vDrives.size() > 1 && printInfo.showUsed) {
414                 std::wcout << "Total " << (printInfo.showUsed ? tUsed : tFree) << unit
415                         << " (" << removeZero(std::round(tUsed / tCap * 100.0)) << "%); ";
416         }
417
418         for (const auto& driveName : wsDrives)
419                 std::wcout << driveName;
420
421         std::wcout << "|";
422
423         for (const auto& perf : wsPerf)
424                 std::wcout << perf;
425
426         std::wcout << '\n';
427
428         return state;
429 }
430
431 int wmain(int argc, WCHAR **argv)
432 {
433         std::vector<drive> vDrives;
434         printInfoStruct printInfo;
435         po::variables_map vm;
436
437         int ret;
438
439         ret = parseArguments(argc, argv, vm, printInfo);
440         if (ret != -1)
441                 return ret;
442
443         printInfo.warn.legal = !printInfo.warn.legal;
444         printInfo.crit.legal = !printInfo.crit.legal;
445
446         if (printInfo.drives.empty())
447                 ret = check_drives(vDrives, printInfo.exclude_drives);
448         else
449                 ret = check_drives(vDrives, printInfo);
450
451         if (ret != -1)
452                 return ret;
453
454         for (std::vector<drive>::iterator it = vDrives.begin(); it != vDrives.end(); ++it) {
455                 if (!getDriveSpaceValues(*it, printInfo.unit)) {
456                         std::wcout << "Failed to access drive at " << it->name << '\n';
457                         return 3;
458                 }
459         }
460
461         return printOutput(printInfo, vDrives);
462 }