Skip to content

Commit d5e9b6b

Browse files
committed
tmp-objdir: do not closedir() when handling a signal
In the tmp-objdir api, tmp_objdir_create will create a temporary directory but also register signal handlers responsible for removing the directory's contents and the directory itself. However, the function responsible for recursively removing the contents and directory, remove_dir_recurse() calls opendir(3) and closedir(3). This can be problematic because these functions allocate and free memory, which are not async-signal-safe functions. This can lead to deadlocks. One place we call tmp_objdir_create() is in git-receive-pack, where we create a temporary quarantine directory "incoming". Incoming objects will be written to this directory before they get moved to the object directory. We have observed this code leading to a deadlock: Thread 1 (Thread 0x7f621ba0b200 (LWP 326305)): #0 __lll_lock_wait_private (futex=futex@entry=0x7f621bbf8b80 <main_arena>) at ./lowlevellock.c:35 #1 0x00007f621baa635b in __GI___libc_malloc (bytes=bytes@entry=32816) at malloc.c:3064 #2 0x00007f621bae9f49 in __alloc_dir (statp=0x7fff2ea7ed60, flags=0, close_fd=true, fd=5) at ../sysdeps/posix/opendir.c:118 #3 opendir_tail (fd=5) at ../sysdeps/posix/opendir.c:69 #4 __opendir (name=<optimized out>) at ../sysdeps/posix/opendir.c:92 #5 0x0000557c19c77de1 in remove_dir_recurse () git#6 0x0000557c19d81a4f in remove_tmp_objdir_on_signal () #7 <signal handler called> git#8 _int_malloc (av=av@entry=0x7f621bbf8b80 <main_arena>, bytes=bytes@entry=7160) at malloc.c:4116 git#9 0x00007f621baa62c9 in __GI___libc_malloc (bytes=7160) at malloc.c:3066 git#10 0x00007f621bd1e987 in inflateInit2_ () from /opt/gitlab/embedded/lib/libz.so.1 git#11 0x0000557c19dbe5f4 in git_inflate_init () git#12 0x0000557c19cee02a in unpack_compressed_entry () git#13 0x0000557c19cf08cb in unpack_entry () git#14 0x0000557c19cf0f32 in packed_object_info () git#15 0x0000557c19cd68cd in do_oid_object_info_extended () git#16 0x0000557c19cd6e2b in read_object_file_extended () git#17 0x0000557c19cdec2f in parse_object () git#18 0x0000557c19c34977 in lookup_commit_reference_gently () git#19 0x0000557c19d69309 in mark_uninteresting () git#20 0x0000557c19d2d180 in do_for_each_repo_ref_iterator () git#21 0x0000557c19d21678 in for_each_ref () git#22 0x0000557c19d6a94f in assign_shallow_commits_to_refs () git#23 0x0000557c19bc02b2 in cmd_receive_pack () git#24 0x0000557c19b29fdd in handle_builtin () git#25 0x0000557c19b2a526 in cmd_main () git#26 0x0000557c19b28ea2 in main () To prevent this, add a flag REMOVE_DIR_SIGNAL that allows remove_dir_recurse() to know that a signal is being handled and avoid calling opendir(3). One implication of this change is that when signal handling, the temporary directory may not get cleaned up properly. Signed-off-by: John Cai <[email protected]>
1 parent 4fd6c5e commit d5e9b6b

File tree

3 files changed

+15
-3
lines changed

3 files changed

+15
-3
lines changed

dir.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3244,7 +3244,7 @@ void strip_dir_trailing_slashes(char *dir)
32443244

32453245
static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
32463246
{
3247-
DIR *dir;
3247+
DIR *dir = NULL;
32483248
struct dirent *e;
32493249
int ret = 0, original_len = path->len, len, kept_down = 0;
32503250
int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
@@ -3261,7 +3261,10 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
32613261
}
32623262

32633263
flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
3264-
dir = opendir(path->buf);
3264+
3265+
if ((flag & REMOVE_DIR_SIGNAL) == 0)
3266+
dir = opendir(path->buf);
3267+
32653268
if (!dir) {
32663269
if (errno == ENOENT)
32673270
return keep_toplevel ? -1 : 0;
@@ -3302,6 +3305,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
33023305
ret = -1;
33033306
break;
33043307
}
3308+
33053309
closedir(dir);
33063310

33073311
strbuf_setlen(path, original_len);

dir.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,9 @@ int get_sparse_checkout_patterns(struct pattern_list *pl);
498498
/* Remove the_original_cwd too */
499499
#define REMOVE_DIR_PURGE_ORIGINAL_CWD 0x08
500500

501+
/* Indicates a signal is being handled */
502+
#define REMOVE_DIR_SIGNAL 0x16
503+
501504
/*
502505
* Remove path and its contents, recursively. flags is a combination
503506
* of the above REMOVE_DIR_* constants. Return 0 on success.

tmp-objdir.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ static void tmp_objdir_free(struct tmp_objdir *t)
3434
static int tmp_objdir_destroy_1(struct tmp_objdir *t, int on_signal)
3535
{
3636
int err;
37+
int flags = 0;
3738

3839
if (!t)
3940
return 0;
@@ -49,7 +50,11 @@ static int tmp_objdir_destroy_1(struct tmp_objdir *t, int on_signal)
4950
* have pre-grown t->path sufficiently so that this
5051
* doesn't happen in practice.
5152
*/
52-
err = remove_dir_recursively(&t->path, 0);
53+
54+
if (on_signal)
55+
flags = flags | REMOVE_DIR_SIGNAL;
56+
57+
err = remove_dir_recursively(&t->path, flags);
5358

5459
/*
5560
* When we are cleaning up due to a signal, we won't bother

0 commit comments

Comments
 (0)