/**********************************************************************
File name: filewait.c
wait for change in specified file
blame: shardy@@differentchairs.com
(derived from Windows sdk fwatch sample.
Test build with VC6.0, and tested on WindowsXP)
**********************************************************************/
#define UNICODE
#define _WIN32_WINNT 0x400
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#define MAX_BUFFER 4096
// this is the all purpose structure that contains
// the directory information of interest and provides
// the input buffer that gets filled with file change data
typedef struct _DIRECTORY_INFO {
HANDLE hDir;
TCHAR WatchedFileName[MAX_PATH];
CHAR lpBuffer[MAX_BUFFER];
DWORD dwBufLength;
OVERLAPPED Overlapped;
} DIRECTORY_INFO, *PDIRECTORY_INFO, *LPDIRECTORY_INFO;
DIRECTORY_INFO DirInfo;
BOOL Done = FALSE;
/**********************************************************************
CheckChangedFile()
Purpose:
Indicates whether file notification matches our watched file.
Parameters:
lpfni - Information about modified file
WatchedFileName - compare notified filename against this
Return Value:
TRUE if matched
********************************************************************/
BOOL WINAPI CheckChangedFile( PFILE_NOTIFY_INFORMATION lpfni,
TCHAR* WatchedFileName)
{
BOOL result = FALSE;
TCHAR *p;
unsigned i, len = lstrlen(WatchedFileName);
if (len!=lpfni->FileNameLength/sizeof(WCHAR))
{
p=0;
}
else {
p = WatchedFileName;
for (i=0; i<len; i++)
{
if (towupper(p[i])!=towupper(lpfni->FileName[i]))
{
p=0;
break;
}
}
}
if(p && *p) {
result = TRUE;
}
return result;
}
/**********************************************************************
HandleDirectoryChanges()
Purpose:
This function receives notification of directory changes and calls
CheckChangedFile() to determine if the change was in our watched file.
Calls ReadDirectoryChangesW to reestablish the watch, if needed.
Parameters:
HANDLE dwCompletionPort - Handle for completion port
Return Value:
None
********************************************************************/
void WINAPI HandleDirectoryChange( DWORD dwCompletionPort ) {
DWORD numBytes;
DWORD cbOffset;
LPDIRECTORY_INFO di = 0;
LPOVERLAPPED lpOverlapped;
PFILE_NOTIFY_INFORMATION fni;
do {
// Retrieve the directory info for this directory
// through the completion key
BOOL x = GetQueuedCompletionStatus( (HANDLE) dwCompletionPort,
&numBytes,
(LPDWORD) &di,
&lpOverlapped,
INFINITE);
if ( di )
{
fni = (PFILE_NOTIFY_INFORMATION)di->lpBuffer;
do {
cbOffset = fni->NextEntryOffset;
if( fni->Action == FILE_ACTION_MODIFIED )
Done=CheckChangedFile( fni, (TCHAR*)di->WatchedFileName );
fni = (PFILE_NOTIFY_INFORMATION)((LPBYTE) fni + cbOffset);
} while( !Done && cbOffset );
if (!Done) // Reissue the watch command
ReadDirectoryChangesW( di->hDir,di->lpBuffer,
MAX_BUFFER,
TRUE,
FILE_NOTIFY_CHANGE_LAST_WRITE,
&di->dwBufLength,
&di->Overlapped,
NULL);
}
} while( di && !Done);
}
/**********************************************************************
WatchDirectories()
Purpose:
This function inovkes the ReadDirectoryChangesW API for
directory associated with given IOCompletionPort. A thread
is created which waits for changes to the directory. This
function waits until the watched file changes, or the timeout elapses.
Parameters:
HANDLE hCompPort - Handle for completion port
timeout the maximum time to wait (in ms) for file change
Return Value:
None
********************************************************************/
void WINAPI WatchDirectories( HANDLE hCompPort, DWORD timeout ) {
DWORD tid;
HANDLE hThread;
// Start watching the directory of interest
ReadDirectoryChangesW(
DirInfo.hDir,
DirInfo.lpBuffer,
MAX_BUFFER,
TRUE,
FILE_NOTIFY_CHANGE_LAST_WRITE,
&DirInfo.dwBufLength,
&DirInfo.Overlapped,
NULL);
// Create a thread to watch directory changes
hThread = CreateThread( NULL,
0,
(LPTHREAD_START_ROUTINE) HandleDirectoryChange,
hCompPort,
0,
&tid);
if (WAIT_TIMEOUT==WaitForSingleObject( hThread, timeout ))
{
PostQueuedCompletionStatus( hCompPort, 0, 0, NULL );
// Wait for the watcher thread to finish before exiting
WaitForSingleObject( hThread, INFINITE );
}
CloseHandle( hThread );
}
void help()
{
printf("\nfilewait sleeps until target file changes or optional ");
printf("\ntimer (in ms) elapses. Return 0 if file change detected.\n");
printf("\nusage: filewait [-t <n>] <filename>\n");
}
/**********************************************************************
main()
Command usage:
filewait [-t <n>] <filename>
Test:
::::::::
@echo off
::
:: test_filewait.cmd
::
setlocal ENABLEDELAYEDEXPANSION
set testfail=
set testcases=c:\dir1\file.txt file.txt c:file.txt \file.txt c:file.txt c:\file.txt
echo.
echo filewait TEST
echo.
echo testcases: +/-[%testcases%]
:: test notify on file changed
start "testcase file updates" cmd /c"(for %%a in (%testcases%) do sleep 2 & echo x >%%a) "
for %%a in (%testcases%) do (
filewait %%a -t 3000 > nul || (
echo FAIL+[%%a]
set testfail=!testfail!.FAIL+[%%a]
)
)
:: test timeout on different/sibling file changed
for %%a in (%testcases%) do (
(start "update [%%a.X]" cmd /c"sleep 2 &echo x>%%a.X")
filewait %%a -t 3000 > nul && (
echo FAIL-[%%a]
set testfail=!testfail!.FAIL-[%%a]
)
)
echo.
if defined testfail (
echo %testfail%
) else (
echo PASS
)
endlocal
::::::::
Return Value:
0 file change detected
1 no change detected before timeout elapsed
2 error. (e.g., invalid filespec)
********************************************************************/
int main(int argc, char *argv[]) {
HANDLE hCompPort=NULL;
int i;
TCHAR FilePath[MAX_PATH];
TCHAR DirName[MAX_PATH];
HANDLE hFile;
TCHAR drive[_MAX_DRIVE];
TCHAR dir[_MAX_DIR];
TCHAR fname[_MAX_FNAME];
TCHAR ext[_MAX_EXT];
DWORD timeout = INFINITE;
#define ERROR_DIR 2
TCHAR watchfile[MAX_PATH+1];
watchfile[0] = 0;
if (2==argc || 4==argc)
for (i=1; i<argc; i++)
{
if (0==strcmpi(argv[i],"/t") ||
0==strcmpi(argv[i],"-t"))
{
timeout = atol(argv[++i]);
}
else
{
int j, len=strlen(argv[i]);
char *fname=argv[i];
for (j=0; j<len; j++)
{
watchfile[j] = fname[j];
}
watchfile[j] = 0;
}
}
if (lstrlen(watchfile)==0)
{
help();
}
else
{
_wsplitpath( watchfile, drive, dir, fname, ext );
if (0==lstrlen(drive) && 0==lstrlen(dir))
{
GetCurrentDirectory( MAX_PATH, DirName );
}
else
{
lstrcpy(DirName, drive);
lstrcat(DirName, dir);
}
lstrcpy(DirInfo.WatchedFileName, fname);
lstrcat(DirInfo.WatchedFileName, ext);
lstrcpy(FilePath, DirName);
lstrcat(FilePath, DirInfo.WatchedFileName);
if( CreateDirectory( DirName, NULL ) )
wprintf( L"Directory %s created\n", DirName );
else
wprintf( L"Directory %s exists\n", DirName );
// Get a handle to the directory
DirInfo.hDir = CreateFile( DirName,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ |
FILE_SHARE_WRITE |
FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS |
FILE_FLAG_OVERLAPPED,
NULL);
if( DirInfo.hDir == INVALID_HANDLE_VALUE ) {
wprintf( L"Unable to open directory %s. GLE=%d. Terminating...\n",
DirName, GetLastError() );
exit( ERROR_DIR );
}
if( INVALID_HANDLE_VALUE !=
(hFile = CreateFile( FilePath,
GENERIC_WRITE,
FILE_SHARE_READ ,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL)) )
{
wprintf( L" File %s created\n", FilePath );
CloseHandle( hFile );
}
else if( INVALID_HANDLE_VALUE !=
(hFile = CreateFile( FilePath,
GENERIC_READ,
FILE_SHARE_READ ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL)) )
{
wprintf( L" File [%s] exists\n", FilePath );
CloseHandle( hFile );
}
else // allow timeout...
wprintf( L" File [%s] could not be created\n", FilePath );
hCompPort=CreateIoCompletionPort( DirInfo.hDir, hCompPort, (DWORD) &DirInfo, 0);
wprintf( L"\n\nwait...\n" );
// Start watching
WatchDirectories( hCompPort, timeout );
CloseHandle( DirInfo.hDir );
CloseHandle( hCompPort );
}
return !Done;
}