Windows: replace lana_OnlyLoopback() calls
[openafs.git] / src / WINNT / afsd / afsd_flushvol.c
1 //
2 //      AFSD_FLUSHVOL.C
3 //
4 //      Routines to handle flushing AFS volumes in response to
5 //      System Power event notification such as Hibernate request.
6 //
7 /////////////////////////////////////////////////////////////////////
8
9 #include <afsconfig.h>
10 #include <afs/param.h>
11 #include <roken.h>
12
13 #include <afs/stds.h>
14
15 #include <windows.h>
16
17 #include <string.h>
18 #include <setjmp.h>
19 #include "afsd.h"
20 #include "afsd_init.h"
21 #include "smb.h"
22 #include "cm_conn.h"
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <malloc.h>
26
27 #include <winsock2.h>
28
29 #include <osi.h>
30
31 #include "afsd_flushvol.h"
32 #include "afsd_eventlog.h"
33 #include "lanahelper.h"
34
35 extern void afsi_log(char *pattern, ...);
36
37 static FLUSHVOLTHREADINFO       gThreadInfo   = {0};
38 static HANDLE                   gThreadHandle = NULL;
39
40
41 /////////////////////////////////////////////////////////////////////
42 //
43 // Call routine found in FS.EXE to flush volume.
44 //
45 // At entry, input param is UNC string for volume,
46 // e.g. '\\afs\all\athena.mit.edu\user\m\h\mholiday'
47 //
48 // I believe that success from 'pioctl' routine
49 // indicated by return value of zero (0).
50 //
51 afs_int32
52 afsd_ServicePerformFlushVolumeCmd(char *data)
53 {
54     afs_int32 code;
55     struct ViceIoctl blob;
56
57     afsi_log("Flushing Volume \"%s\"",data);
58     memset(&blob, '\0', sizeof(blob));
59     code = pioctl(data, VIOC_FLUSHVOLUME, &blob, 0);
60
61     return code;
62 }
63
64 BOOL
65 afsd_ServicePerformFlushVolumes()
66 {
67     CONST CHAR  COLON = ':';
68     CONST CHAR  SLASH = '\\';
69     CONST DWORD NETRESBUFSIZE = 16384;
70     CHAR        bufMessage[1024];
71     UINT        i;
72     DWORD       dwServerSize;
73     DWORD       dwRet;
74     DWORD       dwCount;
75     DWORD       dwNetResBufSize;
76     DWORD       dwTotalVols = 0;
77     DWORD       dwVolBegin, dwVolEnd;
78     DWORD       dwFlushBegin, dwFlushEnd;
79     HANDLE      hEnum;
80     LPNETRESOURCE       lpNetResBuf, lpnr;
81     char        *pszShareName, *pc;
82     afs_int32   afsRet = 0;
83
84     if (cm_noIPAddr == 0) {
85         // Nothing to do if we only have a loopback interface
86         return TRUE;
87     }
88
89     // Determine the root share name (\\AFS\ALL or \\<machine>-AFS\ALL),
90     // and the length of the server name prefix.
91     pszShareName = smb_GetSharename();
92     if (pszShareName == NULL)
93     {
94         LogEvent(EVENTLOG_ERROR_TYPE, MSG_FLUSH_NO_SHARE_NAME, NULL);
95         return FALSE;
96     }
97     pc = strrchr(pszShareName, SLASH);
98     if ((pc == NULL) || ((dwServerSize = (DWORD)(pc - pszShareName)) < 3))
99     {
100         LogEvent(EVENTLOG_ERROR_TYPE, MSG_FLUSH_BAD_SHARE_NAME,
101                   pszShareName, NULL);
102         free(pszShareName);
103         return FALSE;
104     }
105
106     // Allocate a buffer to hold network resources returned by
107     // WNetEnumResource().
108     lpNetResBuf = malloc(NETRESBUFSIZE);
109     if (lpNetResBuf == NULL)
110     {
111         // Out of memory, give up now.
112         LogEvent(EVENTLOG_ERROR_TYPE, MSG_FLUSH_NO_MEMORY, NULL);
113         free(pszShareName);
114         return FALSE;
115     }
116
117     // Initialize the flush timer.  Note that GetTickCount() returns
118     // the number of milliseconds since the system started, in a DWORD,
119     // so that the value wraps around every 49.7 days.  We do not bother
120     // to handle the case where the flush elapsed time is greater than
121     // that.
122     dwFlushBegin = GetTickCount();
123
124     dwRet = WNetOpenEnum(RESOURCE_CONNECTED, RESOURCETYPE_ANY, 0, NULL,
125                           &hEnum);
126     if (dwRet != NO_ERROR)
127     {
128         LogEventMessage(EVENTLOG_ERROR_TYPE, MSG_FLUSH_OPEN_ENUM_ERROR,
129                          dwRet);
130         free(pszShareName);
131         return FALSE;
132     }
133
134     // Loop to enumerate network resources, and flush those associated
135     // with AFS volumes.
136     while (1)
137     {
138         dwCount = -1;
139         memset(lpNetResBuf, 0, NETRESBUFSIZE);
140         dwNetResBufSize = NETRESBUFSIZE;
141         dwRet = WNetEnumResource(hEnum, &dwCount,
142                                   lpNetResBuf, &dwNetResBufSize);
143         if (dwRet != NO_ERROR)
144             break;
145         // Iterate over the returned network resources.
146         for (i = 0, lpnr = lpNetResBuf; i < dwCount; i++, lpnr++)
147         {
148             // Ensure resource has a remote name, and is connected.
149             if ((lpnr->lpRemoteName == NULL) ||
150                  (lpnr->dwScope != RESOURCE_CONNECTED))
151                 continue;
152             if ((_strnicmp(lpnr->lpRemoteName, pszShareName,
153                             dwServerSize) == 0) &&
154                  (lpnr->lpRemoteName[dwServerSize] == SLASH))
155             {
156                 // got one!
157                 // but we don't want to flush '\\[...]afs\all'
158                 if (cm_stricmp_utf8(lpnr->lpRemoteName, pszShareName) == 0)
159                     continue;
160                 ++dwTotalVols;
161
162                 dwVolBegin = GetTickCount();
163                 afsRet = afsd_ServicePerformFlushVolumeCmd(lpnr->lpRemoteName);
164                 dwVolEnd = GetTickCount();
165                 if (afsRet == 0)
166                 {
167                     LogTimingEvent(MSG_TIME_FLUSH_PER_VOLUME,
168                                     lpnr->lpRemoteName,
169                                     dwVolEnd - dwVolBegin);
170                 }
171                 else
172                 {
173                     LogEvent(EVENTLOG_WARNING_TYPE,
174                               MSG_FLUSH_FAILED,
175                               lpnr->lpRemoteName, NULL);
176                 }
177             }
178         }
179     }
180     WNetCloseEnum(hEnum);
181     free(lpNetResBuf);
182     free(pszShareName);
183     if (dwRet != ERROR_NO_MORE_ITEMS)
184     {
185         LogEventMessage(EVENTLOG_ERROR_TYPE, MSG_FLUSH_ENUM_ERROR,
186                          dwRet);
187         return FALSE;
188     }
189
190     dwFlushEnd = GetTickCount();
191
192     // display total volume count in Event Logger
193     sprintf(bufMessage, "%d", dwTotalVols);
194     LogTimingEvent(MSG_TIME_FLUSH_TOTAL, bufMessage,
195                     dwFlushEnd - dwFlushBegin);
196
197     return TRUE;
198 }
199
200 // Report a timing event to the system event log.
201 // The lpszString1 argument is the first substitution string for the
202 // given event ID.  The time argument will be converted into the
203 // second substitution string.
204 static VOID
205 LogTimingEvent(DWORD dwEventID, LPTSTR lpString1, DWORD dwTime)
206 {
207     CHAR        szTime[16];
208
209     sprintf(szTime, "%lu", dwTime);
210     LogEvent(EVENTLOG_INFORMATION_TYPE, dwEventID, lpString1, szTime,
211               NULL);
212 }
213
214
215 /////////////////////////////////////////////////////////////////////
216 //
217 // GetUserToken
218 //
219 // Obtain token for the currently logged-in user.
220 //
221 // This routine looks for a window which we 'know' belongs to
222 // the shell, and from there we follow a route which leads to
223 // getting a handle on an access token owned by the shell.
224 //
225 // The return value is either a handle to a suitable token,
226 // or else null.
227 //
228 // One of the times that this function might return null
229 // is when there is no logged-in user. Other cases include
230 // insufficient access to the desktop, etc.
231 //
232 // Disclaimer:
233 // Portions of this routine found in various newsgroups
234 //
235 HANDLE GetUserToken(DWORD access)
236 {
237     HANDLE hTok = NULL;
238     DWORD pid = 0, tid = 0;
239
240     // Try it the easy way first - look for a window owned by the shell on
241     // our current desktop.  If we find one, use that to get the process id.
242     HWND shell = FindWindowEx(NULL, NULL, "Progman", NULL);
243     if (shell != NULL)
244     {
245         tid = GetWindowThreadProcessId(shell, &pid);
246     }
247
248     // We are possibly running on a private window station and desktop: we must
249     // switch to the default (which we suppose is where we will find the
250     // running shell).
251     else
252     {
253         HWINSTA saveWinSta = GetProcessWindowStation();
254         HDESK saveDesk = GetThreadDesktop(GetCurrentThreadId());
255         HWINSTA winSta = NULL;
256         HDESK desk = NULL;
257         BOOL changeFlag = FALSE;
258         BOOL dummy = saveWinSta != NULL &&
259                      saveDesk != NULL &&
260                      (winSta = OpenWindowStation("WinSta0", FALSE,
261                                                  MAXIMUM_ALLOWED)) != NULL &&
262                      (changeFlag = SetProcessWindowStation(winSta)) != 0 &&
263                      (desk = OpenDesktop("Default", 0, FALSE,
264                                           MAXIMUM_ALLOWED)) != NULL &&
265                      SetThreadDesktop(desk) != 0;
266
267         // Now find the window and process on this desktop
268         shell = FindWindowEx(NULL, NULL, "Progman", NULL);
269         if (shell != NULL)
270         {
271             tid = GetWindowThreadProcessId(shell, &pid);
272         }
273
274         // Restore our own window station and desktop
275         if (changeFlag)
276         {
277             SetProcessWindowStation(saveWinSta);
278             SetThreadDesktop(saveDesk);
279         }
280
281         // Close temporary objects
282         if (winSta != NULL)
283             CloseWindowStation(winSta);
284         if (desk != NULL)
285             CloseDesktop(desk);
286     }
287
288     //
289     // If we have a process id, use that to get the process handle and
290     // from there the process' access token.
291     //
292     if (pid != 0)
293     {
294         HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
295         if (hProc != NULL)
296         {
297             OpenProcessToken(hProc, access, &hTok) || (hTok = NULL);
298             CloseHandle(hProc);
299         }
300     }
301
302     // Return token if we got one
303     return hTok;
304 }
305
306 // impersonate logged-on user as client
307 BOOL
308 ImpersonateClient()
309 {
310     DWORD       dwDesiredAccess = TOKEN_ALL_ACCESS;
311     HANDLE      hUserToken = GetUserToken(dwDesiredAccess);
312
313     if (hUserToken == NULL)
314         return FALSE;
315     if (ImpersonateLoggedOnUser(hUserToken) == 0)
316     {
317         LogEvent(EVENTLOG_ERROR_TYPE, MSG_FLUSH_IMPERSONATE_ERROR,
318                   NULL);
319         return FALSE;
320     }
321     return TRUE;
322 }
323
324 /////////////////////////////////////////////////////////////////////
325 //
326 // Thread proc
327 //
328 DWORD WINAPI
329 afsd_ServiceFlushVolumesThreadProc(LPVOID lpParam)
330 {
331     FLUSHVOLTHREADINFO ThreadInfo;
332     PFLUSHVOLTHREADINFO pThreadInfo = (PFLUSHVOLTHREADINFO) lpParam;
333     HANDLE      arHandles[2] = {0};
334     DWORD       dwWaitState = 0;
335
336     // thread running - get handles
337     ThreadInfo.hEventPowerEvent = pThreadInfo->hEventPowerEvent;
338     ThreadInfo.hEventResumeMain = pThreadInfo->hEventResumeMain;
339     ThreadInfo.hEventTerminate  = pThreadInfo->hEventTerminate;
340
341     // setup to wait
342     arHandles[0] = ThreadInfo.hEventTerminate;
343     arHandles[1] = ThreadInfo.hEventPowerEvent;
344
345     // do stuff ..
346     while (1)
347     {
348         // wait for an event to happen
349         dwWaitState = WaitForMultipleObjectsEx(2, arHandles, FALSE, INFINITE, FALSE);
350
351         switch (dwWaitState)
352         {
353         case WAIT_OBJECT_0:
354             // termination signaled
355             RevertToSelf();
356             CheckAndCloseHandle(ThreadInfo.hEventPowerEvent);
357             CheckAndCloseHandle(ThreadInfo.hEventResumeMain);
358             CheckAndCloseHandle(ThreadInfo.hEventTerminate);
359             ExitThread(0);
360             break;
361
362         case WAIT_OBJECT_0+1:
363             // Power event
364             // - flush 'em!
365             if (ImpersonateClient())
366             {
367                 afsd_ServicePerformFlushVolumes();
368             }
369             // acknowledge event
370             ResetEvent(ThreadInfo.hEventPowerEvent);
371             break;
372
373         case WAIT_ABANDONED_0:
374         case WAIT_ABANDONED_0+1:
375         case WAIT_IO_COMPLETION:
376         case WAIT_TIMEOUT:
377             // sno*
378             LogEvent(EVENTLOG_WARNING_TYPE,
379                       MSG_FLUSH_UNEXPECTED_EVENT, NULL);
380             break;
381
382         }       // end switch
383
384         // signal back to waiting mainline
385         SetEvent(ThreadInfo.hEventResumeMain);
386
387     }   // end while
388
389     // I suppose we never get here
390     ExitThread(0);
391 }
392
393 /////////////////////////////////////////////////////////////////////
394 //
395 // Mainline thread routines
396 //
397
398 VOID
399 CheckAndCloseHandle(HANDLE thisHandle)
400 {
401     if (thisHandle != NULL)
402     {
403         CloseHandle(thisHandle);
404         thisHandle = NULL;
405     }
406 }
407
408 //
409 // Thread Creation
410 //
411 BOOL
412 PowerNotificationThreadCreate()
413 {
414     BOOL        bSuccess = FALSE;
415     DWORD       dwThreadId = 0;
416     char    eventName[MAX_PATH];
417
418     do
419     {
420         // create power event notification event
421         // bManualReset=TRUE, bInitialState=FALSE
422         gThreadInfo.hEventPowerEvent = CreateEvent(NULL, TRUE, FALSE,
423                                                    TEXT("afsd_flushvol_EventPowerEvent"));
424         if ( GetLastError() == ERROR_ALREADY_EXISTS )
425             afsi_log("Event Object Already Exists: %s", eventName);
426         if (gThreadInfo.hEventPowerEvent == NULL)
427             break;
428
429         // create mainline resume event
430         // bManualReset=FALSE, bInitialState=FALSE
431         gThreadInfo.hEventResumeMain = CreateEvent(NULL, FALSE, FALSE,
432                                                    TEXT("afsd_flushvol_EventResumeMain"));
433         if ( GetLastError() == ERROR_ALREADY_EXISTS )
434             afsi_log("Event Object Already Exists: %s", eventName);
435         if (gThreadInfo.hEventResumeMain == NULL)
436             break;
437
438         // create thread terminate event
439         // bManualReset=FALSE, bInitialState=FALSE
440         gThreadInfo.hEventTerminate = CreateEvent(NULL, FALSE, FALSE,
441                                                   TEXT("afsd_flushvol_EventTerminate"));
442         if ( GetLastError() == ERROR_ALREADY_EXISTS )
443             afsi_log("Event Object Already Exists: %s", eventName);
444         if (gThreadInfo.hEventTerminate == NULL)
445             break;
446
447         // good so far - create thread
448         gThreadHandle = CreateThread(NULL, 0,
449                                      afsd_ServiceFlushVolumesThreadProc,
450                                      (LPVOID) &gThreadInfo,
451                                      0, &dwThreadId);
452
453         if (!gThreadHandle)
454             break;
455
456         bSuccess = TRUE;
457
458     } while (0);
459
460
461     if (!bSuccess)
462     {
463         CheckAndCloseHandle(gThreadInfo.hEventPowerEvent);
464         CheckAndCloseHandle(gThreadInfo.hEventResumeMain);
465         CheckAndCloseHandle(gThreadInfo.hEventTerminate);
466         CheckAndCloseHandle(gThreadHandle);
467     }
468
469     return bSuccess;
470 }
471
472 //
473 // Thread Notification
474 //
475 BOOL
476 PowerNotificationThreadNotify()
477 {
478     DWORD               dwRet = 0;
479     BOOL                bRet  = FALSE;
480
481     // Notify thread of power event, and wait for the HardDead timeout period
482     dwRet = SignalObjectAndWait(
483                 gThreadInfo.hEventPowerEvent,   // object to signal
484                 gThreadInfo.hEventResumeMain,   // object to watch
485                 HardDeadtimeout*1000,           // timeout (ms)
486                 FALSE                           // alertable
487                 );
488
489     if (dwRet == WAIT_OBJECT_0)
490         bRet = TRUE;
491
492     return bRet;
493 }
494
495 //
496 // Thread Termination
497 //
498 VOID
499 PowerNotificationThreadExit()
500 {
501     // ExitThread
502     if (gThreadHandle)
503     {
504         SetEvent(gThreadInfo.hEventTerminate);
505         WaitForSingleObject(gThreadHandle, INFINITE);
506         CloseHandle(gThreadHandle);
507     }
508 }
509