forked from facebook/watchman
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cfg.c
308 lines (257 loc) · 7.29 KB
/
cfg.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
/* Copyright 2012-present Facebook, Inc.
* Licensed under the Apache License, Version 2.0 */
#include "watchman.h"
static json_t *global_cfg = NULL;
static json_t *arg_cfg = NULL;
static pthread_rwlock_t cfg_lock = PTHREAD_RWLOCK_INITIALIZER;
/* Called during shutdown to free things so that we run cleanly
* under valgrind */
void cfg_shutdown(void)
{
if (global_cfg) {
json_decref(global_cfg);
}
if (arg_cfg) {
json_decref(arg_cfg);
}
}
void cfg_load_global_config_file(void)
{
json_t *config = NULL;
json_error_t err;
const char *cfg_file = getenv("WATCHMAN_CONFIG_FILE");
#ifdef WATCHMAN_CONFIG_FILE
if (!cfg_file) {
cfg_file = WATCHMAN_CONFIG_FILE;
}
#endif
if (!cfg_file || cfg_file[0] == '\0') {
return;
}
if (!w_path_exists(cfg_file)) {
return;
}
config = json_load_file(cfg_file, 0, &err);
if (!config) {
w_log(W_LOG_ERR, "failed to parse json from %s: %s\n",
cfg_file, err.text);
return;
}
global_cfg = config;
}
void cfg_set_arg(const char *name, json_t *val)
{
pthread_rwlock_wrlock(&cfg_lock);
if (!arg_cfg) {
arg_cfg = json_object();
}
json_object_set_nocheck(arg_cfg, name, val);
pthread_rwlock_unlock(&cfg_lock);
}
void cfg_set_global(const char *name, json_t *val)
{
pthread_rwlock_wrlock(&cfg_lock);
if (!global_cfg) {
global_cfg = json_object();
}
json_object_set_nocheck(global_cfg, name, val);
pthread_rwlock_unlock(&cfg_lock);
}
static json_t *cfg_get_raw(const char *name, json_t **optr)
{
json_t *val = NULL;
pthread_rwlock_rdlock(&cfg_lock);
if (*optr) {
val = json_object_get(*optr, name);
}
pthread_rwlock_unlock(&cfg_lock);
return val;
}
json_t *cfg_get_json(w_root_t *root, const char *name)
{
json_t *val = NULL;
// Highest precedence: options set on the root
if (root && root->config_file) {
val = json_object_get(root->config_file, name);
}
// then: command line arguments
if (!val) {
val = cfg_get_raw(name, &arg_cfg);
}
// then: global config options
if (!val) {
val = cfg_get_raw(name, &global_cfg);
}
return val;
}
const char *cfg_get_string(w_root_t *root, const char *name,
const char *defval)
{
json_t *val = cfg_get_json(root, name);
if (val) {
if (!json_is_string(val)) {
w_log(W_LOG_FATAL, "Expected config value %s to be a string\n", name);
}
return json_string_value(val);
}
return defval;
}
// Return true if the json ref is an array of string values
static bool is_array_of_strings(json_t *ref) {
uint32_t i;
if (!json_is_array(ref)) {
return false;
}
for (i = 0; i < json_array_size(ref); i++) {
if (!json_is_string(json_array_get(ref, i))) {
return false;
}
}
return true;
}
// Given an array of string values, if that array does not contain
// a ".watchmanconfig" entry, prepend it
static void prepend_watchmanconfig_to_array(json_t *ref) {
const char *val;
if (json_array_size(ref) == 0) {
// json_array_insert_new at index can fail when the array is empty,
// so just append in this case.
json_array_append_new(ref, json_string_nocheck(".watchmanconfig"));
return;
}
val = json_string_value(json_array_get(ref, 0));
if (!strcmp(val, ".watchmanconfig")) {
return;
}
json_array_insert_new(ref, 0, json_string_nocheck(".watchmanconfig"));
}
// Compute the effective value of the root_files configuration and
// return a json reference. The caller must decref the ref when done
// (we may synthesize this value). Sets enforcing to indicate whether
// we will only allow watches on the root_files.
// The array returned by this function (if not NULL) is guaranteed to
// list .watchmanconfig as its zeroth element.
json_t *cfg_compute_root_files(bool *enforcing) {
json_t *ref;
*enforcing = false;
ref = cfg_get_json(NULL, "enforce_root_files");
if (ref) {
if (!json_is_boolean(ref)) {
w_log(W_LOG_FATAL,
"Expected config value enforce_root_files to be boolean\n");
}
*enforcing = json_is_true(ref);
}
ref = cfg_get_json(NULL, "root_files");
if (ref) {
if (!is_array_of_strings(ref)) {
w_log(W_LOG_FATAL,
"global config root_files must be an array of strings\n");
*enforcing = false;
return NULL;
}
prepend_watchmanconfig_to_array(ref);
json_incref(ref);
return ref;
}
// Try legacy root_restrict_files configuration
ref = cfg_get_json(NULL, "root_restrict_files");
if (ref) {
if (!is_array_of_strings(ref)) {
w_log(W_LOG_FATAL, "deprecated global config root_restrict_files "
"must be an array of strings\n");
*enforcing = false;
return NULL;
}
prepend_watchmanconfig_to_array(ref);
json_incref(ref);
*enforcing = true;
return ref;
}
// Synthesize our conservative default value.
// .watchmanconfig MUST be first
return json_pack("[ssss]", ".watchmanconfig", ".hg", ".git", ".svn");
}
json_int_t cfg_get_int(w_root_t *root, const char *name,
json_int_t defval)
{
json_t *val = cfg_get_json(root, name);
if (val) {
if (!json_is_integer(val)) {
w_log(W_LOG_FATAL, "Expected config value %s to be an integer\n", name);
}
return json_integer_value(val);
}
return defval;
}
bool cfg_get_bool(w_root_t *root, const char *name, bool defval)
{
json_t *val = cfg_get_json(root, name);
if (val) {
if (!json_is_boolean(val)) {
w_log(W_LOG_FATAL, "Expected config value %s to be a boolean\n", name);
}
return json_is_true(val);
}
return defval;
}
double cfg_get_double(w_root_t *root, const char *name, double defval) {
json_t *val = cfg_get_json(root, name);
if (val) {
if (!json_is_number(val)) {
w_log(W_LOG_FATAL, "Expected config value %s to be a number\n", name);
}
return json_real_value(val);
}
return defval;
}
#define MAKE_GET_PERM(PROP, SUFFIX) \
static mode_t get_ ## PROP ## _perm(const char *name, json_t *val, \
bool write_bits, bool execute_bits) { \
mode_t ret = 0; \
json_t *perm = json_object_get(val, #PROP); \
if (perm) { \
if (!json_is_boolean(perm)) { \
w_log(W_LOG_FATAL, "Expected config value %s." #PROP \
" to be a boolean\n", name); \
} \
if (json_is_true(perm)) { \
ret |= S_IR ## SUFFIX; \
if (write_bits) { \
ret |= S_IW ## SUFFIX; \
} \
if (execute_bits) { \
ret |= S_IX ## SUFFIX; \
} \
} \
} \
return ret; \
}
MAKE_GET_PERM(group, GRP)
MAKE_GET_PERM(others, OTH)
/**
* This function expects the config to be an object containing the keys 'group'
* and 'others', each a bool.
*/
mode_t cfg_get_perms(w_root_t *root, const char *name, bool write_bits,
bool execute_bits) {
json_t *val = cfg_get_json(root, name);
mode_t ret = S_IRUSR | S_IWUSR;
if (execute_bits) {
ret |= S_IXUSR;
}
if (val) {
if (!json_is_object(val)) {
w_log(W_LOG_FATAL, "Expected config value %s to be an object\n", name);
}
ret |= get_group_perm(name, val, write_bits, execute_bits);
ret |= get_others_perm(name, val, write_bits, execute_bits);
}
return ret;
}
const char *cfg_get_trouble_url(void) {
return cfg_get_string(NULL, "troubleshooting_url",
"https://facebook.github.io/watchman/docs/troubleshooting.html");
}
/* vim:ts=2:sw=2:et:
*/