|
3 | 3 | #include "parse-options.h"
|
4 | 4 | #include "fsmonitor.h"
|
5 | 5 | #include "fsmonitor-ipc.h"
|
| 6 | +#include "compat/fsmonitor/fsm-listen.h" |
| 7 | +#include "fsmonitor--daemon.h" |
6 | 8 | #include "simple-ipc.h"
|
7 | 9 | #include "khash.h"
|
8 | 10 |
|
9 | 11 | static const char * const builtin_fsmonitor__daemon_usage[] = {
|
| 12 | + N_("git fsmonitor--daemon run [<options>]"), |
10 | 13 | N_("git fsmonitor--daemon stop"),
|
11 | 14 | N_("git fsmonitor--daemon status"),
|
12 | 15 | NULL
|
13 | 16 | };
|
14 | 17 |
|
15 | 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 | + |
16 | 52 | /*
|
17 | 53 | * Acting as a CLIENT.
|
18 | 54 | *
|
@@ -57,22 +93,212 @@ static int do_as_client__status(void)
|
57 | 93 | }
|
58 | 94 | }
|
59 | 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 | + |
60 | 276 | int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
|
61 | 277 | {
|
62 | 278 | const char *subcmd;
|
63 | 279 |
|
64 | 280 | struct option options[] = {
|
| 281 | + OPT_INTEGER(0, "ipc-threads", |
| 282 | + &fsmonitor__ipc_threads, |
| 283 | + N_("use <n> ipc worker threads")), |
65 | 284 | OPT_END()
|
66 | 285 | };
|
67 | 286 |
|
68 |
| - git_config(git_default_config, NULL); |
| 287 | + git_config(fsmonitor_config, NULL); |
69 | 288 |
|
70 | 289 | argc = parse_options(argc, argv, prefix, options,
|
71 | 290 | builtin_fsmonitor__daemon_usage, 0);
|
72 | 291 | if (argc != 1)
|
73 | 292 | usage_with_options(builtin_fsmonitor__daemon_usage, options);
|
74 | 293 | subcmd = argv[0];
|
75 | 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 | + |
76 | 302 | if (!strcmp(subcmd, "stop"))
|
77 | 303 | return !!do_as_client__send_stop();
|
78 | 304 |
|
|
0 commit comments