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
16185int 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
628851int 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 ;
0 commit comments