diff --git a/run-command.c b/run-command.c index c146a56532..28202a81d8 100644 --- a/run-command.c +++ b/run-command.c @@ -1944,10 +1944,14 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts) int prepare_auto_maintenance(struct repository *r, int quiet, struct child_process *maint) { - int enabled, auto_detach; + int enabled = 1, auto_detach; - if (!repo_config_get_bool(r, "maintenance.auto", &enabled) && - !enabled) + if (repo_config_get_bool(r, "maintenance.auto", &enabled)) { + int gc_threshold; + if (!repo_config_get_int(r, "gc.auto", &gc_threshold)) + enabled = gc_threshold > 0; + } + if (!enabled) return 0; /* diff --git a/setup.c b/setup.c index 17c0662076..5580529e49 100644 --- a/setup.c +++ b/setup.c @@ -2166,12 +2166,26 @@ int daemonize(void) errno = ENOSYS; return -1; #else - switch (fork()) { + pid_t parent_pid = getpid(); + pid_t child_pid = fork(); + + switch (child_pid) { case 0: + /* + * We're in the child process, so we take ownership of + * all tempfiles. + */ + reassign_tempfile_ownership(parent_pid, getpid()); break; case -1: die_errno(_("fork failed")); default: + /* + * We're in the parent process, so we drop ownership of + * all tempfiles to prevent us from removing them upon + * exit. + */ + reassign_tempfile_ownership(parent_pid, child_pid); exit(0); } if (setsid() == -1) diff --git a/setup.h b/setup.h index 80bc6e5f07..b5bc5f280c 100644 --- a/setup.h +++ b/setup.h @@ -149,6 +149,21 @@ void verify_non_filename(const char *prefix, const char *name); int path_inside_repo(const char *prefix, const char *path); void sanitize_stdfds(void); + +/* + * Daemonize the current process by forking and then exiting the parent + * process. Returns 0 when successful, in which case the parent process will + * have exited and it's the child process that continues to run the code. + * Otherwise, a negative error code is returned and the parent process will + * continue execution. + * + * Note that this function will also perform the following changes: + * + * - Standard file descriptors in the child process are closed. + * - The child process is made a session leader via setsid(3p). + * - All tempfiles owned by the parent process are reassigned to the + * daemonized child process. + */ int daemonize(void); /* diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index ed6f2bedb9..d7f82e1bec 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -73,6 +73,31 @@ test_expect_success 'maintenance.auto config option' ' test_subcommand ! git maintenance run --auto --quiet --detach fifo-maint && + + { git maintenance run --task=prefetch --detach 7>&9 & } && + parent="$!" && + + # Reap the parent process so that the exec call below will not + # get SIGCHLD. + wait "$parent" && + + # Open the git-upload-pack(1) FIFO for writing, which will + # block until the upload-pack script opens it for reading. Once + # exec returns, we know that the daemonized child is alive and + # pinned. + exec 8>fifo-uploadpack && + + test_path_is_file .git/objects/maintenance.lock && + test_path_is_file .git/objects/"maintenance~pid.lock" && + + # Verify that the maintenance.lock still exists, and + # that it was created by the parent process, not the + # child. + echo "pid $parent" >expect && + test_cmp expect .git/objects/"maintenance~pid.lock" && + + # Reopen the maintenance FIFO as read-only so that + # git-maintenance(1) is the only writer. This will cause it to + # close the FIFO once the process exits. + exec 9<&- && + exec 9&- && + cat <&9 && + + test_path_is_missing .git/objects/maintenance.lock && + test_path_is_missing .git/objects/"maintenance~pid.lock" + ) +' + test_expect_success '--detach causes maintenance to run in background' ' test_when_finished "rm -rf repo" && git init repo && diff --git a/tempfile.c b/tempfile.c index 82dfa3d82f..f0fdf58279 100644 --- a/tempfile.c +++ b/tempfile.c @@ -373,3 +373,15 @@ int delete_tempfile(struct tempfile **tempfile_p) return err ? -1 : 0; } + +void reassign_tempfile_ownership(pid_t from, pid_t to) +{ + volatile struct volatile_list_head *pos; + + list_for_each(pos, &tempfile_list) { + struct tempfile *p = list_entry(pos, struct tempfile, list); + + if (is_tempfile_active(p) && p->owner == from) + p->owner = to; + } +} diff --git a/tempfile.h b/tempfile.h index 2d2ae5b657..2227a095fd 100644 --- a/tempfile.h +++ b/tempfile.h @@ -282,4 +282,15 @@ int delete_tempfile(struct tempfile **tempfile_p); */ int rename_tempfile(struct tempfile **tempfile_p, const char *path); +/* + * Reassign ownership of all active tempfiles whose `owner` field matches + * `from` to `to`. + * + * This is intended for use by `daemonize()`; after `fork(2)`-ing, the parent + * transfers ownership to the daemonized child so that its atexit handler does + * not unlink tempfiles that should outlive it, and the child claims the + * inherited tempfiles so that they are cleaned up when the daemon exits. + */ +void reassign_tempfile_ownership(pid_t from, pid_t to); + #endif /* TEMPFILE_H */