1 /* Copyright 2012-present Facebook, Inc.
2  * Licensed under the Apache License, Version 2.0 */
3 
4 #include "watchman_system.h"
5 #include "make_unique.h"
6 #include "watchman.h"
7 
8 using watchman::ChildProcess;
9 using watchman::FileDescriptor;
10 using Options = watchman::ChildProcess::Options;
11 using Environment = watchman::ChildProcess::Environment;
12 
13 static std::unique_ptr<watchman_stream> prepare_stdin(
14     struct watchman_trigger_command* cmd,
15     w_query_res* res) {
16   char stdin_file_name[WATCHMAN_NAME_MAX];
17 
18   if (cmd->stdin_style == trigger_input_style::input_dev_null) {
19     return w_stm_open("/dev/null", O_RDONLY|O_CLOEXEC);
20   }
21 
22   // Adjust result to fit within the specified limit
23   if (cmd->max_files_stdin > 0) {
24     auto& fileList = res->resultsArray.array();
25     auto n_files = std::min(size_t(cmd->max_files_stdin), fileList.size());
26     fileList.resize(std::min(fileList.size(), n_files));
27   }
28 
29   /* prepare the input stream for the child process */
30   snprintf(
31       stdin_file_name,
32       sizeof(stdin_file_name),
33       "%s/wmanXXXXXX",
34       watchman_tmp_dir);
35   auto stdin_file = w_mkstemp(stdin_file_name);
36   if (!stdin_file) {
37     w_log(W_LOG_ERR, "unable to create a temporary file: %s %s\n",
38         stdin_file_name, strerror(errno));
39     return NULL;
40   }
41 
42   /* unlink the file, we don't need it in the filesystem;
43    * we'll pass the fd on to the child as stdin */
44   unlink(stdin_file_name); // FIXME: windows path translation
45 
46   switch (cmd->stdin_style) {
47     case input_json:
48       {
49         w_jbuffer_t buffer;
50 
51         w_log(W_LOG_ERR, "input_json: sending json object to stm\n");
52         if (!buffer.jsonEncodeToStream(
53                 res->resultsArray, stdin_file.get(), 0)) {
54           w_log(W_LOG_ERR,
55               "input_json: failed to write json data to stream: %s\n",
56               strerror(errno));
57           return NULL;
58         }
59         break;
60       }
61     case input_name_list:
62       for (auto& name : res->resultsArray.array()) {
63         auto& nameStr = json_to_w_string(name);
64         if (stdin_file->write(nameStr.data(), nameStr.size()) !=
65                 (int)nameStr.size() ||
66             stdin_file->write("\n", 1) != 1) {
67           w_log(
68               W_LOG_ERR,
69               "write failure while producing trigger stdin: %s\n",
70               strerror(errno));
71           return nullptr;
72         }
73       }
74       break;
75     case input_dev_null:
76       // already handled above
77       break;
78   }
79 
80   stdin_file->rewind();
81   return stdin_file;
82 }
83 
84 static void spawn_command(
85     const std::shared_ptr<w_root_t>& root,
86     struct watchman_trigger_command* cmd,
87     w_query_res* res,
88     struct ClockSpec* since_spec) {
89   long arg_max;
90   size_t argspace_remaining;
91   bool file_overflow = false;
92 
93 #ifdef _WIN32
94   arg_max = 32*1024;
95 #else
96   arg_max = sysconf(_SC_ARG_MAX);
97 #endif
98 
99   if (arg_max <= 0) {
100     argspace_remaining = UINT_MAX;
101   } else {
102     argspace_remaining = (uint32_t)arg_max;
103   }
104 
105   // Allow some misc working overhead
106   argspace_remaining -= 32;
107 
108   // Record an overflow before we call prepare_stdin(), which mutates
109   // and resizes the results to fit the specified limit.
110   if (cmd->max_files_stdin > 0 &&
111       res->resultsArray.array().size() > cmd->max_files_stdin) {
112     file_overflow = true;
113   }
114 
115   auto stdin_file = prepare_stdin(cmd, res);
116   if (!stdin_file) {
117     w_log(
118         W_LOG_ERR,
119         "trigger %s:%s %s\n",
120         root->root_path.c_str(),
121         cmd->triggername.c_str(),
122         strerror(errno));
123     return;
124   }
125 
126   // Assumption: that only one thread will be executing on a given
127   // cmd instance so that mutation of cmd->env is safe.
128   // This is guaranteed in the current architecture.
129 
130   // It is way too much of a hassle to try to recreate the clock value if it's
131   // not a relative clock spec, and it's only going to happen on the first run
132   // anyway, so just skip doing that entirely.
133   if (since_spec && since_spec->tag == w_cs_clock) {
134     cmd->env.set("WATCHMAN_SINCE", since_spec->clock.position.toClockString());
135   } else {
136     cmd->env.unset("WATCHMAN_SINCE");
137   }
138 
139   cmd->env.set(
140       "WATCHMAN_CLOCK", res->clockAtStartOfQuery.position().toClockString());
141 
142   if (cmd->query->relative_root) {
143     cmd->env.set("WATCHMAN_RELATIVE_ROOT", cmd->query->relative_root);
144   } else {
145     cmd->env.unset("WATCHMAN_RELATIVE_ROOT");
146   }
147 
148   // Compute args
149   auto args = json_deep_copy(cmd->command);
150 
151   if (cmd->append_files) {
152     // Measure how much space the base args take up
153     for (size_t i = 0; i < json_array_size(args); i++) {
154       const char *ele = json_string_value(json_array_get(args, i));
155 
156       argspace_remaining -= strlen(ele) + 1 + sizeof(char*);
157     }
158 
159     // Dry run with env to compute space
160     size_t env_size;
161     cmd->env.asEnviron(&env_size);
162     argspace_remaining -= env_size;
163 
164     for (const auto& item : res->dedupedFileNames) {
165       // also: NUL terminator and entry in argv
166       uint32_t size = item.size() + 1 + sizeof(char*);
167 
168       if (argspace_remaining < size) {
169         file_overflow = true;
170         break;
171       }
172       argspace_remaining -= size;
173 
174       json_array_append_new(args, w_string_to_json(item));
175     }
176   }
177 
178   cmd->env.set("WATCHMAN_FILES_OVERFLOW", file_overflow);
179 
180   Options opts;
181   opts.environment() = cmd->env;
182 #ifndef _WIN32
183   sigset_t mask;
184   sigemptyset(&mask);
185   opts.setSigMask(mask);
186 #endif
187   opts.setFlags(POSIX_SPAWN_SETPGROUP);
188 
189   opts.dup2(stdin_file->getFileDescriptor(), STDIN_FILENO);
190 
191   if (cmd->stdout_name) {
192     opts.open(STDOUT_FILENO, cmd->stdout_name, cmd->stdout_flags, 0666);
193   } else {
194     opts.dup2(FileDescriptor::stdOut(), STDOUT_FILENO);
195   }
196 
197   if (cmd->stderr_name) {
198     opts.open(STDOUT_FILENO, cmd->stderr_name, cmd->stderr_flags, 0666);
199   } else {
200     opts.dup2(FileDescriptor::stdErr(), STDERR_FILENO);
201   }
202 
203   // Figure out the appropriate cwd
204   w_string working_dir(cmd->query->relative_root);
205   if (!working_dir) {
206     working_dir = root->root_path;
207   }
208 
209   auto cwd = cmd->definition.get_default("chdir");
210   if (cwd) {
211     auto target = json_to_w_string(cwd);
212     if (w_is_path_absolute_cstr_len(target.data(), target.size())) {
213       working_dir = target;
214     } else {
215       working_dir = w_string::pathCat({working_dir, target});
216     }
217   }
218 
219   watchman::log(watchman::DBG, "using ", working_dir, " for working dir\n");
220   opts.chdir(working_dir.c_str());
221 
222   try {
223     if (cmd->current_proc) {
224       cmd->current_proc->kill();
225       cmd->current_proc->wait();
226     }
227     cmd->current_proc =
228         watchman::make_unique<ChildProcess>(args, std::move(opts));
229   } catch (const std::exception& exc) {
230     watchman::log(
231         watchman::ERR,
232         "trigger ",
233         root->root_path,
234         ":",
235         cmd->triggername,
236         " failed: ",
237         exc.what(),
238         "\n");
239   }
240 
241   // We have integration tests that check for this string
242   watchman::log(
243       cmd->current_proc ? watchman::DBG : watchman::ERR,
244       "posix_spawnp: ",
245       cmd->triggername,
246       "\n");
247 }
248 
249 bool watchman_trigger_command::maybeSpawn(
250     const std::shared_ptr<w_root_t>& root) {
251   bool didRun = false;
252 
253   // If it looks like we're in a repo undergoing a rebase or
254   // other similar operation, we want to defer triggers until
255   // things settle down
256   if (root->view()->isVCSOperationInProgress()) {
257     w_log(W_LOG_DBG, "deferring triggers until VCS operations complete\n");
258     return false;
259   }
260 
261   auto since_spec = query->since_spec.get();
262 
263   if (since_spec && since_spec->tag == w_cs_clock) {
264     w_log(
265         W_LOG_DBG,
266         "running trigger \"%s\" rules! since %" PRIu32 "\n",
267         triggername.c_str(),
268         since_spec->clock.position.ticks);
269   } else {
270     w_log(W_LOG_DBG, "running trigger \"%s\" rules!\n", triggername.c_str());
271   }
272 
273   // Triggers never need to sync explicitly; we are only dispatched
274   // at settle points which are by definition sync'd to the present time
275   query->sync_timeout = std::chrono::milliseconds(0);
276   watchman::log(watchman::DBG, "assessing trigger ", triggername, "\n");
277   try {
278     auto res = w_query_execute(query.get(), root, time_generator);
279 
280     watchman::log(
281         watchman::DBG,
282         "trigger \"",
283         triggername,
284         "\" generated ",
285         res.resultsArray.array().size(),
286         " results\n");
287 
288     // create a new spec that will be used the next time
289     auto saved_spec = std::move(query->since_spec);
290     query->since_spec =
291         watchman::make_unique<ClockSpec>(res.clockAtStartOfQuery);
292 
293     watchman::log(
294         watchman::DBG,
295         "updating trigger \"",
296         triggername,
297         "\" use ",
298         res.clockAtStartOfQuery.position().ticks,
299         " ticks next time\n");
300 
301     if (!res.resultsArray.array().empty()) {
302       didRun = true;
303       spawn_command(root, this, &res, saved_spec.get());
304     }
305     return didRun;
306   } catch (const QueryExecError& e) {
307     watchman::log(
308         watchman::ERR,
309         "error running trigger \"",
310         triggername,
311         "\" query: ",
312         e.what(),
313         "\n");
314     return false;
315   }
316 }
317 
318 /* vim:ts=2:sw=2:et:
319  */
320