1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2 
3 #include "config/configcompiler.hpp"
4 #include "config/configitem.hpp"
5 #include "base/logger.hpp"
6 #include "base/utility.hpp"
7 #include "base/loader.hpp"
8 #include "base/context.hpp"
9 #include "base/exception.hpp"
10 #include <fstream>
11 
12 using namespace icinga;
13 
14 std::vector<String> ConfigCompiler::m_IncludeSearchDirs;
15 std::mutex ConfigCompiler::m_ZoneDirsMutex;
16 std::map<String, std::vector<ZoneFragment> > ConfigCompiler::m_ZoneDirs;
17 
18 /**
19  * Constructor for the ConfigCompiler class.
20  *
21  * @param path The path of the configuration file (or another name that
22  *        identifies the source of the configuration text).
23  * @param input Input stream for the configuration file.
24  * @param zone The zone.
25  */
ConfigCompiler(String path,std::istream * input,String zone,String package)26 ConfigCompiler::ConfigCompiler(String path, std::istream *input,
27 	String zone, String package)
28 	: m_Path(std::move(path)), m_Input(input), m_Zone(std::move(zone)),
29 	m_Package(std::move(package)), m_Eof(false), m_OpenBraces(0)
30 {
31 	InitializeScanner();
32 }
33 
34 /**
35  * Destructor for the ConfigCompiler class.
36  */
~ConfigCompiler()37 ConfigCompiler::~ConfigCompiler()
38 {
39 	DestroyScanner();
40 }
41 
42 /**
43  * Reads data from the input stream. Used internally by the lexer.
44  *
45  * @param buffer Where to store data.
46  * @param max_size The maximum number of bytes to read from the stream.
47  * @returns The actual number of bytes read.
48  */
ReadInput(char * buffer,size_t max_size)49 size_t ConfigCompiler::ReadInput(char *buffer, size_t max_size)
50 {
51 	m_Input->read(buffer, max_size);
52 	return static_cast<size_t>(m_Input->gcount());
53 }
54 
55 /**
56  * Retrieves the scanner object.
57  *
58  * @returns The scanner object.
59  */
GetScanner() const60 void *ConfigCompiler::GetScanner() const
61 {
62 	return m_Scanner;
63 }
64 
65 /**
66  * Retrieves the path for the input file.
67  *
68  * @returns The path.
69  */
GetPath() const70 const char *ConfigCompiler::GetPath() const
71 {
72 	return m_Path.CStr();
73 }
74 
SetZone(const String & zone)75 void ConfigCompiler::SetZone(const String& zone)
76 {
77 	m_Zone = zone;
78 }
79 
GetZone() const80 String ConfigCompiler::GetZone() const
81 {
82 	return m_Zone;
83 }
84 
SetPackage(const String & package)85 void ConfigCompiler::SetPackage(const String& package)
86 {
87 	m_Package = package;
88 }
89 
GetPackage() const90 String ConfigCompiler::GetPackage() const
91 {
92 	return m_Package;
93 }
94 
CollectIncludes(std::vector<std::unique_ptr<Expression>> & expressions,const String & file,const String & zone,const String & package)95 void ConfigCompiler::CollectIncludes(std::vector<std::unique_ptr<Expression> >& expressions,
96 	const String& file, const String& zone, const String& package)
97 {
98 	try {
99 		expressions.emplace_back(CompileFile(file, zone, package));
100 	} catch (const std::exception& ex) {
101 		Log(LogWarning, "ConfigCompiler")
102 			<< "Cannot compile file '"
103 			<< file << "': " << DiagnosticInformation(ex);
104 	}
105 }
106 
107 /**
108  * Handles an include directive.
109  *
110  * @param relativeBath The path this include is relative to.
111  * @param path The path from the include directive.
112  * @param search Whether to search global include dirs.
113  * @param debuginfo Debug information.
114  */
HandleInclude(const String & relativeBase,const String & path,bool search,const String & zone,const String & package,const DebugInfo & debuginfo)115 std::unique_ptr<Expression> ConfigCompiler::HandleInclude(const String& relativeBase, const String& path,
116 	bool search, const String& zone, const String& package, const DebugInfo& debuginfo)
117 {
118 	String upath;
119 
120 	if (search || (IsAbsolutePath(path)))
121 		upath = path;
122 	else
123 		upath = relativeBase + "/" + path;
124 
125 	String includePath = upath;
126 
127 	if (search) {
128 		for (const String& dir : m_IncludeSearchDirs) {
129 			String spath = dir + "/" + path;
130 
131 			if (Utility::PathExists(spath)) {
132 				includePath = spath;
133 				break;
134 			}
135 		}
136 	}
137 
138 	std::vector<std::unique_ptr<Expression> > expressions;
139 	auto funcCallback = [&expressions, zone, package](const String& file) { CollectIncludes(expressions, file, zone, package); };
140 
141 	if (!Utility::Glob(includePath, funcCallback, GlobFile) && includePath.FindFirstOf("*?") == String::NPos) {
142 		std::ostringstream msgbuf;
143 		msgbuf << "Include file '" + path + "' does not exist";
144 		BOOST_THROW_EXCEPTION(ScriptError(msgbuf.str(), debuginfo));
145 	}
146 
147 	std::unique_ptr<DictExpression> expr{new DictExpression(std::move(expressions))};
148 	expr->MakeInline();
149 	return std::move(expr);
150 }
151 
152 /**
153  * Handles recursive includes.
154  *
155  * @param relativeBase The path this include is relative to.
156  * @param path The directory path.
157  * @param pattern The file pattern.
158  * @param debuginfo Debug information.
159  */
HandleIncludeRecursive(const String & relativeBase,const String & path,const String & pattern,const String & zone,const String & package,const DebugInfo &)160 std::unique_ptr<Expression> ConfigCompiler::HandleIncludeRecursive(const String& relativeBase, const String& path,
161 	const String& pattern, const String& zone, const String& package, const DebugInfo&)
162 {
163 	String ppath;
164 
165 	if (IsAbsolutePath(path))
166 		ppath = path;
167 	else
168 		ppath = relativeBase + "/" + path;
169 
170 	std::vector<std::unique_ptr<Expression> > expressions;
171 	Utility::GlobRecursive(ppath, pattern, [&expressions, zone, package](const String& file) {
172 		CollectIncludes(expressions, file, zone, package);
173 	}, GlobFile);
174 
175 	std::unique_ptr<DictExpression> dict{new DictExpression(std::move(expressions))};
176 	dict->MakeInline();
177 	return std::move(dict);
178 }
179 
HandleIncludeZone(const String & relativeBase,const String & tag,const String & path,const String & pattern,const String & package,std::vector<std::unique_ptr<Expression>> & expressions)180 void ConfigCompiler::HandleIncludeZone(const String& relativeBase, const String& tag, const String& path, const String& pattern, const String& package, std::vector<std::unique_ptr<Expression> >& expressions)
181 {
182 	String zoneName = Utility::BaseName(path);
183 
184 	String ppath;
185 
186 	if (IsAbsolutePath(path))
187 		ppath = path;
188 	else
189 		ppath = relativeBase + "/" + path;
190 
191 	RegisterZoneDir(tag, ppath, zoneName);
192 
193 	Utility::GlobRecursive(ppath, pattern, [&expressions, zoneName, package](const String& file) {
194 		CollectIncludes(expressions, file, zoneName, package);
195 	}, GlobFile);
196 }
197 
198 /**
199  * Handles zone includes.
200  *
201  * @param relativeBase The path this include is relative to.
202  * @param tag The tag name.
203  * @param path The directory path.
204  * @param pattern The file pattern.
205  * @param debuginfo Debug information.
206  */
HandleIncludeZones(const String & relativeBase,const String & tag,const String & path,const String & pattern,const String & package,const DebugInfo &)207 std::unique_ptr<Expression> ConfigCompiler::HandleIncludeZones(const String& relativeBase, const String& tag,
208 	const String& path, const String& pattern, const String& package, const DebugInfo&)
209 {
210 	String ppath;
211 	String newRelativeBase = relativeBase;
212 
213 	if (IsAbsolutePath(path))
214 		ppath = path;
215 	else {
216 		ppath = relativeBase + "/" + path;
217 		newRelativeBase = ".";
218 	}
219 
220 	std::vector<std::unique_ptr<Expression> > expressions;
221 	Utility::Glob(ppath + "/*", [newRelativeBase, tag, pattern, package, &expressions](const String& path) {
222 		HandleIncludeZone(newRelativeBase, tag, path, pattern, package, expressions);
223 	}, GlobDirectory);
224 
225 	return std::unique_ptr<Expression>(new DictExpression(std::move(expressions)));
226 }
227 
228 /**
229  * Compiles a stream.
230  *
231  * @param path A name identifying the stream.
232  * @param stream The input stream.
233  * @returns Configuration items.
234  */
CompileStream(const String & path,std::istream * stream,const String & zone,const String & package)235 std::unique_ptr<Expression> ConfigCompiler::CompileStream(const String& path,
236 	std::istream *stream, const String& zone, const String& package)
237 {
238 	CONTEXT("Compiling configuration stream with name '" + path + "'");
239 
240 	stream->exceptions(std::istream::badbit);
241 
242 	ConfigCompiler ctx(path, stream, zone, package);
243 
244 	try {
245 		return ctx.Compile();
246 	} catch (const ScriptError& ex) {
247 		return std::unique_ptr<Expression>(new ThrowExpression(MakeLiteral(ex.what()), ex.IsIncompleteExpression(), ex.GetDebugInfo()));
248 	} catch (const std::exception& ex) {
249 		return std::unique_ptr<Expression>(new ThrowExpression(MakeLiteral(DiagnosticInformation(ex)), false));
250 	}
251 }
252 
253 /**
254  * Compiles a file.
255  *
256  * @param path The path.
257  * @returns Configuration items.
258  */
CompileFile(const String & path,const String & zone,const String & package)259 std::unique_ptr<Expression> ConfigCompiler::CompileFile(const String& path, const String& zone,
260 	const String& package)
261 {
262 	CONTEXT("Compiling configuration file '" + path + "'");
263 
264 	std::ifstream stream(path.CStr(), std::ifstream::in);
265 
266 	if (!stream)
267 		BOOST_THROW_EXCEPTION(posix_error()
268 			<< boost::errinfo_api_function("std::ifstream::open")
269 			<< boost::errinfo_errno(errno)
270 			<< boost::errinfo_file_name(path));
271 
272 	Log(LogNotice, "ConfigCompiler")
273 		<< "Compiling config file: " << path;
274 
275 	return CompileStream(path, &stream, zone, package);
276 }
277 
278 /**
279  * Compiles a snippet of text.
280  *
281  * @param path A name identifying the text.
282  * @param text The text.
283  * @returns Configuration items.
284  */
CompileText(const String & path,const String & text,const String & zone,const String & package)285 std::unique_ptr<Expression> ConfigCompiler::CompileText(const String& path, const String& text,
286 	const String& zone, const String& package)
287 {
288 	std::stringstream stream(text);
289 	return CompileStream(path, &stream, zone, package);
290 }
291 
292 /**
293  * Adds a directory to the list of include search dirs.
294  *
295  * @param dir The new dir.
296  */
AddIncludeSearchDir(const String & dir)297 void ConfigCompiler::AddIncludeSearchDir(const String& dir)
298 {
299 	Log(LogInformation, "ConfigCompiler")
300 		<< "Adding include search dir: " << dir;
301 
302 	m_IncludeSearchDirs.push_back(dir);
303 }
304 
GetZoneDirs(const String & zone)305 std::vector<ZoneFragment> ConfigCompiler::GetZoneDirs(const String& zone)
306 {
307 	std::unique_lock<std::mutex> lock(m_ZoneDirsMutex);
308 	auto it = m_ZoneDirs.find(zone);
309 	if (it == m_ZoneDirs.end())
310 		return std::vector<ZoneFragment>();
311 	else
312 		return it->second;
313 }
314 
RegisterZoneDir(const String & tag,const String & ppath,const String & zoneName)315 void ConfigCompiler::RegisterZoneDir(const String& tag, const String& ppath, const String& zoneName)
316 {
317 	ZoneFragment zf;
318 	zf.Tag = tag;
319 	zf.Path = ppath;
320 
321 	std::unique_lock<std::mutex> lock(m_ZoneDirsMutex);
322 	m_ZoneDirs[zoneName].push_back(zf);
323 }
324 
HasZoneConfigAuthority(const String & zoneName)325 bool ConfigCompiler::HasZoneConfigAuthority(const String& zoneName)
326 {
327 	std::vector<ZoneFragment> zoneDirs = m_ZoneDirs[zoneName];
328 
329 	bool empty = zoneDirs.empty();
330 
331 	if (!empty) {
332 		std::vector<String> paths;
333 		paths.reserve(zoneDirs.size());
334 
335 		for (const ZoneFragment& zf : zoneDirs) {
336 			paths.push_back(zf.Path);
337 		}
338 
339 		Log(LogNotice, "ConfigCompiler")
340 			<< "Registered authoritative config directories for zone '" << zoneName << "': " << Utility::NaturalJoin(paths);
341 	}
342 
343 	return !empty;
344 }
345 
346 
IsAbsolutePath(const String & path)347 bool ConfigCompiler::IsAbsolutePath(const String& path)
348 {
349 #ifndef _WIN32
350 	return (path.GetLength() > 0 && path[0] == '/');
351 #else /* _WIN32 */
352 	return !PathIsRelative(path.CStr());
353 #endif /* _WIN32 */
354 }
355 
AddImport(const Expression::Ptr & import)356 void ConfigCompiler::AddImport(const Expression::Ptr& import)
357 {
358 	m_Imports.push_back(import);
359 }
360 
GetImports() const361 std::vector<Expression::Ptr> ConfigCompiler::GetImports() const
362 {
363 	return m_Imports;
364 }
365