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