Extend static functions in CAppUtils with a window handle parameter
[TortoiseGit.git] / src / TortoiseShell / RemoteCacheLink.cpp
blob9ea99fde35aa495e37f4f3b5bce38a20d980ad7c
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2014, 2016-2017 - TortoiseGit
4 // Copyright (C) 2003-2014, 2017 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "RemoteCacheLink.h"
22 #include "ShellExt.h"
23 #include "../TGitCache/CacheInterface.h"
24 #include "TGitPath.h"
25 #include "PathUtils.h"
26 #include "CreateProcessHelper.h"
28 CRemoteCacheLink::CRemoteCacheLink(void)
30 SecureZeroMemory(&m_Overlapped, sizeof(m_Overlapped));
31 m_lastTimeout = 0;
32 m_critSec.Init();
35 CRemoteCacheLink::~CRemoteCacheLink(void)
37 ClosePipe();
38 CloseCommandPipe();
39 m_critSec.Term();
42 bool CRemoteCacheLink::InternalEnsurePipeOpen(CAutoFile& hPipe, const CString& pipeName, bool overlapped) const
44 if (hPipe)
45 return true;
47 int tryleft = 2;
49 while (!hPipe && tryleft--)
51 hPipe = CreateFile(
52 pipeName, // pipe name
53 GENERIC_READ | // read and write access
54 GENERIC_WRITE,
55 0, // no sharing
56 nullptr, // default security attributes
57 OPEN_EXISTING, // opens existing pipe
58 overlapped ? FILE_FLAG_OVERLAPPED : 0, // default attributes
59 nullptr); // no template file
60 if ((!hPipe) && (GetLastError() == ERROR_PIPE_BUSY))
62 // TGitCache is running but is busy connecting a different client.
63 // Do not give up immediately but wait for a few milliseconds until
64 // the server has created the next pipe instance
65 if (!WaitNamedPipe (pipeName, 50))
66 continue;
70 if (hPipe)
72 // The pipe connected; change to message-read mode.
73 DWORD dwMode;
75 dwMode = PIPE_READMODE_MESSAGE;
76 if(!SetNamedPipeHandleState(
77 hPipe, // pipe handle
78 &dwMode, // new pipe mode
79 nullptr, // don't set maximum bytes
80 nullptr)) // don't set maximum time
82 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": SetNamedPipeHandleState failed");
83 hPipe.CloseHandle();
87 return hPipe;
90 bool CRemoteCacheLink::EnsurePipeOpen()
92 AutoLocker lock(m_critSec);
94 if (InternalEnsurePipeOpen(m_hPipe, GetCachePipeName(), true))
96 // create an unnamed (=local) manual reset event for use in the overlapped structure
97 if (m_hEvent)
98 return true;
100 m_hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
101 if (m_hEvent)
102 return true;
104 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CreateEvent failed");
105 ClosePipe();
108 return false;
111 bool CRemoteCacheLink::EnsureCommandPipeOpen()
113 return InternalEnsurePipeOpen(m_hCommandPipe, GetCacheCommandPipeName(), false);
116 void CRemoteCacheLink::ClosePipe()
118 AutoLocker lock(m_critSec);
120 m_hPipe.CloseHandle();
121 m_hEvent.CloseHandle();
124 void CRemoteCacheLink::CloseCommandPipe()
126 AutoLocker lock(m_critSec);
128 if(m_hCommandPipe)
130 // now tell the cache we don't need it's command thread anymore
131 DWORD cbWritten;
132 TGITCacheCommand cmd = { 0 };
133 cmd.command = TGITCACHECOMMAND_END;
134 WriteFile(
135 m_hCommandPipe, // handle to pipe
136 &cmd, // buffer to write from
137 sizeof(cmd), // number of bytes to write
138 &cbWritten, // number of bytes written
139 nullptr); // not overlapped I/O
140 DisconnectNamedPipe(m_hCommandPipe);
141 m_hCommandPipe.CloseHandle();
145 bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTGitPath& Path, TGITCacheResponse* pReturnedStatus, bool bRecursive)
147 if(!EnsurePipeOpen())
149 // We've failed to open the pipe - try and start the cache
150 // but only if the last try to start the cache was a certain time
151 // ago. If we just try over and over again without a small pause
152 // in between, the explorer is rendered unusable!
153 // Failing to start the cache can have different reasons: missing exe,
154 // missing registry key, corrupt exe, ...
155 if (((LONGLONG)GetTickCount64() - m_lastTimeout) < 0)
156 return false;
157 // if we're in protected mode, don't try to start the cache: since we're
158 // here, we know we can't access it anyway and starting a new process will
159 // trigger a warning dialog in IE7+ on Vista - we don't want that.
160 if (GetProcessIntegrityLevel() < SECURITY_MANDATORY_MEDIUM_RID)
161 return false;
163 if (!RunTGitCacheProcess())
164 return false;
166 // Wait for the cache to open
167 LONGLONG endTime = (LONGLONG)GetTickCount64() + 1000;
168 while(!EnsurePipeOpen())
170 if (((LONGLONG)GetTickCount64() - endTime) > 0)
172 m_lastTimeout = (LONGLONG)GetTickCount64() + 10000;
173 return false;
176 m_lastTimeout = (LONGLONG)GetTickCount64() + 10000;
179 AutoLocker lock(m_critSec);
181 DWORD nBytesRead;
182 TGITCacheRequest request;
183 request.flags = TGITCACHE_FLAGS_NONOTIFICATIONS;
184 if(bRecursive)
185 request.flags |= TGITCACHE_FLAGS_RECUSIVE_STATUS;
186 wcsncpy_s(request.path, Path.GetWinPath(), _countof(request.path) - 1);
187 SecureZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));
188 m_Overlapped.hEvent = m_hEvent;
189 // Do the transaction in overlapped mode.
190 // That way, if anything happens which might block this call
191 // we still can get out of it. We NEVER MUST BLOCK THE SHELL!
192 // A blocked shell is a very bad user impression, because users
193 // who don't know why it's blocked might find the only solution
194 // to such a problem is a reboot and therefore they might loose
195 // valuable data.
196 // One particular situation where the shell could hang is when
197 // the cache crashes and our crash report dialog comes up.
198 // Sure, it would be better to have no situations where the shell
199 // even can get blocked, but the timeout of 10 seconds is long enough
200 // so that users still recognize that something might be wrong and
201 // report back to us so we can investigate further.
203 BOOL fSuccess = TransactNamedPipe(m_hPipe,
204 &request, sizeof(request),
205 pReturnedStatus, sizeof(*pReturnedStatus),
206 &nBytesRead, &m_Overlapped);
208 if (!fSuccess)
210 if (GetLastError()!=ERROR_IO_PENDING)
212 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
213 ClosePipe();
214 return false;
217 // TransactNamedPipe is working in an overlapped operation.
218 // Wait for it to finish
219 DWORD dwWait = WaitForSingleObject(m_hEvent, 10000);
220 if (dwWait == WAIT_OBJECT_0)
221 fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE);
222 else
224 // the cache didn't respond!
225 fSuccess = FALSE;
229 if (fSuccess)
230 return true;
231 ClosePipe();
232 return false;
235 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath& path)
237 AutoLocker lock(m_critSec);
238 EnsureCommandPipeOpen();
239 if (m_hCommandPipe)
241 DWORD cbWritten;
242 TGITCacheCommand cmd = { 0 };
243 cmd.command = TGITCACHECOMMAND_RELEASE;
244 wcsncpy_s(cmd.path, path.GetDirectory().GetWinPath(), _countof(cmd.path) - 1);
245 BOOL fSuccess = WriteFile(
246 m_hCommandPipe, // handle to pipe
247 &cmd, // buffer to write from
248 sizeof(cmd), // number of bytes to write
249 &cbWritten, // number of bytes written
250 nullptr); // not overlapped I/O
251 if (! fSuccess || sizeof(cmd) != cbWritten)
253 CloseCommandPipe();
254 return false;
256 return true;
258 return false;
261 DWORD CRemoteCacheLink::GetProcessIntegrityLevel() const
263 DWORD dwIntegrityLevel = SECURITY_MANDATORY_MEDIUM_RID;
265 CAutoGeneralHandle hProcess = GetCurrentProcess();
266 CAutoGeneralHandle hToken;
267 if (OpenProcessToken(hProcess, TOKEN_QUERY |
268 TOKEN_QUERY_SOURCE, hToken.GetPointer()))
270 // Get the Integrity level.
271 DWORD dwLengthNeeded;
272 if (!GetTokenInformation(hToken, TokenIntegrityLevel,
273 nullptr, 0, &dwLengthNeeded))
275 DWORD dwError = GetLastError();
276 if (dwError == ERROR_INSUFFICIENT_BUFFER)
278 PTOKEN_MANDATORY_LABEL pTIL =
279 (PTOKEN_MANDATORY_LABEL)LocalAlloc(0, dwLengthNeeded);
280 if (pTIL)
282 if (GetTokenInformation(hToken, TokenIntegrityLevel,
283 pTIL, dwLengthNeeded, &dwLengthNeeded))
285 dwIntegrityLevel = *GetSidSubAuthority(pTIL->Label.Sid,
286 (DWORD)(UCHAR)(*GetSidSubAuthorityCount(pTIL->Label.Sid)-1));
288 LocalFree(pTIL);
294 return dwIntegrityLevel;
297 bool CRemoteCacheLink::RunTGitCacheProcess()
299 const CString sCachePath = GetTGitCachePath();
300 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath, (LPTSTR)nullptr))
302 // It's not appropriate to do a message box here, because there may be hundreds of calls
303 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Failed to start cache\n");
304 return false;
306 return true;
309 CString CRemoteCacheLink::GetTGitCachePath() const
311 CString sCachePath = CPathUtils::GetAppDirectory(g_hmodThisDll) + L"TGitCache.exe";
312 return sCachePath;