Skip to content

Commit 6866f7f

Browse files
committed
Use libbacktrace to debug file locking issues
On Windows, it is not possible to overwrite files as long as they are in use. This is particularly funny (not!) when the same process still holds open file handles to that file, and it notoriously hard to debug. Let's add a compile-time option to output stacktraces to the offending calls when we fail to overwrite/remove them. Note: it has to be a compile time option because we would have to turn off ASLR otherwise (and it is too good of a first line of defense to just turn off). Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 09fd353 commit 6866f7f

File tree

3 files changed

+268
-0
lines changed

3 files changed

+268
-0
lines changed

compat/mingw.c

+249
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,175 @@
1111
#include "../config.h"
1212
#include "../string-list.h"
1313

14+
#ifdef DEBUG_FILE_LOCKS
15+
16+
#include "libbacktrace/backtrace.h"
17+
#include "../hashmap.h"
18+
19+
struct file_lock_backtrace
20+
{
21+
struct hashmap_entry entry;
22+
int fd, count;
23+
uintptr_t *pcs;
24+
const char *filename;
25+
};
26+
27+
static CRITICAL_SECTION backtrace_mutex;
28+
static struct hashmap file_lock_map;
29+
#define FILE_LOCK_MAX_FD 256
30+
static struct file_lock_backtrace *file_lock_by_fd[FILE_LOCK_MAX_FD];
31+
32+
static int my_backtrace_cb(void *data, uintptr_t pc, const char *filename,
33+
int lineno, const char *function)
34+
{
35+
struct strbuf *buf = data;
36+
37+
if (!function || !strcmp("__tmainCRTStartup", function))
38+
return -1;
39+
40+
strbuf_addf(buf, "%s:%d:\n\t%s\n", filename, lineno, function);
41+
42+
return 0;
43+
}
44+
45+
static void my_error_cb(void *data, const char *msg, int errnum)
46+
{
47+
struct strbuf *buf = data;
48+
49+
strbuf_addf(buf, "error %s (%d)\n", msg, errnum);
50+
}
51+
52+
static void file_lock_backtrace(struct file_lock_backtrace *data,
53+
struct strbuf *buf)
54+
{
55+
static struct backtrace_state *state;
56+
static int initialized;
57+
int i;
58+
59+
if (!initialized) {
60+
EnterCriticalSection(&backtrace_mutex);
61+
if (!initialized) {
62+
state = backtrace_create_state(NULL, 1, my_error_cb,
63+
NULL);
64+
initialized = 1;
65+
}
66+
LeaveCriticalSection(&backtrace_mutex);
67+
}
68+
69+
if (data->fd >= 0)
70+
strbuf_addf(buf, "file '%s' (fd %d) was opened here:\n",
71+
data->filename, data->fd);
72+
for (i = 0; i < data->count; i++)
73+
if (backtrace_pcinfo(state, data->pcs[i], my_backtrace_cb,
74+
my_error_cb, buf) < 0)
75+
break;
76+
}
77+
78+
static struct file_lock_backtrace *alloc_file_lock_backtrace(int fd,
79+
const char *filename)
80+
{
81+
DECLARE_PROC_ADDR(kernel32.dll, USHORT, RtlCaptureStackBackTrace,
82+
ULONG, ULONG, PVOID*, PULONG);
83+
struct file_lock_backtrace *result;
84+
uintptr_t pcs[62];
85+
int count = 0;
86+
size_t pcs_size = 0, size;
87+
88+
if ((fd < 0 || fd >= FILE_LOCK_MAX_FD) && fd != -123)
89+
BUG("Called with fd = %d\n", fd);
90+
91+
if (INIT_PROC_ADDR(RtlCaptureStackBackTrace)) {
92+
count = RtlCaptureStackBackTrace(1, ARRAY_SIZE(pcs),
93+
(void **)pcs, NULL);
94+
pcs_size = sizeof(uintptr_t) * count;
95+
}
96+
size = sizeof(*result) + pcs_size + strlen(filename) + 1;
97+
98+
result = xmalloc(size);
99+
result->fd = fd;
100+
result->count = count;
101+
if (!count)
102+
result->pcs = NULL;
103+
else {
104+
result->pcs = (uintptr_t *)((char *)result + sizeof(*result));
105+
memcpy(result->pcs, pcs, pcs_size);
106+
}
107+
108+
result->filename = ((char *)result + sizeof(*result) + pcs_size);
109+
strcpy((char *)result->filename, filename);
110+
111+
if (fd < 0)
112+
return result;
113+
114+
EnterCriticalSection(&backtrace_mutex);
115+
if (file_lock_by_fd[fd]) {
116+
struct strbuf buf = STRBUF_INIT;
117+
strbuf_addf(&buf, "Bogus file_lock (%d). First trace:\n", fd);
118+
file_lock_backtrace(file_lock_by_fd[fd], &buf);
119+
strbuf_addf(&buf, "\nSecond trace:\n");
120+
file_lock_backtrace(result, &buf);
121+
BUG(buf.buf);
122+
}
123+
file_lock_by_fd[fd] = result;
124+
hashmap_entry_init(&result->entry, strihash(filename));
125+
hashmap_add(&file_lock_map, result);
126+
LeaveCriticalSection(&backtrace_mutex);
127+
128+
return result;
129+
}
130+
131+
static void current_backtrace(struct strbuf *buf)
132+
{
133+
struct file_lock_backtrace *p = alloc_file_lock_backtrace(-123, "");
134+
file_lock_backtrace(p, buf);
135+
free(p);
136+
}
137+
138+
static void remove_file_lock_backtrace(int fd)
139+
{
140+
if (fd < 0 || fd >= FILE_LOCK_MAX_FD)
141+
BUG("Called with fd = %d\n", fd);
142+
143+
EnterCriticalSection(&backtrace_mutex);
144+
if (!file_lock_by_fd[fd])
145+
BUG("trying to release non-existing lock for fd %d", fd);
146+
147+
hashmap_remove(&file_lock_map, file_lock_by_fd[fd], NULL);
148+
free(file_lock_by_fd[fd]);
149+
file_lock_by_fd[fd] = NULL;
150+
LeaveCriticalSection(&backtrace_mutex);
151+
}
152+
153+
static int file_lock_backtrace_cmp(const void *dummy,
154+
const struct file_lock_backtrace *a,
155+
const struct file_lock_backtrace *b,
156+
const void *keydata)
157+
{
158+
return strcasecmp(a->filename,
159+
keydata ? (const char *)keydata : b->filename);
160+
}
161+
162+
static struct file_lock_backtrace *file_lock_lookup(const char *filename)
163+
{
164+
struct hashmap_entry entry;
165+
struct file_lock_backtrace *result;
166+
167+
hashmap_entry_init(&entry, strihash(filename));
168+
EnterCriticalSection(&backtrace_mutex);
169+
result = hashmap_get(&file_lock_map, &entry, filename);
170+
LeaveCriticalSection(&backtrace_mutex);
171+
172+
return result;
173+
}
174+
175+
static void initialize_file_lock_map(void)
176+
{
177+
InitializeCriticalSection(&backtrace_mutex);
178+
hashmap_init(&file_lock_map, (hashmap_cmp_fn)file_lock_backtrace_cmp,
179+
NULL, 0);
180+
}
181+
#endif
182+
14183
#define HCAST(type, handle) ((type)(intptr_t)handle)
15184

16185
int err_win_to_posix(DWORD winerr)
@@ -405,6 +574,21 @@ int mingw_unlink(const char *pathname)
405574
*/
406575
if (!_wrmdir(wpathname))
407576
return 0;
577+
#ifdef DEBUG_FILE_LOCKS
578+
{
579+
struct file_lock_backtrace *p =
580+
file_lock_lookup(pathname);
581+
if (p) {
582+
struct strbuf buf = STRBUF_INIT;
583+
strbuf_addf(&buf, "the file '%s' wants "
584+
"to be deleted here:\n", pathname);
585+
current_backtrace(&buf);
586+
strbuf_addf(&buf, "\nBut it is still open:\n");
587+
file_lock_backtrace(p, &buf);
588+
die("%s\n", buf.buf);
589+
}
590+
}
591+
#endif
408592
} while (retry_ask_yes_no(&tries, "Unlink of file '%s' failed. "
409593
"Should I try again?", pathname));
410594
return -1;
@@ -553,6 +737,10 @@ int mingw_open (const char *filename, int oflags, ...)
553737
if (fd >= 0 && set_hidden_flag(wfilename, 1))
554738
warning("could not mark '%s' as hidden.", filename);
555739
}
740+
#ifdef DEBUG_FILE_LOCKS
741+
if (fd >= 0)
742+
alloc_file_lock_backtrace(fd, filename);
743+
#endif
556744
return fd;
557745
}
558746

@@ -601,6 +789,10 @@ FILE *mingw_fopen (const char *filename, const char *otype)
601789
errno = ENOENT;
602790
if (file && hide && set_hidden_flag(wfilename, 1))
603791
warning("could not mark '%s' as hidden.", filename);
792+
#ifdef DEBUG_FILE_LOCKS
793+
if (file)
794+
alloc_file_lock_backtrace(fileno(file), filename);
795+
#endif
604796
return file;
605797
}
606798

@@ -609,6 +801,9 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
609801
int hide = needs_hiding(filename);
610802
FILE *file;
611803
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
804+
#ifdef DEBUG_FILE_LOCKS
805+
int oldfd = fileno(stream);
806+
#endif
612807
if (filename && !strcmp(filename, "/dev/null"))
613808
filename = "nul";
614809
if (xutftowcs_long_path(wfilename, filename) < 0 ||
@@ -621,9 +816,37 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
621816
file = _wfreopen(wfilename, wotype, stream);
622817
if (file && hide && set_hidden_flag(wfilename, 1))
623818
warning("could not mark '%s' as hidden.", filename);
819+
#ifdef DEBUG_FILE_LOCKS
820+
if (file) {
821+
remove_file_lock_backtrace(oldfd);
822+
alloc_file_lock_backtrace(fileno(file), filename);
823+
}
824+
#endif
624825
return file;
625826
}
626827

828+
#ifdef DEBUG_FILE_LOCKS
829+
#undef close
830+
int mingw_close(int fd)
831+
{
832+
int ret = close(fd);
833+
if (!ret)
834+
remove_file_lock_backtrace(fd);
835+
return ret;
836+
}
837+
#define close mingw_close
838+
839+
#undef fclose
840+
int mingw_fclose(FILE *stream)
841+
{
842+
int fd = fileno(stream), ret = fclose(stream);
843+
if (!ret)
844+
remove_file_lock_backtrace(fd);
845+
return ret;
846+
}
847+
#define fclose mingw_fclose
848+
#endif
849+
627850
#undef fflush
628851
int mingw_fflush(FILE *stream)
629852
{
@@ -2438,6 +2661,26 @@ int mingw_rename(const char *pold, const char *pnew)
24382661
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY))
24392662
goto repeat;
24402663
}
2664+
#ifdef DEBUG_FILE_LOCKS
2665+
{
2666+
struct file_lock_backtrace *p = file_lock_lookup(pnew);
2667+
const char *which = "target";
2668+
if (!p) {
2669+
p = file_lock_lookup(pold);
2670+
which = "source";
2671+
}
2672+
if (p) {
2673+
struct strbuf buf = STRBUF_INIT;
2674+
strbuf_addf(&buf, "the file '%s' wants to be "
2675+
"renamed to '%s' here:\n", pold, pnew);
2676+
current_backtrace(&buf);
2677+
strbuf_addf(&buf, "\nBut the %s is still open:\n",
2678+
which);
2679+
file_lock_backtrace(p, &buf);
2680+
die("%s\n", buf.buf);
2681+
}
2682+
}
2683+
#endif
24412684
if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
24422685
"Should I try again?", pold, pnew))
24432686
goto repeat;
@@ -3313,6 +3556,9 @@ int msc_startup(int argc, wchar_t **w_argv, wchar_t **w_env)
33133556
fsync_object_files = 1;
33143557
maybe_redirect_std_handles();
33153558
adjust_symlink_flags();
3559+
#ifdef DEBUG_FILE_LOCKS
3560+
initialize_file_lock_map();
3561+
#endif
33163562

33173563
/* determine size of argv conversion buffer */
33183564
maxlen = wcslen(_wpgmptr);
@@ -3381,6 +3627,9 @@ void mingw_startup(void)
33813627
fsync_object_files = 1;
33823628
maybe_redirect_std_handles();
33833629
adjust_symlink_flags();
3630+
#ifdef DEBUG_FILE_LOCKS
3631+
initialize_file_lock_map();
3632+
#endif
33843633

33853634
/* get wide char arguments and environment */
33863635
si.newmode = 0;

compat/mingw.h

+8
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,14 @@ int mingw_fflush(FILE *stream);
245245
ssize_t mingw_write(int fd, const void *buf, size_t len);
246246
#define write mingw_write
247247

248+
#ifdef DEBUG_FILE_LOCKS
249+
int mingw_close(int fd);
250+
#define close mingw_close
251+
252+
int mingw_fclose(FILE *stream);
253+
#define fclose mingw_fclose
254+
#endif
255+
248256
int mingw_access(const char *filename, int mode);
249257
#undef access
250258
#define access mingw_access

config.mak.uname

+11
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,15 @@ ifneq (,$(findstring MINGW,$(uname_S)))
635635
NATIVE_CRLF = YesPlease
636636
X = .exe
637637
SPARSE_FLAGS = -Wno-one-bit-signed-bitfield
638+
ifneq (,$(DEBUG_FILE_LOCKS))
639+
BACKTRACE_SOURCES := $(patsubst %,compat/libbacktrace/%,atomic.c \
640+
alloc.c dwarf.c state.c fileline.c posix.c pecoff.c sort.c \
641+
read.c)
642+
BACKTRACE_OBJS := $(patsubst %.c,%.o,$(BACKTRACE_SOURCES))
643+
COMPAT_OBJS += $(BACKTRACE_OBJS)
644+
$(BACKTRACE_OBJS): EXTRA_CPPFLAGS = -I compat/libbacktrace
645+
COMPAT_CFLAGS += -DDEBUG_FILE_LOCKS
646+
endif
638647
ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
639648
htmldir = share/doc/git/$(firstword $(subst -, ,$(GIT_VERSION)))/html
640649
prefix =
@@ -652,9 +661,11 @@ else
652661
# Enable DEP
653662
BASIC_LDFLAGS += -Wl,--nxcompat
654663
# Enable ASLR (unless debugging)
664+
ifeq (,$(DEBUG_FILE_LOCKS))
655665
ifneq (,$(findstring -O,$(CFLAGS)))
656666
BASIC_LDFLAGS += -Wl,--dynamicbase
657667
endif
668+
endif
658669
ifeq (MINGW32,$(MSYSTEM))
659670
prefix = /mingw32
660671
BASIC_LDFLAGS += -Wl,--pic-executable,-e,_mainCRTStartup

0 commit comments

Comments
 (0)