1 /* Copyright 2012-present Facebook, Inc.
2 * Licensed under the Apache License, Version 2.0 */
3
4 #include "watchman.h"
5 #include "InMemoryView.h"
6 #include "FileSystem.h"
7 #include "watchman_error_category.h"
8
9 /* Returns true if the global config root_restrict_files is not defined or if
10 * one of the files in root_restrict_files exists, false otherwise. */
root_check_restrict(const char * watch_path)11 static bool root_check_restrict(const char *watch_path) {
12 uint32_t i;
13 bool enforcing;
14
15 auto root_restrict_files = cfg_compute_root_files(&enforcing);
16 if (!root_restrict_files) {
17 return true;
18 }
19 if (!enforcing) {
20 return true;
21 }
22
23 for (i = 0; i < json_array_size(root_restrict_files); i++) {
24 auto obj = json_array_get(root_restrict_files, i);
25 const char *restrict_file = json_string_value(obj);
26 char *restrict_path;
27 bool rv;
28
29 if (!restrict_file) {
30 w_log(W_LOG_ERR, "resolve_root: global config root_restrict_files "
31 "element %" PRIu32 " should be a string\n", i);
32 continue;
33 }
34
35 ignore_result(asprintf(&restrict_path, "%s/%s", watch_path, restrict_file));
36 rv = w_path_exists(restrict_path);
37 free(restrict_path);
38 if (rv)
39 return true;
40 }
41
42 return false;
43 }
44
check_allowed_fs(const char * filename,char ** errmsg)45 static bool check_allowed_fs(const char *filename, char **errmsg) {
46 auto fs_type = w_fstype(filename);
47 json_t *illegal_fstypes = NULL;
48 json_t *advice_string;
49 uint32_t i;
50 const char *advice = NULL;
51
52 // Report this to the log always, as it is helpful in understanding
53 // problem reports
54 w_log(
55 W_LOG_ERR,
56 "path %s is on filesystem type %s\n",
57 filename,
58 fs_type.c_str());
59
60 illegal_fstypes = cfg_get_json("illegal_fstypes");
61 if (!illegal_fstypes) {
62 return true;
63 }
64
65 advice_string = cfg_get_json("illegal_fstypes_advice");
66 if (advice_string) {
67 advice = json_string_value(advice_string);
68 }
69 if (!advice) {
70 advice = "relocate the dir to an allowed filesystem type";
71 }
72
73 if (!json_is_array(illegal_fstypes)) {
74 w_log(W_LOG_ERR,
75 "resolve_root: global config illegal_fstypes is not an array\n");
76 return true;
77 }
78
79 for (i = 0; i < json_array_size(illegal_fstypes); i++) {
80 auto obj = json_array_get(illegal_fstypes, i);
81 const char *name = json_string_value(obj);
82
83 if (!name) {
84 w_log(W_LOG_ERR, "resolve_root: global config illegal_fstypes "
85 "element %" PRIu32 " should be a string\n", i);
86 continue;
87 }
88
89 if (!w_string_equal_cstring(fs_type, name)) {
90 continue;
91 }
92
93 ignore_result(asprintf(
94 errmsg,
95 "path uses the \"%s\" filesystem "
96 "and is disallowed by global config illegal_fstypes: %s",
97 fs_type.c_str(),
98 advice));
99
100 return false;
101 }
102
103 return true;
104 }
105
root_resolve(const char * filename,bool auto_watch,bool * created,char ** errmsg)106 std::shared_ptr<w_root_t> root_resolve(
107 const char* filename,
108 bool auto_watch,
109 bool* created,
110 char** errmsg) {
111 std::error_code realpath_err;
112 std::shared_ptr<w_root_t> root;
113
114 *created = false;
115
116 // Sanity check that the path is absolute
117 if (!w_is_path_absolute_cstr(filename)) {
118 ignore_result(asprintf(errmsg, "path \"%s\" must be absolute", filename));
119 w_log(W_LOG_ERR, "resolve_root: %s", *errmsg);
120 return nullptr;
121 }
122
123 if (!strcmp(filename, "/")) {
124 ignore_result(asprintf(errmsg, "cannot watch \"/\""));
125 w_log(W_LOG_ERR, "resolve_root: %s", *errmsg);
126 return nullptr;
127 }
128
129 w_string root_str;
130
131 try {
132 root_str = watchman::realPath(filename);
133 try {
134 watchman::getFileInformation(filename);
135 } catch (const std::system_error& exc) {
136 if (exc.code() == watchman::error_code::no_such_file_or_directory) {
137 ignore_result(asprintf(errmsg, "\"%s\" resolved to \"%s\" but we were "
138 "unable to examine \"%s\" using strict "
139 "case sensitive rules. Please check "
140 "each component of the path and make "
141 "sure that that path exactly matches "
142 "the correct case of the files on your "
143 "filesystem.",
144 filename, root_str.c_str(), filename));
145 w_log(W_LOG_ERR, "resolve_root: %s", *errmsg);
146 return nullptr;
147 }
148 ignore_result(
149 asprintf(errmsg, "unable to lstat \"%s\" %s", filename, exc.what()));
150 w_log(W_LOG_ERR, "resolve_root: %s", *errmsg);
151 return nullptr;
152 }
153 } catch (const std::system_error &exc) {
154 realpath_err = exc.code();
155 root_str = w_string(filename, W_STRING_BYTE);
156 }
157
158 {
159 auto map = watched_roots.rlock();
160 const auto& it = map->find(root_str);
161 if (it != map->end()) {
162 root = it->second;
163 }
164 }
165
166 if (!root && realpath_err.value() != 0) {
167 // Path didn't resolve and neither did the name they passed in
168 ignore_result(asprintf(errmsg, "realpath(%s) -> %s", filename,
169 realpath_err.message().c_str()));
170 w_log(W_LOG_ERR, "resolve_root: %s\n", *errmsg);
171 return nullptr;
172 }
173
174 if (root || !auto_watch) {
175 if (!root) {
176 ignore_result(
177 asprintf(errmsg, "directory %s is not watched", root_str.c_str()));
178 w_log(W_LOG_DBG, "resolve_root: %s\n", *errmsg);
179 }
180
181 if (!root) {
182 return nullptr;
183 }
184
185 // Treat this as new activity for aging purposes; this roughly maps
186 // to a client querying something about the root and should extend
187 // the lifetime of the root
188
189 // Note that this write potentially races with the read in consider_reap
190 // but we're "OK" with it because the latter is performed under a write
191 // lock and the worst case side effect is that we (safely) decide to reap
192 // at the same instant that a new command comes in. The reap intervals
193 // are typically on the order of days.
194 time(&root->inner.last_cmd_timestamp);
195 return root;
196 }
197
198 w_log(W_LOG_DBG, "Want to watch %s -> %s\n", filename, root_str.c_str());
199
200 if (!check_allowed_fs(root_str.c_str(), errmsg)) {
201 w_log(W_LOG_ERR, "resolve_root: %s\n", *errmsg);
202 return nullptr;
203 }
204
205 if (!root_check_restrict(root_str.c_str())) {
206 ignore_result(
207 asprintf(errmsg, "Your watchman administrator has configured watchman "
208 "to prevent watching this path. None of the files "
209 "listed in global config root_files are "
210 "present and enforce_root_files is set to true"));
211 w_log(W_LOG_ERR, "resolve_root: %s\n", *errmsg);
212 return nullptr;
213 }
214
215 // created with 1 ref
216 try {
217 root = std::make_shared<w_root_t>(root_str);
218 } catch (const std::exception& e) {
219 watchman::log(watchman::ERR, "while making a new root: ", e.what());
220 *errmsg = strdup(e.what());
221 }
222
223 if (!root) {
224 return nullptr;
225 }
226
227 {
228 auto wlock = watched_roots.wlock();
229 auto& map = *wlock;
230 auto& existing = map[root->root_path];
231 if (existing) {
232 // Someone beat us in this race
233 root = existing;
234 *created = false;
235 } else {
236 existing = root;
237 *created = true;
238 }
239 }
240
241 return root;
242 }
243
244 std::shared_ptr<w_root_t>
w_root_resolve(const char * filename,bool auto_watch,char ** errmsg)245 w_root_resolve(const char* filename, bool auto_watch, char** errmsg) {
246 bool created = false;
247 auto root = root_resolve(filename, auto_watch, &created, errmsg);
248
249 if (created) {
250 try {
251 root->view()->startThreads(root);
252 } catch (const std::exception& e) {
253 watchman::log(
254 watchman::ERR,
255 "w_root_resolve, while calling startThreads: ",
256 e.what());
257 *errmsg = strdup(e.what());
258 root->cancel();
259 return nullptr;
260 }
261 w_state_save();
262 }
263 return root;
264 }
265
w_root_resolve_for_client_mode(const char * filename,char ** errmsg)266 std::shared_ptr<w_root_t> w_root_resolve_for_client_mode(
267 const char* filename,
268 char** errmsg) {
269 bool created = false;
270 auto root = root_resolve(filename, true, &created, errmsg);
271
272 if (created) {
273 auto view = std::dynamic_pointer_cast<watchman::InMemoryView>(root->view());
274 if (!view) {
275 *errmsg = strdup("client mode not available");
276 return nullptr;
277 }
278
279 /* force a walk now */
280 view->clientModeCrawl(root);
281 }
282 return root;
283 }
284
285 /* vim:ts=2:sw=2:et:
286 */
287