1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2013 - 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_dummyStatus
, sizeof(m_dummyStatus
));
31 SecureZeroMemory(&m_Overlapped
, sizeof(m_Overlapped
));
32 // m_dummyStatus.node_status = git_wc_status_none;
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_dummyStatus.repos_node_status = git_wc_status_none;
42 CRemoteCacheLink::~CRemoteCacheLink(void)
49 bool CRemoteCacheLink::InternalEnsurePipeOpen ( CAutoFile
& hPipe
50 , const CString
& pipeName
) const
57 while (!hPipe
&& tryleft
--)
61 pipeName
, // pipe name
62 GENERIC_READ
| // read and write access
65 NULL
, // default security attributes
66 OPEN_EXISTING
, // opens existing pipe
67 FILE_FLAG_OVERLAPPED
, // default attributes
68 NULL
); // no template file
69 if ((!hPipe
) && (GetLastError() == ERROR_PIPE_BUSY
))
71 // TGitCache is running but is busy connecting a different client.
72 // Do not give up immediately but wait for a few milliseconds until
73 // the server has created the next pipe instance
74 if (!WaitNamedPipe (pipeName
, 50))
83 // The pipe connected; change to message-read mode.
86 dwMode
= PIPE_READMODE_MESSAGE
;
87 if(!SetNamedPipeHandleState(
89 &dwMode
, // new pipe mode
90 NULL
, // don't set maximum bytes
91 NULL
)) // don't set maximum time
93 CTraceToOutputDebugString::Instance()(__FUNCTION__
": SetNamedPipeHandleState failed");
101 bool CRemoteCacheLink::EnsurePipeOpen()
103 AutoLocker
lock(m_critSec
);
105 if (InternalEnsurePipeOpen (m_hPipe
, GetCachePipeName()))
107 // create an unnamed (=local) manual reset event for use in the overlapped structure
111 m_hEvent
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
115 CTraceToOutputDebugString::Instance()(__FUNCTION__
": CreateEvent failed");
122 bool CRemoteCacheLink::EnsureCommandPipeOpen()
124 AutoLocker
lock(m_critSec
);
125 return InternalEnsurePipeOpen (m_hCommandPipe
, GetCacheCommandPipeName());
128 void CRemoteCacheLink::ClosePipe()
130 AutoLocker
lock(m_critSec
);
132 m_hPipe
.CloseHandle();
133 m_hEvent
.CloseHandle();
136 void CRemoteCacheLink::CloseCommandPipe()
138 AutoLocker
lock(m_critSec
);
142 // now tell the cache we don't need it's command thread anymore
144 TGITCacheCommand cmd
;
145 SecureZeroMemory(&cmd
, sizeof(TGITCacheCommand
));
146 cmd
.command
= TGITCACHECOMMAND_END
;
148 m_hCommandPipe
, // handle to pipe
149 &cmd
, // buffer to write from
150 sizeof(cmd
), // number of bytes to write
151 &cbWritten
, // number of bytes written
152 NULL
); // not overlapped I/O
153 DisconnectNamedPipe(m_hCommandPipe
);
154 m_hCommandPipe
.CloseHandle();
158 bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTGitPath
& Path
, TGITCacheResponse
* pReturnedStatus
, bool bRecursive
)
160 if(!EnsurePipeOpen())
162 // We've failed to open the pipe - try and start the cache
163 // but only if the last try to start the cache was a certain time
164 // ago. If we just try over and over again without a small pause
165 // in between, the explorer is rendered unusable!
166 // Failing to start the cache can have different reasons: missing exe,
167 // missing registry key, corrupt exe, ...
168 if (((long)GetTickCount() - m_lastTimeout
) < 0)
170 // if we're in protected mode, don't try to start the cache: since we're
171 // here, we know we can't access it anyway and starting a new process will
172 // trigger a warning dialog in IE7+ on Vista - we don't want that.
173 if (GetProcessIntegrityLevel() < SECURITY_MANDATORY_MEDIUM_RID
)
176 if (!RunTGitCacheProcess())
179 // Wait for the cache to open
180 long endTime
= (long)GetTickCount()+1000;
181 while(!EnsurePipeOpen())
183 if(((long)GetTickCount() - endTime
) > 0)
185 m_lastTimeout
= (long)GetTickCount()+10000;
189 m_lastTimeout
= (long)GetTickCount()+10000;
192 AutoLocker
lock(m_critSec
);
195 TGITCacheRequest request
;
196 request
.flags
= TGITCACHE_FLAGS_NONOTIFICATIONS
;
199 request
.flags
|= TGITCACHE_FLAGS_RECUSIVE_STATUS
;
201 wcsncpy_s(request
.path
, Path
.GetWinPath(), _countof(request
.path
) - 1);
202 SecureZeroMemory(&m_Overlapped
, sizeof(OVERLAPPED
));
203 m_Overlapped
.hEvent
= m_hEvent
;
204 // Do the transaction in overlapped mode.
205 // That way, if anything happens which might block this call
206 // we still can get out of it. We NEVER MUST BLOCK THE SHELL!
207 // A blocked shell is a very bad user impression, because users
208 // who don't know why it's blocked might find the only solution
209 // to such a problem is a reboot and therefore they might loose
211 // One particular situation where the shell could hang is when
212 // the cache crashes and our crash report dialog comes up.
213 // Sure, it would be better to have no situations where the shell
214 // even can get blocked, but the timeout of 10 seconds is long enough
215 // so that users still recognize that something might be wrong and
216 // report back to us so we can investigate further.
218 BOOL fSuccess
= TransactNamedPipe(m_hPipe
,
219 &request
, sizeof(request
),
220 pReturnedStatus
, sizeof(*pReturnedStatus
),
221 &nBytesRead
, &m_Overlapped
);
225 if (GetLastError()!=ERROR_IO_PENDING
)
227 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
232 // TransactNamedPipe is working in an overlapped operation.
233 // Wait for it to finish
234 DWORD dwWait
= WaitForSingleObject(m_hEvent
, 10000);
235 if (dwWait
== WAIT_OBJECT_0
)
237 fSuccess
= GetOverlappedResult(m_hPipe
, &m_Overlapped
, &nBytesRead
, FALSE
);
241 // the cache didn't respond!
254 bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath
& path
)
256 EnsureCommandPipeOpen();
260 TGITCacheCommand cmd
;
261 SecureZeroMemory(&cmd
, sizeof(TGITCacheCommand
));
262 cmd
.command
= TGITCACHECOMMAND_RELEASE
;
263 wcsncpy_s(cmd
.path
, path
.GetDirectory().GetWinPath(), _countof(cmd
.path
) - 1);
264 BOOL fSuccess
= WriteFile(
265 m_hCommandPipe
, // handle to pipe
266 &cmd
, // buffer to write from
267 sizeof(cmd
), // number of bytes to write
268 &cbWritten
, // number of bytes written
269 NULL
); // not overlapped I/O
270 if (! fSuccess
|| sizeof(cmd
) != cbWritten
)
280 DWORD
CRemoteCacheLink::GetProcessIntegrityLevel() const
282 DWORD dwIntegrityLevel
= SECURITY_MANDATORY_MEDIUM_RID
;
284 CAutoGeneralHandle hProcess
= GetCurrentProcess();
285 CAutoGeneralHandle hToken
;
286 if (OpenProcessToken(hProcess
, TOKEN_QUERY
|
287 TOKEN_QUERY_SOURCE
, hToken
.GetPointer()))
289 // Get the Integrity level.
290 DWORD dwLengthNeeded
;
291 if (!GetTokenInformation(hToken
, TokenIntegrityLevel
,
292 NULL
, 0, &dwLengthNeeded
))
294 DWORD dwError
= GetLastError();
295 if (dwError
== ERROR_INSUFFICIENT_BUFFER
)
297 PTOKEN_MANDATORY_LABEL pTIL
=
298 (PTOKEN_MANDATORY_LABEL
)LocalAlloc(0, dwLengthNeeded
);
301 if (GetTokenInformation(hToken
, TokenIntegrityLevel
,
302 pTIL
, dwLengthNeeded
, &dwLengthNeeded
))
304 dwIntegrityLevel
= *GetSidSubAuthority(pTIL
->Label
.Sid
,
305 (DWORD
)(UCHAR
)(*GetSidSubAuthorityCount(pTIL
->Label
.Sid
)-1));
313 return dwIntegrityLevel
;
316 bool CRemoteCacheLink::RunTGitCacheProcess()
318 const CString sCachePath
= GetTGitCachePath();
319 if (!CCreateProcessHelper::CreateProcessDetached(sCachePath
, NULL
))
321 // It's not appropriate to do a message box here, because there may be hundreds of calls
322 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Failed to start cache\n");
328 CString
CRemoteCacheLink::GetTGitCachePath() const
330 CString sCachePath
= CPathUtils::GetAppDirectory(g_hmodThisDll
) + _T("TGitCache.exe");