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 => 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.