fsmonitor--daemon: implement 'run' command
Implement `run` command to try to begin listening for file system events. This version defines the thread structure with a single fsmonitor_fs_listen thread to watch for file system events and a simple IPC thread pool to watch for connection from Git clients over a well-known named pipe or Unix domain socket. This commit does not actually do anything yet because the platform backends are still just stubs. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jeff Hostetler committed
Mar 25, 2022 at 18:02 UTC
9dcba0ba08d0e843eda1f141c99c72e0aa67811f
2 files changed
+261
-1
builtin/fsmonitor--daemon.c
+227
-1
@@ -3,16 +3,52 @@
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"
10
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
16
};
17
18
#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
19
+/*
20
+ * Global state loaded from config.
21
+ */
22
+#define FSMONITOR__IPC_THREADS "fsmonitor.ipcthreads"
23
+static int fsmonitor__ipc_threads = 8;
24
+
25
+#define FSMONITOR__ANNOUNCE_STARTUP "fsmonitor.announcestartup"
26
+static int fsmonitor__announce_startup = 0;
27
+
28
+static int fsmonitor_config(const char *var, const char *value, void *cb)
29
+{
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;
37
+ }
38
+
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;
47
+ }
48
+
49
+ return git_default_config(var, value, cb);
50
+}
51
+
52
/*
53
* Acting as a CLIENT.
54
*
@@ -57,15 +93,198 @@ static int do_as_client__status(void)
93
}
94
}
95
96
+static ipc_server_application_cb handle_client;
97
+
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)
102
+{
103
+ /* struct fsmonitor_daemon_state *state = data; */
104
+ int result;
105
+
106
+ /*
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.)
110
+ */
111
+ if (command_len != strlen(command))
112
+ BUG("FSMonitor assumes text messages");
113
+
114
+ trace2_region_enter("fsmonitor", "handle_client", the_repository);
115
+ trace2_data_string("fsmonitor", the_repository, "request", command);
116
+
117
+ result = 0; /* TODO Do something here. */
118
+
119
+ trace2_region_leave("fsmonitor", "handle_client", the_repository);
120
+
121
+ return result;
122
+}
123
+
124
+static void *fsm_listen__thread_proc(void *_state)
125
+{
126
+ struct fsmonitor_daemon_state *state = _state;
127
+
128
+ trace2_thread_start("fsm-listen");
129
+
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);
135
+
136
+ fsm_listen__loop(state);
137
+
138
+ trace2_thread_exit();
139
+ return NULL;
140
+}
141
+
142
+static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
143
+{
144
+ struct ipc_server_opts ipc_opts = {
145
+ .nr_threads = fsmonitor__ipc_threads,
146
+
147
+ /*
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.
152
+ */
153
+ .uds_disallow_chdir = 0
154
+ };
155
+
156
+ /*
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.
160
+ */
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());
167
+
168
+ /*
169
+ * Start the fsmonitor listener thread to collect filesystem
170
+ * events.
171
+ */
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);
176
+
177
+ return error(_("could not start fsmonitor listener thread"));
178
+ }
179
+
180
+ /*
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).
184
+ */
185
+ ipc_server_await(state->ipc_server_data);
186
+
187
+ /*
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.
191
+ */
192
+ fsm_listen__stop_async(state);
193
+ pthread_join(state->listener_thread, NULL);
194
+
195
+ return state->error_code;
196
+}
197
+
198
+static int fsmonitor_run_daemon(void)
199
+{
200
+ struct fsmonitor_daemon_state state;
201
+ int err;
202
+
203
+ memset(&state, 0, sizeof(state));
204
+
205
+ pthread_mutex_init(&state.main_lock, NULL);
206
+ state.error_code = 0;
207
+ state.current_token_data = NULL;
208
+
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;
213
+
214
+ /*
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.
220
+ */
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;
228
+ }
229
+
230
+ /*
231
+ * Confirm that we can create platform-specific resources for the
232
+ * filesystem listener before we bother starting all the threads.
233
+ */
234
+ if (fsm_listen__ctor(&state)) {
235
+ err = error(_("could not initialize listener thread"));
236
+ goto done;
237
+ }
238
+
239
+ err = fsmonitor_run_daemon_1(&state);
240
+
241
+done:
242
+ pthread_mutex_destroy(&state.main_lock);
243
+ fsm_listen__dtor(&state);
244
+
245
+ ipc_server_free(state.ipc_server_data);
246
+
247
+ strbuf_release(&state.path_worktree_watch);
248
+ strbuf_release(&state.path_gitdir_watch);
249
+
250
+ return err;
251
+}
252
+
253
+static int try_to_run_foreground_daemon(void)
254
+{
255
+ /*
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.
259
+ *
260
+ * However, this method gives us a nicer error message for a
261
+ * common error case.
262
+ */
263
+ if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
264
+ die(_("fsmonitor--daemon is already running '%s'"),
265
+ the_repository->worktree);
266
+
267
+ if (fsmonitor__announce_startup) {
268
+ fprintf(stderr, _("running fsmonitor-daemon in '%s'\n"),
269
+ the_repository->worktree);
270
+ fflush(stderr);
271
+ }
272
+
273
+ return !!fsmonitor_run_daemon();
274
+}
275
+
276
int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
277
{
278
const char *subcmd;
279
280
struct option options[] = {
281
+ OPT_INTEGER(0, "ipc-threads",
282
+ &fsmonitor__ipc_threads,
283
+ N_("use <n> ipc worker threads")),
284
OPT_END()
285
};
286
68
- git_config(git_default_config, NULL);
287
+ git_config(fsmonitor_config, NULL);
288
289
argc = parse_options(argc, argv, prefix, options,
290
builtin_fsmonitor__daemon_usage, 0);
@@ -73,6 +292,13 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
292
usage_with_options(builtin_fsmonitor__daemon_usage, options);
293
subcmd = argv[0];
294
295
+ if (fsmonitor__ipc_threads < 1)
296
+ die(_("invalid 'ipc-threads' value (%d)"),
297
+ fsmonitor__ipc_threads);
298
+
299
+ if (!strcmp(subcmd, "run"))
300
+ return !!try_to_run_foreground_daemon();
301
+
302
if (!strcmp(subcmd, "stop"))
303
return !!do_as_client__send_stop();
304
fsmonitor--daemon.h
new
+34
@@ -0,0 +1,34 @@
1
+#ifndef FSMONITOR_DAEMON_H
2
+#define FSMONITOR_DAEMON_H
3
+
4
+#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
5
+
6
+#include "cache.h"
7
+#include "dir.h"
8
+#include "run-command.h"
9
+#include "simple-ipc.h"
10
+#include "thread-utils.h"
11
+
12
+struct fsmonitor_batch;
13
+struct fsmonitor_token_data;
14
+
15
+struct fsmonitor_daemon_backend_data; /* opaque platform-specific data */
16
+
17
+struct fsmonitor_daemon_state {
18
+ pthread_t listener_thread;
19
+ pthread_mutex_t main_lock;
20
+
21
+ struct strbuf path_worktree_watch;
22
+ struct strbuf path_gitdir_watch;
23
+ int nr_paths_watching;
24
+
25
+ struct fsmonitor_token_data *current_token_data;
26
+
27
+ int error_code;
28
+ struct fsmonitor_daemon_backend_data *backend_data;
29
+
30
+ struct ipc_server_data *ipc_server_data;
31
+};
32
+
33
+#endif /* HAVE_FSMONITOR_DAEMON_BACKEND */
34
+#endif /* FSMONITOR_DAEMON_H */