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