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