#define USE_THE_REPOSITORY_VARIABLE
#include "builtin.h"
#include "abspath.h"
#include "config.h"
#include "dir.h"
#include "gettext.h"
#include "parse-options.h"
#include "fsmonitor-ll.h"
#include "fsmonitor-ipc.h"
#include "fsmonitor-settings.h"
#include "compat/fsmonitor/fsm-health.h"
#include "compat/fsmonitor/fsm-listen.h"
#include "fsmonitor--daemon.h"
#include "simple-ipc.h"
#include "khash.h"
#include "run-command.h"
#include "trace.h"
#include "trace2.h"
static const char * const builtin_fsmonitor__daemon_usage[] = …;
#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
#define FSMONITOR__IPC_THREADS …
static int fsmonitor__ipc_threads = 8;
#define FSMONITOR__START_TIMEOUT …
static int fsmonitor__start_timeout_sec = 60;
#define FSMONITOR__ANNOUNCE_STARTUP …
static int fsmonitor__announce_startup = 0;
static int fsmonitor_config(const char *var, const char *value,
const struct config_context *ctx, void *cb)
{
if (!strcmp(var, FSMONITOR__IPC_THREADS)) {
int i = git_config_int(var, value, ctx->kvi);
if (i < 1)
return error(_("value of '%s' out of range: %d"),
FSMONITOR__IPC_THREADS, i);
fsmonitor__ipc_threads = i;
return 0;
}
if (!strcmp(var, FSMONITOR__START_TIMEOUT)) {
int i = git_config_int(var, value, ctx->kvi);
if (i < 0)
return error(_("value of '%s' out of range: %d"),
FSMONITOR__START_TIMEOUT, i);
fsmonitor__start_timeout_sec = i;
return 0;
}
if (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {
int is_bool;
int i = git_config_bool_or_int(var, value, ctx->kvi, &is_bool);
if (i < 0)
return error(_("value of '%s' not bool or int: %d"),
var, i);
fsmonitor__announce_startup = i;
return 0;
}
return git_default_config(var, value, ctx, cb);
}
static int do_as_client__send_stop(void)
{
struct strbuf answer = STRBUF_INIT;
int ret;
ret = fsmonitor_ipc__send_command("quit", &answer);
strbuf_release(&answer);
if (ret)
return ret;
trace2_region_enter("fsm_client", "polling-for-daemon-exit", NULL);
while (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
sleep_millisec(50);
trace2_region_leave("fsm_client", "polling-for-daemon-exit", NULL);
return 0;
}
static int do_as_client__status(void)
{
enum ipc_active_state state = fsmonitor_ipc__get_state();
switch (state) {
case IPC_STATE__LISTENING:
printf(_("fsmonitor-daemon is watching '%s'\n"),
the_repository->worktree);
return 0;
default:
printf(_("fsmonitor-daemon is not watching '%s'\n"),
the_repository->worktree);
return 1;
}
}
enum fsmonitor_cookie_item_result {
FCIR_ERROR = -1,
FCIR_INIT,
FCIR_SEEN,
FCIR_ABORT,
};
struct fsmonitor_cookie_item {
struct hashmap_entry entry;
char *name;
enum fsmonitor_cookie_item_result result;
};
static int cookies_cmp(const void *data UNUSED,
const struct hashmap_entry *he1,
const struct hashmap_entry *he2, const void *keydata)
{
const struct fsmonitor_cookie_item *a =
container_of(he1, const struct fsmonitor_cookie_item, entry);
const struct fsmonitor_cookie_item *b =
container_of(he2, const struct fsmonitor_cookie_item, entry);
return strcmp(a->name, keydata ? keydata : b->name);
}
static enum fsmonitor_cookie_item_result with_lock__wait_for_cookie(
struct fsmonitor_daemon_state *state)
{
int fd;
struct fsmonitor_cookie_item *cookie;
struct strbuf cookie_pathname = STRBUF_INIT;
struct strbuf cookie_filename = STRBUF_INIT;
enum fsmonitor_cookie_item_result result;
int my_cookie_seq;
CALLOC_ARRAY(cookie, 1);
my_cookie_seq = state->cookie_seq++;
strbuf_addf(&cookie_filename, "%i-%i", getpid(), my_cookie_seq);
strbuf_addbuf(&cookie_pathname, &state->path_cookie_prefix);
strbuf_addbuf(&cookie_pathname, &cookie_filename);
cookie->name = strbuf_detach(&cookie_filename, NULL);
cookie->result = FCIR_INIT;
hashmap_entry_init(&cookie->entry, strhash(cookie->name));
hashmap_add(&state->cookies, &cookie->entry);
trace_printf_key(&trace_fsmonitor, "cookie-wait: '%s' '%s'",
cookie->name, cookie_pathname.buf);
fd = open(cookie_pathname.buf, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) {
error_errno(_("could not create fsmonitor cookie '%s'"),
cookie->name);
cookie->result = FCIR_ERROR;
goto done;
}
close(fd);
unlink(cookie_pathname.buf);
while (cookie->result == FCIR_INIT)
pthread_cond_wait(&state->cookies_cond,
&state->main_lock);
done:
hashmap_remove(&state->cookies, &cookie->entry, NULL);
result = cookie->result;
free(cookie->name);
free(cookie);
strbuf_release(&cookie_pathname);
return result;
}
static void with_lock__mark_cookies_seen(struct fsmonitor_daemon_state *state,
const struct string_list *cookie_names)
{
int k;
int nr_seen = 0;
for (k = 0; k < cookie_names->nr; k++) {
struct fsmonitor_cookie_item key;
struct fsmonitor_cookie_item *cookie;
key.name = cookie_names->items[k].string;
hashmap_entry_init(&key.entry, strhash(key.name));
cookie = hashmap_get_entry(&state->cookies, &key, entry, NULL);
if (cookie) {
trace_printf_key(&trace_fsmonitor, "cookie-seen: '%s'",
cookie->name);
cookie->result = FCIR_SEEN;
nr_seen++;
}
}
if (nr_seen)
pthread_cond_broadcast(&state->cookies_cond);
}
static void with_lock__abort_all_cookies(struct fsmonitor_daemon_state *state)
{
struct hashmap_iter iter;
struct fsmonitor_cookie_item *cookie;
int nr_aborted = 0;
hashmap_for_each_entry(&state->cookies, &iter, cookie, entry) {
trace_printf_key(&trace_fsmonitor, "cookie-abort: '%s'",
cookie->name);
cookie->result = FCIR_ABORT;
nr_aborted++;
}
if (nr_aborted)
pthread_cond_broadcast(&state->cookies_cond);
}
struct fsmonitor_token_data {
struct strbuf token_id;
struct fsmonitor_batch *batch_head;
struct fsmonitor_batch *batch_tail;
uint64_t client_ref_count;
};
struct fsmonitor_batch {
struct fsmonitor_batch *next;
uint64_t batch_seq_nr;
const char **interned_paths;
size_t nr, alloc;
time_t pinned_time;
};
static struct fsmonitor_token_data *fsmonitor_new_token_data(void)
{
static int test_env_value = -1;
static uint64_t flush_count = 0;
struct fsmonitor_token_data *token;
struct fsmonitor_batch *batch;
CALLOC_ARRAY(token, 1);
batch = fsmonitor_batch__new();
strbuf_init(&token->token_id, 0);
token->batch_head = batch;
token->batch_tail = batch;
token->client_ref_count = 0;
if (test_env_value < 0)
test_env_value = git_env_bool("GIT_TEST_FSMONITOR_TOKEN", 0);
if (!test_env_value) {
struct timeval tv;
struct tm tm;
time_t secs;
gettimeofday(&tv, NULL);
secs = tv.tv_sec;
gmtime_r(&secs, &tm);
strbuf_addf(&token->token_id,
"%"PRIu64".%d.%4d%02d%02dT%02d%02d%02d.%06ldZ",
flush_count++,
getpid(),
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec,
(long)tv.tv_usec);
} else {
strbuf_addf(&token->token_id, "test_%08x", test_env_value++);
}
if (test_env_value)
batch->pinned_time = time(NULL);
return token;
}
struct fsmonitor_batch *fsmonitor_batch__new(void)
{
struct fsmonitor_batch *batch;
CALLOC_ARRAY(batch, 1);
return batch;
}
void fsmonitor_batch__free_list(struct fsmonitor_batch *batch)
{
while (batch) {
struct fsmonitor_batch *next = batch->next;
free(batch->interned_paths);
free(batch);
batch = next;
}
}
void fsmonitor_batch__add_path(struct fsmonitor_batch *batch,
const char *path)
{
const char *interned_path = strintern(path);
trace_printf_key(&trace_fsmonitor, "event: %s", interned_path);
ALLOC_GROW(batch->interned_paths, batch->nr + 1, batch->alloc);
batch->interned_paths[batch->nr++] = interned_path;
}
static void fsmonitor_batch__combine(struct fsmonitor_batch *batch_dest,
const struct fsmonitor_batch *batch_src)
{
size_t k;
ALLOC_GROW(batch_dest->interned_paths,
batch_dest->nr + batch_src->nr + 1,
batch_dest->alloc);
for (k = 0; k < batch_src->nr; k++)
batch_dest->interned_paths[batch_dest->nr++] =
batch_src->interned_paths[k];
}
#define MY_TIME_DELAY_SECONDS …
static struct fsmonitor_batch *with_lock__truncate_old_batches(
struct fsmonitor_daemon_state *state,
const struct fsmonitor_batch *batch_marker)
{
const struct fsmonitor_batch *batch;
struct fsmonitor_batch *remainder;
if (!batch_marker)
return NULL;
trace_printf_key(&trace_fsmonitor, "Truncate: mark (%"PRIu64",%"PRIu64")",
batch_marker->batch_seq_nr,
(uint64_t)batch_marker->pinned_time);
for (batch = batch_marker; batch; batch = batch->next) {
time_t t;
if (!batch->pinned_time)
continue;
t = batch->pinned_time + MY_TIME_DELAY_SECONDS;
if (t > batch_marker->pinned_time)
continue;
goto truncate_past_here;
}
return NULL;
truncate_past_here:
state->current_token_data->batch_tail = (struct fsmonitor_batch *)batch;
remainder = ((struct fsmonitor_batch *)batch)->next;
((struct fsmonitor_batch *)batch)->next = NULL;
return remainder;
}
static void fsmonitor_free_token_data(struct fsmonitor_token_data *token)
{
if (!token)
return;
assert(token->client_ref_count == 0);
strbuf_release(&token->token_id);
fsmonitor_batch__free_list(token->batch_head);
free(token);
}
static void with_lock__do_force_resync(struct fsmonitor_daemon_state *state)
{
struct fsmonitor_token_data *free_me = NULL;
struct fsmonitor_token_data *new_one = NULL;
new_one = fsmonitor_new_token_data();
if (state->current_token_data->client_ref_count == 0)
free_me = state->current_token_data;
state->current_token_data = new_one;
fsmonitor_free_token_data(free_me);
with_lock__abort_all_cookies(state);
}
void fsmonitor_force_resync(struct fsmonitor_daemon_state *state)
{
pthread_mutex_lock(&state->main_lock);
with_lock__do_force_resync(state);
pthread_mutex_unlock(&state->main_lock);
}
static void with_lock__format_response_token(
struct strbuf *response_token,
const struct strbuf *response_token_id,
const struct fsmonitor_batch *batch)
{
strbuf_reset(response_token);
strbuf_addf(response_token, "builtin:%s:%"PRIu64,
response_token_id->buf, batch->batch_seq_nr);
}
static int fsmonitor_parse_client_token(const char *buf_token,
struct strbuf *requested_token_id,
uint64_t *seq_nr)
{
const char *p;
char *p_end;
strbuf_reset(requested_token_id);
*seq_nr = 0;
if (!skip_prefix(buf_token, "builtin:", &p))
return -1;
while (*p && *p != ':')
strbuf_addch(requested_token_id, *p++);
if (!*p++)
return -1;
*seq_nr = (uint64_t)strtoumax(p, &p_end, 10);
if (*p_end)
return -1;
return 0;
}
KHASH_INIT(str, const char *, int, 0, kh_str_hash_func, kh_str_hash_equal)
static int do_handle_client(struct fsmonitor_daemon_state *state,
const char *command,
ipc_server_reply_cb *reply,
struct ipc_server_reply_data *reply_data)
{
struct fsmonitor_token_data *token_data = NULL;
struct strbuf response_token = STRBUF_INIT;
struct strbuf requested_token_id = STRBUF_INIT;
struct strbuf payload = STRBUF_INIT;
uint64_t requested_oldest_seq_nr = 0;
uint64_t total_response_len = 0;
const char *p;
const struct fsmonitor_batch *batch_head;
const struct fsmonitor_batch *batch;
struct fsmonitor_batch *remainder = NULL;
intmax_t count = 0, duplicates = 0;
kh_str_t *shown;
int hash_ret;
int do_trivial = 0;
int do_flush = 0;
int do_cookie = 0;
enum fsmonitor_cookie_item_result cookie_result;
if (!strcmp(command, "quit")) {
return SIMPLE_IPC_QUIT;
} else if (!strcmp(command, "flush")) {
do_flush = 1;
do_trivial = 1;
} else if (!skip_prefix(command, "builtin:", &p)) {
char *p_end;
strtoumax(command, &p_end, 10);
trace_printf_key(&trace_fsmonitor,
((*p_end) ?
"fsmonitor: invalid command line '%s'" :
"fsmonitor: unsupported V1 protocol '%s'"),
command);
do_trivial = 1;
do_cookie = 1;
} else {
if (fsmonitor_parse_client_token(command, &requested_token_id,
&requested_oldest_seq_nr)) {
trace_printf_key(&trace_fsmonitor,
"fsmonitor: invalid V2 protocol token '%s'",
command);
do_trivial = 1;
do_cookie = 1;
} else {
do_cookie = 1;
}
}
pthread_mutex_lock(&state->main_lock);
if (!state->current_token_data)
BUG("fsmonitor state does not have a current token");
if (do_cookie) {
cookie_result = with_lock__wait_for_cookie(state);
if (cookie_result != FCIR_SEEN) {
error(_("fsmonitor: cookie_result '%d' != SEEN"),
cookie_result);
do_trivial = 1;
}
}
if (do_flush)
with_lock__do_force_resync(state);
token_data = state->current_token_data;
batch_head = token_data->batch_head;
((struct fsmonitor_batch *)batch_head)->pinned_time = time(NULL);
with_lock__format_response_token(&response_token,
&token_data->token_id, batch_head);
reply(reply_data, response_token.buf, response_token.len + 1);
total_response_len += response_token.len + 1;
trace2_data_string("fsmonitor", the_repository, "response/token",
response_token.buf);
trace_printf_key(&trace_fsmonitor, "response token: %s",
response_token.buf);
if (!do_trivial) {
if (strcmp(requested_token_id.buf, token_data->token_id.buf)) {
trace2_data_string("fsmonitor", the_repository,
"response/token", "different");
do_trivial = 1;
} else if (requested_oldest_seq_nr <
token_data->batch_tail->batch_seq_nr) {
trace_printf_key(&trace_fsmonitor,
"client requested truncated data");
do_trivial = 1;
}
}
if (do_trivial) {
pthread_mutex_unlock(&state->main_lock);
reply(reply_data, "/", 2);
trace2_data_intmax("fsmonitor", the_repository,
"response/trivial", 1);
goto cleanup;
}
token_data->client_ref_count++;
pthread_mutex_unlock(&state->main_lock);
shown = kh_init_str();
for (batch = batch_head;
batch && batch->batch_seq_nr > requested_oldest_seq_nr;
batch = batch->next) {
size_t k;
for (k = 0; k < batch->nr; k++) {
const char *s = batch->interned_paths[k];
size_t s_len;
if (kh_get_str(shown, s) != kh_end(shown))
duplicates++;
else {
kh_put_str(shown, s, &hash_ret);
trace_printf_key(&trace_fsmonitor,
"send[%"PRIuMAX"]: %s",
count, s);
s_len = strlen(s) + 1;
if (payload.len + s_len >=
LARGE_PACKET_DATA_MAX) {
reply(reply_data, payload.buf,
payload.len);
total_response_len += payload.len;
strbuf_reset(&payload);
}
strbuf_add(&payload, s, s_len);
count++;
}
}
}
if (payload.len) {
reply(reply_data, payload.buf, payload.len);
total_response_len += payload.len;
}
kh_release_str(shown);
pthread_mutex_lock(&state->main_lock);
if (token_data->client_ref_count > 0)
token_data->client_ref_count--;
if (token_data->client_ref_count == 0) {
if (token_data != state->current_token_data) {
fsmonitor_free_token_data(token_data);
} else if (batch) {
remainder = with_lock__truncate_old_batches(state,
batch);
}
}
pthread_mutex_unlock(&state->main_lock);
if (remainder)
fsmonitor_batch__free_list(remainder);
trace2_data_intmax("fsmonitor", the_repository, "response/length", total_response_len);
trace2_data_intmax("fsmonitor", the_repository, "response/count/files", count);
trace2_data_intmax("fsmonitor", the_repository, "response/count/duplicates", duplicates);
cleanup:
strbuf_release(&response_token);
strbuf_release(&requested_token_id);
strbuf_release(&payload);
return 0;
}
static ipc_server_application_cb handle_client;
static int handle_client(void *data,
const char *command, size_t command_len,
ipc_server_reply_cb *reply,
struct ipc_server_reply_data *reply_data)
{
struct fsmonitor_daemon_state *state = data;
int result;
if (command_len != strlen(command))
BUG("FSMonitor assumes text messages");
trace_printf_key(&trace_fsmonitor, "requested token: %s", command);
trace2_region_enter("fsmonitor", "handle_client", the_repository);
trace2_data_string("fsmonitor", the_repository, "request", command);
result = do_handle_client(state, command, reply, reply_data);
trace2_region_leave("fsmonitor", "handle_client", the_repository);
return result;
}
#define FSMONITOR_DIR …
#define FSMONITOR_COOKIE_DIR …
#define FSMONITOR_COOKIE_PREFIX …
enum fsmonitor_path_type fsmonitor_classify_path_workdir_relative(
const char *rel)
{
if (fspathncmp(rel, ".git", 4))
return IS_WORKDIR_PATH;
rel += 4;
if (!*rel)
return IS_DOT_GIT;
if (*rel != '/')
return IS_WORKDIR_PATH;
rel++;
if (!fspathncmp(rel, FSMONITOR_COOKIE_PREFIX,
strlen(FSMONITOR_COOKIE_PREFIX)))
return IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX;
return IS_INSIDE_DOT_GIT;
}
enum fsmonitor_path_type fsmonitor_classify_path_gitdir_relative(
const char *rel)
{
if (!fspathncmp(rel, FSMONITOR_COOKIE_PREFIX,
strlen(FSMONITOR_COOKIE_PREFIX)))
return IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX;
return IS_INSIDE_GITDIR;
}
static enum fsmonitor_path_type try_classify_workdir_abs_path(
struct fsmonitor_daemon_state *state,
const char *path)
{
const char *rel;
if (fspathncmp(path, state->path_worktree_watch.buf,
state->path_worktree_watch.len))
return IS_OUTSIDE_CONE;
rel = path + state->path_worktree_watch.len;
if (!*rel)
return IS_WORKDIR_PATH;
if (*rel != '/')
return IS_OUTSIDE_CONE;
rel++;
return fsmonitor_classify_path_workdir_relative(rel);
}
enum fsmonitor_path_type fsmonitor_classify_path_absolute(
struct fsmonitor_daemon_state *state,
const char *path)
{
const char *rel;
enum fsmonitor_path_type t;
t = try_classify_workdir_abs_path(state, path);
if (state->nr_paths_watching == 1)
return t;
if (t != IS_OUTSIDE_CONE)
return t;
if (fspathncmp(path, state->path_gitdir_watch.buf,
state->path_gitdir_watch.len))
return IS_OUTSIDE_CONE;
rel = path + state->path_gitdir_watch.len;
if (!*rel)
return IS_GITDIR;
if (*rel != '/')
return IS_OUTSIDE_CONE;
rel++;
return fsmonitor_classify_path_gitdir_relative(rel);
}
#define MY_COMBINE_LIMIT …
void fsmonitor_publish(struct fsmonitor_daemon_state *state,
struct fsmonitor_batch *batch,
const struct string_list *cookie_names)
{
if (!batch && !cookie_names->nr)
return;
pthread_mutex_lock(&state->main_lock);
if (batch) {
struct fsmonitor_batch *head;
head = state->current_token_data->batch_head;
if (!head) {
BUG("token does not have batch");
} else if (head->pinned_time) {
batch->batch_seq_nr = head->batch_seq_nr + 1;
batch->next = head;
state->current_token_data->batch_head = batch;
} else if (!head->batch_seq_nr) {
fsmonitor_batch__free_list(batch);
} else if (head->nr + batch->nr > MY_COMBINE_LIMIT) {
batch->batch_seq_nr = head->batch_seq_nr + 1;
batch->next = head;
state->current_token_data->batch_head = batch;
} else {
fsmonitor_batch__combine(head, batch);
fsmonitor_batch__free_list(batch);
}
}
if (cookie_names->nr)
with_lock__mark_cookies_seen(state, cookie_names);
pthread_mutex_unlock(&state->main_lock);
}
static void *fsm_health__thread_proc(void *_state)
{
struct fsmonitor_daemon_state *state = _state;
trace2_thread_start("fsm-health");
fsm_health__loop(state);
trace2_thread_exit();
return NULL;
}
static void *fsm_listen__thread_proc(void *_state)
{
struct fsmonitor_daemon_state *state = _state;
trace2_thread_start("fsm-listen");
trace_printf_key(&trace_fsmonitor, "Watching: worktree '%s'",
state->path_worktree_watch.buf);
if (state->nr_paths_watching > 1)
trace_printf_key(&trace_fsmonitor, "Watching: gitdir '%s'",
state->path_gitdir_watch.buf);
fsm_listen__loop(state);
pthread_mutex_lock(&state->main_lock);
if (state->current_token_data &&
state->current_token_data->client_ref_count == 0)
fsmonitor_free_token_data(state->current_token_data);
state->current_token_data = NULL;
pthread_mutex_unlock(&state->main_lock);
trace2_thread_exit();
return NULL;
}
static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
{
struct ipc_server_opts ipc_opts = {
.nr_threads = fsmonitor__ipc_threads,
.uds_disallow_chdir = 0
};
int health_started = 0;
int listener_started = 0;
int err = 0;
if (ipc_server_run_async(&state->ipc_server_data,
state->path_ipc.buf, &ipc_opts,
handle_client, state))
return error_errno(
_("could not start IPC thread pool on '%s'"),
state->path_ipc.buf);
if (pthread_create(&state->listener_thread, NULL,
fsm_listen__thread_proc, state)) {
ipc_server_stop_async(state->ipc_server_data);
err = error(_("could not start fsmonitor listener thread"));
goto cleanup;
}
listener_started = 1;
if (pthread_create(&state->health_thread, NULL,
fsm_health__thread_proc, state)) {
ipc_server_stop_async(state->ipc_server_data);
err = error(_("could not start fsmonitor health thread"));
goto cleanup;
}
health_started = 1;
cleanup:
ipc_server_await(state->ipc_server_data);
if (listener_started) {
fsm_listen__stop_async(state);
pthread_join(state->listener_thread, NULL);
}
if (health_started) {
fsm_health__stop_async(state);
pthread_join(state->health_thread, NULL);
}
if (err)
return err;
if (state->listen_error_code)
return state->listen_error_code;
if (state->health_error_code)
return state->health_error_code;
return 0;
}
static int fsmonitor_run_daemon(void)
{
struct fsmonitor_daemon_state state;
const char *home;
int err;
memset(&state, 0, sizeof(state));
hashmap_init(&state.cookies, cookies_cmp, NULL, 0);
pthread_mutex_init(&state.main_lock, NULL);
pthread_cond_init(&state.cookies_cond, NULL);
state.listen_error_code = 0;
state.health_error_code = 0;
state.current_token_data = fsmonitor_new_token_data();
strbuf_init(&state.path_worktree_watch, 0);
strbuf_addstr(&state.path_worktree_watch,
absolute_path(repo_get_work_tree(the_repository)));
state.nr_paths_watching = 1;
strbuf_init(&state.alias.alias, 0);
strbuf_init(&state.alias.points_to, 0);
if ((err = fsmonitor__get_alias(state.path_worktree_watch.buf, &state.alias)))
goto done;
strbuf_init(&state.path_gitdir_watch, 0);
strbuf_addbuf(&state.path_gitdir_watch, &state.path_worktree_watch);
strbuf_addstr(&state.path_gitdir_watch, "/.git");
if (!is_directory(state.path_gitdir_watch.buf)) {
strbuf_reset(&state.path_gitdir_watch);
strbuf_addstr(&state.path_gitdir_watch,
absolute_path(repo_get_git_dir(the_repository)));
strbuf_strip_suffix(&state.path_gitdir_watch, "/.");
state.nr_paths_watching = 2;
}
strbuf_init(&state.path_cookie_prefix, 0);
strbuf_addbuf(&state.path_cookie_prefix, &state.path_gitdir_watch);
strbuf_addch(&state.path_cookie_prefix, '/');
strbuf_addstr(&state.path_cookie_prefix, FSMONITOR_DIR);
mkdir(state.path_cookie_prefix.buf, 0777);
strbuf_addch(&state.path_cookie_prefix, '/');
strbuf_addstr(&state.path_cookie_prefix, FSMONITOR_COOKIE_DIR);
mkdir(state.path_cookie_prefix.buf, 0777);
strbuf_addch(&state.path_cookie_prefix, '/');
strbuf_init(&state.path_ipc, 0);
strbuf_addstr(&state.path_ipc,
absolute_path(fsmonitor_ipc__get_path(the_repository)));
if (fsm_listen__ctor(&state)) {
err = error(_("could not initialize listener thread"));
goto done;
}
if (fsm_health__ctor(&state)) {
err = error(_("could not initialize health thread"));
goto done;
}
home = getenv("HOME");
if (home && *home && chdir(home))
die_errno(_("could not cd home '%s'"), home);
err = fsmonitor_run_daemon_1(&state);
done:
pthread_cond_destroy(&state.cookies_cond);
pthread_mutex_destroy(&state.main_lock);
fsm_listen__dtor(&state);
fsm_health__dtor(&state);
ipc_server_free(state.ipc_server_data);
strbuf_release(&state.path_worktree_watch);
strbuf_release(&state.path_gitdir_watch);
strbuf_release(&state.path_cookie_prefix);
strbuf_release(&state.path_ipc);
strbuf_release(&state.alias.alias);
strbuf_release(&state.alias.points_to);
return err;
}
static int try_to_run_foreground_daemon(int detach_console MAYBE_UNUSED)
{
if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
die(_("fsmonitor--daemon is already running '%s'"),
the_repository->worktree);
if (fsmonitor__announce_startup) {
fprintf(stderr, _("running fsmonitor-daemon in '%s'\n"),
the_repository->worktree);
fflush(stderr);
}
#ifdef GIT_WINDOWS_NATIVE
if (detach_console)
FreeConsole();
#endif
return !!fsmonitor_run_daemon();
}
static start_bg_wait_cb bg_wait_cb;
static int bg_wait_cb(const struct child_process *cp UNUSED,
void *cb_data UNUSED)
{
enum ipc_active_state s = fsmonitor_ipc__get_state();
switch (s) {
case IPC_STATE__LISTENING:
return 0;
case IPC_STATE__NOT_LISTENING:
case IPC_STATE__PATH_NOT_FOUND:
return 1;
default:
case IPC_STATE__INVALID_PATH:
case IPC_STATE__OTHER_ERROR:
return -1;
}
}
static int try_to_start_background_daemon(void)
{
struct child_process cp = CHILD_PROCESS_INIT;
enum start_bg_result sbgr;
if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
die(_("fsmonitor--daemon is already running '%s'"),
the_repository->worktree);
if (fsmonitor__announce_startup) {
fprintf(stderr, _("starting fsmonitor-daemon in '%s'\n"),
the_repository->worktree);
fflush(stderr);
}
cp.git_cmd = 1;
strvec_push(&cp.args, "fsmonitor--daemon");
strvec_push(&cp.args, "run");
strvec_push(&cp.args, "--detach");
strvec_pushf(&cp.args, "--ipc-threads=%d", fsmonitor__ipc_threads);
cp.no_stdin = 1;
cp.no_stdout = 1;
cp.no_stderr = 1;
sbgr = start_bg_command(&cp, bg_wait_cb, NULL,
fsmonitor__start_timeout_sec);
switch (sbgr) {
case SBGR_READY:
return 0;
default:
case SBGR_ERROR:
case SBGR_CB_ERROR:
return error(_("daemon failed to start"));
case SBGR_TIMEOUT:
return error(_("daemon not online yet"));
case SBGR_DIED:
return error(_("daemon terminated"));
}
}
int cmd_fsmonitor__daemon(int argc,
const char **argv,
const char *prefix,
struct repository *repo UNUSED)
{
const char *subcmd;
enum fsmonitor_reason reason;
int detach_console = 0;
struct option options[] = {
OPT_BOOL(0, "detach", &detach_console, N_("detach from console")),
OPT_INTEGER(0, "ipc-threads",
&fsmonitor__ipc_threads,
N_("use <n> ipc worker threads")),
OPT_INTEGER(0, "start-timeout",
&fsmonitor__start_timeout_sec,
N_("max seconds to wait for background daemon startup")),
OPT_END()
};
git_config(fsmonitor_config, NULL);
argc = parse_options(argc, argv, prefix, options,
builtin_fsmonitor__daemon_usage, 0);
if (argc != 1)
usage_with_options(builtin_fsmonitor__daemon_usage, options);
subcmd = argv[0];
if (fsmonitor__ipc_threads < 1)
die(_("invalid 'ipc-threads' value (%d)"),
fsmonitor__ipc_threads);
prepare_repo_settings(the_repository);
fsm_settings__set_ipc(the_repository);
reason = fsm_settings__get_reason(the_repository);
if (reason > FSMONITOR_REASON_OK)
die("%s",
fsm_settings__get_incompatible_msg(the_repository,
reason));
if (!strcmp(subcmd, "start"))
return !!try_to_start_background_daemon();
if (!strcmp(subcmd, "run"))
return !!try_to_run_foreground_daemon(detach_console);
if (!strcmp(subcmd, "stop"))
return !!do_as_client__send_stop();
if (!strcmp(subcmd, "status"))
return !!do_as_client__status();
die(_("Unhandled subcommand '%s'"), subcmd);
}
#else
int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix UNUSED, struct repository *repo UNUSED)
{ … }
#endif