1
0
forked from 0ad/0ad

more faithfully emulating the real FAM now (keeping track of watches by request number). not finished

This was SVN commit r404.
This commit is contained in:
janwas 2004-06-06 23:58:52 +00:00
parent 916ddc8c02
commit 092fa131ff
2 changed files with 101 additions and 61 deletions

View File

@ -73,6 +73,9 @@ struct Watch
const std::string dir_name;
HANDLE hDir;
// user pointer from from FAMMonitorDirectory; passed to FAMEvent
void* user_data;
// history to detect series of notifications, so we can skip
// redundant reloads (slow)
std::string last_path;
@ -88,11 +91,11 @@ struct Watch
// we miss changes made to a directory.
// issue code uses sizeof(change_buf) to determine size.
FAMConnection* fc;
FAMRequest fr;
// these are returned in FAMEvent. could get them via FAMNextEvent's
// fc parameter and associating packets with FAMRequest,
// but storing them here is more convenient.
FAMConnection* fc;
FAMRequest* fr;
Watch(const std::string& _dir_name, HANDLE _hDir)
@ -103,8 +106,6 @@ struct Watch
last_action = 0;
last_ticks = 0;
memset(&ovl, 0, sizeof(ovl));
// change_buf[] doesn't need init
}
@ -118,8 +119,8 @@ struct Watch
// list of all active watches to detect duplicates and
// for easier cleanup. only store pointer in container -
// they're not copy-equivalent.
typedef std::map<std::string, Watch*> Watches;
// they're not copy-equivalent (dtor would close hDir).
typedef std::map<uint, Watch*> Watches;
typedef Watches::iterator WatchIt;
typedef std::list<FAMEvent> Events;
@ -149,11 +150,15 @@ struct AppState
// they're not copy-equivalent.
Watches watches;
uint last_reqnum;
AppState(const char* _app_name)
: app_name(_app_name)
{
hIOCP = 0;
// not INVALID_HANDLE_VALUE! (CreateIoCompletionPort requirement)
last_reqnum = 0;
}
~AppState()
@ -170,22 +175,33 @@ struct AppState
// macros to return pointers to the above from the FAM* structs (pImpl)
// (macro instead of function so we can bail out of the "calling" function)
#define GET_APP_STATE(fc, ptr_name)\
AppState* const ptr_name = (AppState*)fc->internal;\
if(!ptr_name)\
//
// WARNING: expands to multiple statements. can't use STMT, because
// the macros defined variables (*_ptr_name) that must be visible.
#define GET_APP_STATE(fc, state_ptr_var)\
AppState* state_ptr_var = (AppState*)fc->internal;\
if(!state_ptr_var)\
{\
debug_warn("no FAM connection");\
return -1;\
}
#define GET_WATCH(fr, ptr_name)\
Watch* const ptr_name = (Watch*)fr->internal;\
if(!ptr_name)\
{\
debug_warn("FAMRequest.internal invalid!");\
return -1;\
static int find_watch(FAMConnection* const fc, const FAMRequest* const fr, Watch*& w)
{
GET_APP_STATE(fc, state);
Watches& watches = state->watches;
WatchIt it = watches.find(fr->reqnum);
if(it == watches.end())
return -1;
w = it->second;
return 0;
}
#define GET_WATCH(fc, fr, watch_ptr_var)\
Watch* watch_ptr_var;\
CHECK_ERR(find_watch(fc, fr, watch_ptr_var);
int FAMOpen2(FAMConnection* const fc, const char* const app_name)
{
@ -217,49 +233,60 @@ int FAMClose(FAMConnection* const fc)
// HACK - see call site
static void get_packet(AppState*);
static int get_packet(FAMConnection* vc);
int FAMMonitorDirectory(FAMConnection* const fc, char* const _dir, FAMRequest* const fr, void* const user)
int FAMMonitorDirectory(FAMConnection* const fc, const char* const _dir, FAMRequest* const fr, void* const user_data)
{
const std::string dir(_dir);
GET_APP_STATE(fc, state);
Watches& watches = state->watches;
HANDLE& hIOCP = state->hIOCP;
const std::string dir(_dir);
uint& last_reqnum = state->last_reqnum;
// make sure dir is not already being watched
WatchIt it = watches.find(dir);
if(it != watches.end())
for(WatchIt it = watches.begin(); it != watches.end(); ++it)
if(dir == it->second->dir_name)
return -1;
// open handle to directory
const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
const DWORD flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
HANDLE hDir = CreateFile(_dir, FILE_LIST_DIRECTORY, share, 0, OPEN_EXISTING, flags, 0);
const HANDLE hDir = CreateFile(_dir, FILE_LIST_DIRECTORY, share, 0, OPEN_EXISTING, flags, 0);
if(hDir == INVALID_HANDLE_VALUE)
return -1;
// create Watch and associate with FAM structs
Watch* const w = new Watch(dir, hDir);
watches[dir] = w;
w->fc = fc;
w->fr = fr;
// assign a new (unique) request number. don't do this earlier - prevents
// DOS via wasting reqnums due to invalid directory parameters.
// need it before binding dir to IOCP because it is our "key".
if(last_reqnum == UINT_MAX)
{
debug_warn("FAMMonitorDirectory: request numbers are no longer unique");
return -1;
}
const uint reqnum = ++last_reqnum;
fr->reqnum = reqnum;
// associate Watch* with the directory handle. when we receive a packet
// from the IOCP, we will need to re-issue the watch and find the
// corresponding FAMRequest.
const ULONG_PTR key = (ULONG_PTR)w;
// from the IOCP, we will need to re-issue the watch.
const ULONG_PTR key = (ULONG_PTR)reqnum;
// create IOCP (if not already done) and attach hDir to it
hIOCP = CreateIoCompletionPort(hDir, hIOCP, key, 0);
if(hIOCP == 0 || hIOCP == INVALID_HANDLE_VALUE)
{
delete w;
CloseHandle(hDir);
return -1;
}
// create Watch and associate with FAM structs
Watch* const w = new Watch(dir, hDir);
watches[reqnum] = w;
w->fc = fc;
w->fr = *fr;
w->user_data = user_data;
// post a dummy kickoff packet; the IOCP polling code will "re"issue
// the corresponding watch. this keeps the ReadDirectoryChangesW call
// and directory <--> Watch association code in one place.
@ -267,9 +294,7 @@ int FAMMonitorDirectory(FAMConnection* const fc, char* const _dir, FAMRequest* c
// we call get_packet so that it's issued immediately,
// instead of only at the next call to FAMPending.
PostQueuedCompletionStatus(hIOCP, 0, key, 0);
get_packet(state);
fr->internal = w;
get_packet(fc);
return 0;
}
@ -278,18 +303,19 @@ int FAMMonitorDirectory(FAMConnection* const fc, char* const _dir, FAMRequest* c
int FAMCancelMonitor(FAMConnection* const fc, FAMRequest* const fr)
{
GET_APP_STATE(fc, state);
GET_WATCH(fr, w)
// GET_WATCH(fc, fr, w);
// TODO
// contrary to dox, the RDC IOs do not "complete" - no packet received on the IOCP in test.
/// int a = CancelIo(w->hDir);
return -1;
return 0;
}
static int extract_events(Watch* const w)
{
FAMConnection* const fc = w->fc;
FAMRequest* const fr = w->fr;
const FAMRequest& fr = w->fr;
GET_APP_STATE(fc, state);
Events& events = state->pending_events;
@ -297,7 +323,8 @@ static int extract_events(Watch* const w)
// will be modified for each event and added to events
FAMEvent event_template;
event_template.fc = fc;
event_template.fr = *fr;
event_template.fr = fr;
event_template.user = w->user_data;
// points to current FILE_NOTIFY_INFORMATION;
// char* simplifies advancing to the next (variable length) FNI.
@ -377,18 +404,23 @@ static int extract_events(Watch* const w)
// if a packet is pending, extract its events and re-issue its watch.
void get_packet(AppState* const state)
int get_packet(FAMConnection* fc)
{
GET_APP_STATE(fc, state);
const HANDLE hIOCP = state->hIOCP;
// poll for change notifications from all pending FAMRequests
DWORD bytes_transferred;
// used to determine if packet is valid or a kickoff
ULONG_PTR key;
OVERLAPPED* povl;
BOOL got_packet = GetQueuedCompletionStatus(state->hIOCP, &bytes_transferred, &key, &povl, 0);
BOOL got_packet = GetQueuedCompletionStatus(hIOCP, &bytes_transferred, &key, &povl, 0);
if(!got_packet) // no new packet - done
return;
return 1;
Watch* w = (Watch*)key;
const FAMRequest fr = { (uint)key };
Watch* w;
CHECK_ERR(find_watch(fc, &fr, w));
// this is an actual packet, not just a kickoff for issuing the watch.
// extract the events and push them onto AppState's queue.
@ -401,9 +433,12 @@ void get_packet(AppState* const state)
FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_CREATION;
const DWORD buf_size = sizeof(w->change_buf);
memset(&w->ovl, 0, sizeof(w->ovl));
BOOL ret = ReadDirectoryChangesW(w->hDir, w->change_buf, buf_size, FALSE, filter, 0, &w->ovl, 0);
if(!ret)
debug_warn("ReadDirectoryChangesW failed");
return 0;
}
@ -417,7 +452,7 @@ int FAMPending(FAMConnection* const fc)
if(!pending_events.empty())
return 1;
get_packet(state);
get_packet(fc);
return !pending_events.empty();
}

View File

@ -18,27 +18,28 @@
#ifndef WFAM_H__
#define WFAM_H__
#include "posix.h" // PATH_MAX
#ifdef __cplusplus
extern "C" {
#endif
// FAM documentation: http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?coll=0650&db=bks&fname=/SGI_Developer/books/IIDsktp_IG/sgi_html/ch08.html
// opaque structs are too hard to keep in sync with the real definition,
// and we don't want to expose the internals. therefore, use the pImpl idiom.
struct FAMRequest
{
void* internal;
// reqnum not needed, since FAMMonitorDirectory2 isn't supported.
};
// don't want to expose the internals, and opaque structs are too hard to
// keep in sync with the real definition, therefore, use the pImpl idiom.
struct FAMConnection
{
void* internal;
};
struct FAMRequest
{
uint reqnum;
};
enum FAMCodes { FAMDeleted, FAMCreated, FAMChanged };
typedef struct
@ -55,11 +56,15 @@ FAMEvent;
extern int FAMOpen2(FAMConnection*, const char* app_name);
extern int FAMClose(FAMConnection*);
extern int FAMMonitorDirectory(FAMConnection*, const char* dir, FAMRequest* req, void* user);
extern int FAMMonitorDirectory(FAMConnection*, const char* dir, FAMRequest* req, void* user_data);
extern int FAMCancelMonitor(FAMConnection*, FAMRequest* req);
extern int FAMPending(FAMConnection*);
extern int FAMNextEvent(FAMConnection*, FAMEvent* event);
#ifdef __cplusplus
}
#endif
#endif // #ifndef WFAM_H__