NAME
Linux::Event::Kernel::Inotify - Watch files and directories for filesystem changes
SYNOPSIS
use v5.36;
use Linux::Event::Loop;
use Linux::Event::Kernel::Inotify;
my $loop = Linux::Event::Loop->new;
my $inotify = Linux::Event::Kernel::Inotify->new(
loop => $loop,
);
my $watch = $inotify->watch(
'log.txt',
on_modify => sub ($event) {
say $event->path . ' changed';
},
on_close_write => sub ($event) {
say $event->path . ' finished being written';
},
);
$loop->run;
DESCRIPTION
Linux::Event::Kernel::Inotify provides Linux filesystem notifications through Linux::Event::Loop.
It can notify an application when a watched file or directory is:
created
opened
read
modified
closed
renamed or moved
deleted
removed from the filesystem
Linux::Event uses Linux inotify internally.
THE THREE OBJECTS
Inotify uses three related object types.
Inotify
my $inotify = Linux::Event::Kernel::Inotify->new(
loop => $loop,
);
The parent Inotify object owns the Linux inotify instance.
One parent can contain many watches.
Watch
Calling watch returns a Linux::Event::Kernel::Inotify::Watch:
my $watch = $inotify->watch(
'log.txt',
on_modify => sub ($event) {
...
},
);
The Watch represents one logical filesystem subscription.
Use the Watch when you want to cancel that particular subscription or inspect its state.
Event
When something happens, the callback receives a Linux::Event::Kernel::Inotify::Event:
on_modify => sub ($event) {
say $event->path;
}
The Event describes the particular filesystem notification.
So the relationship is:
Inotify
|
+-- Watch
| |
| +-- Event
| +-- Event
|
+-- Watch
|
+-- Event
WATCHING A FILE
For example:
my $watch = $inotify->watch(
'/var/log/myapp.log',
on_modify => sub ($event) {
say "log changed";
},
);
watch returns immediately with a Watch object.
If the parent Inotify object is already attached to a Loop, the kernel watch is activated immediately.
WATCHING A DIRECTORY
Watching a directory also reports activity involving entries inside that directory:
my $watch = $inotify->watch(
'/srv/uploads',
on_create => sub ($event) {
say "created: " . $event->path;
},
on_delete => sub ($event) {
say "deleted: " . $event->path;
},
);
For a directory event, $event->name contains the child name supplied by Linux.
$event->path combines the watched directory with that name.
For example, if the watched path is:
/srv/uploads
and Linux reports:
photo.jpg
then:
$event->path
returns:
/srv/uploads/photo.jpg
WATCH PATHS BECOME ABSOLUTE
Relative paths are converted to absolute paths when watch is called.
For example:
my $watch = $inotify->watch(
'log.txt',
on_modify => sub ($event) {
...
},
);
captures the absolute path that log.txt referred to at that moment.
A later chdir does not silently retarget the Watch.
WATCH CALLBACKS
The ordinary filesystem callbacks are:
on_access-
The watched file, or an entry within a watched directory, was accessed.
on_modify-
File contents were modified.
on_attrib-
Metadata changed, such as permissions, ownership, timestamps, or similar attributes.
on_close_write-
A file that had been opened for writing was closed.
This is often useful when an application wants to wait until another program has finished writing a file.
on_close_nowrite-
A file that had not been opened for writing was closed.
on_open-
The file was opened.
on_moved_from-
An entry was moved out of a watched directory.
on_moved_to-
An entry was moved into a watched directory.
on_create-
An entry was created inside a watched directory.
on_delete-
An entry was deleted from a watched directory.
on_delete_self-
The watched object itself was deleted.
on_move_self-
The watched object itself was moved.
EVENT CALLBACK ARGUMENT
Specific callbacks receive one Event object:
on_modify => sub ($event) {
say $event->path;
}
Unlike many other Linux::Event resources, the callback does not receive the parent Inotify object or Watch as its first argument.
The Event can lead back to the logical Watch:
my $watch = $event->watch;
and the Watch can lead back to its parent:
my $inotify = $watch->inotify;
CATCH-ALL CALLBACK
on_event
on_event receives every event selected for that logical Watch:
my $watch = $inotify->watch(
'/srv/data',
on_modify => sub ($event) {
say "modified: " . $event->path;
},
on_event => sub ($event) {
say "raw mask: " . $event->mask;
},
);
Specific callbacks run first.
on_event runs last for the same Event.
Using only on_event
on_event may also be used by itself:
my $watch = $inotify->watch(
'/srv/data',
on_event => sub ($event) {
say $event->path;
},
);
When on_event is the only monitorable callback, Linux::Event requests all ordinary inotify events for that Watch.
When specific monitorable callbacks are also supplied, those callbacks define the kernel event mask and on_event sees the records selected by that mask.
CALLBACK ORDER
One Linux inotify record can contain more than one event bit.
When several callbacks match the same record, Linux::Event invokes them in this order:
on_create
on_open
on_access
on_modify
on_attrib
on_close_write
on_close_nowrite
on_moved_from
on_moved_to
on_move_self
on_delete
on_delete_self
on_unmount
on_ignored
on_event
The same Event object is passed to all callbacks for that logical Watch and kernel record.
If one callback cancels the Watch, later callbacks for that Watch are not run.
MOVE COOKIES
Linux inotify provides a numeric cookie that can associate the two sides of a rename or move.
For example:
on_moved_from => sub ($event) {
say "moved from cookie=" . $event->cookie;
}
on_moved_to => sub ($event) {
say "moved to cookie=" . $event->cookie;
}
Applications that need to pair rename operations can use:
$event->cookie
to correlate related on_moved_from and on_moved_to notifications.
DIRECTORY EVENTS
is_directory
An Event reports whether Linux marked it as referring to a directory:
if ($event->is_directory) {
say $event->path . ' is a directory';
}
This checks the Linux IN_ISDIR event modifier.
WATCH LIFECYCLE EVENTS
Two additional callbacks describe kernel watch lifecycle conditions.
on_unmount
on_unmount => sub ($event) {
...
}
The filesystem containing the watched object was unmounted.
on_ignored
on_ignored => sub ($event) {
...
}
Linux has invalidated the underlying inotify watch.
For example, this may happen when the watched object disappears or the kernel watch is otherwise removed.
on_unmount and on_ignored do not by themselves establish the ordinary event mask.
Use at least one ordinary monitorable callback or on_event when creating the Watch.
CANCELLING ONE WATCH
cancel
watch returns a Watch object:
my $watch = $inotify->watch(
'/srv/data',
on_modify => sub ($event) {
...
},
);
Cancel only that subscription with:
$watch->cancel;
Cancellation is immediate and terminal.
After cancel returns, that Watch receives no later callback.
Calling cancel again is harmless.
WATCH STATE
A Watch can be inspected through its own methods.
path
my $path = $watch->path;
Return the absolute path captured when watch was called.
state
my $state = $watch->state;
Common Watch states include:
pending
active
cancelled
ignored
closed
failed
Managed-fork operations may also produce terminal states such as not_inherited or moved.
is_active
if ($watch->is_active) {
...
}
Return true while the kernel subscription is active.
is_terminal
if ($watch->is_terminal) {
...
}
Return true once that Watch can no longer receive callbacks.
WATCHING BEFORE LOOP ATTACHMENT
An Inotify object can be configured before it is attached:
my $inotify = Linux::Event::Kernel::Inotify->new;
my $watch = $inotify->watch(
'/srv/data',
on_modify => sub ($event) {
...
},
);
$loop->add($inotify);
Before attachment, the Watch is pending.
Linux::Event records the logical Watch but does not create the kernel inotify watch yet.
When the parent attaches to the Loop, its pending watches are activated.
This means you can fully configure the object before it begins receiving filesystem events.
ADDING WATCHES AFTER ATTACHMENT
A running Inotify object can also receive new watches later:
my $inotify = Linux::Event::Kernel::Inotify->new(
loop => $loop,
);
my $watch = $inotify->watch(
'/srv/data',
on_modify => sub ($event) {
...
},
);
Because the parent is already active, watch activates the new kernel subscription before returning successfully.
SEVERAL LOGICAL WATCHES FOR THE SAME OBJECT
Linux itself may represent multiple subscriptions to the same underlying inode with one kernel watch descriptor.
Linux::Event still treats each watch call as its own logical Watch.
For example:
my $logger = $inotify->watch(
'/srv/data',
on_modify => sub ($event) {
log_change($event->path);
},
);
my $counter = $inotify->watch(
'/srv/data',
on_modify => sub ($event) {
$modified++;
},
);
Both logical Watches can receive the same underlying kernel event.
Cancelling one does not automatically cancel the other.
Linux::Event maintains the combined kernel mask required by the surviving logical Watches.
WATCH OPTIONS
Three optional flags may be supplied to watch.
only_dir
only_dir => 1
Require the watched path to be a directory.
This maps to Linux IN_ONLYDIR behavior.
dont_follow
dont_follow => 1
Do not follow a symbolic link when establishing the watch.
This maps to Linux IN_DONT_FOLLOW.
excl_unlink
excl_unlink => 1
Use Linux IN_EXCL_UNLINK behavior.
This can suppress events for directory children after those children have been unlinked.
When several logical Watches resolve to the same underlying kernel watch, excl_unlink must agree between them.
PARENT OVERFLOW CALLBACK
on_overflow
The kernel inotify queue can overflow if filesystem events are produced faster than they can be consumed.
Handle that condition on the parent Inotify object:
my $inotify = Linux::Event::Kernel::Inotify->new(
loop => $loop,
on_overflow => sub ($self) {
rebuild_filesystem_state();
},
);
Queue overflow is significant because some filesystem changes may have been lost.
The safe response is usually to treat cached filesystem state as potentially stale and rebuild or rescan it.
If on_overflow is not provided, Linux::Event throws an exception instead of silently pretending no information was lost.
PARENT ERROR CALLBACK
on_error
Fatal inotify-source errors may be handled with:
my $inotify = Linux::Event::Kernel::Inotify->new(
loop => $loop,
on_error => sub ($self, $error) {
warn "inotify failed: $error";
},
);
After a fatal source error, the parent Inotify object is closed.
APPLICATION DATA
data
The parent Inotify object can retain arbitrary application state:
my $inotify = Linux::Event::Kernel::Inotify->new(
loop => $loop,
data => $state,
);
Retrieve or replace it with:
my $data = $inotify->data;
$inotify->data($new_data);
The parent releases that data when it is closed.
CLOSING THE PARENT
close
$inotify->close;
Closing the parent is different from cancelling one Watch.
close:
removes the Inotify object from its Loop
closes the Linux inotify descriptor
makes every remaining child Watch terminal
discards pending decoded events
releases parent application data
close is idempotent.
After parent close, no Watch can receive another callback.
ZERO WATCHES
An attached Inotify object does not automatically close merely because it has zero Watches.
For example:
my $inotify = Linux::Event::Kernel::Inotify->new(
loop => $loop,
);
may remain active with no current subscriptions.
This allows applications to add Watches dynamically later.
Use:
$inotify->close;
when the whole inotify service is no longer needed.
PARENT STATE
state
my $state = $inotify->state;
Normal parent states include:
unattached
active
closed
Managed-fork operations may also produce not_inherited or moved.
is_active
if ($inotify->is_active) {
...
}
Return true while attached to a Loop.
is_terminal
if ($inotify->is_terminal) {
...
}
Return true once the parent can no longer be used.
watch_count
my $count = $inotify->watch_count;
Return the number of currently retained logical Watch objects.
fd
my $fd = $inotify->fd;
Return the active inotify file descriptor.
It is undefined while detached or after termination.
Most applications do not need this method.
loop
my $loop = $inotify->loop;
Return the owning Loop while the parent remains usable.
EVENT INFORMATION
Every watch callback receives an immutable Event value.
The most useful methods are:
path-
The composed useful path for the event.
name-
The optional child name supplied by Linux for directory events.
watch-
The logical Watch that received the event.
mask-
The raw Linux inotify event mask.
-
The Linux move/rename cookie.
is_directory-
Whether Linux marked the event as referring to a directory.
For example:
on_create => sub ($event) {
say "path: " . $event->path;
if ($event->is_directory) {
say "created object is a directory";
}
}
DISPATCH FAIRNESS
A very busy filesystem can produce many inotify records in one read.
Linux::Event limits one dispatch pass to 256 decoded records.
If more records remain, Linux::Event schedules continuation through:
$loop->defer(...)
This allows sockets, timers, processes, and other ready resources to continue making progress instead of allowing a filesystem burst to monopolize the Loop.
CALLBACK EXCEPTIONS
If a Watch callback throws, dispatch of that current record stops and the exception propagates through the Loop.
Already-read later filesystem records are retained and may be resumed on a later Loop turn.
This prevents one callback exception from silently discarding already-decoded kernel events.
LOOP-AWARE FORKING
Inotify supports clone and move with Linux::Event::Loop managed fork.
clone
my $pid = $loop->fork(
clone => [$inotify],
);
The parent keeps its existing Inotify resource.
The child creates a fresh independent Linux inotify instance and recreates its logical Watches there.
The parent and child then observe filesystem changes independently.
move
my $pid = $loop->fork(
move => [$inotify],
);
The child keeps the inherited inotify instance and attaches it to the child's fresh Loop.
After the child reconstruction succeeds, the parent gives up its copy.
The parent Inotify and its Watch objects become terminal with state moved.
Unlisted Inotify objects
An active Inotify omitted from all disposition lists remains parent-only.
Its inherited child copy is discarded and becomes not_inherited.
share
share is not supported for Inotify.
IMPLEMENTATION MODEL
One Inotify parent owns one Linux inotify file descriptor.
Many logical Watch objects may share underlying kernel watch descriptors when they refer to the same inode.
Linux::Event keeps the logical subscriptions separate and performs fan-out at dispatch time.
This provides independent cancellation and callbacks without requiring the application to understand the kernel's watch-descriptor sharing behavior.
SEE ALSO
Linux::Event, Linux::Event::Loop, Linux::Event::Kernel::Inotify::Watch, Linux::Event::Kernel::Inotify::Event.