inotify - Filesystem Monitoring and Event-Driven Automation for Raspberry Pi
Introduction
inotify is a powerful Linux kernel subsystem that monitors filesystem events in real-time, enabling responsive, event-driven automation. Instead of polling directories repeatedly to check for changes, inotify notifies your application instantly when files are created, modified, deleted, or accessed.
This technology transforms how you can interact with filesystems:
- Instant Response: React to file changes within milliseconds
- Efficient Monitoring: No CPU-wasting polling loops
- Scalable: Monitor thousands of files with minimal overhead
- Versatile: Track creation, modification, deletion, access, and metadata changes
- Kernel-Level: Built into the Linux kernel for reliability and performance
Common use cases for Raspberry Pi:
- Automatic File Synchronization: Sync files to cloud/NAS immediately after changes
- Hot Reload Development: Automatically restart services when code changes
- Security Monitoring: Detect unauthorized file modifications in real-time
- Media Processing: Auto-convert/compress photos and videos on upload
- Backup Triggers: Incremental backups triggered by file changes
- Log Monitoring: Alert on specific log file patterns
- Configuration Management: Reload services when config files change
- Document Processing: OCR/index documents as they arrive
- Smart Home Automation: Trigger actions based on file events
This comprehensive guide covers:
- inotify Basics: Understanding kernel events and watch descriptors
- Command-Line Tools: inotifywait and inotifywatch for quick automation
- Python Programming: Building robust file monitoring applications
- Bash Scripting: Shell-based automation with inotify-tools
- Advanced Patterns: Recursive monitoring, filtering, and event batching
- Production Deployment: Systemd integration and resource management
- Performance Tuning: Optimizing inotify for Raspberry Pi
- Real-World Projects: Cloud sync, hot reload, security monitoring, media processing
Perfect for:
- DevOps Engineers: Automated deployment and configuration management
- System Administrators: File integrity monitoring and log analysis
- Developers: Hot reload workflows and build automation
- Media Enthusiasts: Automatic photo/video processing pipelines
- Security Professionals: Intrusion detection and audit logging
- Backup Administrators: Event-driven incremental backups
- IoT Developers: Responsive file-based integrations
- Home Automation: Smart home file-triggered actions
Understanding inotify
How inotify Works
| ┌─────────────────────────────────────────────────────────┐
│ User Application │
│ │
│ 1. Create inotify instance │
│ 2. Add watches to files/directories │
│ 3. Read events from inotify file descriptor │
│ 4. Process events and take action │
└────────────────────┬────────────────────────────────────┘
│ syscalls: inotify_init, inotify_add_watch, read
▼
┌─────────────────────────────────────────────────────────┐
│ Linux Kernel │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ inotify Subsystem │ │
│ │ │ │
│ │ Watch List: │ │
│ │ Watch 1: /var/log/syslog │ │
│ │ Watch 2: /home/pi/uploads/ │ │
│ │ Watch 3: /etc/nginx/nginx.conf │ │
│ └──────────────────┬───────────────────────┘ │
│ │ │
│ │ Event notifications │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Filesystem Layer (VFS) │ │
│ │ │ │
│ │ File operations trigger events: │ │
│ │ - open(), close() │ │
│ │ - read(), write() │ │
│ │ - create(), delete() │ │
│ │ - rename(), chmod() │ │
│ └──────────────────┬───────────────────────┘ │
└────────────────────┼────────────────────────────────────┘
│
▼
Actual Filesystem
(ext4, btrfs, tmpfs, etc.)
|
inotify Events
| Event |
Description |
Use Case |
| IN_ACCESS |
File was read |
Log access tracking |
| IN_MODIFY |
File content changed |
Trigger reprocessing |
| IN_ATTRIB |
Metadata changed (permissions, timestamps) |
Security monitoring |
| IN_CLOSE_WRITE |
File opened for writing was closed |
Safe file processing |
| IN_CLOSE_NOWRITE |
File opened read-only was closed |
Read tracking |
| IN_OPEN |
File was opened |
Access logging |
| IN_MOVED_FROM |
File moved out of watched directory |
Track relocations |
| IN_MOVED_TO |
File moved into watched directory |
Process new arrivals |
| IN_CREATE |
File/directory created |
Process new files |
| IN_DELETE |
File/directory deleted |
Cleanup actions |
| IN_DELETE_SELF |
Watched file/directory deleted |
Remove watch |
| IN_MOVE_SELF |
Watched file/directory moved |
Update watch |
Key Concepts
Watch Descriptor (wd):
- Unique integer identifying each watch
- Returned when you add a watch
- Used to identify which file/directory triggered event
Event Queue:
- Kernel buffers events in queue
- Application reads events from queue
- Queue can overflow if not read fast enough
Limitations:
- inotify watches files/directories, not paths
- Moving watched file removes watch
- Only monitors one level (not recursive by default)
- Limited number of watches per user (configurable)
| # Update package list
sudo apt update
# Install inotify-tools (provides inotifywait and inotifywatch)
sudo apt install -y inotify-tools
# Verify installation
inotifywait --version
# Should show: inotifywait 3.x.x
inotifywatch --version
# Should show: inotifywatch 3.x.x
# Check current limits
cat /proc/sys/fs/inotify/max_user_watches
# Default: 8192 (can be increased)
cat /proc/sys/fs/inotify/max_user_instances
# Default: 128
cat /proc/sys/fs/inotify/max_queued_events
# Default: 16384
|
Increasing inotify Limits
| # For systems monitoring many files (e.g., IDE, sync tools)
# Temporarily increase limits
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl fs.inotify.max_user_instances=256
sudo sysctl fs.inotify.max_queued_events=32768
# Make permanent
sudo tee -a /etc/sysctl.conf <<EOF
fs.inotify.max_user_watches=524288
fs.inotify.max_user_instances=256
fs.inotify.max_queued_events=32768
EOF
# Apply changes
sudo sysctl -p
|
inotifywait: Real-Time File Monitoring
Basic Usage
| # Watch a single file
inotifywait /var/log/syslog
# Output when file changes:
# /var/log/syslog MODIFY
# Watch a directory
inotifywait /home/pi/uploads/
# Output when file created:
# /home/pi/uploads/ CREATE newfile.txt
# Monitor continuously (loop after each event)
inotifywait -m /home/pi/uploads/
# This runs indefinitely, reporting each event
|
Event Filtering
| # Watch specific events only
inotifywait -m -e create /home/pi/uploads/
# Only reports file creation
# Multiple events
inotifywait -m -e create,modify,delete /home/pi/uploads/
# All write-related events
inotifywait -m -e close_write /home/pi/uploads/
# Triggers when file writing is complete (safe to process)
# Exclude events
inotifywait -m --exclude '.*\.tmp$' /home/pi/uploads/
# Ignore .tmp files
|
| # Custom format
inotifywait -m --format '%T %w %f %e' --timefmt '%Y-%m-%d %H:%M:%S' /home/pi/uploads/
# Output example:
# 2025-11-23 14:30:45 /home/pi/uploads/ newfile.txt CREATE
# 2025-11-23 14:30:46 /home/pi/uploads/ newfile.txt MODIFY
# 2025-11-23 14:30:47 /home/pi/uploads/ newfile.txt CLOSE_WRITE,CLOSE
# Format specifiers:
# %w - Watched directory
# %f - Filename
# %e - Event name
# %T - Time (with --timefmt)
# CSV format for logging
inotifywait -m --csv --format '%w,%f,%e,%T' --timefmt '%s' /home/pi/uploads/
|
Recursive Monitoring
| # Monitor directory and all subdirectories
inotifywait -m -r /home/pi/projects/
# Recursively with specific events
inotifywait -m -r -e modify /home/pi/projects/
# Exclude patterns
inotifywait -m -r --exclude '(\.git|node_modules|__pycache__)' /home/pi/projects/
# Ignore version control and build directories
|
Practical Automation Scripts
Auto-Sync to Cloud Storage
| #!/bin/bash
# auto-sync-rclone.sh - Automatically sync changed files to cloud
WATCH_DIR="/home/pi/Documents"
REMOTE="gdrive:Backup/Documents"
LOG_FILE="/var/log/auto-sync.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Starting auto-sync for $WATCH_DIR -> $REMOTE"
inotifywait -m -r -e close_write,moved_to,create "$WATCH_DIR" --format '%w%f' |
while read -r CHANGED_FILE; do
# Wait a moment to let file finish writing
sleep 1
# Calculate relative path
REL_PATH="${CHANGED_FILE#$WATCH_DIR/}"
log "Change detected: $REL_PATH"
# Sync the specific file
if rclone copy "$CHANGED_FILE" "$REMOTE/$(dirname "$REL_PATH")" -v >> "$LOG_FILE" 2>&1; then
log "✓ Synced: $REL_PATH"
else
log "✗ Failed to sync: $REL_PATH"
fi
done
|
Run as systemd service:
| # /etc/systemd/system/auto-sync.service
[Unit]
Description=Auto-sync files to cloud storage
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=pi
ExecStart=/usr/local/bin/auto-sync-rclone.sh
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
|
Hot Reload for Development
| #!/bin/bash
# hot-reload-webserver.sh - Restart web server when files change
WATCH_DIR="/home/pi/website"
SERVICE_NAME="nginx"
echo "Watching $WATCH_DIR for changes..."
echo "Will reload $SERVICE_NAME on modifications"
inotifywait -m -r -e close_write --format '%w%f' \
--exclude '(\.git|\.swp|\.tmp)' \
"$WATCH_DIR" |
while read -r CHANGED_FILE; do
echo "[$(date '+%H:%M:%S')] Changed: $CHANGED_FILE"
# Debounce - wait for multiple rapid changes
sleep 0.5
# Test configuration before reloading
if sudo nginx -t 2>/dev/null; then
echo "Configuration valid, reloading..."
sudo systemctl reload nginx
echo "✓ Reloaded $SERVICE_NAME"
else
echo "✗ Configuration error, not reloading"
sudo nginx -t
fi
done
|
| #!/bin/bash
# auto-process-photos.sh - Convert and optimize photos on upload
WATCH_DIR="/home/pi/camera-uploads"
OUTPUT_DIR="/home/pi/processed-photos"
ARCHIVE_DIR="/home/pi/archive"
mkdir -p "$OUTPUT_DIR" "$ARCHIVE_DIR"
echo "Watching for new photos in $WATCH_DIR"
inotifywait -m -e close_write --format '%w%f' \
"$WATCH_DIR" |
while read -r FILE; do
# Only process image files
if [[ $FILE =~ \.(jpg|jpeg|png|raw)$ ]]; then
FILENAME=$(basename "$FILE")
BASENAME="${FILENAME%.*}"
echo "Processing: $FILENAME"
# 1. Resize to 1920px wide (preserve aspect ratio)
convert "$FILE" -resize 1920x\> "$OUTPUT_DIR/${BASENAME}_resized.jpg"
# 2. Create thumbnail (300px wide)
convert "$FILE" -resize 300x\> "$OUTPUT_DIR/${BASENAME}_thumb.jpg"
# 3. Extract EXIF data
exiftool "$FILE" > "$OUTPUT_DIR/${BASENAME}_exif.txt"
# 4. Optimize JPEGs
jpegoptim --max=85 --strip-all "$OUTPUT_DIR/${BASENAME}_resized.jpg"
# 5. Move original to archive
mv "$FILE" "$ARCHIVE_DIR/"
echo "✓ Processed: $FILENAME"
echo " - Resized: ${BASENAME}_resized.jpg"
echo " - Thumbnail: ${BASENAME}_thumb.jpg"
echo " - Archived: $ARCHIVE_DIR/$FILENAME"
fi
done
|
Security File Integrity Monitor
| #!/bin/bash
# file-integrity-monitor.sh - Detect unauthorized file changes
WATCH_DIRS=(
"/etc"
"/usr/bin"
"/usr/sbin"
"/boot"
"/root"
)
LOG_FILE="/var/log/file-integrity.log"
ALERT_EMAIL="admin@example.com"
log_alert() {
local SEVERITY=$1
local MESSAGE=$2
local TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] [$SEVERITY] $MESSAGE" | tee -a "$LOG_FILE"
# Send email alert for critical events
if [ "$SEVERITY" == "CRITICAL" ]; then
echo "$MESSAGE" | mail -s "CRITICAL: File Integrity Alert" "$ALERT_EMAIL"
fi
}
# Calculate file hash
get_hash() {
sha256sum "$1" 2>/dev/null | awk '{print $1}'
}
log_alert "INFO" "File integrity monitoring started"
# Monitor all specified directories
inotifywait -m -r -e modify,attrib,delete,create,move \
--format '%e %w%f' \
"${WATCH_DIRS[@]}" |
while read -r EVENT FILE; do
case $EVENT in
MODIFY)
HASH=$(get_hash "$FILE")
log_alert "WARNING" "File modified: $FILE (SHA256: $HASH)"
;;
ATTRIB)
PERMS=$(stat -c '%a' "$FILE" 2>/dev/null)
OWNER=$(stat -c '%U:%G' "$FILE" 2>/dev/null)
log_alert "WARNING" "Attributes changed: $FILE (Perms: $PERMS, Owner: $OWNER)"
;;
DELETE)
log_alert "CRITICAL" "File deleted: $FILE"
;;
CREATE)
log_alert "WARNING" "File created: $FILE"
;;
MOVED_TO|MOVED_FROM)
log_alert "WARNING" "File moved: $FILE"
;;
esac
done
|
Automatic Backup Trigger
| #!/bin/bash
# incremental-backup-trigger.sh - Trigger backups on file changes
WATCH_DIR="/home/pi/important-data"
BACKUP_SCRIPT="/usr/local/bin/backup.sh"
MIN_INTERVAL=300 # Minimum 5 minutes between backups
LAST_BACKUP=0
echo "Monitoring $WATCH_DIR for changes"
echo "Will trigger backup after modifications (max every $MIN_INTERVAL seconds)"
inotifywait -m -r -e close_write,delete,moved_to \
--exclude '(\.tmp|\.swp|~)$' \
"$WATCH_DIR" |
while read -r DIR EVENT FILE; do
CURRENT_TIME=$(date +%s)
TIME_SINCE_LAST=$((CURRENT_TIME - LAST_BACKUP))
if [ $TIME_SINCE_LAST -ge $MIN_INTERVAL ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Change detected: $FILE ($EVENT)"
echo "Triggering backup..."
if $BACKUP_SCRIPT; then
LAST_BACKUP=$CURRENT_TIME
echo "✓ Backup completed"
else
echo "✗ Backup failed"
fi
else
REMAINING=$((MIN_INTERVAL - TIME_SINCE_LAST))
echo "Change detected but waiting ${REMAINING}s before next backup"
fi
done
|
Python Programming with inotify
Using pyinotify Library
| # Install pyinotify
pip3 install pyinotify
|
Basic Example:
| #!/usr/bin/env python3
# basic-file-monitor.py
import pyinotify
class EventHandler(pyinotify.ProcessEvent):
def process_IN_CREATE(self, event):
print(f"Created: {event.pathname}")
def process_IN_DELETE(self, event):
print(f"Deleted: {event.pathname}")
def process_IN_MODIFY(self, event):
print(f"Modified: {event.pathname}")
def process_IN_CLOSE_WRITE(self, event):
print(f"File closed after writing: {event.pathname}")
# Watch manager
wm = pyinotify.WatchManager()
# Event mask
mask = pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY | pyinotify.IN_CLOSE_WRITE
# Event handler
handler = EventHandler()
# Notifier
notifier = pyinotify.Notifier(wm, handler)
# Add watch
wm.add_watch('/home/pi/watched', mask, rec=True, auto_add=True)
print("Monitoring /home/pi/watched (Ctrl+C to stop)")
# Start monitoring
notifier.loop()
|
Advanced: Multi-Directory Monitor with Filtering
| #!/usr/bin/env python3
# advanced-monitor.py
import pyinotify
import os
import logging
from pathlib import Path
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/file-monitor.log'),
logging.StreamHandler()
]
)
class SmartFileHandler(pyinotify.ProcessEvent):
def __init__(self, config):
self.config = config
self.ignored_extensions = config.get('ignored_extensions', [])
self.ignored_patterns = config.get('ignored_patterns', [])
def should_ignore(self, path):
"""Check if file should be ignored"""
# Ignore hidden files
if os.path.basename(path).startswith('.'):
return True
# Ignore by extension
for ext in self.ignored_extensions:
if path.endswith(ext):
return True
# Ignore by pattern
for pattern in self.ignored_patterns:
if pattern in path:
return True
return False
def process_IN_CLOSE_WRITE(self, event):
if self.should_ignore(event.pathname):
return
logging.info(f"File saved: {event.pathname}")
# Trigger custom actions based on file type
if event.pathname.endswith('.py'):
self.handle_python_file(event.pathname)
elif event.pathname.endswith(('.jpg', '.png', '.gif')):
self.handle_image_file(event.pathname)
elif event.pathname.endswith('.log'):
self.handle_log_file(event.pathname)
def process_IN_CREATE(self, event):
if event.is_dir:
logging.info(f"Directory created: {event.pathname}")
elif not self.should_ignore(event.pathname):
logging.info(f"File created: {event.pathname}")
def process_IN_DELETE(self, event):
if not event.is_dir and not self.should_ignore(event.pathname):
logging.warning(f"File deleted: {event.pathname}")
def handle_python_file(self, path):
"""Handle Python file changes"""
logging.info(f"Python file detected: {path}")
# Could run linter, tests, etc.
# os.system(f"pylint {path}")
def handle_image_file(self, path):
"""Handle image file changes"""
logging.info(f"Image file detected: {path}")
# Could trigger image processing
# subprocess.run(['convert', path, '-resize', '1920x', output])
def handle_log_file(self, path):
"""Handle log file changes"""
# Could parse logs, send alerts, etc.
pass
# Configuration
config = {
'watched_directories': [
'/home/pi/projects',
'/var/log/apps',
'/home/pi/uploads'
],
'ignored_extensions': ['.tmp', '.swp', '.pyc', '~'],
'ignored_patterns': ['__pycache__', '.git', 'node_modules']
}
# Setup inotify
wm = pyinotify.WatchManager()
handler = SmartFileHandler(config)
notifier = pyinotify.Notifier(wm, handler)
# Event mask
mask = (pyinotify.IN_CLOSE_WRITE |
pyinotify.IN_CREATE |
pyinotify.IN_DELETE)
# Add watches
for directory in config['watched_directories']:
if os.path.exists(directory):
wm.add_watch(directory, mask, rec=True, auto_add=True)
logging.info(f"Watching: {directory}")
else:
logging.warning(f"Directory not found: {directory}")
logging.info("File monitoring started")
try:
notifier.loop()
except KeyboardInterrupt:
logging.info("File monitoring stopped")
|
Production-Ready Monitor with Error Handling
| #!/usr/bin/env python3
# production-file-monitor.py
import pyinotify
import sys
import signal
import logging
import queue
import threading
from datetime import datetime
class FileEventQueue:
"""Thread-safe event queue for batch processing"""
def __init__(self, batch_size=10, batch_timeout=5):
self.queue = queue.Queue()
self.batch_size = batch_size
self.batch_timeout = batch_timeout
self.running = True
def add_event(self, event):
self.queue.put(event)
def process_worker(self):
"""Background worker to process events in batches"""
batch = []
while self.running:
try:
# Wait for event with timeout
event = self.queue.get(timeout=self.batch_timeout)
batch.append(event)
# Process when batch is full
if len(batch) >= self.batch_size:
self.process_batch(batch)
batch = []
except queue.Empty:
# Timeout - process accumulated events
if batch:
self.process_batch(batch)
batch = []
def process_batch(self, events):
"""Process a batch of events"""
logging.info(f"Processing batch of {len(events)} events")
for event in events:
logging.debug(f" - {event['type']}: {event['path']}")
# Here you could:
# - Batch upload to cloud
# - Aggregate statistics
# - Send batch notifications
# - Update database in one transaction
def stop(self):
self.running = False
class RobustEventHandler(pyinotify.ProcessEvent):
def __init__(self, event_queue):
self.event_queue = event_queue
self.stats = {
'created': 0,
'modified': 0,
'deleted': 0,
'errors': 0
}
def process_default(self, event):
"""Catch-all for all events"""
try:
event_data = {
'type': event.maskname,
'path': event.pathname,
'is_dir': event.dir,
'timestamp': datetime.now().isoformat()
}
self.event_queue.add_event(event_data)
# Update statistics
if 'CREATE' in event.maskname:
self.stats['created'] += 1
elif 'MODIFY' in event.maskname or 'CLOSE_WRITE' in event.maskname:
self.stats['modified'] += 1
elif 'DELETE' in event.maskname:
self.stats['deleted'] += 1
except Exception as e:
logging.error(f"Error processing event: {e}")
self.stats['errors'] += 1
def get_stats(self):
return self.stats.copy()
# Setup
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# Event queue
event_queue = FileEventQueue(batch_size=10, batch_timeout=5)
# Start processing worker thread
worker_thread = threading.Thread(target=event_queue.process_worker, daemon=True)
worker_thread.start()
# inotify setup
wm = pyinotify.WatchManager()
handler = RobustEventHandler(event_queue)
notifier = pyinotify.Notifier(wm, handler)
# Graceful shutdown
def signal_handler(signum, frame):
logging.info("Shutting down...")
event_queue.stop()
notifier.stop()
stats = handler.get_stats()
logging.info(f"Final statistics: {stats}")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# Add watches
mask = pyinotify.ALL_EVENTS
wm.add_watch('/home/pi/monitored', mask, rec=True, auto_add=True)
logging.info("Production file monitor started")
# Main loop
notifier.loop()
|
inotifywatch: Statistics Collection
| # Collect statistics on file access
inotifywatch -v -t 60 -r /home/pi/projects
# Output after 60 seconds:
# Establishing watches...
# Finished establishing watches, now collecting statistics.
#
# total modify close_write create delete filename
# 45 23 15 5 2 /home/pi/projects
# Monitor specific events
inotifywatch -e modify,delete -t 30 /var/log/
# CSV output
inotifywatch -c -t 60 /home/pi/
|
Efficient Recursive Monitoring
| #!/bin/bash
# efficient-recursive-watch.sh
WATCH_ROOT="/home/pi/large-directory"
# Problem: Adding thousands of watches can be slow and memory-intensive
# Solution: Use excludes to skip unnecessary directories
inotifywait -m -r \
--exclude '(\.git|node_modules|__pycache__|\.cache|build|dist|\.venv)' \
--format '%w%f %e' \
"$WATCH_ROOT"
|
Event Batching to Reduce CPU
| #!/bin/bash
# batch-processing.sh - Process events in batches
WATCH_DIR="/home/pi/uploads"
BATCH_SIZE=10
BATCH_TIMEOUT=5
declare -a EVENT_BATCH=()
process_batch() {
# shellcheck disable=SC2128
if [ "$EVENT_BATCH" = "" ]; then
return
fi
# shellcheck disable=SC2128
echo "Processing batch of files"
# Process all files at once (more efficient)
for file in "${EVENT_BATCH[@]}"; do
echo " - $file"
# Your processing here
done
# Clear batch
EVENT_BATCH=()
}
# Timeout handler
trap 'process_batch' ALRM
inotifywait -m -e close_write --format '%w%f' "$WATCH_DIR" |
while read -r FILE; do
EVENT_BATCH+=("$FILE")
# Process when batch is full (check array length differently)
if [ "${EVENT_BATCH[*]}" != "" ]; then
COUNT=0
for _ in "${EVENT_BATCH[@]}"; do
COUNT=$((COUNT + 1))
done
if [ $COUNT -ge $BATCH_SIZE ]; then
process_batch
fi
fi
# Also set timeout to process smaller batches
( sleep $BATCH_TIMEOUT && kill -ALRM $$ ) &
done
|
Memory-Efficient Large Directory Monitoring
| #!/usr/bin/env python3
# memory-efficient-monitor.py
import pyinotify
import os
class MemoryEfficientHandler(pyinotify.ProcessEvent):
"""Process events immediately without buffering"""
def process_default(self, event):
# Process immediately and discard
print(f"{event.maskname}: {event.pathname}")
# Don't store event data
# Don't accumulate statistics (unless essential)
# Use minimal event mask
mask = pyinotify.IN_CLOSE_WRITE # Only what you need
wm = pyinotify.WatchManager()
handler = MemoryEfficientHandler()
# Smaller event queue
notifier = pyinotify.Notifier(wm, handler, default_proc_fun=handler, max_queued_events=1024)
wm.add_watch('/path/to/large/directory', mask, rec=True, auto_add=True)
notifier.loop()
|
Troubleshooting
Check Current Watches
| # List all watches for all processes
sudo cat /proc/*/fd/* 2>/dev/null | grep inotify | wc -l
# Find processes using inotify
for pid in /proc/[0-9]*; do
if grep -q inotify "$pid/fd/"* 2>/dev/null; then
echo "PID $(basename $pid): $(cat $pid/comm 2>/dev/null)"
ls -l $pid/fd/ 2>/dev/null | grep inotify | wc -l
fi
done
# Check limit reached
dmesg | grep -i inotify
|
"Too many open files" Error
| # Increase limits temporarily
sudo sysctl fs.inotify.max_user_watches=524288
# Make permanent
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# Check current usage
find /proc/*/fd -lname anon_inode:inotify 2>/dev/null | wc -l
|
Events Not Triggering
| # Test if inotify works on filesystem
cd /tmp
touch test.txt
inotifywait -e modify test.txt &
echo "test" >> test.txt
# Should show event
# Some filesystems don't support inotify:
# - NFS (older versions)
# - FUSE (some implementations)
# - Network mounts (SMB, CIFS)
# Check filesystem type
df -T /path/to/watch
|
High CPU Usage
| # Reduce event frequency
# Use IN_CLOSE_WRITE instead of IN_MODIFY
# (IN_MODIFY fires on every write(), IN_CLOSE_WRITE only when file closed)
# Bad (too many events):
inotifywait -m -e modify /var/log/syslog
# Good (fewer events):
inotifywait -m -e close_write /var/log/syslog
# Add exclusions
inotifywait -m --exclude '\.tmp$' /path
# Use event batching in scripts
|
Real-World Project: Cloud Photo Sync
| #!/bin/bash
# cloud-photo-sync.sh - Complete photo management pipeline
set -euo pipefail
# Configuration
CAMERA_IMPORT="/home/pi/camera-import"
PROCESSED_DIR="/home/pi/photos/processed"
CLOUD_REMOTE="gdrive:Photos"
ARCHIVE_DIR="/mnt/nas/photo-archive"
LOG_FILE="/var/log/photo-sync.log"
# Ensure directories exist
mkdir -p "$CAMERA_IMPORT" "$PROCESSED_DIR" "$ARCHIVE_DIR"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
process_photo() {
local SRC=$1
local FILENAME=$(basename "$SRC")
local BASENAME="${FILENAME%.*}"
local EXT="${FILENAME##*.}"
# Extract date from EXIF
local PHOTO_DATE=$(exiftool -DateTimeOriginal -d "%Y-%m-%d" "$SRC" 2>/dev/null | awk -F': ' '{print $2}')
if [ -z "$PHOTO_DATE" ]; then
PHOTO_DATE=$(date -r "$SRC" '+%Y-%m-%d')
fi
# Create date-based directory
local YEAR=$(echo $PHOTO_DATE | cut -d'-' -f1)
local MONTH=$(echo $PHOTO_DATE | cut -d'-' -f2)
local DEST_DIR="$PROCESSED_DIR/$YEAR/$MONTH"
mkdir -p "$DEST_DIR"
# Process image
log "Processing: $FILENAME (Date: $PHOTO_DATE)"
# 1. Copy original
cp "$SRC" "$DEST_DIR/"
# 2. Create web-optimized version
convert "$SRC" \
-auto-orient \
-resize 2048x2048\> \
-quality 85 \
-strip \
"$DEST_DIR/${BASENAME}_web.jpg"
# 3. Create thumbnail
convert "$SRC" \
-auto-orient \
-resize 400x400^ \
-gravity center \
-extent 400x400 \
-quality 80 \
"$DEST_DIR/${BASENAME}_thumb.jpg"
# 4. Upload to cloud
log "Uploading to cloud: $YEAR/$MONTH/$FILENAME"
rclone copy "$DEST_DIR/" "$CLOUD_REMOTE/$YEAR/$MONTH/" -v --transfers 1
# 5. Archive original to NAS
ARCHIVE_PATH="$ARCHIVE_DIR/$YEAR/$MONTH"
mkdir -p "$ARCHIVE_PATH"
mv "$SRC" "$ARCHIVE_PATH/"
log "✓ Completed: $FILENAME"
}
# Start monitoring
log "Photo sync service started"
log "Watching: $CAMERA_IMPORT"
inotifywait -m -e close_write \
--format '%w%f' \
"$CAMERA_IMPORT" |
while read -r PHOTO; do
# Only process image files
if [[ $PHOTO =~ \.(jpg|jpeg|png|raw|cr2|nef|arw)$ ]]; then
# Process in background to avoid blocking
process_photo "$PHOTO" &
else
log "Ignored non-image file: $(basename $PHOTO)"
fi
done
|
Systemd service:
| # /etc/systemd/system/photo-sync.service
[Unit]
Description=Automatic Photo Processing and Cloud Sync
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=pi
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
ExecStart=/usr/local/bin/cloud-photo-sync.sh
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
|
Summary
This comprehensive guide covered inotify filesystem monitoring on Raspberry Pi:
✅ Core Concepts
- inotify kernel subsystem architecture
- Event types and watch descriptors
- Event queue management
- Filesystem limitations and considerations
- inotifywait: Real-time event monitoring with filtering
- inotifywatch: Statistical analysis of file access
- Custom output formatting and CSV export
- Recursive monitoring with exclusions
✅ Practical Automation
- Auto-sync: Cloud storage synchronization on file changes
- Hot reload: Development server restart on code changes
- Media processing: Automatic photo/video conversion pipeline
- Security monitoring: File integrity detection
- Backup triggers: Event-driven incremental backups
✅ Python Programming
- pyinotify library integration
- Advanced filtering and event handling
- Multi-directory monitoring
- Thread-safe event queuing
- Production-ready error handling
- Efficient recursive monitoring with exclusions
- Event batching to reduce CPU usage
- Memory-efficient large directory handling
- Smart event filtering (IN_CLOSE_WRITE vs IN_MODIFY)
✅ Troubleshooting
- Watch limit management (max_user_watches)
- Filesystem compatibility checks
- CPU usage optimization
- Event debugging techniques
✅ Production Deployment
- Systemd service integration
- Logging and error handling
- Graceful shutdown handling
- Resource monitoring
✅ Real-World Projects
- Complete cloud photo sync pipeline with EXIF processing
- Multi-stage media conversion workflows
- Security monitoring with alerting
- Automated backup systems
Next Steps
Advanced Topics:
- fanotify: More advanced filesystem monitoring (requires root)
- auditd Integration: Combine with Linux audit framework
- eBPF Filesystem Monitoring: Modern kernel-level tracing
- Distributed Monitoring: Aggregate events across multiple Pis
- AI-Powered Classification: Use ML to auto-categorize files
Related Guides:
With inotify, your Raspberry Pi can respond instantly to filesystem changes, enabling sophisticated automation workflows that would be impossible with traditional polling approaches!