1/*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkRTConf.h"
9#include "SkOSFile.h"
10
11#include <stdlib.h>
12
13SkRTConfRegistry::SkRTConfRegistry(): fConfs(100) {
14
15    FILE *fp = sk_fopen(configFileLocation(), kRead_SkFILE_Flag);
16
17    if (!fp) {
18        return;
19    }
20
21    char line[1024];
22
23    while (!sk_feof(fp)) {
24
25        if (!sk_fgets(line, sizeof(line), fp)) {
26            break;
27        }
28
29        char *commentptr = strchr(line, '#');
30        if (commentptr == line) {
31            continue;
32        }
33        if (commentptr) {
34            *commentptr = '\0';
35        }
36
37        char sep[] = " \t\r\n";
38
39        char *keyptr = strtok(line, sep);
40        if (!keyptr) {
41            continue;
42        }
43
44        char *valptr = strtok(nullptr, sep);
45        if (!valptr) {
46            continue;
47        }
48
49        SkString *key = new SkString(keyptr);
50        SkString *val = new SkString(valptr);
51
52        fConfigFileKeys.append(1, &key);
53        fConfigFileValues.append(1, &val);
54    }
55    sk_fclose(fp);
56}
57
58SkRTConfRegistry::~SkRTConfRegistry() {
59    ConfMap::Iter iter(fConfs);
60    SkTDArray<SkRTConfBase *> *confArray;
61
62    while (iter.next(&confArray)) {
63        delete confArray;
64    }
65
66    for (int i = 0 ; i < fConfigFileKeys.count() ; i++) {
67        delete fConfigFileKeys[i];
68        delete fConfigFileValues[i];
69    }
70}
71
72const char *SkRTConfRegistry::configFileLocation() const {
73    return "skia.conf"; // for now -- should probably do something fancier like home directories or whatever.
74}
75
76// dump all known runtime config options to the file with their default values.
77// to trigger this, make a config file of zero size.
78void SkRTConfRegistry::possiblyDumpFile() const {
79    const char *path = configFileLocation();
80    FILE *fp = sk_fopen(path, kRead_SkFILE_Flag);
81    if (!fp) {
82        return;
83    }
84    size_t configFileSize = sk_fgetsize(fp);
85    if (configFileSize == 0) {
86        printAll(path);
87    }
88    sk_fclose(fp);
89}
90
91// Run through every provided configuration option and print a warning if the user hasn't
92// declared a correponding configuration object somewhere.
93void SkRTConfRegistry::validate() const {
94    for (int i = 0 ; i < fConfigFileKeys.count() ; i++) {
95        if (!fConfs.find(fConfigFileKeys[i]->c_str())) {
96            SkDebugf("WARNING: You have config value %s in your configuration file, but I've never heard of that.\n", fConfigFileKeys[i]->c_str());
97        }
98    }
99}
100
101void SkRTConfRegistry::printAll(const char *fname) const {
102    SkWStream *o;
103
104    if (fname) {
105        o = new SkFILEWStream(fname);
106    } else {
107        o = new SkDebugWStream();
108    }
109
110    ConfMap::Iter iter(fConfs);
111    SkTDArray<SkRTConfBase *> *confArray;
112
113    while (iter.next(&confArray)) {
114        if (confArray->getAt(0)->isDefault()) {
115            o->writeText("# ");
116        }
117        confArray->getAt(0)->print(o);
118        o->newline();
119    }
120
121    delete o;
122}
123
124bool SkRTConfRegistry::hasNonDefault() const {
125    ConfMap::Iter iter(fConfs);
126    SkTDArray<SkRTConfBase *> *confArray;
127    while (iter.next(&confArray)) {
128        if (!confArray->getAt(0)->isDefault()) {
129            return true;
130        }
131    }
132    return false;
133}
134
135void SkRTConfRegistry::printNonDefault(const char *fname) const {
136    SkWStream *o;
137
138    if (fname) {
139        o = new SkFILEWStream(fname);
140    } else {
141        o = new SkDebugWStream();
142    }
143    ConfMap::Iter iter(fConfs);
144    SkTDArray<SkRTConfBase *> *confArray;
145
146    while (iter.next(&confArray)) {
147        if (!confArray->getAt(0)->isDefault()) {
148            confArray->getAt(0)->print(o);
149            o->newline();
150        }
151    }
152
153    delete o;
154}
155
156// register a configuration variable after its value has been set by the parser.
157// we maintain a vector of these things instead of just a single one because the
158// user might set the value after initialization time and we need to have
159// all the pointers lying around, not just one.
160void SkRTConfRegistry::registerConf(SkRTConfBase *conf) {
161    SkTDArray<SkRTConfBase *> *confArray;
162    if (fConfs.find(conf->getName(), &confArray)) {
163        if (!conf->equals(confArray->getAt(0))) {
164            SkDebugf("WARNING: Skia config \"%s\" was registered more than once in incompatible ways.\n", conf->getName());
165        } else {
166            confArray->append(1, &conf);
167        }
168    } else {
169        confArray = new SkTDArray<SkRTConfBase *>;
170        confArray->append(1, &conf);
171        fConfs.set(conf->getName(),confArray);
172    }
173}
174
175template <typename T> T doParse(const char *, bool *success ) {
176    SkDebugf("WARNING: Invoked non-specialized doParse function...\n");
177    if (success) {
178        *success = false;
179    }
180    return (T) 0;
181}
182
183template<> bool doParse<bool>(const char *s, bool *success) {
184    if (success) {
185        *success = true;
186    }
187    if (!strcmp(s,"1") || !strcmp(s,"true")) {
188        return true;
189    }
190    if (!strcmp(s,"0") || !strcmp(s,"false")) {
191        return false;
192    }
193    if (success) {
194        *success = false;
195    }
196    return false;
197}
198
199template<> const char * doParse<const char *>(const char * s, bool *success) {
200    if (success) {
201        *success = true;
202    }
203    return s;
204}
205
206template<> int doParse<int>(const char * s, bool *success) {
207    if (success) {
208        *success = true;
209    }
210    return atoi(s);
211}
212
213template<> unsigned int doParse<unsigned int>(const char * s, bool *success) {
214    if (success) {
215        *success = true;
216    }
217    return (unsigned int) atoi(s);
218}
219
220template<> float doParse<float>(const char * s, bool *success) {
221    if (success) {
222        *success = true;
223    }
224    return (float) atof(s);
225}
226
227template<> double doParse<double>(const char * s, bool *success) {
228    if (success) {
229        *success = true;
230    }
231    return atof(s);
232}
233
234static inline void str_replace(char *s, char search, char replace) {
235    for (char *ptr = s ; *ptr ; ptr++) {
236        if (*ptr == search) {
237            *ptr = replace;
238        }
239    }
240}
241
242template<typename T> bool SkRTConfRegistry::parse(const char *name, T* value) {
243    const char *str = nullptr;
244
245    for (int i = fConfigFileKeys.count() - 1 ; i >= 0; i--) {
246        if (fConfigFileKeys[i]->equals(name)) {
247            str = fConfigFileValues[i]->c_str();
248            break;
249        }
250    }
251
252    SkString environment_variable("skia.");
253    environment_variable.append(name);
254
255    const char *environment_value = getenv(environment_variable.c_str());
256    if (environment_value) {
257        str = environment_value;
258    } else {
259        // apparently my shell doesn't let me have environment variables that
260        // have periods in them, so also let the user substitute underscores.
261        SkAutoTMalloc<char> underscore_name(SkStrDup(environment_variable.c_str()));
262        str_replace(underscore_name.get(), '.', '_');
263        environment_value = getenv(underscore_name.get());
264        if (environment_value) {
265            str = environment_value;
266        }
267    }
268
269    if (!str) {
270        return false;
271    }
272
273    bool success;
274    T new_value = doParse<T>(str, &success);
275    if (success) {
276        *value = new_value;
277    } else {
278        SkDebugf("WARNING: Couldn't parse value \'%s\' for variable \'%s\'\n",
279                 str, name);
280    }
281    return success;
282}
283
284// need to explicitly instantiate the parsing function for every config type we might have...
285
286template bool SkRTConfRegistry::parse(const char *name, bool *value);
287template bool SkRTConfRegistry::parse(const char *name, int *value);
288template bool SkRTConfRegistry::parse(const char *name, unsigned int *value);
289template bool SkRTConfRegistry::parse(const char *name, float *value);
290template bool SkRTConfRegistry::parse(const char *name, double *value);
291template bool SkRTConfRegistry::parse(const char *name, const char **value);
292
293template <typename T> void SkRTConfRegistry::set(const char *name,
294                                                 T value,
295                                                 bool warnIfNotFound) {
296    SkTDArray<SkRTConfBase *> *confArray;
297    if (!fConfs.find(name, &confArray)) {
298        if (warnIfNotFound) {
299            SkDebugf("WARNING: Attempting to set configuration value \"%s\","
300                     " but I've never heard of that.\n", name);
301        }
302        return;
303    }
304    SkASSERT(confArray != nullptr);
305    for (SkRTConfBase **confBase = confArray->begin(); confBase != confArray->end(); confBase++) {
306        // static_cast here is okay because there's only one kind of child class.
307        SkRTConf<T> *concrete = static_cast<SkRTConf<T> *>(*confBase);
308
309        if (concrete) {
310            concrete->set(value);
311        }
312    }
313}
314
315template void SkRTConfRegistry::set(const char *name, bool value, bool);
316template void SkRTConfRegistry::set(const char *name, int value, bool);
317template void SkRTConfRegistry::set(const char *name, unsigned int value, bool);
318template void SkRTConfRegistry::set(const char *name, float value, bool);
319template void SkRTConfRegistry::set(const char *name, double value, bool);
320template void SkRTConfRegistry::set(const char *name, char * value, bool);
321
322SkRTConfRegistry &skRTConfRegistry() {
323    static SkRTConfRegistry r;
324    return r;
325}
326