fsmonitor--daemon: implement 'run' command
[git/debian.git] / builtin / fsmonitor--daemon.c
blob5591339399a8ca94d7934124568580932be6418e
1 #include "builtin.h"
2 #include "config.h"
3 #include "parse-options.h"
4 #include "fsmonitor.h"
5 #include "fsmonitor-ipc.h"
6 #include "compat/fsmonitor/fsm-listen.h"
7 #include "fsmonitor--daemon.h"
8 #include "simple-ipc.h"
9 #include "khash.h"
11 static const char * const builtin_fsmonitor__daemon_usage[] = {
12 N_("git fsmonitor--daemon run [<options>]"),
13 N_("git fsmonitor--daemon stop"),
14 N_("git fsmonitor--daemon status"),
15 NULL
18 #ifdef HAVE_FSMONITOR_DAEMON_BACKEND
20 * Global state loaded from config.
22 #define FSMONITOR__IPC_THREADS "fsmonitor.ipcthreads"
23 static int fsmonitor__ipc_threads = 8;
25 #define FSMONITOR__ANNOUNCE_STARTUP "fsmonitor.announcestartup"
26 static int fsmonitor__announce_startup = 0;
28 static int fsmonitor_config(const char *var, const char *value, void *cb)
30 if (!strcmp(var, FSMONITOR__IPC_THREADS)) {
31 int i = git_config_int(var, value);
32 if (i < 1)
33 return error(_("value of '%s' out of range: %d"),
34 FSMONITOR__IPC_THREADS, i);
35 fsmonitor__ipc_threads = i;
36 return 0;
39 if (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {
40 int is_bool;
41 int i = git_config_bool_or_int(var, value, &is_bool);
42 if (i < 0)
43 return error(_("value of '%s' not bool or int: %d"),
44 var, i);
45 fsmonitor__announce_startup = i;
46 return 0;
49 return git_default_config(var, value, cb);
53 * Acting as a CLIENT.
55 * Send a "quit" command to the `git-fsmonitor--daemon` (if running)
56 * and wait for it to shutdown.
58 static int do_as_client__send_stop(void)
60 struct strbuf answer = STRBUF_INIT;
61 int ret;
63 ret = fsmonitor_ipc__send_command("quit", &answer);
65 /* The quit command does not return any response data. */
66 strbuf_release(&answer);
68 if (ret)
69 return ret;
71 trace2_region_enter("fsm_client", "polling-for-daemon-exit", NULL);
72 while (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
73 sleep_millisec(50);
74 trace2_region_leave("fsm_client", "polling-for-daemon-exit", NULL);
76 return 0;
79 static int do_as_client__status(void)
81 enum ipc_active_state state = fsmonitor_ipc__get_state();
83 switch (state) {
84 case IPC_STATE__LISTENING:
85 printf(_("fsmonitor-daemon is watching '%s'\n"),
86 the_repository->worktree);
87 return 0;
89 default:
90 printf(_("fsmonitor-daemon is not watching '%s'\n"),
91 the_repository->worktree);
92 return 1;
96 static ipc_server_application_cb handle_client;
98 static int handle_client(void *data,
99 const char *command, size_t command_len,
100 ipc_server_reply_cb *reply,
101 struct ipc_server_reply_data *reply_data)
103 /* struct fsmonitor_daemon_state *state = data; */
104 int result;
107 * The Simple IPC API now supports {char*, len} arguments, but
108 * FSMonitor always uses proper null-terminated strings, so
109 * we can ignore the command_len argument. (Trust, but verify.)
111 if (command_len != strlen(command))
112 BUG("FSMonitor assumes text messages");
114 trace2_region_enter("fsmonitor", "handle_client", the_repository);
115 trace2_data_string("fsmonitor", the_repository, "request", command);
117 result = 0; /* TODO Do something here. */
119 trace2_region_leave("fsmonitor", "handle_client", the_repository);
121 return result;
124 static void *fsm_listen__thread_proc(void *_state)
126 struct fsmonitor_daemon_state *state = _state;
128 trace2_thread_start("fsm-listen");
130 trace_printf_key(&trace_fsmonitor, "Watching: worktree '%s'",
131 state->path_worktree_watch.buf);
132 if (state->nr_paths_watching > 1)
133 trace_printf_key(&trace_fsmonitor, "Watching: gitdir '%s'",
134 state->path_gitdir_watch.buf);
136 fsm_listen__loop(state);
138 trace2_thread_exit();
139 return NULL;
142 static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
144 struct ipc_server_opts ipc_opts = {
145 .nr_threads = fsmonitor__ipc_threads,
148 * We know that there are no other active threads yet,
149 * so we can let the IPC layer temporarily chdir() if
150 * it needs to when creating the server side of the
151 * Unix domain socket.
153 .uds_disallow_chdir = 0
157 * Start the IPC thread pool before the we've started the file
158 * system event listener thread so that we have the IPC handle
159 * before we need it.
161 if (ipc_server_run_async(&state->ipc_server_data,
162 fsmonitor_ipc__get_path(), &ipc_opts,
163 handle_client, state))
164 return error_errno(
165 _("could not start IPC thread pool on '%s'"),
166 fsmonitor_ipc__get_path());
169 * Start the fsmonitor listener thread to collect filesystem
170 * events.
172 if (pthread_create(&state->listener_thread, NULL,
173 fsm_listen__thread_proc, state) < 0) {
174 ipc_server_stop_async(state->ipc_server_data);
175 ipc_server_await(state->ipc_server_data);
177 return error(_("could not start fsmonitor listener thread"));
181 * The daemon is now fully functional in background threads.
182 * Wait for the IPC thread pool to shutdown (whether by client
183 * request or from filesystem activity).
185 ipc_server_await(state->ipc_server_data);
188 * The fsmonitor listener thread may have received a shutdown
189 * event from the IPC thread pool, but it doesn't hurt to tell
190 * it again. And wait for it to shutdown.
192 fsm_listen__stop_async(state);
193 pthread_join(state->listener_thread, NULL);
195 return state->error_code;
198 static int fsmonitor_run_daemon(void)
200 struct fsmonitor_daemon_state state;
201 int err;
203 memset(&state, 0, sizeof(state));
205 pthread_mutex_init(&state.main_lock, NULL);
206 state.error_code = 0;
207 state.current_token_data = NULL;
209 /* Prepare to (recursively) watch the <worktree-root> directory. */
210 strbuf_init(&state.path_worktree_watch, 0);
211 strbuf_addstr(&state.path_worktree_watch, absolute_path(get_git_work_tree()));
212 state.nr_paths_watching = 1;
215 * We create and delete cookie files somewhere inside the .git
216 * directory to help us keep sync with the file system. If
217 * ".git" is not a directory, then <gitdir> is not inside the
218 * cone of <worktree-root>, so set up a second watch to watch
219 * the <gitdir> so that we get events for the cookie files.
221 strbuf_init(&state.path_gitdir_watch, 0);
222 strbuf_addbuf(&state.path_gitdir_watch, &state.path_worktree_watch);
223 strbuf_addstr(&state.path_gitdir_watch, "/.git");
224 if (!is_directory(state.path_gitdir_watch.buf)) {
225 strbuf_reset(&state.path_gitdir_watch);
226 strbuf_addstr(&state.path_gitdir_watch, absolute_path(get_git_dir()));
227 state.nr_paths_watching = 2;
231 * Confirm that we can create platform-specific resources for the
232 * filesystem listener before we bother starting all the threads.
234 if (fsm_listen__ctor(&state)) {
235 err = error(_("could not initialize listener thread"));
236 goto done;
239 err = fsmonitor_run_daemon_1(&state);
241 done:
242 pthread_mutex_destroy(&state.main_lock);
243 fsm_listen__dtor(&state);
245 ipc_server_free(state.ipc_server_data);
247 strbuf_release(&state.path_worktree_watch);
248 strbuf_release(&state.path_gitdir_watch);
250 return err;
253 static int try_to_run_foreground_daemon(void)
256 * Technically, we don't need to probe for an existing daemon
257 * process, since we could just call `fsmonitor_run_daemon()`
258 * and let it fail if the pipe/socket is busy.
260 * However, this method gives us a nicer error message for a
261 * common error case.
263 if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
264 die(_("fsmonitor--daemon is already running '%s'"),
265 the_repository->worktree);
267 if (fsmonitor__announce_startup) {
268 fprintf(stderr, _("running fsmonitor-daemon in '%s'\n"),
269 the_repository->worktree);
270 fflush(stderr);
273 return !!fsmonitor_run_daemon();
276 int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
278 const char *subcmd;
280 struct option options[] = {
281 OPT_INTEGER(0, "ipc-threads",
282 &fsmonitor__ipc_threads,
283 N_("use <n> ipc worker threads")),
284 OPT_END()
287 git_config(fsmonitor_config, NULL);
289 argc = parse_options(argc, argv, prefix, options,
290 builtin_fsmonitor__daemon_usage, 0);
291 if (argc != 1)
292 usage_with_options(builtin_fsmonitor__daemon_usage, options);
293 subcmd = argv[0];
295 if (fsmonitor__ipc_threads < 1)
296 die(_("invalid 'ipc-threads' value (%d)"),
297 fsmonitor__ipc_threads);
299 if (!strcmp(subcmd, "run"))
300 return !!try_to_run_foreground_daemon();
302 if (!strcmp(subcmd, "stop"))
303 return !!do_as_client__send_stop();
305 if (!strcmp(subcmd, "status"))
306 return !!do_as_client__status();
308 die(_("Unhandled subcommand '%s'"), subcmd);
311 #else
312 int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
314 struct option options[] = {
315 OPT_END()
318 if (argc == 2 && !strcmp(argv[1], "-h"))
319 usage_with_options(builtin_fsmonitor__daemon_usage, options);
321 die(_("fsmonitor--daemon not supported on this platform"));
323 #endif