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