2074
return result;
2075
}
2076
2077
+static int real_is_systemd_timer_available(void)
2078
+{
2079
+ struct child_process child = CHILD_PROCESS_INIT;
2080
+
2081
+ strvec_pushl(&child.args, "systemctl", "--user", "list-timers", NULL);
2082
+ child.no_stdin = 1;
2083
+ child.no_stdout = 1;
2084
+ child.no_stderr = 1;
2085
+ child.silent_exec_failure = 1;
2086
+
2087
+ if (start_command(&child))
2088
+ return 0;
2089
+ if (finish_command(&child))
2090
+ return 0;
2091
+ return 1;
2092
+}
2093
+
2094
+static int is_systemd_timer_available(void)
2095
+{
2096
+ const char *cmd = "systemctl";
2097
+ int is_available;
2098
+
2099
+ if (get_schedule_cmd(&cmd, &is_available))
2100
+ return is_available;
2101
+
2102
+ return real_is_systemd_timer_available();
2103
+}
2104
+
2105
+static char *xdg_config_home_systemd(const char *filename)
2106
+{
2107
+ return xdg_config_home_for("systemd/user", filename);
2108
+}
2109
+
2110
+static int systemd_timer_enable_unit(int enable,
2111
+ enum schedule_priority schedule)
2112
+{
2113
+ const char *cmd = "systemctl";
2114
+ struct child_process child = CHILD_PROCESS_INIT;
2115
+ const char *frequency = get_frequency(schedule);
2116
+
2117
+ /*
2118
+ * Disabling the systemd unit while it is already disabled makes
2119
+ * systemctl print an error.
2120
+ * Let's ignore it since it means we already are in the expected state:
2121
+ * the unit is disabled.
2122
+ *
2123
+ * On the other hand, enabling a systemd unit which is already enabled
2124
+ * produces no error.
2125
+ */
2126
+ if (!enable)
2127
+ child.no_stderr = 1;
2128
+
2129
+ get_schedule_cmd(&cmd, NULL);
2130
+ strvec_split(&child.args, cmd);
2131
+ strvec_pushl(&child.args, "--user", enable ? "enable" : "disable",
2132
+ "--now", NULL);
2133
+ strvec_pushf(&child.args, "git-maintenance@%s.timer", frequency);
2134
+
2135
+ if (start_command(&child))
2136
+ return error(_("failed to start systemctl"));
2137
+ if (finish_command(&child))
2138
+ /*
2139
+ * Disabling an already disabled systemd unit makes
2140
+ * systemctl fail.
2141
+ * Let's ignore this failure.
2142
+ *
2143
+ * Enabling an enabled systemd unit doesn't fail.
2144
+ */
2145
+ if (enable)
2146
+ return error(_("failed to run systemctl"));
2147
+ return 0;
2148
+}
2149
+
2150
+static int systemd_timer_delete_unit_templates(void)
2151
+{
2152
+ int ret = 0;
2153
+ char *filename = xdg_config_home_systemd("git-maintenance@.timer");
2154
+ if (unlink(filename) && !is_missing_file_error(errno))
2155
+ ret = error_errno(_("failed to delete '%s'"), filename);
2156
+ FREE_AND_NULL(filename);
2157
+
2158
+ filename = xdg_config_home_systemd("git-maintenance@.service");
2159
+ if (unlink(filename) && !is_missing_file_error(errno))
2160
+ ret = error_errno(_("failed to delete '%s'"), filename);
2161
+
2162
+ free(filename);
2163
+ return ret;
2164
+}
2165
+
2166
+static int systemd_timer_delete_units(void)
2167
+{
2168
+ return systemd_timer_enable_unit(0, SCHEDULE_HOURLY) ||
2169
+ systemd_timer_enable_unit(0, SCHEDULE_DAILY) ||
2170
+ systemd_timer_enable_unit(0, SCHEDULE_WEEKLY) ||
2171
+ systemd_timer_delete_unit_templates();
2172
+}
2173
+
2174
+static int systemd_timer_write_unit_templates(const char *exec_path)
2175
+{
2176
+ char *filename;
2177
+ FILE *file;
2178
+ const char *unit;
2179
+
2180
+ filename = xdg_config_home_systemd("git-maintenance@.timer");
2181
+ if (safe_create_leading_directories(filename)) {
2182
+ error(_("failed to create directories for '%s'"), filename);
2183
+ goto error;
2184
+ }
2185
+ file = fopen_or_warn(filename, "w");
2186
+ if (file == NULL)
2187
+ goto error;
2188
+
2189
+ unit = "# This file was created and is maintained by Git.\n"
2190
+ "# Any edits made in this file might be replaced in the future\n"
2191
+ "# by a Git command.\n"
2192
+ "\n"
2193
+ "[Unit]\n"
2194
+ "Description=Optimize Git repositories data\n"
2195
+ "\n"
2196
+ "[Timer]\n"
2197
+ "OnCalendar=%i\n"
2198
+ "Persistent=true\n"
2199
+ "\n"
2200
+ "[Install]\n"
2201
+ "WantedBy=timers.target\n";
2202
+ if (fputs(unit, file) == EOF) {
2203
+ error(_("failed to write to '%s'"), filename);
2204
+ fclose(file);
2205
+ goto error;
2206
+ }
2207
+ if (fclose(file) == EOF) {
2208
+ error_errno(_("failed to flush '%s'"), filename);
2209
+ goto error;
2210
+ }
2211
+ free(filename);
2212
+
2213
+ filename = xdg_config_home_systemd("git-maintenance@.service");
2214
+ file = fopen_or_warn(filename, "w");
2215
+ if (file == NULL)
2216
+ goto error;
2217
+
2218
+ unit = "# This file was created and is maintained by Git.\n"
2219
+ "# Any edits made in this file might be replaced in the future\n"
2220
+ "# by a Git command.\n"
2221
+ "\n"
2222
+ "[Unit]\n"
2223
+ "Description=Optimize Git repositories data\n"
2224
+ "\n"
2225
+ "[Service]\n"
2226
+ "Type=oneshot\n"
2227
+ "ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%i\n"
2228
+ "LockPersonality=yes\n"
2229
+ "MemoryDenyWriteExecute=yes\n"
2230
+ "NoNewPrivileges=yes\n"
2231
+ "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\n"
2232
+ "RestrictNamespaces=yes\n"
2233
+ "RestrictRealtime=yes\n"
2234
+ "RestrictSUIDSGID=yes\n"
2235
+ "SystemCallArchitectures=native\n"
2236
+ "SystemCallFilter=@system-service\n";
2237
+ if (fprintf(file, unit, exec_path, exec_path) < 0) {
2238
+ error(_("failed to write to '%s'"), filename);
2239
+ fclose(file);
2240
+ goto error;
2241
+ }
2242
+ if (fclose(file) == EOF) {
2243
+ error_errno(_("failed to flush '%s'"), filename);
2244
+ goto error;
2245
+ }
2246
+ free(filename);
2247
+ return 0;
2248
+
2249
+error:
2250
+ free(filename);
2251
+ systemd_timer_delete_unit_templates();
2252
+ return -1;
2253
+}
2254
+
2255
+static int systemd_timer_setup_units(void)
2256
+{
2257
+ const char *exec_path = git_exec_path();
2258
+
2259
+ int ret = systemd_timer_write_unit_templates(exec_path) ||
2260
+ systemd_timer_enable_unit(1, SCHEDULE_HOURLY) ||
2261
+ systemd_timer_enable_unit(1, SCHEDULE_DAILY) ||
2262
+ systemd_timer_enable_unit(1, SCHEDULE_WEEKLY);
2263
+ if (ret)
2264
+ systemd_timer_delete_units();
2265
+ return ret;
2266
+}
2267
+
2268
+static int systemd_timer_update_schedule(int run_maintenance, int fd)
2269
+{
2270
+ if (run_maintenance)
2271
+ return systemd_timer_setup_units();
2272
+ else
2273
+ return systemd_timer_delete_units();
2274
+}
2275
+
2276
enum scheduler {
2277
SCHEDULER_INVALID = -1,
2278
SCHEDULER_AUTO,
2279
SCHEDULER_CRON,
2280
+ SCHEDULER_SYSTEMD,
2281
SCHEDULER_LAUNCHCTL,
2282
SCHEDULER_SCHTASKS,
2283
};
2292
.is_available = is_crontab_available,
2293
.update_schedule = crontab_update_schedule,
2294
},
2295
+ [SCHEDULER_SYSTEMD] = {
2296
+ .name = "systemctl",
2297
+ .is_available = is_systemd_timer_available,
2298
+ .update_schedule = systemd_timer_update_schedule,
2299
+ },
2300
[SCHEDULER_LAUNCHCTL] = {
2301
.name = "launchctl",
2302
.is_available = is_launchctl_available,
2317
return SCHEDULER_AUTO;
2318
else if (!strcasecmp(value, "cron") || !strcasecmp(value, "crontab"))
2319
return SCHEDULER_CRON;
2320
+ else if (!strcasecmp(value, "systemd") ||
2321
+ !strcasecmp(value, "systemd-timer"))
2322
+ return SCHEDULER_SYSTEMD;
2323
else if (!strcasecmp(value, "launchctl"))
2324
return SCHEDULER_LAUNCHCTL;
2325
else if (!strcasecmp(value, "schtasks"))
2356
#elif defined(GIT_WINDOWS_NATIVE)
2357
return SCHEDULER_SCHTASKS;
2358
2359
+#elif defined(__linux__)
2360
+ if (is_systemd_timer_available())
2361
+ return SCHEDULER_SYSTEMD;
2362
+ else if (is_crontab_available())
2363
+ return SCHEDULER_CRON;
2364
+ else
2365
+ die(_("neither systemd timers nor crontab are available"));
2366
+
2367
#else
2368
return SCHEDULER_CRON;
2369
#endif