Fix typo in doc
[TortoiseGit.git] / src / TortoiseShell / RemoteCacheLink.cpp
blob132f604fd04570d8ade2c9ce477a2bfecadd6ecc
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2014, 2016-2017 - TortoiseGit
4 // Copyright (C) 2003-2014 - 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
43 , const CString& pipeName) const
45 if (hPipe)
46 return true;
48 int tryleft = 2;
50 while (!hPipe && tryleft--)
52 hPipe = CreateFile(
53 pipeName, // pipe name
54 GENERIC_READ | // read and write access
55 GENERIC_WRITE,
56 0, // no sharing
57 nullptr, // default security attributes
58 OPEN_EXISTING, // opens existing pipe
59 FILE_FLAG_OVERLAPPED, // default attributes
60 nullptr); // no template file
61 if ((!hPipe) && (GetLastError() == ERROR_PIPE_BUSY))
63 // TGitCache is running but is busy connecting a different client.
64 // Do not give up immediately but wait for a few milliseconds until
65 // the server has created the next pipe instance
66 if (!WaitNamedPipe (pipeName, 50))
67 continue;
71 if (hPipe)
73 // The pipe connected; change to message-read mode.
74 DWORD dwMode;
76 dwMode = PIPE_READMODE_MESSAGE;
77 if(!SetNamedPipeHandleState(
78 hPipe, // pipe handle
79 &dwMode, // new pipe mode
80 nullptr, // don't set maximum bytes
81 nullptr)) // don't set maximum time
83 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": SetNamedPipeHandleState failed");
84 hPipe.CloseHandle();
88 return hPipe;
91 bool CRemoteCacheLink::EnsurePipeOpen()
93 AutoLocker lock(m_critSec);
95 if (InternalEnsurePipeOpen (m_hPipe, GetCachePipeName()))
97 // create an unnamed (=local) manual reset event for use in the overlapped structure
98 if (m_hEvent)
99 return true;
101 m_hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
102 if (m_hEvent)
103 return true;
105 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CreateEvent failed");
106 ClosePipe();
109 return false;
112 bool CRemoteCacheLink::EnsureCommandPipeOpen()
114 AutoLocker lock(m_critSec);
115 return InternalEnsurePipeOpen (m_hCommandPipe, GetCacheCommandPipeName());
118 void CRemoteCacheLink::ClosePipe()
120 AutoLocker lock(m_critSec);
122 m_hPipe.CloseHandle();
123 m_hEvent.CloseHandle();
126 void CRemoteCacheLink::CloseCommandPipe()
128 AutoLocker lock(m_critSec);
130 if(m_hCommandPipe)
132 // now tell the cache we don't need it's command thread anymore
133 DWORD cbWritten;
134 TGITCacheCommand cmd = { 0 };
135 cmd.command = TGITCACHECOMMAND_END;
136 WriteFile(
137 m_hCommandPipe, // handle to pipe
138 &cmd, // buffer to write from
139 sizeof(cmd), // number of bytes to write
140 &cbWritten, // number of bytes written
141 nullptr); // not overlapped I/O
142 DisconnectNamedPipe(m_hCommandPipe);
143 m_hCommandPipe.CloseHandle();
147 bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTGitPath& Path, TGITCacheResponse* pReturnedStatus, bool bRecursive)
149 if(!EnsurePipeOpen())
151 // We've failed to open the pipe - try and start the cache
152 // but only if the last try to start the cache was a certain time
153 // ago. If we just try over and over again without a small pause
154 // in between, the explorer is rendered unusable!
155 // Failing to start the cache can have different reasons: missing exe,
156 // missing registry key, corrupt exe, ...
157 if (((LONGLONG)GetTickCount64() - m_lastTimeout) < 0)
158 return false;
159 // if we're in protected mode, don't try to start the cache: since we're
160 // here, we know we can't access it anyway and starting a new process will
161 // trigger a warning dialog in IE7+ on Vista - we don't want that.
162 if (GetProcessIntegrityLevel() < SECURITY_MANDATORY_MEDIUM_RID)
163 return false;
165 if (!RunTGitCacheProcess())
166 return false;
168 // Wait for the cache to open
169 LONGLONG endTime = (LONGLONG)GetTickCount64() + 1000;
170 while(!EnsurePipeOpen())
172 if (((LONGLONG)GetTickCount64() - endTime) > 0)
174 m_lastTimeout = (LONGLONG)GetTickCount64() + 10000;
175 return false;
178 m_lastTimeout = (LONGLONG)GetTickCount64() + 10000;
181 AutoLocker lock(m_critSec);
183 DWORD nBytesRead;
184 TGITCacheRequest request;
185 request.flags = TGITCACHE_FLAGS_NONOTIFICATIONS;
186 if(bRecursive)
187 request.flags |= TGITCACHE_FLAGS_RECUSIVE_STATUS;
188 wcsncpy_s(request.path, Path.GetWinPath(), _countof(request.path) - 1);
189 SecureZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));
190 m_Overlapped.hEvent = m_hEvent;
191 // Do the transaction in overlapped mode.
192 // That way, if anything happens which might block this call
193 // we still can get out of it. We NEVER MUST BLOCK THE SHELL!
194 // A blocked shell is a very bad user impression, because users
195 // who don't know why it's blocked might find the only solution
196 // to such a problem is a reboot and therefore they might loose
197 // valuable data.
198 // One particular situation where the shell could hang is when
199 // the cache crashes and our crash report dialog comes up.
200 // Sure, it would be better to have no situations where the shell
201 // even can get blocked, but the timeout of 10 seconds is long enough
202 // so that users still recognize that something might be wrong and
203 // report back to us so we can investigate further.
205 BOOL fSuccess = TransactNamedPipe(m_hPipe,
206 &request, sizeof(request),
207 pReturnedStatus, sizeof(*pReturnedStatus),
208 &nBytesRead, &m_Overlapped);
210 if (!fSuccess)
212 if (GetLastError()!=ERROR_IO_PENDING)
214 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
215 ClosePipe();
216 return false;
219 // TransactNamedPipe is working in an overlapped operation.
220 // Wait for it to finish
221 DWORD dwWait = WaitForSingleObject(m_hEvent, 10000);
222 if (dwWait == WAIT_OBJECT_0)
223 fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE);
224 else
226 // the cache didn't respond!
227 fSuccess = FALSE;
231 if (fSuccess)
232 return true;
233 ClosePipe();
234 return false;
237 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath& path)
239 EnsureCommandPipeOpen();
240 if (m_hCommandPipe)
242 DWORD cbWritten;
243 TGITCacheCommand cmd = { 0 };
244 cmd.command = TGITCACHECOMMAND_RELEASE;
245 wcsncpy_s(cmd.path, path.GetDirectory().GetWinPath(), _countof(cmd.path) - 1);
246 BOOL fSuccess = WriteFile(
247 m_hCommandPipe, // handle to pipe
248 &cmd, // buffer to write from
249 sizeof(cmd), // number of bytes to write
250 &cbWritten, // number of bytes written
251 nullptr); // not overlapped I/O
252 if (! fSuccess || sizeof(cmd) != cbWritten)
254 CloseCommandPipe();
255 return false;
257 return true;
259 return false;
262 DWORD CRemoteCacheLink::GetProcessIntegrityLevel() const
264 DWORD dwIntegrityLevel = SECURITY_MANDATORY_MEDIUM_RID;
266 CAutoGeneralHandle hProcess = GetCurrentProcess();
267 CAutoGeneralHandle hToken;
268 if (OpenProcessToken(hProcess, TOKEN_QUERY |
269 TOKEN_QUERY_SOURCE, hToken.GetPointer()))
271 // Get the Integrity level.
272 DWORD dwLengthNeeded;
273 if (!GetTokenInformation(hToken, TokenIntegrityLevel,
274 nullptr, 0, &dwLengthNeeded))
276 DWORD dwError = GetLastError();
277 if (dwError == ERROR_INSUFFICIENT_BUFFER)
279 PTOKEN_MANDATORY_LABEL pTIL =
280 (PTOKEN_MANDATORY_LABEL)LocalAlloc(0, dwLengthNeeded);
281 if (pTIL)
283 if (GetTokenInformation(hToken, TokenIntegrityLevel,
284 pTIL, dwLengthNeeded, &dwLengthNeeded))
286 dwIntegrityLevel = *GetSidSubAuthority(pTIL->Label.Sid,
287 (DWORD)(UCHAR)(*GetSidSubAuthorityCount(pTIL->Label.Sid)-1));
289 LocalFree(pTIL);
295 return dwIntegrityLevel;
298 bool CRemoteCacheLink::RunTGitCacheProcess()
300 const CString sCachePath = GetTGitCachePath();
301 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath, (LPTSTR)nullptr))
303 // It's not appropriate to do a message box here, because there may be hundreds of calls
304 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Failed to start cache\n");
305 return false;
307 return true;
310 CString CRemoteCacheLink::GetTGitCachePath() const
312 CString sCachePath = CPathUtils::GetAppDirectory(g_hmodThisDll) + L"TGitCache.exe";
313 return sCachePath;