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.
21 #include "RemoteCacheLink.h"
23 #include "../TGitCache/CacheInterface.h"
25 #include "PathUtils.h"
26 #include "CreateProcessHelper.h"
28 CRemoteCacheLink::CRemoteCacheLink(void)
30 SecureZeroMemory(&m_Overlapped
, sizeof(m_Overlapped
));
35 CRemoteCacheLink::~CRemoteCacheLink(void)
42 bool CRemoteCacheLink::InternalEnsurePipeOpen ( CAutoFile
& hPipe
43 , const CString
& pipeName
) const
50 while (!hPipe
&& tryleft
--)
53 pipeName
, // pipe name
54 GENERIC_READ
| // read and write access
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))
73 // The pipe connected; change to message-read mode.
76 dwMode
= PIPE_READMODE_MESSAGE
;
77 if(!SetNamedPipeHandleState(
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");
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
101 m_hEvent
= CreateEvent(nullptr, TRUE
, FALSE
, nullptr);
105 CTraceToOutputDebugString::Instance()(__FUNCTION__
": CreateEvent failed");
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
);
132 // now tell the cache we don't need it's command thread anymore
134 TGITCacheCommand cmd
= { 0 };
135 cmd
.command
= TGITCACHECOMMAND_END
;
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)
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
)
165 if (!RunTGitCacheProcess())
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;
178 m_lastTimeout
= (LONGLONG
)GetTickCount64() + 10000;
181 AutoLocker
lock(m_critSec
);
184 TGITCacheRequest request
;
185 request
.flags
= TGITCACHE_FLAGS_NONOTIFICATIONS
;
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
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
);
212 if (GetLastError()!=ERROR_IO_PENDING
)
214 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
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
);
226 // the cache didn't respond!
237 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath
& path
)
239 EnsureCommandPipeOpen();
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
)
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
);
283 if (GetTokenInformation(hToken
, TokenIntegrityLevel
,
284 pTIL
, dwLengthNeeded
, &dwLengthNeeded
))
286 dwIntegrityLevel
= *GetSidSubAuthority(pTIL
->Label
.Sid
,
287 (DWORD
)(UCHAR
)(*GetSidSubAuthorityCount(pTIL
->Label
.Sid
)-1));
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");
310 CString
CRemoteCacheLink::GetTGitCachePath() const
312 CString sCachePath
= CPathUtils::GetAppDirectory(g_hmodThisDll
) + L
"TGitCache.exe";