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.
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
, const CString
& pipeName
, bool overlapped
) const
49 while (!hPipe
&& tryleft
--)
52 pipeName
, // pipe name
53 GENERIC_READ
| // read and write access
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))
72 // The pipe connected; change to message-read mode.
75 dwMode
= PIPE_READMODE_MESSAGE
;
76 if(!SetNamedPipeHandleState(
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");
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
100 m_hEvent
= CreateEvent(nullptr, TRUE
, FALSE
, nullptr);
104 CTraceToOutputDebugString::Instance()(__FUNCTION__
": CreateEvent failed");
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
);
130 // now tell the cache we don't need it's command thread anymore
132 TGITCacheCommand cmd
= { 0 };
133 cmd
.command
= TGITCACHECOMMAND_END
;
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)
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
)
163 if (!RunTGitCacheProcess())
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;
176 m_lastTimeout
= (LONGLONG
)GetTickCount64() + 10000;
179 AutoLocker
lock(m_critSec
);
182 TGITCacheRequest request
;
183 request
.flags
= TGITCACHE_FLAGS_NONOTIFICATIONS
;
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
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
);
210 if (GetLastError()!=ERROR_IO_PENDING
)
212 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
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
);
224 // the cache didn't respond!
235 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath
& path
)
237 AutoLocker
lock(m_critSec
);
238 EnsureCommandPipeOpen();
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
)
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
);
282 if (GetTokenInformation(hToken
, TokenIntegrityLevel
,
283 pTIL
, dwLengthNeeded
, &dwLengthNeeded
))
285 dwIntegrityLevel
= *GetSidSubAuthority(pTIL
->Label
.Sid
,
286 (DWORD
)(UCHAR
)(*GetSidSubAuthorityCount(pTIL
->Label
.Sid
)-1));
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");
309 CString
CRemoteCacheLink::GetTGitCachePath() const
311 CString sCachePath
= CPathUtils::GetAppDirectory(g_hmodThisDll
) + L
"TGitCache.exe";