Skip to content

Commit 9dcba0b

Browse files
jeffhostetlergitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f67df25 commit 9dcba0b

File tree

2 files changed

+261
-1
lines changed

2 files changed

+261
-1
lines changed

builtin/fsmonitor--daemon.c

Lines changed: 227 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,52 @@
33
#include "parse-options.h"
44
#include "fsmonitor.h"
55
#include "fsmonitor-ipc.h"
6+
#include "compat/fsmonitor/fsm-listen.h"
7+
#include "fsmonitor--daemon.h"
68
#include "simple-ipc.h"
79
#include "khash.h"
810

911
static const char * const builtin_fsmonitor__daemon_usage[] = {
12+
N_("git fsmonitor--daemon run [<options>]"),
1013
N_("git fsmonitor--daemon stop"),
1114
N_("git fsmonitor--daemon status"),
1215
NULL
1316
};
1417

1518
#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+
1652
/*
1753
* Acting as a CLIENT.
1854
*
@@ -57,22 +93,212 @@ static int do_as_client__status(void)
5793
}
5894
}
5995

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+
60276
int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
61277
{
62278
const char *subcmd;
63279

64280
struct option options[] = {
281+
OPT_INTEGER(0, "ipc-threads",
282+
&fsmonitor__ipc_threads,
283+
N_("use <n> ipc worker threads")),
65284
OPT_END()
66285
};
67286

68-
git_config(git_default_config, NULL);
287+
git_config(fsmonitor_config, NULL);
69288

70289
argc = parse_options(argc, argv, prefix, options,
71290
builtin_fsmonitor__daemon_usage, 0);
72291
if (argc != 1)
73292
usage_with_options(builtin_fsmonitor__daemon_usage, options);
74293
subcmd = argv[0];
75294

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+
76302
if (!strcmp(subcmd, "stop"))
77303
return !!do_as_client__send_stop();
78304

fsmonitor--daemon.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 */

0 commit comments

Comments
 (0)