1 /* Copyright 2013-present Facebook, Inc.
2 * Licensed under the Apache License, Version 2.0 */
3
4 #include "watchman.h"
5
6 /* trigger-del /root triggername
7 * Delete a trigger from a root
8 */
cmd_trigger_delete(struct watchman_client * client,json_t * args)9 static void cmd_trigger_delete(struct watchman_client *client, json_t *args)
10 {
11 w_root_t *root;
12 json_t *resp;
13 const char *name;
14 w_string_t *tname;
15 bool res;
16
17 root = resolve_root_or_err(client, args, 1, false);
18 if (!root) {
19 return;
20 }
21
22 if (json_array_size(args) != 3) {
23 send_error_response(client, "wrong number of arguments");
24 w_root_delref(root);
25 return;
26 }
27 name = json_string_value(json_array_get(args, 2));
28 if (!name) {
29 send_error_response(client, "expected 2nd parameter to be trigger name");
30 w_root_delref(root);
31 return;
32 }
33 tname = w_string_new(name);
34
35 w_root_lock(root);
36 res = w_ht_del(root->commands, w_ht_ptr_val(tname));
37 w_root_unlock(root);
38
39 if (res) {
40 w_state_save();
41 }
42
43 w_string_delref(tname);
44
45 resp = make_response();
46 set_prop(resp, "deleted", json_boolean(res));
47 set_prop(resp, "trigger", json_string_nocheck(name));
48 send_and_dispose_response(client, resp);
49 w_root_delref(root);
50 }
51 W_CMD_REG("trigger-del", cmd_trigger_delete, CMD_DAEMON, w_cmd_realpath_root)
52
53 /* trigger-list /root
54 * Displays a list of registered triggers for a given root
55 */
cmd_trigger_list(struct watchman_client * client,json_t * args)56 static void cmd_trigger_list(struct watchman_client *client, json_t *args)
57 {
58 w_root_t *root;
59 json_t *resp;
60 json_t *arr;
61
62 root = resolve_root_or_err(client, args, 1, false);
63 if (!root) {
64 return;
65 }
66
67 resp = make_response();
68 w_root_lock(root);
69 arr = w_root_trigger_list_to_json(root);
70 w_root_unlock(root);
71
72 set_prop(resp, "triggers", arr);
73 send_and_dispose_response(client, resp);
74 w_root_delref(root);
75 }
76 W_CMD_REG("trigger-list", cmd_trigger_list, CMD_DAEMON, w_cmd_realpath_root)
77
build_legacy_trigger(w_root_t * root,struct watchman_client * client,json_t * args)78 static json_t *build_legacy_trigger(
79 w_root_t *root,
80 struct watchman_client *client,
81 json_t *args)
82 {
83 json_t *trig, *expr;
84 json_t *command;
85 char *errmsg;
86 uint32_t next_arg = 0;
87 uint32_t i;
88 size_t n;
89 w_query *query;
90
91 trig = json_pack("{s:O, s:b, s:[s, s, s, s, s]}",
92 "name", json_array_get(args, 2),
93 "append_files", true,
94 "stdin",
95 // [
96 "name", "exists", "new", "size", "mode"
97 // ]
98 );
99
100 query = w_query_parse_legacy(root, args, &errmsg, 3, &next_arg, NULL, &expr);
101 if (!query) {
102 send_error_response(client, "invalid rule spec: %s", errmsg);
103 free(errmsg);
104 json_decref(trig);
105 return NULL;
106 }
107 w_query_delref(query);
108
109 json_object_set(trig, "expression", json_object_get(expr, "expression"));
110 json_decref(expr);
111
112 if (next_arg >= json_array_size(args)) {
113 send_error_response(client, "no command was specified");
114 json_decref(trig);
115 return NULL;
116 }
117
118 n = json_array_size(args) - next_arg;
119 command = json_array_of_size(n);
120 for (i = 0; i < n; i++) {
121 json_t *ele = json_array_get(args, i + next_arg);
122 if (!json_is_string(ele)) {
123 send_error_response(client, "expected argument %d to be a string", i);
124 json_decref(trig);
125 return NULL;
126 }
127 json_array_append(command, ele);
128 }
129 json_object_set_new(trig, "command", command);
130
131 return trig;
132 }
133
parse_redirection(const char ** name_p,int * flags,const char * label,char ** errmsg)134 static bool parse_redirection(const char **name_p, int *flags,
135 const char *label, char **errmsg)
136 {
137 const char *name = *name_p;
138
139 if (!name) {
140 return true;
141 }
142
143 if (name[0] != '>') {
144 ignore_result(asprintf(errmsg,
145 "%s: must be prefixed with either > or >>, got %s",
146 label, name));
147 return false;
148 }
149
150 *flags = O_CREAT|O_CLOEXEC|O_WRONLY;
151
152 if (name[1] == '>') {
153 #ifdef _WIN32
154 ignore_result(asprintf(errmsg,
155 "Windows does not support O_APPEND"));
156 return false;
157 #else
158 *flags |= O_APPEND;
159 *name_p = name + 2;
160 #endif
161 } else {
162 *flags |= O_TRUNC;
163 *name_p = name + 1;
164 }
165
166 return true;
167 }
168
w_trigger_command_free(struct watchman_trigger_command * cmd)169 void w_trigger_command_free(struct watchman_trigger_command *cmd)
170 {
171 if (cmd->triggername) {
172 w_string_delref(cmd->triggername);
173 }
174
175 if (cmd->command) {
176 json_decref(cmd->command);
177 }
178
179 if (cmd->definition) {
180 json_decref(cmd->definition);
181 }
182
183 if (cmd->query) {
184 w_query_delref(cmd->query);
185 }
186
187 if (cmd->envht) {
188 w_ht_free(cmd->envht);
189 }
190
191 free(cmd);
192 }
193
w_build_trigger_from_def(w_root_t * root,json_t * trig,char ** errmsg)194 struct watchman_trigger_command *w_build_trigger_from_def(
195 w_root_t *root, json_t *trig, char **errmsg)
196 {
197 struct watchman_trigger_command *cmd;
198 json_t *ele, *query, *relative_root;
199 json_int_t jint;
200 const char *name = NULL;
201
202 cmd = calloc(1, sizeof(*cmd));
203 if (!cmd) {
204 *errmsg = strdup("no memory");
205 return NULL;
206 }
207
208 cmd->definition = trig;
209 json_incref(cmd->definition);
210
211 query = json_pack("{s:O}", "expression",
212 json_object_get(cmd->definition, "expression"));
213 relative_root = json_object_get(cmd->definition, "relative_root");
214 if (relative_root) {
215 json_object_set_nocheck(query, "relative_root", relative_root);
216 }
217
218 cmd->query = w_query_parse(root, query, errmsg);
219 json_decref(query);
220
221 if (!cmd->query) {
222 w_trigger_command_free(cmd);
223 return NULL;
224 }
225
226 json_unpack(trig, "{s:s}", "name", &name);
227 if (!name) {
228 *errmsg = strdup("invalid or missing name");
229 w_trigger_command_free(cmd);
230 return NULL;
231 }
232
233 cmd->triggername = w_string_new(name);
234 cmd->command = json_object_get(trig, "command");
235 if (cmd->command) {
236 json_incref(cmd->command);
237 }
238 if (!cmd->command || !json_is_array(cmd->command) ||
239 !json_array_size(cmd->command)) {
240 *errmsg = strdup("invalid command array");
241 w_trigger_command_free(cmd);
242 return NULL;
243 }
244
245 json_unpack(trig, "{s:b}", "append_files", &cmd->append_files);
246
247 ele = json_object_get(trig, "stdin");
248 if (!ele) {
249 cmd->stdin_style = input_dev_null;
250 } else if (json_is_array(ele)) {
251 cmd->stdin_style = input_json;
252 if (!parse_field_list(ele, &cmd->field_list, errmsg)) {
253 w_trigger_command_free(cmd);
254 return NULL;
255 }
256 } else if (json_is_string(ele)) {
257 const char *str = json_string_value(ele);
258 if (!strcmp(str, "/dev/null")) {
259 cmd->stdin_style = input_dev_null;
260 } else if (!strcmp(str, "NAME_PER_LINE")) {
261 cmd->stdin_style = input_name_list;
262 } else {
263 ignore_result(asprintf(errmsg, "invalid stdin value %s", str));
264 w_trigger_command_free(cmd);
265 return NULL;
266 }
267 } else {
268 *errmsg = strdup("invalid value for stdin");
269 w_trigger_command_free(cmd);
270 return NULL;
271 }
272
273 jint = 0; // unlimited unless specified
274 json_unpack(trig, "{s:I}", "max_files_stdin", &jint);
275 if (jint < 0) {
276 *errmsg = strdup("max_files_stdin must be >= 0");
277 w_trigger_command_free(cmd);
278 return NULL;
279 }
280 cmd->max_files_stdin = (uint32_t)jint;
281
282 json_unpack(trig, "{s:s}", "stdout", &cmd->stdout_name);
283 json_unpack(trig, "{s:s}", "stderr", &cmd->stderr_name);
284
285 if (!parse_redirection(&cmd->stdout_name, &cmd->stdout_flags,
286 "stdout", errmsg)) {
287 w_trigger_command_free(cmd);
288 return NULL;
289 }
290
291 if (!parse_redirection(&cmd->stderr_name, &cmd->stderr_flags,
292 "stderr", errmsg)) {
293 w_trigger_command_free(cmd);
294 return NULL;
295 }
296
297 // Copy current environment
298 cmd->envht = w_envp_make_ht();
299
300 // Set some standard vars
301 w_envp_set(cmd->envht, "WATCHMAN_ROOT", root->root_path);
302 w_envp_set_cstring(cmd->envht, "WATCHMAN_SOCK", get_sock_name());
303 w_envp_set(cmd->envht, "WATCHMAN_TRIGGER", cmd->triggername);
304
305 return cmd;
306 }
307
308 /* trigger /root triggername [watch patterns] -- cmd to run
309 * Sets up a trigger so that we can execute a command when a change
310 * is detected */
cmd_trigger(struct watchman_client * client,json_t * args)311 static void cmd_trigger(struct watchman_client *client, json_t *args)
312 {
313 w_root_t *root;
314 struct watchman_trigger_command *cmd, *old;
315 json_t *resp;
316 json_t *trig;
317 char *errmsg = NULL;
318 bool need_save = true;
319
320 root = resolve_root_or_err(client, args, 1, true);
321 if (!root) {
322 return;
323 }
324
325 if (json_array_size(args) < 3) {
326 send_error_response(client, "not enough arguments");
327 goto done;
328 }
329
330 trig = json_array_get(args, 2);
331 if (json_is_string(trig)) {
332 trig = build_legacy_trigger(root, client, args);
333 if (!trig) {
334 goto done;
335 }
336 } else {
337 // Add a ref so that we don't need to conditionally decref later
338 // for the legacy case later
339 json_incref(trig);
340 }
341
342 cmd = w_build_trigger_from_def(root, trig, &errmsg);
343 json_decref(trig);
344
345 if (!cmd) {
346 send_error_response(client, "%s", errmsg);
347 goto done;
348 }
349
350 resp = make_response();
351 set_prop(resp, "triggerid", w_string_to_json(cmd->triggername));
352
353 w_root_lock(root);
354
355 old = w_ht_val_ptr(w_ht_get(root->commands,
356 w_ht_ptr_val(cmd->triggername)));
357 if (old && json_equal(cmd->definition, old->definition)) {
358 // Same definition: we don't and shouldn't touch things, so that we
359 // preserve the associated trigger clock and don't cause the trigger
360 // to re-run immediately
361 set_prop(resp, "disposition", json_string_nocheck("already_defined"));
362 w_trigger_command_free(cmd);
363 cmd = NULL;
364 need_save = false;
365 } else {
366 set_prop(resp, "disposition", json_string_nocheck(
367 old ? "replaced" : "created"));
368 w_ht_replace(root->commands, w_ht_ptr_val(cmd->triggername),
369 w_ht_ptr_val(cmd));
370 // Force the trigger to be eligible to run now
371 root->ticks++;
372 root->pending_trigger_tick = root->ticks;
373 }
374 w_root_unlock(root);
375
376 if (need_save) {
377 w_state_save();
378 }
379
380 send_and_dispose_response(client, resp);
381
382 done:
383 if (errmsg) {
384 free(errmsg);
385 }
386 w_root_delref(root);
387 }
388 W_CMD_REG("trigger", cmd_trigger, CMD_DAEMON, w_cmd_realpath_root)
389
390 /* vim:ts=2:sw=2:et:
391 */
392