1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "Remotecachelink.h"
22 #include "..\TGitCache\CacheInterface.h"
24 #include "CreateProcessHelper.h"
25 #include "PathUtils.h"
27 CRemoteCacheLink::CRemoteCacheLink(void)
28 : m_hPipe(INVALID_HANDLE_VALUE
)
29 , m_hEvent(INVALID_HANDLE_VALUE
)
30 , m_hCommandPipe(INVALID_HANDLE_VALUE
)
32 SecureZeroMemory(&m_dummyStatus
, sizeof(m_dummyStatus
));
33 m_dummyStatus
.text_status
= git_wc_status_none
;
34 m_dummyStatus
.prop_status
= git_wc_status_none
;
35 // m_dummyStatus.repos_text_status = git_wc_status_none;
36 // m_dummyStatus.repos_prop_status = git_wc_status_none;
41 CRemoteCacheLink::~CRemoteCacheLink(void)
48 bool CRemoteCacheLink::EnsurePipeOpen()
50 AutoLocker
lock(m_critSec
);
51 if(m_hPipe
!= INVALID_HANDLE_VALUE
)
57 GetCachePipeName(), // pipe name
58 GENERIC_READ
| // read and write access
61 NULL
, // default security attributes
62 OPEN_EXISTING
, // opens existing pipe
63 FILE_FLAG_OVERLAPPED
, // default attributes
64 NULL
); // no template file
66 if (m_hPipe
== INVALID_HANDLE_VALUE
&& GetLastError() == ERROR_PIPE_BUSY
)
68 // TSVNCache is running but is busy connecting a different client.
69 // Do not give up immediately but wait for a few milliseconds until
70 // the server has created the next pipe instance
71 if (WaitNamedPipe(GetCachePipeName(), 50))
74 GetCachePipeName(), // pipe name
75 GENERIC_READ
| // read and write access
78 NULL
, // default security attributes
79 OPEN_EXISTING
, // opens existing pipe
80 FILE_FLAG_OVERLAPPED
, // default attributes
81 NULL
); // no template file
86 if (m_hPipe
!= INVALID_HANDLE_VALUE
)
88 // The pipe connected; change to message-read mode.
91 dwMode
= PIPE_READMODE_MESSAGE
;
92 if(!SetNamedPipeHandleState(
93 m_hPipe
, // pipe handle
94 &dwMode
, // new pipe mode
95 NULL
, // don't set maximum bytes
96 NULL
)) // don't set maximum time
98 ATLTRACE("SetNamedPipeHandleState failed");
100 m_hPipe
= INVALID_HANDLE_VALUE
;
103 // create an unnamed (=local) manual reset event for use in the overlapped structure
104 m_hEvent
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
107 ATLTRACE("CreateEvent failed");
115 bool CRemoteCacheLink::EnsureCommandPipeOpen()
117 AutoLocker
lock(m_critSec
);
118 if(m_hCommandPipe
!= INVALID_HANDLE_VALUE
)
123 m_hCommandPipe
= CreateFile(
124 GetCacheCommandPipeName(), // pipe name
125 GENERIC_READ
| // read and write access
128 NULL
, // default security attributes
129 OPEN_EXISTING
, // opens existing pipe
130 FILE_FLAG_OVERLAPPED
, // default attributes
131 NULL
); // no template file
133 if (m_hCommandPipe
== INVALID_HANDLE_VALUE
&& GetLastError() == ERROR_PIPE_BUSY
)
135 // TSVNCache is running but is busy connecting a different client.
136 // Do not give up immediately but wait for a few milliseconds until
137 // the server has created the next pipe instance
138 if (WaitNamedPipe(GetCacheCommandPipeName(), 50))
140 m_hCommandPipe
= CreateFile(
141 GetCacheCommandPipeName(), // pipe name
142 GENERIC_READ
| // read and write access
145 NULL
, // default security attributes
146 OPEN_EXISTING
, // opens existing pipe
147 FILE_FLAG_OVERLAPPED
, // default attributes
148 NULL
); // no template file
153 if (m_hCommandPipe
!= INVALID_HANDLE_VALUE
)
155 // The pipe connected; change to message-read mode.
158 dwMode
= PIPE_READMODE_MESSAGE
;
159 if(!SetNamedPipeHandleState(
160 m_hCommandPipe
, // pipe handle
161 &dwMode
, // new pipe mode
162 NULL
, // don't set maximum bytes
163 NULL
)) // don't set maximum time
165 ATLTRACE("SetNamedPipeHandleState failed");
166 CloseHandle(m_hCommandPipe
);
167 m_hCommandPipe
= INVALID_HANDLE_VALUE
;
176 void CRemoteCacheLink::ClosePipe()
178 AutoLocker
lock(m_critSec
);
180 if(m_hPipe
!= INVALID_HANDLE_VALUE
)
182 CloseHandle(m_hPipe
);
183 CloseHandle(m_hEvent
);
184 m_hPipe
= INVALID_HANDLE_VALUE
;
185 m_hEvent
= INVALID_HANDLE_VALUE
;
189 void CRemoteCacheLink::CloseCommandPipe()
191 AutoLocker
lock(m_critSec
);
193 if(m_hCommandPipe
!= INVALID_HANDLE_VALUE
)
195 // now tell the cache we don't need it's command thread anymore
197 TGITCacheCommand cmd
;
198 SecureZeroMemory(&cmd
, sizeof(TGITCacheCommand
));
199 cmd
.command
= TGITCACHECOMMAND_END
;
201 m_hCommandPipe
, // handle to pipe
202 &cmd
, // buffer to write from
203 sizeof(cmd
), // number of bytes to write
204 &cbWritten
, // number of bytes written
205 NULL
); // not overlapped I/O
206 DisconnectNamedPipe(m_hCommandPipe
);
207 CloseHandle(m_hCommandPipe
);
208 m_hCommandPipe
= INVALID_HANDLE_VALUE
;
212 bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTGitPath
& Path
, TGITCacheResponse
* pReturnedStatus
, bool bRecursive
)
214 if(!EnsurePipeOpen())
216 // We've failed to open the pipe - try and start the cache
217 // but only if the last try to start the cache was a certain time
218 // ago. If we just try over and over again without a small pause
219 // in between, the explorer is rendered unusable!
220 // Failing to start the cache can have different reasons: missing exe,
221 // missing registry key, corrupt exe, ...
222 if (((long)GetTickCount() - m_lastTimeout
) < 0)
225 PROCESS_INFORMATION process
;
226 memset(&startup
, 0, sizeof(startup
));
227 startup
.cb
= sizeof(startup
);
228 memset(&process
, 0, sizeof(process
));
230 CString sCachePath
= CPathUtils::GetAppDirectory(g_hmodThisDll
) + _T("TGitCache.exe");
232 typedef BOOL (WINAPI
*LPFN_ISWOW64PROCESS
) (HANDLE
, PBOOL
);
233 LPFN_ISWOW64PROCESS fnIsWow64Process
;
234 fnIsWow64Process
= (LPFN_ISWOW64PROCESS
)GetProcAddress(GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
236 if (NULL
!= fnIsWow64Process
)
238 BOOL bIsWow64
= false;
239 if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64
))
245 CRegString tgitinstalled64
= CRegString(_T("Software\\TortoiseGit\\CachePath"), _T(""), false, HKEY_LOCAL_MACHINE
, KEY_WOW64_64KEY
);
246 if (!CString(tgitinstalled64
).IsEmpty())
247 sCachePath
= tgitinstalled64
;
250 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath
, NULL
))
252 ATLTRACE("Failed to start x64 cache\n");
253 CString sCachePath
= CPathUtils::GetAppDirectory(g_hmodThisDll
) + _T("TGitCache.exe");
254 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath
, NULL
))
256 // It's not appropriate to do a message box here, because there may be hundreds of calls
257 ATLTRACE("Failed to start cache\n");
262 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath
, NULL
))
264 // It's not appropriate to do a message box here, because there may be hundreds of calls
265 ATLTRACE("Failed to start cache\n");
269 CloseHandle(process
.hThread
);
270 CloseHandle(process
.hProcess
);
271 sCachePath
.ReleaseBuffer();
273 // Wait for the cache to open
274 long endTime
= (long)GetTickCount()+1000;
275 while(!EnsurePipeOpen())
277 if(((long)GetTickCount() - endTime
) > 0)
279 m_lastTimeout
= (long)GetTickCount()+10000;
285 AutoLocker
lock(m_critSec
);
288 TGITCacheRequest request
;
289 request
.flags
= TGITCACHE_FLAGS_NONOTIFICATIONS
;
292 request
.flags
|= TGITCACHE_FLAGS_RECUSIVE_STATUS
;
294 wcsncpy_s(request
.path
, MAX_PATH
+1, Path
.GetWinPath(), MAX_PATH
);
295 SecureZeroMemory(&m_Overlapped
, sizeof(OVERLAPPED
));
296 m_Overlapped
.hEvent
= m_hEvent
;
297 // Do the transaction in overlapped mode.
298 // That way, if anything happens which might block this call
299 // we still can get out of it. We NEVER MUST BLOCK THE SHELL!
300 // A blocked shell is a very bad user impression, because users
301 // who don't know why it's blocked might find the only solution
302 // to such a problem is a reboot and therefore they might lose
304 // One particular situation where the shell could hang is when
305 // the cache crashes and our crash report dialog comes up.
306 // Sure, it would be better to have no situations where the shell
307 // even can get blocked, but the timeout of 10 seconds is long enough
308 // so that users still recognize that something might be wrong and
309 // report back to us so we can investigate further.
311 BOOL fSuccess
= TransactNamedPipe(m_hPipe
,
312 &request
, sizeof(request
),
313 pReturnedStatus
, sizeof(*pReturnedStatus
),
314 &nBytesRead
, &m_Overlapped
);
318 if (GetLastError()!=ERROR_IO_PENDING
)
320 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
325 // TransactNamedPipe is working in an overlapped operation.
326 // Wait for it to finish
327 DWORD dwWait
= WaitForSingleObject(m_hEvent
, 10000);
328 if (dwWait
== WAIT_OBJECT_0
)
330 fSuccess
= GetOverlappedResult(m_hPipe
, &m_Overlapped
, &nBytesRead
, FALSE
);
334 // the cache didn't respond!
347 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath
& path
)
349 EnsureCommandPipeOpen();
350 if (m_hCommandPipe
!= INVALID_HANDLE_VALUE
)
353 TGITCacheCommand cmd
;
354 SecureZeroMemory(&cmd
, sizeof(TGITCacheCommand
));
355 cmd
.command
= TGITCACHECOMMAND_RELEASE
;
356 wcsncpy_s(cmd
.path
, MAX_PATH
+1, path
.GetDirectory().GetWinPath(), MAX_PATH
);
357 BOOL fSuccess
= WriteFile(
358 m_hCommandPipe
, // handle to pipe
359 &cmd
, // buffer to write from
360 sizeof(cmd
), // number of bytes to write
361 &cbWritten
, // number of bytes written
362 NULL
); // not overlapped I/O
363 if (! fSuccess
|| sizeof(cmd
) != cbWritten
)