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