Sans Pareil Technologies, Inc.

Key To Your Business

File Event Notification (FEN)



File Event Notification is an event source under the Event Ports framework that was introduced in Solaris 10. FEN can be used to monitor files or directories for various types of events - modification, attribute change, access etc. SPT uses FEN in various caching/queueing systems when associated files/directories are modified by external processes. Here we will demonstrate a Poco Runnable based monitor class that was adapted from a FEN blog entry.

FileMonitor Definition

The FileMonitor class implements a Poco::Runnable interface and is used to monitor specified files for modification events. The primary method exposed by this class is a monitor( const char* ) method which is used to register the specified file with FEN. Note that in this implementation we do not prevent duplicate requests for the same resource to be monitored, since in our case the callers ensure that only unique files are monitored. A more generic implementation would maintain a local hash map to ensure this.

When a FEN notification is received for a file being monitored, the registered delegate is notified of the event. The delegate implementation may allow only a single notification to a caller, or maintain a list of callers to notify. The stop() method is used to close the event port and stop all monitoring.

#pragma once

#include <servlet/model/FileMonitorDelegate.h>

#include <Poco/Logger.h>
#include <Poco/Runnable.h>

using Poco::Logger;
using Poco::Runnable;

#include <port.h>
#include <sys/stat.h>

namespace spt
{
namespace servlet
{
namespace model
{
class FileMonitor : public Runnable
{
public:
FileMonitor( FileMonitorDelegate& );

virtual void run();
void stop();
void monitor( const char* );

private:
int port;
FileMonitorDelegate& delegate;
static Logger& logger;

struct fileinfo
{
struct file_obj fobj;
int events;
int port;
};

/** Process file modification event notifications */
void processEvent( struct fileinfo*, int );
};
}
}
}

FileMonitorDelegate Definition

A FileMonitorDelegate implementation is registered with a FileMonitor when it is instantiated. The FileMonitor invokes the fileModified( const QString ) method on the delegate with the path of the file that was modified. The delegate implementation may then take appropriate action, or notify a list of observers as the case may be.

#pragma once

#include <QtCore/QString>

namespace spt
{
namespace servlet
{
namespace model
{
class FileMonitorDelegate
{
public:
virtual void fileModified( const QString ) = 0;
};
}
}
}

FileMonitor Implementation

The FileMonitor implementation retrieves the port to monitor from the Event Port framework in its constructor. The Runnable:run() method implementation continually monitors the port using the port_get() function. The stop() method closes the port, which will then cause the run() method to terminate.

Callers register files they wish to monitor through the monitor( const char* ) method. This method allocates a new instance of the fileinfo structure and sets the event of interest to FILE_MODIFIED. It then invokes the processEvent() method which performs some error checking before using the port_associate() function to register with FEN for file modification event notifications. The run() method continually polls the port using port_get() and notifies the delegate when there is a notification.

#include <servlet/model/FileMonitor.h>
#include <QtCore/QString>

using spt::servlet::model::FileMonitorDelegate;
using Poco::format;
using std::string;

Logger &spt::servlet::model::FileMonitor::logger =
Logger::get( "spt.servlet.model.FileMonitor" );


spt::servlet::model::FileMonitor::FileMonitor( FileMonitorDelegate& d ) :
port( -1 ), delegate( d )
{
if ( ( port = port_create() ) == -1 )
{
logger.warning( "Unable to create file event notification port" );
}
}


void spt::servlet::model::FileMonitor::stop()
{
if ( port != -1 ) close( port );
}


void spt::servlet::model::FileMonitor::run()
{
if ( port == -1 ) return;

port_event_t pe;

while ( ! port_get( port, &pe, NULL ) )
{
switch ( pe.portev_source )
{
case PORT_SOURCE_FILE:
processEvent( (struct fileinfo*) pe.portev_object, pe.portev_events );
break;
default:
logger.warning( "Event from unexpected source" );
}
}

logger.information( "FileMonitor thread exiting" );
}


void spt::servlet::model::FileMonitor::monitor( const char* filePath )
{
struct fileinfo* finf = new fileinfo;
if ( ! finf )
{
logger.error( format( "Unable to allocate memory for fileinfo struct for file: %s", string( filePath ) ) );
return;
}

if ( ( finf->fobj.fo_name = strdup( filePath ) ) == NULL )
{
logger.error( format( "Unable to copy file path into fileinfo struct for file: %s", string( filePath ) ) );
delete finf;
return;
}

finf->events = FILE_MODIFIED;
finf->port = port;
logger.information( format( "Adding %s to file event notification.", string( filePath ) ) );
processEvent( finf, 0 );
}


void spt::servlet::model::FileMonitor::processEvent( struct fileinfo* finf, int revents )
{
struct file_obj* fobjp = &finf->fobj;
int port = finf->port;
struct stat sb;

if ( ! ( revents & FILE_EXCEPTION ) && stat( fobjp->fo_name, &sb ) == -1 )
{
logger.warning(
format( "Failed to stat file: %s - errno %d", string( fobjp->fo_name ), errno ) );
free( finf->fobj.fo_name );
delete finf;
return;
}

if ( revents & FILE_EXCEPTION )
{
free( finf->fobj.fo_name );
delete finf;
return;
}

if ( revents & FILE_MODIFIED )
{
logger.information( format( "%s modified", string( fobjp->fo_name ) ) );
delegate.fileModified( QString( fobjp->fo_name ) );
}

fobjp->fo_atime = sb.st_atim;
fobjp->fo_mtime = sb.st_mtim;
fobjp->fo_ctime = sb.st_ctim;

if ( port_associate( port, PORT_SOURCE_FILE,
(uintptr_t) fobjp, finf->events, static_cast<void*>( finf ) ) == -1 )
{
logger.warning(
format( "Failed to register file: %s - errno %d", string( fobjp->fo_name ), errno ) );
free( finf->fobj.fo_name );
delete finf;
}
}

Sample Caller

A sample caller class would look something like the following:

#include <servlet/model/FileMetaData.h>
#include <servlet/model/FileMonitorDelegate.h>
#include <Poco/File.h>
#include <Poco/RWLock.h>
#include <Poco/Thread.h>
#include <QtCore/QHash>
#include <QtCore/QString>

using Poco::File;
using Poco::RWLock;
using Poco::ScopedWriteRWLock;
using Poco::Thread;

class FileMonitorClient : public FileMonitorDelegate
{
public:
FileMonitorClient() : monitor( new FileMonitor( *this ) )
{
thread.start( *monitor );

const char* name1 = "/tmp/file1.txt";
File f1( name1 );
cache->insert( name1, f1 );
monitor->monitor( name1 );

const char* name2 = "/tmp/file2.txt";
File f2( name2 );
cache->insert( name2, f2 );
monitor->monitor( name2 );
}

~FileMonitorClient()
{
monitor->stop();
thread.join();
delete monitor;
}

void fileModified( const QString name )
{
ScopedWriteRWLock wl( lock );
File f = cache->take( name );
// refresh and put back into cache as appropriate
}

private:
FileMonitor* monitor;
QHash<QString,File> cache;
RWLock lock;
Thread thread;
}

We have demonstrated how we use the Solaris File Event Notification (FEN) event source under the Event Port framework to monitor for external modifications to files of interest. This is used as part of application specific caching logic. When an external modification event is trapped, the stale entry is removed/refreshed in the cache.