Skip to content
60 changes: 41 additions & 19 deletions lib/rb-inotify/native/flags.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,31 @@ module Flags
IN_ATTRIB = 0x00000004
# Writtable file was closed.
IN_CLOSE_WRITE = 0x00000008
# File was modified.
IN_MODIFY = 0x00000002
# Unwrittable file closed.
IN_CLOSE_NOWRITE = 0x00000010
# File was opened.
IN_OPEN = 0x00000020
# File was moved from X.
IN_MOVED_FROM = 0x00000040
# File was moved to Y.
IN_MOVED_TO = 0x00000080
# Subfile was created.
IN_CREATE = 0x00000100
# Subfile was deleted.
IN_DELETE = 0x00000200
# Self was deleted.
IN_DELETE_SELF = 0x00000400
# File was modified.
IN_MODIFY = 0x00000002
# Self was moved.
IN_MOVE_SELF = 0x00000800
# File was moved from X.
IN_MOVED_FROM = 0x00000040
# File was moved to Y.
IN_MOVED_TO = 0x00000080
# File was opened.
IN_OPEN = 0x00000020

## Helper events.

# Close.
IN_CLOSE = (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
# Moves.
IN_MOVE = (IN_MOVED_FROM | IN_MOVED_TO)
# Close.
IN_CLOSE = (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
# All events which a program can wait on.
IN_ALL_EVENTS = (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE |
IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE |
Expand All @@ -44,26 +44,35 @@ module Flags

## Special flags.

# Only watch the path if it is a directory.
IN_ONLYDIR = 0x01000000
# Do not follow a sym link.
# Do not follow a sym link
# available since Linux 2.6.15, causes EINVAL othervise
IN_DONT_FOLLOW = 0x02000000
# Exclude events on unlinked objects.
# available since Linux 2.6.36, causes EINVAL othervise
IN_EXCL_UNLINK = 0x04000000
# Add to the mask of an already existing watch.
IN_MASK_ADD = 0x20000000
# Only send event once.
IN_ONESHOT = 0x80000000

# Only watch the path if it is a directory.
# available since Linux 2.6.15, causes EINVAL othervise
IN_ONLYDIR = 0x01000000
# Only create watches.
# available since Linux 4.18, causes EINVAL othervise
IN_MASK_CREATE = 0x10000000

## Events sent by the kernel.

# Backing fs was unmounted.
IN_UNMOUNT = 0x00002000
# Event queued overflowed.
IN_Q_OVERFLOW = 0x00004000
# File was ignored.
IN_IGNORED = 0x00008000
# Event occurred against dir.
IN_ISDIR = 0x40000000
# Event queued overflowed.
IN_Q_OVERFLOW = 0x00004000
# Backing fs was unmounted.
IN_UNMOUNT = 0x00002000

EVENT_ONLY_FLAGS = IN_IGNORED | IN_ISDIR | IN_UNMOUNT | IN_Q_OVERFLOW

## fpathconf Macros

Expand All @@ -75,10 +84,23 @@ module Flags
# @param flags [Array<Symbol>]
# @return [Fixnum]
def self.to_mask(flags)
flags.map {|flag| const_get("IN_#{flag.to_s.upcase}")}.
flags.map {|flag| to_add_watch_flag(flag) }.
inject(0) {|mask, flag| mask | flag}
end

# Converts a flag to the value that can be used in inotify_add_watch
#
# @param flag [Symbol]
# @return [Fixnum]
# @raise [NameError] if the flag is not supported
# @raise [ArgumentError] if the flag is defined, but can't be used in inotify_add_watch
def self.to_add_watch_flag(flag)
res = const_get("IN_#{flag.to_s.upcase}")
raise ArgumentError, "Invalid flag: #{flag}" if EVENT_ONLY_FLAGS & res != 0

res
end

# Converts a bitmask from the C API into a list of flags.
#
# @param mask [Fixnum]
Expand Down
10 changes: 8 additions & 2 deletions lib/rb-inotify/notifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,16 @@ def to_io
# Instead, they specify options for the watcher.
#
# `:onlydir`
# : Only watch the path if it's a directory.
# : Only watch the path if it's a directory; since Linux 2.6.15.
#
# `:dont_follow`
# : Don't follow symlinks.
# : Don't follow symlinks; since Linux 2.6.15.
#
# `:excl_unlink`
# : Exclude events on unlinked objects; since Linux 2.6.36.
#
# `:mask_create`
# : Watch pathname only if it does not already have a watch associated with it, Errno::EEXIST is raised otherwise; since Linux 4.18.
#
# `:mask_add`
# : Add these flags to the pre-existing flags for this path.
Expand Down
20 changes: 20 additions & 0 deletions spec/notifier_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@
expect(events.first.absolute_name).to eq(dir.join("test.txt").to_s)
end

it "ensures that new watches do not modify existing ones" do
recording(dir, :create, :oneshot, :mask_create)
expect do
recording(dir, :create, :oneshot, :mask_create)
end.to raise_error(Errno::EEXIST)
dir.join("test.txt").write("hello world")
expect do
recording(dir, :create, :oneshot, :mask_create)
end.not_to raise_error
end

it "fails if flags are not supported" do
expect do
recording(dir, :create, :oneshot, :unknown_flag)
end.to raise_error(NameError, 'uninitialized constant INotify::Native::Flags::IN_UNKNOWN_FLAG')
expect do
recording(dir, :create, :isdir)
end.to raise_error(ArgumentError, 'Invalid flag: isdir')
end

it "gets simultaneous events" do
events = recording(dir, :create)

Expand Down