Coverage Report

Created: 2024-09-08 06:23

/src/git/fsmonitor-settings.c
Line
Count
Source (jump to first uncovered line)
1
#include "git-compat-util.h"
2
#include "config.h"
3
#include "gettext.h"
4
#include "repository.h"
5
#include "fsmonitor-ipc.h"
6
#include "fsmonitor-settings.h"
7
#include "fsmonitor-path-utils.h"
8
9
/*
10
 * We keep this structure defintion private and have getters
11
 * for all fields so that we can lazy load it as needed.
12
 */
13
struct fsmonitor_settings {
14
  enum fsmonitor_mode mode;
15
  enum fsmonitor_reason reason;
16
  char *hook_path;
17
};
18
19
/*
20
 * Remote working directories are problematic for FSMonitor.
21
 *
22
 * The underlying file system on the server machine and/or the remote
23
 * mount type dictates whether notification events are available at
24
 * all to remote client machines.
25
 *
26
 * Kernel differences between the server and client machines also
27
 * dictate the how (buffering, frequency, de-dup) the events are
28
 * delivered to client machine processes.
29
 *
30
 * A client machine (such as a laptop) may choose to suspend/resume
31
 * and it is unclear (without lots of testing) whether the watcher can
32
 * resync after a resume.  We might be able to treat this as a normal
33
 * "events were dropped by the kernel" event and do our normal "flush
34
 * and resync" --or-- we might need to close the existing (zombie?)
35
 * notification fd and create a new one.
36
 *
37
 * In theory, the above issues need to be addressed whether we are
38
 * using the Hook or IPC API.
39
 *
40
 * So (for now at least), mark remote working directories as
41
 * incompatible unless 'fsmonitor.allowRemote' is true.
42
 *
43
 */
44
#ifdef HAVE_FSMONITOR_OS_SETTINGS
45
static enum fsmonitor_reason check_remote(struct repository *r)
46
{
47
  int allow_remote = -1; /* -1 unset, 0 not allowed, 1 allowed */
48
  int is_remote = fsmonitor__is_fs_remote(r->worktree);
49
50
  switch (is_remote) {
51
    case 0:
52
      return FSMONITOR_REASON_OK;
53
    case 1:
54
      repo_config_get_bool(r, "fsmonitor.allowremote", &allow_remote);
55
      if (allow_remote < 1)
56
        return FSMONITOR_REASON_REMOTE;
57
      else
58
        return FSMONITOR_REASON_OK;
59
    default:
60
      return FSMONITOR_REASON_ERROR;
61
  }
62
}
63
#endif
64
65
static enum fsmonitor_reason check_for_incompatible(struct repository *r,
66
                int ipc MAYBE_UNUSED)
67
0
{
68
0
  if (!r->worktree) {
69
    /*
70
     * Bare repositories don't have a working directory and
71
     * therefore have nothing to watch.
72
     */
73
0
    return FSMONITOR_REASON_BARE;
74
0
  }
75
76
#ifdef HAVE_FSMONITOR_OS_SETTINGS
77
  {
78
    enum fsmonitor_reason reason;
79
80
    reason = check_remote(r);
81
    if (reason != FSMONITOR_REASON_OK)
82
      return reason;
83
    reason = fsm_os__incompatible(r, ipc);
84
    if (reason != FSMONITOR_REASON_OK)
85
      return reason;
86
  }
87
#endif
88
89
0
  return FSMONITOR_REASON_OK;
90
0
}
91
92
static struct fsmonitor_settings *alloc_settings(void)
93
0
{
94
0
  struct fsmonitor_settings *s;
95
96
0
  CALLOC_ARRAY(s, 1);
97
0
  s->mode = FSMONITOR_MODE_DISABLED;
98
0
  s->reason = FSMONITOR_REASON_UNTESTED;
99
100
0
  return s;
101
0
}
102
103
static void lookup_fsmonitor_settings(struct repository *r)
104
0
{
105
0
  const char *const_str;
106
0
  char *to_free = NULL;
107
0
  int bool_value;
108
109
0
  if (r->settings.fsmonitor)
110
0
    return;
111
112
  /*
113
   * Overload the existing "core.fsmonitor" config setting (which
114
   * has historically been either unset or a hook pathname) to
115
   * now allow a boolean value to enable the builtin FSMonitor
116
   * or to turn everything off.  (This does imply that you can't
117
   * use a hook script named "true" or "false", but that's OK.)
118
   */
119
0
  switch (repo_config_get_maybe_bool(r, "core.fsmonitor", &bool_value)) {
120
121
0
  case 0: /* config value was set to <bool> */
122
0
    if (bool_value)
123
0
      fsm_settings__set_ipc(r);
124
0
    else
125
0
      fsm_settings__set_disabled(r);
126
0
    return;
127
128
0
  case 1: /* config value was unset */
129
0
    const_str = getenv("GIT_TEST_FSMONITOR");
130
0
    break;
131
132
0
  case -1: /* config value set to an arbitrary string */
133
0
    if (repo_config_get_pathname(r, "core.fsmonitor", &to_free))
134
0
      return; /* should not happen */
135
0
    const_str = to_free;
136
0
    break;
137
138
0
  default: /* should not happen */
139
0
    return;
140
0
  }
141
142
0
  if (const_str && *const_str)
143
0
    fsm_settings__set_hook(r, const_str);
144
0
  else
145
0
    fsm_settings__set_disabled(r);
146
0
  free(to_free);
147
0
}
148
149
enum fsmonitor_mode fsm_settings__get_mode(struct repository *r)
150
0
{
151
0
  if (!r->settings.fsmonitor)
152
0
    lookup_fsmonitor_settings(r);
153
154
0
  return r->settings.fsmonitor->mode;
155
0
}
156
157
const char *fsm_settings__get_hook_path(struct repository *r)
158
0
{
159
0
  if (!r->settings.fsmonitor)
160
0
    lookup_fsmonitor_settings(r);
161
162
0
  return r->settings.fsmonitor->hook_path;
163
0
}
164
165
void fsm_settings__set_ipc(struct repository *r)
166
0
{
167
0
  enum fsmonitor_reason reason = check_for_incompatible(r, 1);
168
169
0
  if (reason != FSMONITOR_REASON_OK) {
170
0
    fsm_settings__set_incompatible(r, reason);
171
0
    return;
172
0
  }
173
174
  /*
175
   * Caller requested IPC explicitly, so avoid (possibly
176
   * recursive) config lookup.
177
   */
178
0
  if (!r->settings.fsmonitor)
179
0
    r->settings.fsmonitor = alloc_settings();
180
181
0
  r->settings.fsmonitor->mode = FSMONITOR_MODE_IPC;
182
0
  r->settings.fsmonitor->reason = reason;
183
0
  FREE_AND_NULL(r->settings.fsmonitor->hook_path);
184
0
}
185
186
void fsm_settings__set_hook(struct repository *r, const char *path)
187
0
{
188
0
  enum fsmonitor_reason reason = check_for_incompatible(r, 0);
189
190
0
  if (reason != FSMONITOR_REASON_OK) {
191
0
    fsm_settings__set_incompatible(r, reason);
192
0
    return;
193
0
  }
194
195
  /*
196
   * Caller requested hook explicitly, so avoid (possibly
197
   * recursive) config lookup.
198
   */
199
0
  if (!r->settings.fsmonitor)
200
0
    r->settings.fsmonitor = alloc_settings();
201
202
0
  r->settings.fsmonitor->mode = FSMONITOR_MODE_HOOK;
203
0
  r->settings.fsmonitor->reason = reason;
204
0
  FREE_AND_NULL(r->settings.fsmonitor->hook_path);
205
0
  r->settings.fsmonitor->hook_path = strdup(path);
206
0
}
207
208
void fsm_settings__set_disabled(struct repository *r)
209
0
{
210
0
  if (!r->settings.fsmonitor)
211
0
    r->settings.fsmonitor = alloc_settings();
212
213
0
  r->settings.fsmonitor->mode = FSMONITOR_MODE_DISABLED;
214
0
  r->settings.fsmonitor->reason = FSMONITOR_REASON_OK;
215
0
  FREE_AND_NULL(r->settings.fsmonitor->hook_path);
216
0
}
217
218
void fsm_settings__set_incompatible(struct repository *r,
219
            enum fsmonitor_reason reason)
220
0
{
221
0
  if (!r->settings.fsmonitor)
222
0
    r->settings.fsmonitor = alloc_settings();
223
224
0
  r->settings.fsmonitor->mode = FSMONITOR_MODE_INCOMPATIBLE;
225
0
  r->settings.fsmonitor->reason = reason;
226
0
  FREE_AND_NULL(r->settings.fsmonitor->hook_path);
227
0
}
228
229
enum fsmonitor_reason fsm_settings__get_reason(struct repository *r)
230
0
{
231
0
  if (!r->settings.fsmonitor)
232
0
    lookup_fsmonitor_settings(r);
233
234
0
  return r->settings.fsmonitor->reason;
235
0
}
236
237
char *fsm_settings__get_incompatible_msg(struct repository *r,
238
           enum fsmonitor_reason reason)
239
0
{
240
0
  struct strbuf msg = STRBUF_INIT;
241
0
  const char *socket_dir;
242
243
0
  switch (reason) {
244
0
  case FSMONITOR_REASON_UNTESTED:
245
0
  case FSMONITOR_REASON_OK:
246
0
    goto done;
247
248
0
  case FSMONITOR_REASON_BARE: {
249
0
    char *cwd = xgetcwd();
250
251
0
    strbuf_addf(&msg,
252
0
          _("bare repository '%s' is incompatible with fsmonitor"),
253
0
          cwd);
254
0
    free(cwd);
255
0
    goto done;
256
0
  }
257
258
0
  case FSMONITOR_REASON_ERROR:
259
0
    strbuf_addf(&msg,
260
0
          _("repository '%s' is incompatible with fsmonitor due to errors"),
261
0
          r->worktree);
262
0
    goto done;
263
264
0
  case FSMONITOR_REASON_REMOTE:
265
0
    strbuf_addf(&msg,
266
0
          _("remote repository '%s' is incompatible with fsmonitor"),
267
0
          r->worktree);
268
0
    goto done;
269
270
0
  case FSMONITOR_REASON_VFS4GIT:
271
0
    strbuf_addf(&msg,
272
0
          _("virtual repository '%s' is incompatible with fsmonitor"),
273
0
          r->worktree);
274
0
    goto done;
275
276
0
  case FSMONITOR_REASON_NOSOCKETS:
277
0
    socket_dir = dirname((char *)fsmonitor_ipc__get_path(r));
278
0
    strbuf_addf(&msg,
279
0
          _("socket directory '%s' is incompatible with fsmonitor due"
280
0
            " to lack of Unix sockets support"),
281
0
          socket_dir);
282
0
    goto done;
283
0
  }
284
285
0
  BUG("Unhandled case in fsm_settings__get_incompatible_msg: '%d'",
286
0
      reason);
287
288
0
done:
289
0
  return strbuf_detach(&msg, NULL);
290
0
}