Reduce variable scope
[TortoiseGit.git] / src / TortoiseShell / RemoteCacheLink.cpp
blobd65d7ee4b71cd0703cb7f02f9cc61a8dfc518f3b
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.
19 #include "stdafx.h"
20 #include "Remotecachelink.h"
21 #include "ShellExt.h"
22 #include "..\TGitCache\CacheInterface.h"
23 #include "TGitPath.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;
37 m_lastTimeout = 0;
38 m_critSec.Init();
41 CRemoteCacheLink::~CRemoteCacheLink(void)
43 ClosePipe();
44 CloseCommandPipe();
45 m_critSec.Term();
48 bool CRemoteCacheLink::EnsurePipeOpen()
50 AutoLocker lock(m_critSec);
51 if(m_hPipe != INVALID_HANDLE_VALUE)
53 return true;
56 m_hPipe = CreateFile(
57 GetCachePipeName(), // pipe name
58 GENERIC_READ | // read and write access
59 GENERIC_WRITE,
60 0, // no sharing
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))
73 m_hPipe = CreateFile(
74 GetCachePipeName(), // pipe name
75 GENERIC_READ | // read and write access
76 GENERIC_WRITE,
77 0, // no sharing
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.
89 DWORD dwMode;
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");
99 CloseHandle(m_hPipe);
100 m_hPipe = INVALID_HANDLE_VALUE;
101 return false;
103 // create an unnamed (=local) manual reset event for use in the overlapped structure
104 m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
105 if (m_hEvent)
106 return true;
107 ATLTRACE("CreateEvent failed");
108 ClosePipe();
109 return false;
112 return false;
115 bool CRemoteCacheLink::EnsureCommandPipeOpen()
117 AutoLocker lock(m_critSec);
118 if(m_hCommandPipe != INVALID_HANDLE_VALUE)
120 return true;
123 m_hCommandPipe = CreateFile(
124 GetCacheCommandPipeName(), // pipe name
125 GENERIC_READ | // read and write access
126 GENERIC_WRITE,
127 0, // no sharing
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
143 GENERIC_WRITE,
144 0, // no sharing
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.
156 DWORD dwMode;
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;
168 return false;
170 return true;
173 return false;
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
196 DWORD cbWritten;
197 TGITCacheCommand cmd;
198 SecureZeroMemory(&cmd, sizeof(TGITCacheCommand));
199 cmd.command = TGITCACHECOMMAND_END;
200 WriteFile(
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)
223 return false;
224 STARTUPINFO startup;
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");
231 #ifndef _WIN64
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))
241 bIsWow64 = false;
243 if (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");
258 return false;
261 #else
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");
266 return false;
268 #endif
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;
280 return false;
285 AutoLocker lock(m_critSec);
287 DWORD nBytesRead;
288 TGITCacheRequest request;
289 request.flags = TGITCACHE_FLAGS_NONOTIFICATIONS;
290 if(bRecursive)
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
303 // valuable data.
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);
316 if (!fSuccess)
318 if (GetLastError()!=ERROR_IO_PENDING)
320 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
321 ClosePipe();
322 return false;
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);
332 else
334 // the cache didn't respond!
335 fSuccess = FALSE;
339 if (fSuccess)
341 return true;
343 ClosePipe();
344 return false;
347 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath& path)
349 EnsureCommandPipeOpen();
350 if (m_hCommandPipe != INVALID_HANDLE_VALUE)
352 DWORD cbWritten;
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)
365 CloseCommandPipe();
366 return false;
368 return true;
370 return false;