private void AddDirectoryWatchUnlocked(WatchedDirectory parent, string directoryName)
{
string fullPath = parent != null ? parent.GetPath(false, directoryName) : directoryName;
// inotify_add_watch will fail if this is a symlink, so check that we didn't get a symlink
Interop.Sys.FileStatus status = default(Interop.Sys.FileStatus);
if ((Interop.Sys.LStat(fullPath, out status) == 0) &&
((status.Mode & (uint)Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK))
{
return;
}
// Add a watch for the full path. If the path is already being watched, this will return
// the existing descriptor. This works even in the case of a rename. We also add the DONT_FOLLOW
// and EXCL_UNLINK flags to keep parity with Windows where we don't pickup symlinks or unlinked
// files (which don't exist in Windows)
int wd = Interop.Sys.INotifyAddWatch(_inotifyHandle, fullPath, (uint)(this._notifyFilters | Interop.Sys.NotifyEvents.IN_DONT_FOLLOW | Interop.Sys.NotifyEvents.IN_EXCL_UNLINK));
if (wd == -1)
{
// If we get an error when trying to add the watch, don't let that tear down processing. Instead,
// raise the Error event with the exception and let the user decide how to handle it.
Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
Exception exc;
if (error.Error == Interop.Error.ENOSPC)
{
string maxValue = ReadMaxUserLimit(MaxUserWatchesPath);
string message = !string.IsNullOrEmpty(maxValue) ?
SR.Format(SR.IOException_INotifyWatchesUserLimitExceeded_Value, maxValue) :
SR.IOException_INotifyWatchesUserLimitExceeded;
exc = new IOException(message, error.RawErrno);
}
else
{
exc = Interop.GetExceptionForIoErrno(error, fullPath);
}
FileSystemWatcher watcher;
if (_weakWatcher.TryGetTarget(out watcher))
{
watcher.OnError(new ErrorEventArgs(exc));
}
return;
}
// Then store the path information into our map.
WatchedDirectory directoryEntry;
bool isNewDirectory = false;
if (_wdToPathMap.TryGetValue(wd, out directoryEntry))
{
// The watch descriptor was already in the map. Hard links on directories
// aren't possible, and symlinks aren't annotated as IN_ISDIR,
// so this is a rename. (In extremely remote cases, this could be
// a recycled watch descriptor if many, many events were lost
// such that our dictionary got very inconsistent with the state
// of the world, but there's little that can be done about that.)
if (directoryEntry.Parent != parent)
{
if (directoryEntry.Parent != null)
{
directoryEntry.Parent.Children.Remove (directoryEntry);
}
directoryEntry.Parent = parent;
if (parent != null)
{
parent.InitializedChildren.Add (directoryEntry);
}
}
directoryEntry.Name = directoryName;
}
else
{
// The watch descriptor wasn't in the map. This is a creation.
directoryEntry = new WatchedDirectory
{
Parent = parent,
WatchDescriptor = wd,
Name = directoryName
};
if (parent != null)
{
parent.InitializedChildren.Add (directoryEntry);
}
_wdToPathMap.Add(wd, directoryEntry);
isNewDirectory = true;
}
// Since inotify doesn't handle nesting implicitly, explicitly
// add a watch for each child directory if the developer has
// asked for subdirectories to be included.
if (isNewDirectory && _includeSubdirectories)
{
// This method is recursive. If we expect to see hierarchies
// so deep that it would cause us to overflow the stack, we could
// consider using an explicit stack object rather than recursion.
// This is unlikely, however, given typical directory names
// and max path limits.
foreach (string subDir in Directory.EnumerateDirectories(fullPath))
{
AddDirectoryWatchUnlocked(directoryEntry, System.IO.Path.GetFileName(subDir));
// AddDirectoryWatchUnlocked will add the new directory to
// this.Children, so we don't have to / shouldn't also do it here.
}
}
}