1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // Copyright (C) 2003-2008 - TortoiseSVN
\r
5 // This program is free software; you can redistribute it and/or
\r
6 // modify it under the terms of the GNU General Public License
\r
7 // as published by the Free Software Foundation; either version 2
\r
8 // of the License, or (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, write to the Free Software Foundation,
\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
20 #include "Remotecachelink.h"
\r
21 #include "ShellExt.h"
\r
22 //#include "..\TSVNCache\CacheInterface.h"
\r
23 //#include "TSVNPath.h"
\r
24 #define GetCachePipeName() _T("HH")
\r
25 #define GetCacheCommandPipeName() _T("CC")
\r
27 CRemoteCacheLink::CRemoteCacheLink(void)
\r
28 : m_hPipe(INVALID_HANDLE_VALUE)
\r
29 , m_hCommandPipe(INVALID_HANDLE_VALUE)
\r
31 // SecureZeroMemory(&m_dummyStatus, sizeof(m_dummyStatus));
\r
32 // m_dummyStatus.text_status = svn_wc_status_none;
\r
33 // m_dummyStatus.prop_status = svn_wc_status_none;
\r
34 // m_dummyStatus.repos_text_status = svn_wc_status_none;
\r
35 // m_dummyStatus.repos_prop_status = svn_wc_status_none;
\r
40 CRemoteCacheLink::~CRemoteCacheLink(void)
\r
47 bool CRemoteCacheLink::EnsurePipeOpen()
\r
49 AutoLocker lock(m_critSec);
\r
50 if(m_hPipe != INVALID_HANDLE_VALUE)
\r
55 m_hPipe = CreateFile(
\r
56 GetCachePipeName(), // pipe name
\r
57 GENERIC_READ | // read and write access
\r
60 NULL, // default security attributes
\r
61 OPEN_EXISTING, // opens existing pipe
\r
62 FILE_FLAG_OVERLAPPED, // default attributes
\r
63 NULL); // no template file
\r
65 if (m_hPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)
\r
67 // TSVNCache is running but is busy connecting a different client.
\r
68 // Do not give up immediately but wait for a few milliseconds until
\r
69 // the server has created the next pipe instance
\r
70 if (WaitNamedPipe(GetCachePipeName(), 50))
\r
72 m_hPipe = CreateFile(
\r
73 GetCachePipeName(), // pipe name
\r
74 GENERIC_READ | // read and write access
\r
77 NULL, // default security attributes
\r
78 OPEN_EXISTING, // opens existing pipe
\r
79 FILE_FLAG_OVERLAPPED, // default attributes
\r
80 NULL); // no template file
\r
85 if (m_hPipe != INVALID_HANDLE_VALUE)
\r
87 // The pipe connected; change to message-read mode.
\r
90 dwMode = PIPE_READMODE_MESSAGE;
\r
91 if(!SetNamedPipeHandleState(
\r
92 m_hPipe, // pipe handle
\r
93 &dwMode, // new pipe mode
\r
94 NULL, // don't set maximum bytes
\r
95 NULL)) // don't set maximum time
\r
97 ATLTRACE("SetNamedPipeHandleState failed");
\r
98 CloseHandle(m_hPipe);
\r
99 m_hPipe = INVALID_HANDLE_VALUE;
\r
102 // create an unnamed (=local) manual reset event for use in the overlapped structure
\r
103 m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
\r
106 ATLTRACE("CreateEvent failed");
\r
114 bool CRemoteCacheLink::EnsureCommandPipeOpen()
\r
116 AutoLocker lock(m_critSec);
\r
117 if(m_hCommandPipe != INVALID_HANDLE_VALUE)
\r
122 m_hCommandPipe = CreateFile(
\r
123 GetCacheCommandPipeName(), // pipe name
\r
124 GENERIC_READ | // read and write access
\r
127 NULL, // default security attributes
\r
128 OPEN_EXISTING, // opens existing pipe
\r
129 FILE_FLAG_OVERLAPPED, // default attributes
\r
130 NULL); // no template file
\r
132 if (m_hCommandPipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY)
\r
134 // TSVNCache is running but is busy connecting a different client.
\r
135 // Do not give up immediately but wait for a few milliseconds until
\r
136 // the server has created the next pipe instance
\r
137 if (WaitNamedPipe(GetCacheCommandPipeName(), 50))
\r
139 m_hCommandPipe = CreateFile(
\r
140 GetCacheCommandPipeName(), // pipe name
\r
141 GENERIC_READ | // read and write access
\r
144 NULL, // default security attributes
\r
145 OPEN_EXISTING, // opens existing pipe
\r
146 FILE_FLAG_OVERLAPPED, // default attributes
\r
147 NULL); // no template file
\r
152 if (m_hCommandPipe != INVALID_HANDLE_VALUE)
\r
154 // The pipe connected; change to message-read mode.
\r
157 dwMode = PIPE_READMODE_MESSAGE;
\r
158 if(!SetNamedPipeHandleState(
\r
159 m_hCommandPipe, // pipe handle
\r
160 &dwMode, // new pipe mode
\r
161 NULL, // don't set maximum bytes
\r
162 NULL)) // don't set maximum time
\r
164 ATLTRACE("SetNamedPipeHandleState failed");
\r
165 CloseHandle(m_hCommandPipe);
\r
166 m_hCommandPipe = INVALID_HANDLE_VALUE;
\r
175 void CRemoteCacheLink::ClosePipe()
\r
177 AutoLocker lock(m_critSec);
\r
179 if(m_hPipe != INVALID_HANDLE_VALUE)
\r
181 CloseHandle(m_hPipe);
\r
182 CloseHandle(m_hEvent);
\r
183 m_hPipe = INVALID_HANDLE_VALUE;
\r
184 m_hEvent = INVALID_HANDLE_VALUE;
\r
188 void CRemoteCacheLink::CloseCommandPipe()
\r
190 AutoLocker lock(m_critSec);
\r
192 if(m_hCommandPipe != INVALID_HANDLE_VALUE)
\r
194 // now tell the cache we don't need it's command thread anymore
\r
196 /* TSVNCacheCommand cmd;
\r
197 SecureZeroMemory(&cmd, sizeof(TSVNCacheCommand));
\r
198 cmd.command = TSVNCACHECOMMAND_END;
\r
200 m_hCommandPipe, // handle to pipe
\r
201 &cmd, // buffer to write from
\r
202 sizeof(cmd), // number of bytes to write
\r
203 &cbWritten, // number of bytes written
\r
204 NULL); // not overlapped I/O
\r
205 DisconnectNamedPipe(m_hCommandPipe);
\r
206 CloseHandle(m_hCommandPipe);
\r
207 m_hCommandPipe = INVALID_HANDLE_VALUE;
\r
212 bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTSVNPath& Path, TSVNCacheResponse* pReturnedStatus, bool bRecursive)
\r
214 if(!EnsurePipeOpen())
\r
216 // We've failed to open the pipe - try and start the cache
\r
217 // but only if the last try to start the cache was a certain time
\r
218 // ago. If we just try over and over again without a small pause
\r
219 // in between, the explorer is rendered unusable!
\r
220 // Failing to start the cache can have different reasons: missing exe,
\r
221 // missing registry key, corrupt exe, ...
\r
222 if (((long)GetTickCount() - m_lastTimeout) < 0)
\r
224 STARTUPINFO startup;
\r
225 PROCESS_INFORMATION process;
\r
226 memset(&startup, 0, sizeof(startup));
\r
227 startup.cb = sizeof(startup);
\r
228 memset(&process, 0, sizeof(process));
\r
230 CRegString cachePath(_T("Software\\TortoiseGit\\CachePath"), _T("TSVNCache.exe"), false, HKEY_LOCAL_MACHINE);
\r
231 CString sCachePath = cachePath;
\r
232 if (CreateProcess(sCachePath.GetBuffer(sCachePath.GetLength()+1), _T(""), NULL, NULL, FALSE, 0, 0, 0, &startup, &process)==0)
\r
234 // It's not appropriate to do a message box here, because there may be hundreds of calls
\r
235 sCachePath.ReleaseBuffer();
\r
236 ATLTRACE("Failed to start cache\n");
\r
239 CloseHandle(process.hThread);
\r
240 CloseHandle(process.hProcess);
\r
241 sCachePath.ReleaseBuffer();
\r
243 // Wait for the cache to open
\r
244 long endTime = (long)GetTickCount()+1000;
\r
245 while(!EnsurePipeOpen())
\r
247 if(((long)GetTickCount() - endTime) > 0)
\r
249 m_lastTimeout = (long)GetTickCount()+10000;
\r
255 AutoLocker lock(m_critSec);
\r
258 TSVNCacheRequest request;
\r
259 request.flags = TSVNCACHE_FLAGS_NONOTIFICATIONS;
\r
262 request.flags |= TSVNCACHE_FLAGS_RECUSIVE_STATUS;
\r
264 wcsncpy_s(request.path, MAX_PATH+1, Path.GetWinPath(), MAX_PATH);
\r
265 SecureZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));
\r
266 m_Overlapped.hEvent = m_hEvent;
\r
267 // Do the transaction in overlapped mode.
\r
268 // That way, if anything happens which might block this call
\r
269 // we still can get out of it. We NEVER MUST BLOCK THE SHELL!
\r
270 // A blocked shell is a very bad user impression, because users
\r
271 // who don't know why it's blocked might find the only solution
\r
272 // to such a problem is a reboot and therefore they might loose
\r
274 // One particular situation where the shell could hang is when
\r
275 // the cache crashes and our crash report dialog comes up.
\r
276 // Sure, it would be better to have no situations where the shell
\r
277 // even can get blocked, but the timeout of 10 seconds is long enough
\r
278 // so that users still recognize that something might be wrong and
\r
279 // report back to us so we can investigate further.
\r
281 BOOL fSuccess = TransactNamedPipe(m_hPipe,
\r
282 &request, sizeof(request),
\r
283 pReturnedStatus, sizeof(*pReturnedStatus),
\r
284 &nBytesRead, &m_Overlapped);
\r
288 if (GetLastError()!=ERROR_IO_PENDING)
\r
290 //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n");
\r
295 // TransactNamedPipe is working in an overlapped operation.
\r
296 // Wait for it to finish
\r
297 DWORD dwWait = WaitForSingleObject(m_hEvent, 10000);
\r
298 if (dwWait == WAIT_OBJECT_0)
\r
300 fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE);
\r
304 // the cache didn't respond!
\r
311 if(nBytesRead == sizeof(TSVNCacheResponse))
\r
313 // This is a full response - we need to fix-up some pointers
\r
314 pReturnedStatus->m_status.entry = &pReturnedStatus->m_entry;
\r
315 pReturnedStatus->m_entry.url = pReturnedStatus->m_url;
\r
319 pReturnedStatus->m_status.entry = NULL;
\r
328 bool CRemoteCacheLink::ReleaseLockForPath(const CTSVNPath& path)
\r
330 EnsureCommandPipeOpen();
\r
331 if (m_hCommandPipe != INVALID_HANDLE_VALUE)
\r
334 TSVNCacheCommand cmd;
\r
335 SecureZeroMemory(&cmd, sizeof(TSVNCacheCommand));
\r
336 cmd.command = TSVNCACHECOMMAND_RELEASE;
\r
337 wcsncpy_s(cmd.path, MAX_PATH+1, path.GetDirectory().GetWinPath(), MAX_PATH);
\r
338 BOOL fSuccess = WriteFile(
\r
339 m_hCommandPipe, // handle to pipe
\r
340 &cmd, // buffer to write from
\r
341 sizeof(cmd), // number of bytes to write
\r
342 &cbWritten, // number of bytes written
\r
343 NULL); // not overlapped I/O
\r
344 if (! fSuccess || sizeof(cmd) != cbWritten)
\r
346 CloseCommandPipe();
\r