Skip to content

Commit e84a546

Browse files
author
Eric Siron
committed
fixes for segfault from large number of files
fixes for makefile
1 parent 8dbbf39 commit e84a546

8 files changed

+63
-91
lines changed

README.md

+19-9
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Download and compile the source:
8484
cd ~
8585
git clone https://github.com/ProjectRunspace/xlatnagiosdata.git
8686
cd xlatnagiosdata
87+
git submodule update --init
8788
make all
8889
```
8990

@@ -122,11 +123,12 @@ Perform these steps to manually install xlatnagiosdata:
122123
2. Create a directory named ```/etc/xlatnagiosdata```
123124
3. From the download directory, copy ```./daemon/config/xlatnagiosdatad.toml``` to ```/etc/xlatnagiosdata```
124125
4. From the download directory, copy ```./daemon/config/xlatnagiosdatad.service``` to ```/etc/systemd/system``` (assuming that this is where your distribution places system service files)
125-
5. From the download directory, copy ```xlatnagiosdatad``` to ```/usr/local/bin```. You may use an alternative location, but you must edit the service file from #4 to match.
126-
6. If desired, edit the configuration file from step 3.
127-
7. Reload system daemons: ```sudo systemctl daemon-reload```
128-
8. Enable the xlatnagiosdata daemon: ```sudo systemctl enable xlatnagiosdatad``` (this sets the daemon to auto-start, you can save this step for later)
129-
9. Start the xlatnagiosdata daemon: ```sudo systemctl start xlatnagiosdatad```
126+
5. Open the file that you just copied to ```etc/systemd/system```. Find the line that reads ```User=root```. If Nagios runs under a different account, change accordingly.
127+
6. From the download directory, copy ```xlatnagiosdatad``` to ```/usr/local/bin```. You may use an alternative location, but you must edit the service file from #4 to match.
128+
7. If desired, edit the configuration file from step 3.
129+
8. Reload system daemons: ```sudo systemctl daemon-reload```
130+
9. Enable the xlatnagiosdata daemon: ```sudo systemctl enable xlatnagiosdatad``` (this sets the daemon to auto-start, you can save this step for later)
131+
10. Start the xlatnagiosdata daemon: ```sudo systemctl start xlatnagiosdatad```
130132

131133
At this point, the daemon should be running. Unless you installed over a previous installation, it's probably not getting any data to gather. Skip to the **Post-Installation Setup** section.
132134

@@ -173,10 +175,18 @@ After install, you need to do a few things:
173175
Nagios Core as installed from the Nagios web page installs to ```/usr/local/nagios```. Other repositories and repackaged solutions might use other locations. Under its root install location, Nagios creates a ```/var/spool``` sub-directory. Create an ```xlatnagiosdata``` directory. Example:
174176

175177
```
176-
sudo mkdir /usr/local/nagios/xlatnagiosdata
178+
sudo mkdir /usr/local/nagios/var/spool/xlatnagiosdata
177179
```
178180

179-
**Note**: If you use a different location than ```/usr/local/nagios/xlatnagiosdata```, make sure to edit ```/etc/xlatnagiosdata/xlatnagiosdatad.toml``` to change the "spool_directory" key accordingly.
181+
Since the nagios service commonly runs under an account other than root, and because this service runs as root, you need to make sure that both accounts can access this directory. The easiest way to do this is give this new folder the same ownership as all its peer folders (view with ```ls -la```). Make sure that ```root``` belongs to the same group as the Nagios account. Example:
182+
183+
```
184+
sudo chown nagios /usr/local/nagios/var/spool/xlatnagiosdata
185+
sudo chgrp nagcmd /usr/local/nagios/var/spool/xlatnagiosdata
186+
sudo adduser root nagcmd
187+
```
188+
189+
**Note**: If you use a different location than ```/usr/local/nagios/var/spool/xlatnagiosdata```, make sure to edit ```/etc/xlatnagiosdata/xlatnagiosdatad.toml``` to change the "spool_directory" key accordingly.
180190

181191
### 2. Enable Nagios to Write Performance Data
182192

@@ -224,12 +234,12 @@ In any .cfg file of your choosing, typically in the ```objects``` directory unde
224234
```
225235
define command {
226236
command_name process-host-perfdata-xlatnagiosdata
227-
command_line mv /usr/local/nagios/var/host-perfdata /var/spool/xlatnagiosdata/$TIMET$.perfdata.host
237+
command_line mv /usr/local/nagios/var/host-perfdata /usr/local/nagios/var/spool/xlatnagiosdata/$TIMET$.perfdata.host
228238
}
229239
230240
define command {
231241
command_name process-service-perfdata-xlatnagiosdata
232-
command_line mv /usr/local/nagios/var/service-perfdata /var/spool/xlatnagiosdata/$TIMET$.perfdata.service
242+
command_line mv /usr/local/nagios/var/service-perfdata /usr/local/nagios/var/spool/xlatnagiosdata/$TIMET$.perfdata.service
233243
}
234244
```
235245

daemon/source/filedatacollector.cpp

+35-70
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,25 @@
1313
#include "logwriter.hpp"
1414
#include "utility.hpp"
1515

16-
constexpr const size_t MaxBlockSize{1024}; // 1024 lines per block (approximate; may go over for large files with many small lines)
17-
constexpr const size_t MaxFileLineLength{1024}; // 1024 characters per line (also approximate and seems high, but it's a reasonable start)
18-
constexpr const size_t MaxReadChunk{MaxFileLineLength * MaxBlockSize}; // max amount of data to read into a block before sending block to caller
19-
static_assert(MaxReadChunk < std::numeric_limits<long>::max(), "MaxReadChunk cannot exceed the maximum size of a long");
16+
constexpr const size_t MaxFileSize{std::numeric_limits<long>::max()};
17+
static_assert(MaxFileSize <= std::numeric_limits<size_t>::max(), "MaxFileSize cannot exceed maximum size of type long");
18+
constexpr const size_t MaxBlockSize{1024}; // 1024 lines per block
2019

2120
// collector logging constants
2221
constexpr const std::string_view AddedPerfFileForProcessing{"Added file for perfdata processing"};
2322
constexpr const std::string_view DeleteFile{"Delete file"};
2423
constexpr const std::string_view FileRead{"Read file"};
2524
constexpr const std::string_view FileSeek{"Move to position in file"};
25+
constexpr const std::string_view FileTooLarge{"File too large to process"};
2626
constexpr const std::string_view SpoolDirectory{"Locating spool directory"};
2727
constexpr const std::string_view ExtractedLine{"Extracted cleaned line"};
2828
constexpr const std::string_view GettingNextBlock{"Getting next data block from disk"};
29-
constexpr const std::string_view IndecipherableContent{"Indecipherable content in file"};
3029
constexpr const std::string_view NoMoreLines{"No more lines to process"};
3130
constexpr const std::string_view OpenFile{"Open file"};
32-
constexpr const std::string_view RolledBackCharacters{"Rolled back characters in a partial line"};
3331
constexpr const std::string_view SkippedEmpty{"Skipped empty file"};
3432

3533
// returns true if it had to log error, false if it logged a debug message
36-
static bool LogIOError(ILogWriter &Log, const std::string_view &Activity, const std::string_view &Item, std::set<std::string> &ItemLog, FILE *CurrentFile)
34+
static bool LogIOError(ILogWriter &Log, const std::string_view &Activity, const std::string_view &Item, FILE *CurrentFile)
3735
{
3836
int &ErrorCode = errno;
3937
if (CurrentFile)
@@ -42,7 +40,6 @@ static bool LogIOError(ILogWriter &Log, const std::string_view &Activity, const
4240
}
4341
if (ErrorCode != 0)
4442
{
45-
ItemLog.insert(std::string{Item});
4643
Log.WriteErrorAnnotated(Activity, Item, std::strerror(ErrorCode));
4744
errno = 0;
4845
if (CurrentFile)
@@ -55,89 +52,51 @@ static bool LogIOError(ILogWriter &Log, const std::string_view &Activity, const
5552
return false;
5653
}
5754

58-
// TODO: this might be a lot for a single function
59-
static void GetNextLineBlock(std::queue<std::string> &UnprocessedLines, std::queue<PendingFile> &PendingFiles, std::queue<std::string> &CompletedFiles, ILogWriter &Log, std::set<std::string> &ErrorFiles)
55+
static void GetNextLineBlock(std::queue<std::string> &UnprocessedLines, std::queue<PendingFile> &PendingFiles, std::queue<std::string> &CompletedFiles, ILogWriter &Log)
6056
{
6157
if (!PendingFiles.empty())
6258
{
6359
errno = 0;
64-
size_t CharsProcessed{0};
65-
auto GetCharsToRead{[&]() -> size_t
66-
{
67-
if (CharsProcessed < MaxReadChunk)
68-
{
69-
size_t MaxChars{MaxReadChunk - CharsProcessed};
70-
return PendingFiles.front().FileSize < MaxChars ? PendingFiles.front().FileSize : MaxChars;
71-
}
72-
return 0;
73-
}};
7460
std::vector<char> Buffer{};
75-
size_t CharsToRead;
76-
while (!PendingFiles.empty() && UnprocessedLines.size() < MaxBlockSize && (CharsToRead = GetCharsToRead()) > 0)
61+
while (!PendingFiles.empty() && UnprocessedLines.size() < MaxBlockSize)
7762
{
78-
bool FileErrorState{false};
79-
auto const &[FileName, FileSize, StartPosition]{PendingFiles.front()};
80-
if (Buffer.size() < CharsToRead)
63+
bool FileInErrorState{false};
64+
auto const &[FileName, FileSize]{PendingFiles.front()};
65+
if (Buffer.size() < FileSize)
8166
{
82-
Buffer.resize(CharsToRead);
67+
Buffer.resize(FileSize, '\0');
8368
}
8469

8570
auto CurrentFile{fopen(PendingFiles.front().FileName.c_str(), "r")};
86-
FileErrorState = LogIOError(Log, OpenFile, PendingFiles.front().FileName, ErrorFiles, CurrentFile);
71+
FileInErrorState = LogIOError(Log, OpenFile, PendingFiles.front().FileName, CurrentFile);
8772
size_t CharactersRead{0};
8873
if (CurrentFile)
8974
{
90-
fseek(CurrentFile, StartPosition, SEEK_SET);
91-
FileErrorState = LogIOError(Log, FileSeek, PendingFiles.front().FileName, ErrorFiles, CurrentFile);
92-
if (!std::feof(CurrentFile) && !std::ferror(CurrentFile) && CharactersRead < CharsToRead)
93-
{
94-
CharactersRead += std::fread(Buffer.data(), sizeof(char), CharsToRead, CurrentFile);
95-
FileErrorState = LogIOError(Log, FileRead, PendingFiles.front().FileName, ErrorFiles, CurrentFile);
96-
}
97-
}
98-
if (CurrentFile && !FileErrorState)
99-
{
100-
if (std::feof(CurrentFile))
75+
FileInErrorState = LogIOError(Log, FileSeek, PendingFiles.front().FileName, CurrentFile);
76+
if (!std::feof(CurrentFile) && !std::ferror(CurrentFile) && CharactersRead < FileSize)
10177
{
102-
CompletedFiles.push(std::move(PendingFiles.front().FileName));
103-
}
104-
else
105-
{
106-
PendingFiles.front().StartPosition = ftell(CurrentFile);
78+
CharactersRead += std::fread(Buffer.data(), sizeof(char), FileSize, CurrentFile);
79+
FileInErrorState = LogIOError(Log, FileRead, PendingFiles.front().FileName, CurrentFile);
10780
}
10881
}
109-
if (CurrentFile == nullptr || std::feof(CurrentFile) || FileErrorState)
82+
83+
#pragma GCC diagnostic push
84+
#pragma GCC diagnostic ignored "-Wparentheses"
85+
if ((CurrentFile && !FileInErrorState) && ((CharactersRead == PendingFiles.front().FileSize)) || std::feof(CurrentFile))
11086
{
111-
PendingFiles.pop();
87+
CompletedFiles.push(std::move(PendingFiles.front().FileName));
11288
}
89+
PendingFiles.pop();
90+
#pragma GCC diagnostic pop
91+
11392
if (CurrentFile != nullptr)
11493
{
11594
std::fclose(CurrentFile);
11695
}
11796

11897
if (CharactersRead > 0)
11998
{
120-
// non zero-length file always ends with \n.
121-
// start at the end and walk backward until we find a newline
122-
// this allows proper reset of the file position for the next read and prevents attempts to parse partial lines
123-
long NonNewlineChars{0};
124-
for (auto BufferIterator{Buffer.crbegin()}; BufferIterator != Buffer.crend() && *BufferIterator != '\n'; BufferIterator++)
125-
{
126-
NonNewlineChars++;
127-
}
128-
if (NonNewlineChars == static_cast<long>(CharactersRead) - 1)
129-
{
130-
// TODO: this means that there isn't a newline in the entire thing, which is probably ill-formed input
131-
Log.WriteWarnAnnotated(IndecipherableContent, PendingFiles.front().FileName, std::string_view{Buffer.data(), CharactersRead});
132-
}
133-
else if (NonNewlineChars > 0)
134-
{
135-
// if we have non-newline characters, move the file position to the start of the last line
136-
// TODO: if this is somehow less than zero, potential for lost perfdata, but almost certainly means ill-formed input. loop will handle gracefully
137-
PendingFiles.front().StartPosition -= NonNewlineChars;
138-
Log.WriteDebugAnnoted(RolledBackCharacters, std::to_string(NonNewlineChars));
139-
}
140-
std::string_view BufferView{Buffer.data(), CharactersRead - NonNewlineChars};
99+
std::string_view BufferView{Buffer.data(), CharactersRead};
141100
Utility::DelimitedBlockProcessor RawDataProcessor{BufferView, '\n'};
142101
while (RawDataProcessor.More())
143102
{
@@ -184,7 +143,14 @@ FileDataCollector::FileDataCollector(const std::string &SourcePath, ILogWriter &
184143
{
185144
Log.WriteDebugAnnoted(AddedPerfFileForProcessing, FilePath);
186145
PendingFile NextFile{std::move(FilePath), direntry.file_size()};
187-
PendingFiles.push(std::move(NextFile));
146+
if (direntry.file_size() > MaxFileSize)
147+
{
148+
Log.WriteErrorAnnotated(FileTooLarge, NextFile.FileName, std::to_string(direntry.file_size()));
149+
}
150+
else
151+
{
152+
PendingFiles.push(std::move(NextFile));
153+
}
188154
}
189155
}
190156
}
@@ -208,7 +174,7 @@ std::string FileDataCollector::GetNextLine()
208174
Log.WriteDebug(GettingNextBlock);
209175
while (!PendingFiles.empty() && UnprocessedLines.size() < MaxBlockSize)
210176
{
211-
GetNextLineBlock(UnprocessedLines, PendingFiles, CompletedFiles, Log, ErrorFiles);
177+
GetNextLineBlock(UnprocessedLines, PendingFiles, CompletedFiles, Log);
212178
}
213179
}
214180
if (UnprocessedLines.empty())
@@ -226,11 +192,10 @@ std::string FileDataCollector::GetNextLine()
226192

227193
FileDataCollector::~FileDataCollector()
228194
{
229-
std::set<std::string> DestructorFileLog{}; // not doing anything with this, used for LogIOError
230195
while (!CompletedFiles.empty())
231196
{
232197
std::remove(CompletedFiles.front().c_str());
233-
LogIOError(Log, DeleteFile, CompletedFiles.front(), DestructorFileLog, nullptr);
198+
LogIOError(Log, DeleteFile, CompletedFiles.front(), nullptr);
234199
CompletedFiles.pop();
235200
}
236201
}

daemon/source/filedatacollector.hpp

-2
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,13 @@ struct PendingFile
1010
PendingFile(std::string FileName, size_t FileSize) : FileName{FileName}, FileSize{FileSize} {}
1111
std::string FileName;
1212
size_t FileSize;
13-
long StartPosition{0};
1413
};
1514

1615
class FileDataCollector
1716
{
1817
private:
1918
std::string SourcePath{};
2019
ILogWriter &Log;
21-
std::set<std::string> ErrorFiles{};
2220
std::queue<PendingFile> PendingFiles{};
2321
std::queue<std::string> CompletedFiles{};
2422
std::queue<std::string> UnprocessedLines{};

daemon/source/influxtranslator.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ std::vector<std::string> InfluxTranslator::TranslateNagiosData(const NagiosPerfo
124124
LineLength += SetItem(Fields, "crit", PerfData.Crit, true);
125125
LineLength += SetItem(Fields, "min", PerfData.Min, true);
126126
LineLength += SetItem(Fields, "max", PerfData.Max, true);
127-
LineLength += SetItem(Fields, "unit", ConvertFromNagiosUnit(PerfData.Unit, UnitTranslationMap), true);
127+
LineLength += SetItem(Tags, "unit", ConvertFromNagiosUnit(PerfData.Unit, UnitTranslationMap), true);
128128
TranslatedData.emplace_back(TranslateLine(LineLength));
129129
}
130130
return TranslatedData;

daemon/source/outputfile.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ int OutputFile::PrepareFile()
3939

4040
void OutputFile::Cleanup()
4141
{
42-
std::mutex CleanupMutex;
4342
std::unique_lock TimedLock{CleanupMutex};
4443
std::unique_lock WriterLock{WriterMutex, std::defer_lock};
4544
while (!CloseTimer.TimedOut() && !CleanupThread.get_stop_token().stop_requested())

daemon/source/outputfile.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class OutputFile
2323
void Cleanup();
2424

2525
std::condition_variable CleanupWaitCondition;
26+
std::mutex CleanupMutex;
2627
std::mutex WriterMutex;
2728
ThreadTimer CloseTimer;
2829
std::jthread CleanupThread{};

daemon/source/utility.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
#include <tuple>
66
#include "utility.hpp"
77

8-
#include <iostream>
9-
108
class NumberRunner
119
{
1210
protected:

makefile

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
CXXFLAGS = -Wall -Wextra -pedantic -std=c++20 -c -fno-exceptions -fno-rtti
1+
CXXFLAGS = -Wall -Wextra -pedantic -std=c++20 -c -fno-rtti
22
DEBUG ?= 0
33
ifeq ($(DEBUG), 1)
4-
CXXFLAGS +=-g3 -DDEBUG
4+
CXXFLAGS +=-g3 -ggdb -DDEBUG
55
else
6-
CXXFLAGS +=-g0 -DNDEBUG -O3
6+
CXXFLAGS +=-g0 -DNDEBUG -O3 -fno-exceptions
77
endif
88

99
CXX = g++ $(CXXFLAGS)
@@ -96,7 +96,7 @@ install:
9696
systemctl daemon-reload
9797
systemctl enable $(DAEMON_EXECUTABLE)
9898
systemctl start $(DAEMON_EXECUTABLE)
99-
journalctl -u $(DAEMON_EXECUTABLE)
99+
journalctl -xeu $(DAEMON_EXECUTABLE) --no-pager
100100
@echo
101101
@echo Check journalctl output for successful start.
102102
@echo Modify main nagios.cfg file:
@@ -109,12 +109,12 @@ install:
109109
@echo Create rules in a .cfg file in /usr/local/nagios/etc/objects:
110110
@echo " define command {"
111111
@echo " command_name process-host-perfdata-$(PACKAGE)"
112-
@echo " command_line mv /usr/local/nagios/var/host-perfdata /var/spool/$(PACKAGE)/\$$TIMET\$$.perfdata.host"
112+
@echo " command_line mv /usr/local/nagios/var/host-perfdata /usr/local/nagios/var/spool/$(PACKAGE)/\$$TIMET\$$.perfdata.host"
113113
@echo " }"
114114
@echo
115115
@echo " define command {"
116116
@echo " command_name process-service-perfdata-$(PACKAGE)"
117-
@echo " command_line mv /usr/local/nagios/var/service-perfdata /var/spool/$(PACKAGE)/\$$TIMET\$$.perfdata.service"
117+
@echo " command_line mv /usr/local/nagios/var/service-perfdata /usr/local/var/spool/$(PACKAGE)/\$$TIMET\$$.perfdata.service"
118118
@echo " }"
119119
@echo
120120
@echo To generate data, mark hosts and services as "process_perf_data 1"
@@ -134,6 +134,7 @@ uninstall:
134134
-rm $(INSTALL_SERVICE_DIR)$(DAEMON_SERVICE_FILE)
135135
-systemctl daemon-reload
136136
-rm $(INSTALL_EXECUTABLE_DIR)$(DAEMON_EXECUTABLE)
137+
-rm -rf /var/run/$(DAEMON_EXECUTABLE)
137138

138139
tar:
139140
@echo $(MAKEFILE_DIRECTORY)

0 commit comments

Comments
 (0)