1/*
2 * Copyright (C) 2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ConfigFile.h"
28
29#include "Options.h"
30#include <limits.h>
31#include <mutex>
32#include <stdio.h>
33#include <string.h>
34#include <wtf/ASCIICType.h>
35#include <wtf/DataLog.h>
36#include <wtf/text/StringBuilder.h>
37
38#if HAVE(REGEX_H)
39#include <regex.h>
40#endif
41
42#if OS(UNIX)
43#include <unistd.h>
44#endif
45
46namespace JSC {
47
48static const size_t s_processNameMax = 128;
49char ConfigFile::s_processName[s_processNameMax + 1] = { 0 };
50char ConfigFile::s_parentProcessName[s_processNameMax + 1] = { 0 };
51
52class ConfigFileScanner {
53public:
54 ConfigFileScanner(const char* filename)
55 : m_filename(filename)
56 , m_lineNumber(0)
57 {
58 m_srcPtr = &m_buffer[0];
59 m_bufferEnd = &m_buffer[0];
60 }
61
62 bool start()
63 {
64 m_file = fopen(m_filename, "r");
65 if (!m_file) {
66 dataLogF("Failed to open file JSC Config file '%s'.\n", m_filename);
67 return false;
68 }
69
70 return true;
71 }
72
73 unsigned lineNumber()
74 {
75 return m_lineNumber;
76 }
77
78 const char* currentBuffer()
79 {
80 if (!m_srcPtr || m_srcPtr == m_bufferEnd)
81 return "";
82
83 return m_srcPtr;
84 }
85
86 bool atFileEnd()
87 {
88 if (!fillBufferIfNeeded())
89 return true;
90
91 return false;
92 }
93
94 bool tryConsume(char c)
95 {
96 if (!fillBufferIfNeeded())
97 return false;
98
99 if (c == *m_srcPtr) {
100 m_srcPtr++;
101 return true;
102 }
103
104 return false;
105 }
106
107 template <size_t length>
108 bool tryConsume(const char (&token) [length])
109 {
110 if (!fillBufferIfNeeded())
111 return false;
112
113 size_t tokenLength = length - 1;
114 if (!strncmp(m_srcPtr, token, tokenLength)) {
115 m_srcPtr += tokenLength;
116 return true;
117 }
118
119 return false;
120 }
121
122 char* tryConsumeString()
123 {
124 if (!fillBufferIfNeeded())
125 return nullptr;
126
127 if (*m_srcPtr != '"')
128 return nullptr;
129
130 char* stringStart = ++m_srcPtr;
131
132 char* stringEnd = strchr(m_srcPtr, '"');
133 if (stringEnd) {
134 *stringEnd = '\0';
135 m_srcPtr = stringEnd + 1;
136 return stringStart;
137 }
138
139 return nullptr;
140 }
141
142 char* tryConsumeRegExPattern(bool& ignoreCase)
143 {
144 if (!fillBufferIfNeeded())
145 return nullptr;
146
147 if (*m_srcPtr != '/')
148 return nullptr;
149
150 char* stringStart = m_srcPtr + 1;
151
152 char* stringEnd = strchr(stringStart, '/');
153 if (stringEnd) {
154 *stringEnd = '\0';
155 m_srcPtr = stringEnd + 1;
156 if (*m_srcPtr == 'i') {
157 ignoreCase = true;
158 m_srcPtr++;
159 } else
160 ignoreCase = false;
161
162 return stringStart;
163 }
164
165 return nullptr;
166 }
167
168 char* tryConsumeUpto(bool& foundChar, char c)
169 {
170 if (!fillBufferIfNeeded())
171 return nullptr;
172
173 char* start = m_srcPtr;
174 foundChar = false;
175
176 char* cPosition = strchr(m_srcPtr, c);
177 if (cPosition) {
178 *cPosition = '\0';
179 m_srcPtr = cPosition + 1;
180 foundChar = true;
181 } else
182 m_srcPtr = m_bufferEnd;
183
184 return start;
185 }
186
187private:
188 bool fillBufferIfNeeded()
189 {
190 if (!m_srcPtr)
191 return false;
192
193 while (true) {
194 while (m_srcPtr != m_bufferEnd && isASCIISpace(*m_srcPtr))
195 m_srcPtr++;
196
197 if (m_srcPtr != m_bufferEnd)
198 break;
199
200 if (!fillBuffer())
201 return false;
202 }
203
204 return true;
205 }
206
207 bool fillBuffer()
208 {
209 do {
210 m_srcPtr = fgets(m_buffer, sizeof(m_buffer), m_file);
211 if (!m_srcPtr) {
212 fclose(m_file);
213 return false;
214 }
215
216 m_lineNumber++;
217
218 m_bufferEnd = strchr(m_srcPtr, '#');
219
220 if (m_bufferEnd)
221 *m_bufferEnd = '\0';
222 else {
223 m_bufferEnd = m_srcPtr + strlen(m_srcPtr);
224 if (m_bufferEnd > m_srcPtr && m_bufferEnd[-1] == '\n') {
225 m_bufferEnd--;
226 *m_bufferEnd = '\0';
227 }
228 }
229 } while (m_bufferEnd == m_srcPtr);
230
231 return true;
232 }
233
234 const char* m_filename;
235 unsigned m_lineNumber;
236 FILE* m_file;
237 char m_buffer[BUFSIZ];
238 char* m_srcPtr;
239 char* m_bufferEnd;
240};
241
242ConfigFile::ConfigFile(const char* filename)
243{
244 if (!filename)
245 m_filename[0] = '\0';
246 else {
247 strncpy(m_filename, filename, s_maxPathLength);
248 m_filename[s_maxPathLength] = '\0';
249 }
250
251 m_configDirectory[0] = '\0';
252}
253
254void ConfigFile::setProcessName(const char* processName)
255{
256 strncpy(s_processName, processName, s_processNameMax);
257}
258
259void ConfigFile::setParentProcessName(const char* parentProcessName)
260{
261 strncpy(s_parentProcessName, parentProcessName, s_processNameMax);
262}
263
264void ConfigFile::parse()
265{
266 enum StatementNesting { TopLevelStatment, NestedStatement, NestedStatementFailedCriteria };
267 enum ParseResult { ParseOK, ParseError, NestedStatementDone };
268
269 canonicalizePaths();
270
271 ConfigFileScanner scanner(m_filename);
272
273 if (!scanner.start())
274 return;
275
276 char logPathname[s_maxPathLength + 1] = { 0 };
277
278 StringBuilder jscOptionsBuilder;
279
280 auto parseLogFile = [&](StatementNesting statementNesting) {
281 char* filename = nullptr;
282 if (scanner.tryConsume('=') && (filename = scanner.tryConsumeString())) {
283 if (statementNesting != NestedStatementFailedCriteria) {
284 if (filename[0] != '/') {
285 int spaceRequired = snprintf(logPathname, s_maxPathLength + 1, "%s/%s", m_configDirectory, filename);
286 if (static_cast<unsigned>(spaceRequired) > s_maxPathLength)
287 return ParseError;
288 } else
289 strncpy(logPathname, filename, s_maxPathLength);
290 }
291
292 return ParseOK;
293 }
294
295 return ParseError;
296 };
297
298 auto parseJSCOptions = [&](StatementNesting statementNesting) {
299 if (scanner.tryConsume('{')) {
300 StringBuilder builder;
301
302 bool foundClosingBrace = false;
303 char* currentLine = nullptr;
304
305 while ((currentLine = scanner.tryConsumeUpto(foundClosingBrace, '}'))) {
306 char* p = currentLine;
307
308 do {
309 if (foundClosingBrace && !*p)
310 break;
311
312 char* optionNameStart = p;
313
314 while (*p && !isASCIISpace(*p) && *p != '=')
315 p++;
316
317 builder.appendCharacters(optionNameStart, p - optionNameStart);
318
319 while (*p && isASCIISpace(*p) && *p != '=')
320 p++;
321
322 if (!*p)
323 return ParseError;
324 p++; // Advance past the '='
325
326 builder.append('=');
327
328 while (*p && isASCIISpace(*p))
329 p++;
330
331 if (!*p)
332 return ParseError;
333
334 char* optionValueStart = p;
335
336 while (*p && !isASCIISpace(*p))
337 p++;
338
339 builder.appendCharacters(optionValueStart, p - optionValueStart);
340 builder.append('\n');
341
342 while (*p && isASCIISpace(*p))
343 p++;
344 } while (*p);
345
346 if (foundClosingBrace)
347 break;
348 }
349
350 if (statementNesting != NestedStatementFailedCriteria)
351 jscOptionsBuilder.append(builder);
352
353 return ParseOK;
354 }
355
356 return ParseError;
357 };
358
359 auto parseNestedStatement = [&](StatementNesting statementNesting) {
360 if (scanner.tryConsume("jscOptions"))
361 return parseJSCOptions(statementNesting);
362
363 if (scanner.tryConsume("logFile"))
364 return parseLogFile(statementNesting);
365
366 if (scanner.tryConsume('}'))
367 return NestedStatementDone;
368
369 return ParseError;
370 };
371
372 auto parsePredicate = [&](bool& predicateMatches, const char* matchValue) {
373 if (scanner.tryConsume("==")) {
374 char* predicateValue = nullptr;
375 if ((predicateValue = scanner.tryConsumeString()) && matchValue) {
376 predicateMatches = !strcmp(predicateValue, matchValue);
377 return true;
378 }
379 }
380#if HAVE(REGEX_H)
381 else if (scanner.tryConsume("=~")) {
382 char* predicateRegExString = nullptr;
383 bool ignoreCase { false };
384 if ((predicateRegExString = scanner.tryConsumeRegExPattern(ignoreCase)) && matchValue) {
385 regex_t predicateRegEx;
386 int regexFlags = REG_EXTENDED;
387 if (ignoreCase)
388 regexFlags |= REG_ICASE;
389 if (regcomp(&predicateRegEx, predicateRegExString, regexFlags))
390 return false;
391
392 predicateMatches = !regexec(&predicateRegEx, matchValue, 0, nullptr, 0);
393 return true;
394 }
395 }
396#endif
397
398 return false;
399 };
400
401 auto parseConditionalBlock = [&](StatementNesting statementNesting) {
402 if (statementNesting == NestedStatement) {
403 StatementNesting subNesting = NestedStatement;
404
405 while (true) {
406 bool predicateMatches;
407 const char* actualValue = nullptr;
408
409 if (scanner.tryConsume("processName"))
410 actualValue = s_processName;
411 else if (scanner.tryConsume("parentProcessName"))
412 actualValue = s_parentProcessName;
413 else if (scanner.tryConsume("build"))
414#ifndef NDEBUG
415 actualValue = "Debug";
416#else
417 actualValue = "Release";
418#endif
419 else
420 return ParseError;
421
422 if (parsePredicate(predicateMatches, actualValue)) {
423 if (!predicateMatches)
424 subNesting = NestedStatementFailedCriteria;
425
426 if (!scanner.tryConsume("&&"))
427 break;
428 }
429 }
430
431 if (!scanner.tryConsume('{'))
432 return ParseError;
433
434 ParseResult parseResult = ParseOK;
435 while (parseResult == ParseOK && !scanner.atFileEnd())
436 parseResult = parseNestedStatement(subNesting);
437
438 if (parseResult == NestedStatementDone)
439 return ParseOK;
440 }
441
442 return ParseError;
443 };
444
445 auto parseStatement = [&](StatementNesting statementNesting) {
446 if (scanner.tryConsume("jscOptions"))
447 return parseJSCOptions(statementNesting);
448
449 if (scanner.tryConsume("logFile"))
450 return parseLogFile(statementNesting);
451
452 if (statementNesting == TopLevelStatment)
453 return parseConditionalBlock(NestedStatement);
454
455 return ParseError;
456 };
457
458 ParseResult parseResult = ParseOK;
459
460 while (parseResult == ParseOK && !scanner.atFileEnd())
461 parseResult = parseStatement(TopLevelStatment);
462
463 if (parseResult == ParseOK) {
464 if (strlen(logPathname))
465 WTF::setDataFile(logPathname);
466
467 if (!jscOptionsBuilder.isEmpty()) {
468 JSC::Config::enableRestrictedOptions();
469 Options::setOptions(jscOptionsBuilder.toString().utf8().data());
470 }
471 } else
472 WTF::dataLogF("Error in JSC Config file on or near line %u, parsing '%s'\n", scanner.lineNumber(), scanner.currentBuffer());
473}
474
475void ConfigFile::canonicalizePaths()
476{
477 if (!m_filename[0])
478 return;
479
480#if OS(UNIX) || OS(DARWIN)
481 if (m_filename[0] != '/') {
482 // Relative path
483 char filenameBuffer[s_maxPathLength + 1];
484
485 if (getcwd(filenameBuffer, sizeof(filenameBuffer))) {
486 size_t pathnameLength = strlen(filenameBuffer);
487 bool shouldAddPathSeparator = filenameBuffer[pathnameLength - 1] != '/';
488 if (sizeof(filenameBuffer) - 1 >= pathnameLength + shouldAddPathSeparator) {
489 if (shouldAddPathSeparator)
490 strncat(filenameBuffer, "/", 2); // Room for '/' plus NUL
491#if COMPILER(GCC)
492#if GCC_VERSION_AT_LEAST(8, 0, 0)
493 IGNORE_WARNINGS_BEGIN("stringop-truncation")
494#endif
495#endif
496 strncat(filenameBuffer, m_filename, sizeof(filenameBuffer) - strlen(filenameBuffer) - 1);
497#if COMPILER(GCC)
498#if GCC_VERSION_AT_LEAST(8, 0, 0)
499 IGNORE_WARNINGS_END
500#endif
501#endif
502 strncpy(m_filename, filenameBuffer, s_maxPathLength);
503 m_filename[s_maxPathLength] = '\0';
504 }
505 }
506 }
507#endif
508
509 char* lastPathSeperator = strrchr(m_filename, '/');
510
511 if (lastPathSeperator) {
512 unsigned dirnameLength = lastPathSeperator - &m_filename[0];
513 strncpy(m_configDirectory, m_filename, dirnameLength);
514 m_configDirectory[dirnameLength] = '\0';
515 } else {
516 m_configDirectory[0] = '/';
517 m_configDirectory[1] = '\0';
518 }
519}
520
521void processConfigFile(const char* configFilename, const char* processName, const char* parentProcessName)
522{
523 static std::once_flag processConfigFileOnceFlag;
524
525 if (!configFilename || !strlen(configFilename))
526 return;
527
528 std::call_once(processConfigFileOnceFlag, [&]{
529 if (configFilename) {
530 ConfigFile configFile(configFilename);
531 configFile.setProcessName(processName);
532 if (parentProcessName)
533 configFile.setParentProcessName(parentProcessName);
534 configFile.parse();
535 }
536 });
537}
538
539} // namespace JSC
540