2024-01-08 23:13:17 +01:00
// What is this module called?
module monitor ;
// What does this module require to function?
2016-02-04 15:36:25 +01:00
import core.stdc.errno ;
2020-11-25 20:35:20 +01:00
import core.stdc.stdlib ;
2024-01-08 23:13:17 +01:00
import core.sys.linux.sys.inotify ;
import core.sys.posix.poll ;
import core.sys.posix.unistd ;
import core.sys.posix.sys.select ;
2024-02-12 07:12:20 +01:00
import core.thread ;
2024-01-08 23:13:17 +01:00
import core.time ;
import std.algorithm ;
import std.concurrency ;
import std.exception ;
import std.file ;
import std.path ;
2024-02-13 19:02:48 +01:00
import std.process ;
2024-01-08 23:13:17 +01:00
import std.regex ;
import std.stdio ;
import std.string ;
import std.conv ;
// What other modules that we have created do we need to import?
2017-03-24 22:30:03 +01:00
import config ;
import util ;
2024-01-08 23:13:17 +01:00
import log ;
import clientSideFiltering ;
2015-09-10 23:05:15 +02:00
2024-01-08 23:13:17 +01:00
// Relevant inotify events
2020-05-29 11:30:03 +02:00
private immutable uint32_t mask = IN_CLOSE_WRITE | IN_CREATE | IN_DELETE | IN_MOVE | IN_IGNORED | IN_Q_OVERFLOW ;
2015-09-10 23:05:15 +02:00
2024-01-08 23:13:17 +01:00
class MonitorException : ErrnoException {
@safe this ( string msg , string file = __FILE__ , size_t line = __LINE__ ) {
2015-09-17 00:16:23 +02:00
super ( msg , file , line ) ;
2015-09-10 23:05:15 +02:00
}
}
2024-02-13 19:02:48 +01:00
class MonitorBackgroundWorker {
2015-09-10 23:05:15 +02:00
// inotify file descriptor
2024-01-08 23:13:17 +01:00
int fd ;
2024-02-13 19:02:48 +01:00
Pipe p ;
bool isAlive ;
2024-01-08 23:13:17 +01:00
2024-02-13 19:02:48 +01:00
this ( ) {
isAlive = true ;
p = pipe ( ) ;
}
shared void initialise ( ) {
2024-01-08 23:13:17 +01:00
fd = inotify_init ( ) ;
if ( fd < 0 ) throw new MonitorException ( "inotify_init failed" ) ;
}
// Add this path to be monitored
2024-02-13 19:02:48 +01:00
shared int addInotifyWatch ( string pathname ) {
2024-01-08 23:13:17 +01:00
int wd = inotify_add_watch ( fd , toStringz ( pathname ) , mask ) ;
if ( wd < 0 ) {
if ( errno ( ) = = ENOSPC ) {
// Get the current value
ulong maxInotifyWatches = to ! int ( strip ( readText ( "/proc/sys/fs/inotify/max_user_watches" ) ) ) ;
addLogEntry ( "The user limit on the total number of inotify watches has been reached." ) ;
addLogEntry ( "Your current limit of inotify watches is: " ~ to ! string ( maxInotifyWatches ) ) ;
addLogEntry ( "It is recommended that you change the max number of inotify watches to at least double your existing value." ) ;
addLogEntry ( "To change the current max number of watches to " ~ to ! string ( ( maxInotifyWatches * 2 ) ) ~ " run:" ) ;
addLogEntry ( "EXAMPLE: sudo sysctl fs.inotify.max_user_watches=" ~ to ! string ( ( maxInotifyWatches * 2 ) ) ) ;
}
if ( errno ( ) = = 13 ) {
addLogEntry ( "WARNING: inotify_add_watch failed - permission denied: " ~ pathname , [ "verbose" ] ) ;
}
// Flag any other errors
addLogEntry ( "ERROR: inotify_add_watch failed: " ~ pathname ) ;
return wd ;
}
// Add path to inotify watch - required regardless if a '.folder' or 'folder'
addLogEntry ( "inotify_add_watch successfully added for: " ~ pathname , [ "debug" ] ) ;
// Do we log that we are monitoring this directory?
if ( isDir ( pathname ) ) {
// Log that this is directory is being monitored
addLogEntry ( "Monitoring directory: " ~ pathname , [ "verbose" ] ) ;
}
return wd ;
}
2024-02-13 19:02:48 +01:00
shared int removeInotifyWatch ( int wd ) {
2024-01-08 23:13:17 +01:00
return inotify_rm_watch ( fd , wd ) ;
}
2024-02-13 19:02:48 +01:00
shared void watch ( Tid callerTid ) {
2024-01-08 23:13:17 +01:00
// On failure, send -1 to caller
int res ;
// wait for the caller to be ready
2024-02-13 19:02:48 +01:00
receiveOnly ! int ( ) ;
2024-01-08 23:13:17 +01:00
while ( isAlive ) {
fd_set fds ;
FD_ZERO ( & fds ) ;
FD_SET ( fd , & fds ) ;
2024-02-13 19:02:48 +01:00
// Listen for messages from the caller
FD_SET ( ( cast ( ) p ) . readEnd . fileno , & fds ) ;
2024-01-08 23:13:17 +01:00
res = select ( FD_SETSIZE , & fds , null , null , null ) ;
if ( res = = - 1 ) {
if ( errno ( ) = = EINTR ) {
// Received an interrupt signal but no events are available
2024-02-13 19:02:48 +01:00
// directly watch again
2024-01-08 23:13:17 +01:00
} else {
// Error occurred, tell caller to terminate.
2024-02-13 19:02:48 +01:00
callerTid . send ( - 1 ) ;
2024-01-08 23:13:17 +01:00
break ;
}
} else {
// Wake up caller
2024-02-13 19:02:48 +01:00
callerTid . send ( 1 ) ;
// wait for the caller to be ready
if ( isAlive )
isAlive = receiveOnly ! bool ( ) ;
2024-01-08 23:13:17 +01:00
}
}
}
2024-02-13 19:02:48 +01:00
shared void interrupt ( ) {
isAlive = false ;
( cast ( ) p ) . writeEnd . writeln ( "done" ) ;
( cast ( ) p ) . writeEnd . flush ( ) ;
2024-01-08 23:13:17 +01:00
}
2024-02-13 19:02:48 +01:00
shared void shutdown ( ) {
isAlive = false ;
2024-01-08 23:13:17 +01:00
if ( fd > 0 ) {
close ( fd ) ;
fd = 0 ;
2024-02-13 19:02:48 +01:00
( cast ( ) p ) . close ( ) ;
2024-01-08 23:13:17 +01:00
}
}
}
void startMonitorJob ( shared ( MonitorBackgroundWorker ) worker , Tid callerTid )
{
try {
worker . watch ( callerTid ) ;
} catch ( OwnerTerminated error ) {
// caller is terminated
2024-02-13 19:02:48 +01:00
worker . shutdown ( ) ;
2024-01-08 23:13:17 +01:00
}
}
2024-02-12 07:12:20 +01:00
enum ActionType {
moved ,
deleted ,
changed ,
createDir
}
struct Action {
ActionType type ;
bool skipped ;
string src ;
string dst ;
}
struct ActionHolder {
Action [ ] actions ;
2024-03-12 09:16:17 +01:00
size_t [ string ] srcMap ;
2024-02-12 07:12:20 +01:00
void append ( ActionType type , string src , string dst = null ) {
2024-03-12 09:16:17 +01:00
size_t [ ] pendingTargets ;
2024-02-12 07:12:20 +01:00
switch ( type ) {
case ActionType . changed :
if ( src in srcMap & & actions [ srcMap [ src ] ] . type = = ActionType . changed ) {
// skip duplicate operations
return ;
}
break ;
case ActionType . createDir :
break ;
case ActionType . deleted :
if ( src in srcMap ) {
2024-03-12 09:16:17 +01:00
size_t pendingTarget = srcMap [ src ] ;
2024-02-12 07:12:20 +01:00
// Skip operations require reading local file that is gone
switch ( actions [ pendingTarget ] . type ) {
case ActionType . changed :
case ActionType . createDir :
actions [ srcMap [ src ] ] . skipped = true ;
srcMap . remove ( src ) ;
break ;
default :
break ;
}
}
break ;
case ActionType . moved :
for ( int i = 0 ; i < actions . length ; i + + ) {
// Only match for latest operation
if ( actions [ i ] . src in srcMap ) {
switch ( actions [ i ] . type ) {
case ActionType . changed :
case ActionType . createDir :
// check if the source is the prefix of the target
string prefix = src ~ "/" ;
string target = actions [ i ] . src ;
if ( prefix [ 0 ] ! = '.' )
prefix = "./" ~ prefix ;
if ( target [ 0 ] ! = '.' )
target = "./" ~ target ;
string comm = commonPrefix ( prefix , target ) ;
if ( src = = actions [ i ] . src | | comm . length = = prefix . length ) {
// Hold operations require reading local file that is moved after the target is moved online
pendingTargets ~ = i ;
actions [ i ] . skipped = true ;
srcMap . remove ( actions [ i ] . src ) ;
if ( comm . length = = target . length )
actions [ i ] . src = dst ;
else
actions [ i ] . src = dst ~ target [ comm . length - 1 . . target . length ] ;
}
break ;
default :
break ;
}
}
}
break ;
default :
break ;
}
actions ~ = Action ( type , false , src , dst ) ;
srcMap [ src ] = actions . length - 1 ;
foreach ( pendingTarget ; pendingTargets ) {
actions ~ = actions [ pendingTarget ] ;
actions [ $ - 1 ] . skipped = false ;
srcMap [ actions [ $ - 1 ] . src ] = actions . length - 1 ;
}
}
}
2024-01-08 23:13:17 +01:00
final class Monitor {
// Class variables
ApplicationConfig appConfig ;
ClientSideFiltering selectiveSync ;
// Are we verbose in logging output
bool verbose = false ;
// skip symbolic links
bool skip_symlinks = false ;
// check for .nosync if enabled
bool check_nosync = false ;
// check if initialised
bool initialised = false ;
2024-02-13 19:02:48 +01:00
// Worker Tid
Tid workerTid ;
2024-01-08 23:13:17 +01:00
// Configure Private Class Variables
shared ( MonitorBackgroundWorker ) worker ;
2015-09-17 00:16:23 +02:00
// map every inotify watch descriptor to its directory
private string [ int ] wdToDirName ;
2015-09-10 23:05:15 +02:00
// map the inotify cookies of move_from events to their path
2015-09-11 11:02:07 +02:00
private string [ int ] cookieToPath ;
2015-09-10 23:05:15 +02:00
// buffer to receive the inotify events
private void [ ] buffer ;
2017-03-24 22:30:03 +01:00
2024-01-08 23:13:17 +01:00
// Configure function delegates
2015-09-11 18:33:22 +02:00
void delegate ( string path ) onDirCreated ;
2024-02-12 07:12:20 +01:00
void delegate ( string [ ] path ) onFileChanged ;
2015-09-11 18:33:22 +02:00
void delegate ( string path ) onDelete ;
void delegate ( string from , string to ) onMove ;
2024-01-08 23:13:17 +01:00
2024-01-11 20:01:42 +01:00
// List of paths that were moved, not deleted
bool [ string ] movedNotDeleted ;
2024-02-12 07:12:20 +01:00
ActionHolder actionHolder ;
2024-01-11 20:01:42 +01:00
2024-01-08 23:13:17 +01:00
// Configure the class varaible to consume the application configuration including selective sync
this ( ApplicationConfig appConfig , ClientSideFiltering selectiveSync ) {
this . appConfig = appConfig ;
2017-03-24 22:30:03 +01:00
this . selectiveSync = selectiveSync ;
}
2024-01-08 23:13:17 +01:00
// Initialise the monitor class
void initialise ( ) {
// Configure the variables
skip_symlinks = appConfig . getValueBool ( "skip_symlinks" ) ;
check_nosync = appConfig . getValueBool ( "check_nosync" ) ;
if ( appConfig . getValueLong ( "verbose" ) > 0 ) {
verbose = true ;
}
2018-09-13 00:43:29 +02:00
assert ( onDirCreated & & onFileChanged & & onDelete & & onMove ) ;
2015-09-17 00:16:23 +02:00
if ( ! buffer ) buffer = new void [ 4096 ] ;
2024-02-13 19:02:48 +01:00
worker = cast ( shared ) new MonitorBackgroundWorker ;
2024-01-08 23:13:17 +01:00
worker . initialise ( ) ;
2019-11-02 00:41:18 +01:00
// from which point do we start watching for changes?
string monitorPath ;
2024-01-08 23:13:17 +01:00
if ( appConfig . getValueString ( "single_directory" ) ! = "" ) {
// single directory in use, monitor only this path
monitorPath = "./" ~ appConfig . getValueString ( "single_directory" ) ;
2019-11-02 00:41:18 +01:00
} else {
// default
monitorPath = "." ;
}
addRecursive ( monitorPath ) ;
2024-02-13 19:02:48 +01:00
// Start monitoring
workerTid = spawn ( & startMonitorJob , worker , thisTid ) ;
initialised = true ;
}
// Communication with worker
void send ( bool isAlive ) {
workerTid . send ( isAlive ) ;
2015-09-10 23:05:15 +02:00
}
2024-01-08 23:13:17 +01:00
// Shutdown the monitor class
void shutdown ( ) {
if ( ! initialised )
return ;
2024-02-13 19:02:48 +01:00
initialised = false ;
// Release all resources
removeAll ( ) ;
// Notify the worker that the monitor has been shutdown
worker . interrupt ( ) ;
send ( false ) ;
2015-09-17 00:16:23 +02:00
wdToDirName = null ;
2015-09-10 23:05:15 +02:00
}
2024-01-08 23:13:17 +01:00
// Recursivly add this path to be monitored
private void addRecursive ( string dirname ) {
2018-12-06 10:28:03 +01:00
// skip non existing/disappeared items
if ( ! exists ( dirname ) ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Not adding non-existing/disappeared directory: " ~ dirname , [ "verbose" ] ) ;
2018-12-06 10:28:03 +01:00
return ;
}
2020-07-07 09:39:09 +02:00
// Skip the monitoring of any user filtered items
2017-03-14 17:20:18 +01:00
if ( dirname ! = "." ) {
2020-07-07 09:39:09 +02:00
// Is the directory name a match to a skip_dir entry?
// The path that needs to be checked needs to include the '/'
// This due to if the user has specified in skip_dir an exclusive path: '/path' - that is what must be matched
if ( isDir ( dirname ) ) {
if ( selectiveSync . isDirNameExcluded ( dirname . strip ( '.' ) ) ) {
// dont add a watch for this item
2024-01-08 23:13:17 +01:00
addLogEntry ( "Skipping monitoring due to skip_dir match: " ~ dirname , [ "debug" ] ) ;
2020-07-07 09:39:09 +02:00
return ;
}
2019-03-14 20:55:05 +01:00
}
2020-07-07 09:39:09 +02:00
if ( isFile ( dirname ) ) {
// Is the filename a match to a skip_file entry?
// The path that needs to be checked needs to include the '/'
// This due to if the user has specified in skip_file an exclusive path: '/path/file' - that is what must be matched
if ( selectiveSync . isFileNameExcluded ( dirname . strip ( '.' ) ) ) {
// dont add a watch for this item
2024-01-08 23:13:17 +01:00
addLogEntry ( "Skipping monitoring due to skip_file match: " ~ dirname , [ "debug" ] ) ;
2020-07-07 09:39:09 +02:00
return ;
}
2017-03-14 17:20:18 +01:00
}
2020-04-06 12:05:06 +02:00
// is the path exluded by sync_list?
2019-10-08 08:34:35 +02:00
if ( selectiveSync . isPathExcludedViaSyncList ( buildNormalizedPath ( dirname ) ) ) {
2020-04-06 12:05:06 +02:00
// dont add a watch for this item
2024-01-08 23:13:17 +01:00
addLogEntry ( "Skipping monitoring due to sync_list match: " ~ dirname , [ "debug" ] ) ;
2017-03-14 17:20:18 +01:00
return ;
}
}
2020-04-06 12:05:06 +02:00
2018-08-17 00:03:21 +02:00
// skip symlinks if configured
if ( isSymlink ( dirname ) ) {
// if config says so we skip all symlinked items
if ( skip_symlinks ) {
// dont add a watch for this directory
return ;
}
}
2019-03-02 19:58:36 +01:00
// Do we need to check for .nosync? Only if check_nosync is true
if ( check_nosync ) {
if ( exists ( buildNormalizedPath ( dirname ) ~ "/.nosync" ) ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Skipping watching path - .nosync found & --check-for-nosync enabled: " ~ buildNormalizedPath ( dirname ) , [ "verbose" ] ) ;
return ;
}
}
if ( isDir ( dirname ) ) {
// This is a directory
// is the path exluded if skip_dotfiles configured and path is a .folder?
if ( ( selectiveSync . getSkipDotfiles ( ) ) & & ( isDotFile ( dirname ) ) ) {
// dont add a watch for this directory
2019-03-02 19:58:36 +01:00
return ;
}
}
2020-04-06 12:05:06 +02:00
// passed all potential exclusions
2020-11-25 20:35:20 +01:00
// add inotify watch for this path / directory / file
2024-01-08 23:13:17 +01:00
addLogEntry ( "Calling worker.addInotifyWatch() for this dirname: " ~ dirname , [ "debug" ] ) ;
int wd = worker . addInotifyWatch ( dirname ) ;
if ( wd > 0 ) {
wdToDirName [ wd ] = buildNormalizedPath ( dirname ) ~ "/" ;
}
2020-11-25 20:35:20 +01:00
// if this is a directory, recursivly add this path
if ( isDir ( dirname ) ) {
// try and get all the directory entities for this path
try {
auto pathList = dirEntries ( dirname , SpanMode . shallow , false ) ;
foreach ( DirEntry entry ; pathList ) {
if ( entry . isDir ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Calling addRecursive() for this directory: " ~ entry . name , [ "debug" ] ) ;
2020-11-25 20:35:20 +01:00
addRecursive ( entry . name ) ;
}
}
// catch any error which is generated
} catch ( std . file . FileException e ) {
// Standard filesystem error
2020-12-09 04:18:16 +01:00
displayFileSystemErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2020-11-25 20:35:20 +01:00
return ;
} catch ( Exception e ) {
// Issue #1154 handling
// Need to check for: Failed to stat file in error message
if ( canFind ( e . msg , "Failed to stat file" ) ) {
// File system access issue
2024-01-08 23:13:17 +01:00
addLogEntry ( "ERROR: The local file system returned an error with the following message:" ) ;
addLogEntry ( " Error Message: " ~ e . msg ) ;
addLogEntry ( "ACCESS ERROR: Please check your UID and GID access to this file, as the permissions on this file is preventing this application to read it" ) ;
addLogEntry ( "\nFATAL: Forcing exiting application to avoid deleting data due to local file system access issues\n" ) ;
2020-11-25 20:35:20 +01:00
// Must exit here
exit ( - 1 ) ;
} else {
// some other error
2020-12-09 04:18:16 +01:00
displayFileSystemErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2020-11-25 20:35:20 +01:00
return ;
2019-06-15 01:23:32 +02:00
}
2015-09-17 17:34:58 +02:00
}
2015-09-17 00:16:23 +02:00
}
2015-09-10 23:05:15 +02:00
}
2024-01-08 23:13:17 +01:00
// Remove a watch descriptor
2024-02-13 19:02:48 +01:00
private void removeAll ( ) {
string [ int ] copy = wdToDirName . dup ;
foreach ( wd , path ; copy ) {
remove ( wd ) ;
}
}
2024-01-08 23:13:17 +01:00
private void remove ( int wd ) {
2015-09-17 00:16:23 +02:00
assert ( wd in wdToDirName ) ;
2024-02-13 19:02:48 +01:00
int ret = worker . removeInotifyWatch ( wd ) ;
2018-09-13 00:43:29 +02:00
if ( ret < 0 ) throw new MonitorException ( "inotify_rm_watch failed" ) ;
2024-01-08 23:13:17 +01:00
addLogEntry ( "Monitored directory removed: " ~ to ! string ( wdToDirName [ wd ] ) , [ "verbose" ] ) ;
2015-09-17 00:16:23 +02:00
wdToDirName . remove ( wd ) ;
2015-09-10 23:05:15 +02:00
}
2024-01-08 23:13:17 +01:00
// Remove the watch descriptors associated to the given path
private void remove ( const ( char ) [ ] path ) {
2015-09-19 14:25:39 +02:00
path ~ = "/" ;
foreach ( wd , dirname ; wdToDirName ) {
if ( dirname . startsWith ( path ) ) {
2024-02-13 19:02:48 +01:00
int ret = worker . removeInotifyWatch ( wd ) ;
2018-09-13 00:43:29 +02:00
if ( ret < 0 ) throw new MonitorException ( "inotify_rm_watch failed" ) ;
2015-09-19 14:25:39 +02:00
wdToDirName . remove ( wd ) ;
2024-01-08 23:13:17 +01:00
addLogEntry ( "Monitored directory removed: " ~ dirname , [ "verbose" ] ) ;
2015-09-19 14:25:39 +02:00
}
}
}
2024-01-08 23:13:17 +01:00
// Return the file path from an inotify event
private string getPath ( const ( inotify_event ) * event ) {
2015-09-17 00:16:23 +02:00
string path = wdToDirName [ event . wd ] ;
2015-09-10 23:05:15 +02:00
if ( event . len > 0 ) path ~ = fromStringz ( event . name . ptr ) ;
2024-01-08 23:13:17 +01:00
addLogEntry ( "inotify path event for: " ~ path , [ "debug" ] ) ;
2015-09-10 23:05:15 +02:00
return path ;
}
2024-01-08 23:13:17 +01:00
// Update
void update ( bool useCallbacks = true ) {
if ( ! initialised )
return ;
2018-09-13 00:43:29 +02:00
pollfd fds = {
2024-01-08 23:13:17 +01:00
fd : worker . fd ,
2018-09-13 00:43:29 +02:00
events : POLLIN
} ;
2015-09-10 23:05:15 +02:00
2015-09-17 00:16:23 +02:00
while ( true ) {
2024-02-12 07:12:20 +01:00
bool hasNotification = false ;
2024-02-20 20:45:03 +01:00
int sleep_counter = 0 ;
// Batch events up to 5 seconds
while ( sleep_counter < 5 ) {
2024-02-12 07:12:20 +01:00
int ret = poll ( & fds , 1 , 0 ) ;
if ( ret = = - 1 ) throw new MonitorException ( "poll failed" ) ;
else if ( ret = = 0 ) break ; // no events available
hasNotification = true ;
size_t length = read ( worker . fd , buffer . ptr , buffer . length ) ;
if ( length = = - 1 ) throw new MonitorException ( "read failed" ) ;
int i = 0 ;
while ( i < length ) {
inotify_event * event = cast ( inotify_event * ) & buffer [ i ] ;
string path ;
string evalPath ;
// inotify event debug
addLogEntry ( "inotify event wd: " ~ to ! string ( event . wd ) , [ "debug" ] ) ;
addLogEntry ( "inotify event mask: " ~ to ! string ( event . mask ) , [ "debug" ] ) ;
addLogEntry ( "inotify event cookie: " ~ to ! string ( event . cookie ) , [ "debug" ] ) ;
addLogEntry ( "inotify event len: " ~ to ! string ( event . len ) , [ "debug" ] ) ;
addLogEntry ( "inotify event name: " ~ to ! string ( event . name ) , [ "debug" ] ) ;
// inotify event handling
if ( event . mask & IN_ACCESS ) addLogEntry ( "inotify event flag: IN_ACCESS" , [ "debug" ] ) ;
if ( event . mask & IN_MODIFY ) addLogEntry ( "inotify event flag: IN_MODIFY" , [ "debug" ] ) ;
if ( event . mask & IN_ATTRIB ) addLogEntry ( "inotify event flag: IN_ATTRIB" , [ "debug" ] ) ;
if ( event . mask & IN_CLOSE_WRITE ) addLogEntry ( "inotify event flag: IN_CLOSE_WRITE" , [ "debug" ] ) ;
if ( event . mask & IN_CLOSE_NOWRITE ) addLogEntry ( "inotify event flag: IN_CLOSE_NOWRITE" , [ "debug" ] ) ;
if ( event . mask & IN_MOVED_FROM ) addLogEntry ( "inotify event flag: IN_MOVED_FROM" , [ "debug" ] ) ;
if ( event . mask & IN_MOVED_TO ) addLogEntry ( "inotify event flag: IN_MOVED_TO" , [ "debug" ] ) ;
if ( event . mask & IN_CREATE ) addLogEntry ( "inotify event flag: IN_CREATE" , [ "debug" ] ) ;
if ( event . mask & IN_DELETE ) addLogEntry ( "inotify event flag: IN_DELETE" , [ "debug" ] ) ;
if ( event . mask & IN_DELETE_SELF ) addLogEntry ( "inotify event flag: IN_DELETE_SELF" , [ "debug" ] ) ;
if ( event . mask & IN_MOVE_SELF ) addLogEntry ( "inotify event flag: IN_MOVE_SELF" , [ "debug" ] ) ;
if ( event . mask & IN_UNMOUNT ) addLogEntry ( "inotify event flag: IN_UNMOUNT" , [ "debug" ] ) ;
if ( event . mask & IN_Q_OVERFLOW ) addLogEntry ( "inotify event flag: IN_Q_OVERFLOW" , [ "debug" ] ) ;
if ( event . mask & IN_IGNORED ) addLogEntry ( "inotify event flag: IN_IGNORED" , [ "debug" ] ) ;
if ( event . mask & IN_CLOSE ) addLogEntry ( "inotify event flag: IN_CLOSE" , [ "debug" ] ) ;
if ( event . mask & IN_MOVE ) addLogEntry ( "inotify event flag: IN_MOVE" , [ "debug" ] ) ;
if ( event . mask & IN_ONLYDIR ) addLogEntry ( "inotify event flag: IN_ONLYDIR" , [ "debug" ] ) ;
if ( event . mask & IN_DONT_FOLLOW ) addLogEntry ( "inotify event flag: IN_DONT_FOLLOW" , [ "debug" ] ) ;
if ( event . mask & IN_EXCL_UNLINK ) addLogEntry ( "inotify event flag: IN_EXCL_UNLINK" , [ "debug" ] ) ;
if ( event . mask & IN_MASK_ADD ) addLogEntry ( "inotify event flag: IN_MASK_ADD" , [ "debug" ] ) ;
if ( event . mask & IN_ISDIR ) addLogEntry ( "inotify event flag: IN_ISDIR" , [ "debug" ] ) ;
if ( event . mask & IN_ONESHOT ) addLogEntry ( "inotify event flag: IN_ONESHOT" , [ "debug" ] ) ;
if ( event . mask & IN_ALL_EVENTS ) addLogEntry ( "inotify event flag: IN_ALL_EVENTS" , [ "debug" ] ) ;
// skip events that need to be ignored
if ( event . mask & IN_IGNORED ) {
// forget the directory associated to the watch descriptor
wdToDirName . remove ( event . wd ) ;
2020-07-07 09:39:09 +02:00
goto skip ;
2024-02-12 07:12:20 +01:00
} else if ( event . mask & IN_Q_OVERFLOW ) {
throw new MonitorException ( "inotify overflow, inotify events will be missing" ) ;
2020-07-07 09:39:09 +02:00
}
2024-02-12 07:12:20 +01:00
// if the event is not to be ignored, obtain path
path = getPath ( event ) ;
// configure the skip_dir & skip skip_file comparison item
evalPath = path . strip ( '.' ) ;
// Skip events that should be excluded based on application configuration
// We cant use isDir or isFile as this information is missing from the inotify event itself
// Thus this causes a segfault when attempting to query this - https://github.com/abraunegg/onedrive/issues/995
// Based on the 'type' of event & object type (directory or file) check that path against the 'right' user exclusions
// Directory events should only be compared against skip_dir and file events should only be compared against skip_file
if ( event . mask & IN_ISDIR ) {
// The event in question contains IN_ISDIR event mask, thus highly likely this is an event on a directory
// This due to if the user has specified in skip_dir an exclusive path: '/path' - that is what must be matched
if ( selectiveSync . isDirNameExcluded ( evalPath ) ) {
// The path to evaluate matches a path that the user has configured to skip
goto skip ;
}
} else {
// The event in question missing the IN_ISDIR event mask, thus highly likely this is an event on a file
// This due to if the user has specified in skip_file an exclusive path: '/path/file' - that is what must be matched
if ( selectiveSync . isFileNameExcluded ( evalPath ) ) {
// The path to evaluate matches a file that the user has configured to skip
goto skip ;
}
}
// is the path, excluded via sync_list
if ( selectiveSync . isPathExcludedViaSyncList ( path ) ) {
// The path to evaluate matches a directory or file that the user has configured not to include in the sync
2020-07-07 09:39:09 +02:00
goto skip ;
}
2024-02-12 07:12:20 +01:00
// handle the inotify events
if ( event . mask & IN_MOVED_FROM ) {
addLogEntry ( "event IN_MOVED_FROM: " ~ path , [ "debug" ] ) ;
cookieToPath [ event . cookie ] = path ;
movedNotDeleted [ path ] = true ; // Mark as moved, not deleted
} else if ( event . mask & IN_MOVED_TO ) {
addLogEntry ( "event IN_MOVED_TO: " ~ path , [ "debug" ] ) ;
if ( event . mask & IN_ISDIR ) addRecursive ( path ) ;
auto from = event . cookie in cookieToPath ;
if ( from ) {
cookieToPath . remove ( event . cookie ) ;
if ( useCallbacks ) actionHolder . append ( ActionType . moved , * from , path ) ;
movedNotDeleted . remove ( * from ) ; // Clear moved status
} else {
// Handle file moved in from outside
if ( event . mask & IN_ISDIR ) {
if ( useCallbacks ) actionHolder . append ( ActionType . createDir , path ) ;
} else {
if ( useCallbacks ) actionHolder . append ( ActionType . changed , path ) ;
}
}
} else if ( event . mask & IN_CREATE ) {
addLogEntry ( "event IN_CREATE: " ~ path , [ "debug" ] ) ;
2015-09-17 00:16:23 +02:00
if ( event . mask & IN_ISDIR ) {
2024-02-12 07:12:20 +01:00
addRecursive ( path ) ;
if ( useCallbacks ) actionHolder . append ( ActionType . createDir , path ) ;
}
} else if ( event . mask & IN_DELETE ) {
if ( path in movedNotDeleted ) {
movedNotDeleted . remove ( path ) ; // Ignore delete for moved files
2015-09-17 00:16:23 +02:00
} else {
2024-02-12 07:12:20 +01:00
addLogEntry ( "event IN_DELETE: " ~ path , [ "debug" ] ) ;
if ( useCallbacks ) actionHolder . append ( ActionType . deleted , path ) ;
2015-09-17 00:16:23 +02:00
}
2024-02-12 07:12:20 +01:00
} else if ( ( event . mask & IN_CLOSE_WRITE ) & & ! ( event . mask & IN_ISDIR ) ) {
addLogEntry ( "event IN_CLOSE_WRITE and not IN_ISDIR: " ~ path , [ "debug" ] ) ;
if ( useCallbacks ) actionHolder . append ( ActionType . changed , path ) ;
2024-01-11 20:01:42 +01:00
} else {
2024-02-12 07:12:20 +01:00
addLogEntry ( "event unhandled: " ~ path , [ "debug" ] ) ;
assert ( 0 ) ;
2024-01-11 20:01:42 +01:00
}
2015-09-18 22:56:09 +02:00
2024-02-12 07:12:20 +01:00
skip :
i + = inotify_event . sizeof + event . len ;
}
2024-02-20 20:45:03 +01:00
// Sleep for one second to prevent missing fast-changing events.
if ( poll ( & fds , 1 , 0 ) = = 0 ) {
sleep_counter + = 1 ;
Thread . sleep ( dur ! "seconds" ( 1 ) ) ;
}
2015-09-17 00:16:23 +02:00
}
2024-02-12 07:12:20 +01:00
if ( ! hasNotification ) break ;
processChanges ( ) ;
2024-01-08 23:13:17 +01:00
// Assume that the items moved outside the watched directory have been deleted
2015-09-17 00:16:23 +02:00
foreach ( cookie , path ; cookieToPath ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Deleting cookie|watch (post loop): " ~ path , [ "debug" ] ) ;
2015-10-10 22:18:33 +02:00
if ( useCallbacks ) onDelete ( path ) ;
2015-09-19 14:25:39 +02:00
remove ( path ) ;
2015-09-17 00:16:23 +02:00
cookieToPath . remove ( cookie ) ;
2015-09-10 23:05:15 +02:00
}
2024-01-08 23:13:17 +01:00
// Debug Log that all inotify events are flushed
addLogEntry ( "inotify events flushed" , [ "debug" ] ) ;
2015-09-10 23:05:15 +02:00
}
}
2024-02-13 19:02:48 +01:00
2024-02-12 07:12:20 +01:00
private void processChanges ( ) {
string [ ] changes ;
foreach ( action ; actionHolder . actions ) {
if ( action . skipped )
continue ;
switch ( action . type ) {
case ActionType . changed :
changes ~ = action . src ;
break ;
case ActionType . deleted :
onDelete ( action . src ) ;
break ;
case ActionType . createDir :
onDirCreated ( action . src ) ;
break ;
case ActionType . moved :
onMove ( action . src , action . dst ) ;
break ;
default :
break ;
}
}
if ( ! changes . empty )
onFileChanged ( changes ) ;
object . destroy ( actionHolder ) ;
}
2015-09-10 23:05:15 +02:00
}