/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 | } |