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_hCommandPipe(INVALID_HANDLE_VALUE
)
31 SecureZeroMemory(&m_dummyStatus
, sizeof(m_dummyStatus
));
32 m_dummyStatus
.text_status
= git_wc_status_none
;
33 m_dummyStatus
.prop_status
= git_wc_status_none
;
34 // m_dummyStatus.repos_text_status = git_wc_status_none;
35 // m_dummyStatus.repos_prop_status = git_wc_status_none;
40 CRemoteCacheLink::~CRemoteCacheLink(void)
47 bool CRemoteCacheLink::EnsurePipeOpen()
49 AutoLocker
lock(m_critSec
);
50 if(m_hPipe
!= INVALID_HANDLE_VALUE
)
56 GetCachePipeName(), // pipe name
57 GENERIC_READ
| // read and write access
60 NULL
, // default security attributes
61 OPEN_EXISTING
, // opens existing pipe
62 FILE_FLAG_OVERLAPPED
, // default attributes
63 NULL
); // no template file
65 if (m_hPipe
== INVALID_HANDLE_VALUE
&& GetLastError() == ERROR_PIPE_BUSY
)
67 // TSVNCache is running but is busy connecting a different client.
68 // Do not give up immediately but wait for a few milliseconds until
69 // the server has created the next pipe instance
70 if (WaitNamedPipe(GetCachePipeName(), 50))
73 GetCachePipeName(), // pipe name
74 GENERIC_READ
| // read and write access
77 NULL
, // default security attributes
78 OPEN_EXISTING
, // opens existing pipe
79 FILE_FLAG_OVERLAPPED
, // default attributes
80 NULL
); // no template file
85 if (m_hPipe
!= INVALID_HANDLE_VALUE
)
87 // The pipe connected; change to message-read mode.
90 dwMode
= PIPE_READMODE_MESSAGE
;
91 if(!SetNamedPipeHandleState(
92 m_hPipe
, // pipe handle
93 &dwMode
, // new pipe mode
94 NULL
, // don't set maximum bytes
95 NULL
)) // don't set maximum time
97 ATLTRACE("SetNamedPipeHandleState failed");
99 m_hPipe
= INVALID_HANDLE_VALUE
;
102 // create an unnamed (=local) manual reset event for use in the overlapped structure
103 m_hEvent
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
106 ATLTRACE("CreateEvent failed");
114 bool CRemoteCacheLink::EnsureCommandPipeOpen()
116 AutoLocker
lock(m_critSec
);
117 if(m_hCommandPipe
!= INVALID_HANDLE_VALUE
)
122 m_hCommandPipe
= CreateFile(
123 GetCacheCommandPipeName(), // pipe name
124 GENERIC_READ
| // read and write access
127 NULL
, // default security attributes
128 OPEN_EXISTING
, // opens existing pipe
129 FILE_FLAG_OVERLAPPED
, // default attributes
130 NULL
); // no template file
132 if (m_hCommandPipe
== INVALID_HANDLE_VALUE
&& GetLastError() == ERROR_PIPE_BUSY
)
134 // TSVNCache is running but is busy connecting a different client.
135 // Do not give up immediately but wait for a few milliseconds until
136 // the server has created the next pipe instance
137 if (WaitNamedPipe(GetCacheCommandPipeName(), 50))
139 m_hCommandPipe
= CreateFile(
140 GetCacheCommandPipeName(), // pipe name
141 GENERIC_READ
| // read and write access
144 NULL
, // default security attributes
145 OPEN_EXISTING
, // opens existing pipe
146 FILE_FLAG_OVERLAPPED
, // default attributes
147 NULL
); // no template file
152 if (m_hCommandPipe
!= INVALID_HANDLE_VALUE
)
154 // The pipe connected; change to message-read mode.
157 dwMode
= PIPE_READMODE_MESSAGE
;
158 if(!SetNamedPipeHandleState(
159 m_hCommandPipe
, // pipe handle
160 &dwMode
, // new pipe mode
161 NULL
, // don't set maximum bytes
162 NULL
)) // don't set maximum time
164 ATLTRACE("SetNamedPipeHandleState failed");
165 CloseHandle(m_hCommandPipe
);
166 m_hCommandPipe
= INVALID_HANDLE_VALUE
;
175 void CRemoteCacheLink::ClosePipe()
177 AutoLocker
lock(m_critSec
);
179 if(m_hPipe
!= INVALID_HANDLE_VALUE
)
181 CloseHandle(m_hPipe
);
182 CloseHandle(m_hEvent
);
183 m_hPipe
= INVALID_HANDLE_VALUE
;
184 m_hEvent
= INVALID_HANDLE_VALUE
;
188 void CRemoteCacheLink::CloseCommandPipe()
190 AutoLocker
lock(m_critSec
);
192 if(m_hCommandPipe
!= INVALID_HANDLE_VALUE
)
194 // now tell the cache we don't need it's command thread anymore
196 TGITCacheCommand cmd
;
197 SecureZeroMemory(&cmd
, sizeof(TGITCacheCommand
));
198 cmd
.command
= TGITCACHECOMMAND_END
;
200 m_hCommandPipe
, // handle to pipe
201 &cmd
, // buffer to write from
202 sizeof(cmd
), // number of bytes to write
203 &cbWritten
, // number of bytes written
204 NULL
); // not overlapped I/O
205 DisconnectNamedPipe(m_hCommandPipe
);
206 CloseHandle(m_hCommandPipe
);
207 m_hCommandPipe
= INVALID_HANDLE_VALUE
;
211 bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTGitPath
& Path
, TGITCacheResponse
* pReturnedStatus
, bool bRecursive
)
213 if(!EnsurePipeOpen())
215 // We've failed to open the pipe - try and start the cache
216 // but only if the last try to start the cache was a certain time
217 // ago. If we just try over and over again without a small pause
218 // in between, the explorer is rendered unusable!
219 // Failing to start the cache can have different reasons: missing exe,
220 // missing registry key, corrupt exe, ...
221 if (((long)GetTickCount() - m_lastTimeout
) < 0)
224 PROCESS_INFORMATION process
;
225 memset(&startup
, 0, sizeof(startup
));
226 startup
.cb
= sizeof(startup
);
227 memset(&process
, 0, sizeof(process
));
229 CString sCachePath
= CPathUtils::GetAppDirectory(g_hmodThisDll
) + _T("TGitCache.exe");
231 typedef BOOL (WINAPI
*LPFN_ISWOW64PROCESS
) (HANDLE
, PBOOL
);
232 LPFN_ISWOW64PROCESS fnIsWow64Process
;
233 fnIsWow64Process
= (LPFN_ISWOW64PROCESS
)GetProcAddress(GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
235 if (NULL
!= fnIsWow64Process
)
237 BOOL bIsWow64
= false;
238 if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64
))
244 CRegString tgitinstalled64
= CRegString(_T("Software\\TortoiseGit\\CachePath"), _T(""), false, HKEY_LOCAL_MACHINE
, KEY_WOW64_64KEY
);
245 if (!CString(tgitinstalled64
).IsEmpty())
246 sCachePath
= tgitinstalled64
;
249 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath
, NULL
))
251 ATLTRACE("Failed to start x64 cache\n");
252 CString sCachePath
= CPathUtils::GetAppDirectory(g_hmodThisDll
) + _T("TGitCache.exe");
253 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath
, NULL
))
255 // It's not appropriate to do a message box here, because there may be hundreds of calls
256 ATLTRACE("Failed to start cache\n");
261 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath
, NULL
))
263 // It's not appropriate to do a message box here, because there may be hundreds of calls
264 ATLTRACE("Failed to start cache\n");
268 CloseHandle(process
.hThread
);
269 CloseHandle(process
.hProcess
);
270 sCachePath
.ReleaseBuffer();
272 // Wait for the cache to open
273 long endTime
= (long)GetTickCount()+1000;
274 while(!EnsurePipeOpen())
276 if(((long)GetTickCount() - endTime
) > 0)
278 m_lastTimeout
= (long)GetTickCount()+10000;
284 AutoLocker
lock(m_critSec
);
287 TGITCacheRequest request
;
288 request
.flags
= TGITCACHE_FLAGS_NONOTIFICATIONS
;
291 request
.flags
|= TGITCACHE_FLAGS_RECUSIVE_STATUS
;
293 wcsncpy_s(request
.path
, MAX_PATH
+1, Path
.GetWinPath(), MAX_PATH
);
294 SecureZeroMemory(&m_Overlapped
, sizeof(OVERLAPPED
));
295 m_Overlapped
.hEvent
= m_hEvent
;
296 // Do the transaction in overlapped mode.
297 // That way, if anything happens which might block this call
298 // we still can get out of it. We NEVER MUST BLOCK THE SHELL!
299 // A blocked shell is a very bad user impression, because users
300 // who don't know why it's blocked might find the only solution
301 // to such a problem is a reboot and therefore they might lose
303 // One particular situation where the shell could hang is when
304 // the cache crashes and our crash report dialog comes up.
305 // Sure, it would be better to have no situations where the shell
306 // even can get blocked, but the timeout of 10 seconds is long enough
307 // so that users still recognize that something might be wrong and
308 // report back to us so we can investigate further.
310 BOOL fSuccess
= TransactNamedPipe(m_hPipe
,
311 &request
, sizeof(request
),
312 pReturnedStatus
, sizeof(*pReturnedStatus
),
313 &nBytesRead
, &m_Overlapped
);
317 if (GetLastError()!=ERROR_IO_PENDING
)
319 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
324 // TransactNamedPipe is working in an overlapped operation.
325 // Wait for it to finish
326 DWORD dwWait
= WaitForSingleObject(m_hEvent
, 10000);
327 if (dwWait
== WAIT_OBJECT_0
)
329 fSuccess
= GetOverlappedResult(m_hPipe
, &m_Overlapped
, &nBytesRead
, FALSE
);
333 // the cache didn't respond!
346 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath
& path
)
348 EnsureCommandPipeOpen();
349 if (m_hCommandPipe
!= INVALID_HANDLE_VALUE
)
352 TGITCacheCommand cmd
;
353 SecureZeroMemory(&cmd
, sizeof(TGITCacheCommand
));
354 cmd
.command
= TGITCACHECOMMAND_RELEASE
;
355 wcsncpy_s(cmd
.path
, MAX_PATH
+1, path
.GetDirectory().GetWinPath(), MAX_PATH
);
356 BOOL fSuccess
= WriteFile(
357 m_hCommandPipe
, // handle to pipe
358 &cmd
, // buffer to write from
359 sizeof(cmd
), // number of bytes to write
360 &cbWritten
, // number of bytes written
361 NULL
); // not overlapped I/O
362 if (! fSuccess
|| sizeof(cmd
) != cbWritten
)