inotify and recursion

Gary Willoughby dev at nomad.so
Fri Dec 27 04:56:25 PST 2013


On Friday, 27 December 2013 at 03:39:58 UTC, Hugo Florentino 
wrote:
> BTW, it it a requirement to use malloc, and if so, when would I 
> need to free the memory allocated by it?

I use a static ubyte array.

I've been using inotify quite a bit and found it to be very good 
but there are a few things to keep in mind though. First it can 
block further program execution when watching files. To avoid 
this use the select function in 'core.sys.posix.sys.select'. 
Second, if the file doesn't exist or is moved once watched, the 
inotifiy instance or the watch descriptor will be invalid and 
need to be re-initialised. Tip: moving can occur if edited with a 
text editor. As i found out trying to test inotify by changing a 
file using vim! That was debug pain!

Here is a snippet of code to show how i used it. It's not 
complete but it shows you how to use the select function and how 
all the bits fit together. I used an infinite loop because this 
was part of a daemon that runs forever so you may want to handle 
that better.

/**
  * Module.
  */
module common.file.watcher.inotifyengine;

/**
  * Imports.
  */
import core.sys.linux.sys.inotify;
import core.sys.posix.sys.select;
import core.sys.posix.unistd;
import common.file.logger;
import common.file.watcher.engine;
import std.string;

/**
  * A class to watch for changes in a file.
  *
  * Uses the linux inotify subsystem.
  */
class INotifyEngine : Engine
{
	/**
	 * The event buffer length.
	 *
	 * This gives us for 1024 events which should be more than 
enough.
	 */
	private enum eventBufferLength = (inotify_event.sizeof + 16) * 
1024;

	/**
	 * The notification flags.
	 *
	 * These are what inotify uses to descriminate on which events 
to nofify us about.
	 */
	private enum notificationFlags  = IN_MODIFY | IN_ATTRIB | 
IN_DELETE_SELF | IN_MOVE_SELF | IN_IGNORED;

	/**
	 * The inotify instance.
	 */
	private int _inotifyInstance;

	/**
	 * The watch descriptor.
	 */
	private int _watchDescriptor;

	/**
	 * The file descriptor set for the select call.
	 */
	private fd_set _fileDescriptorSet;

	/**
	 * The timeout for the select call.
	 */
	private timeval _timeout;

	/**
	 * Constructor.
	 *
	 * Params:
	 *     logger = The logger object used to log messages.
	 *
	 * See_Also:
	 *     Engine
	 */
	public this(Logger logger)
	{
		super(logger);
	}

	/**
	 * Initialize inotify.
	 *
	 * Throws:
	 *     Exception if inotify fails to initialize.
	 */
	private void initInotify()
	{
		this._inotifyInstance = inotify_init();

		if (this._inotifyInstance < 0)
		{
			this._logger.error("Inotify failed to initialize.");
		}

		this._watchDescriptor = 
inotify_add_watch(this._inotifyInstance, 
cast(char*)this._lastFileName.toStringz(), notificationFlags);
	}

	/**
	 * Stop inotify.
	 */
	private void closeInotify()
	{
		inotify_rm_watch(this._inotifyInstance, this._watchDescriptor);
		close(this._inotifyInstance);
	}

	/**
	 * Change the watcher if the file name changes.
	 */
	private void checkFileNameChange()
	{
		string currentFileName = this.getFileName();

		if (currentFileName != this._lastFileName)
		{
			this._logger.warning("Watched file name changed to '%s'", 
currentFileName);

			this.notify(this._lastFileName);
			this._lastFileName = currentFileName;
			this.notify(this._lastFileName);

			this.closeInotify();
			this.initInotify();

			this._logger.info("Starting to watch '%s' for alerts (%d, 
%d)", this._lastFileName, this._inotifyInstance, 
this._watchDescriptor);
		}
	}

	/**
	 * Retry watching if there was a problem e.g. the file doesn't 
exist yet.
	 */
	private void checkWatchStatus()
	{
		if (this._inotifyInstance == -1 || this._watchDescriptor == -1)
		{
			this._logger.error("Failed watching '%s' for alerts, 
retrying...", this._lastFileName);

			this.closeInotify();
			this.initInotify();

			this._logger.info("Starting to watch '%s' for alerts (%d, 
%d)", this._lastFileName, this._inotifyInstance, 
this._watchDescriptor);
		}
	}

	/**
	 * Check the file for any changes.
	 *
	 * If changes occur then execute the action if one has been 
assigned.
	 * We are using the select call to perform non blocking event 
handling waiting for inotify.
	 * If inotify detects a change in the file, select returns 
immediately.
	 *
	 * See_Also:
	 *     IEngine
	 */
	public void start()
	{
		this._lastFileName = this.getFileName();

		this.notify(this._lastFileName);

		this.initInotify();

		this._logger.info("Starting to watch '%s' for alerts (%d, %d)", 
this._lastFileName, this._inotifyInstance, this._watchDescriptor);

		ubyte[eventBufferLength] eventBuffer;
		void* eventPointer;

		while (true)
		{
			FD_ZERO(&this._fileDescriptorSet);
			FD_SET(this._inotifyInstance, &this._fileDescriptorSet);

			this._timeout.tv_usec = 0;
			this._timeout.tv_sec  = this._interval;

			if (select(FD_SETSIZE, &this._fileDescriptorSet, null, null, 
&this._timeout))
			{
				auto bytesRead = read(this._inotifyInstance, eventBuffer.ptr, 
eventBuffer.length);

				for (eventPointer = eventBuffer.ptr; eventPointer < 
eventBuffer.ptr + bytesRead; null)
				{
					inotify_event* event = cast(inotify_event*)eventPointer;

					if (event.mask & IN_MODIFY || event.mask & IN_ATTRIB)
					{
						this.notify(this._lastFileName);
					}

					if (event.mask & IN_DELETE_SELF || event.mask & IN_MOVE_SELF 
|| event.mask & IN_IGNORED)
					{
						this.notify(this._lastFileName);

						this.closeInotify();
						this.initInotify();

						this._logger.warning("Alert log moved or deleted, 
re-initialized to watch '%s' again (%d, %d)", this._lastFileName, 
this._inotifyInstance, this._watchDescriptor);
					}

					eventPointer += inotify_event.sizeof + event.len;
				}
			}

			this.checkFileNameChange();
			this.checkWatchStatus();
		}
	}

	/**
	 * Destructor.
	 */
	~this()
	{
		this.closeInotify();
	}
}


More information about the Digitalmars-d-learn mailing list