#!/usr/bin/perl
use common::sense;
use Linux::Inotify2;
use Getopt::Std;

my $params = &load_from_cli;

(print "Abortamos, hay otro proceso vigilando al directorio ".$params->{base_path}."\n" and exit(1))
    if (&other_process_watching_dir($params->{base_path}));

&run_payload($params->{payload}) if($params->{boot});

my $inotify = Linux::Inotify2->new();

my $flags = IN_CREATE|IN_MODIFY|IN_DELETE|IN_MOVE|IN_CLOSE_WRITE;


sub run_payload {
    my ($payload) = @_;
    system($payload);
}


sub callback {
    my $e = $_[0];

    # cancelamos watcher
    $e->w->cb(sub {});
    #executamos payload
    #print 'Evento '.$e->mask.' en '.$e->fullname." \n";
    &run_payload($params->{payload});
    # reactivamos watcher
    $e->w->cb(\&callback);

    if($e->IN_CREATE && -d $e->fullname ){

        &install_watcher_in_dir_recursive(
            $e->fullname, 
            $inotify, 
            $flags, 
            \&callback
        );
    }

    # Remove watcher
    if($e->IN_DELETE_SELF){
        $e->w->cancel;
    }

};

&install_watcher_in_dir_recursive($params->{base_path}, $inotify, $flags, \&callback);


1 while $inotify->poll;




sub install_watcher_in_dir_recursive {

    my ($path, $inotify, $flags, $callback) = @_;

    $path =~ s/\/+$//;

    $inotify->watch($path, $flags, $callback) || die(
        "Error installing watcher in $path: $!\n". 
        "Probably max_user_watches limit reached. See: 
        '/proc/sys/fs/inotify/max_user_watches'"
    );

    opendir my $d, $path || die("Failed open directory $path:$!");

    while(my $file = readdir($d)){

        next if($file =~ /^\.+$/);

        my $actual_path = "$path/$file";

        if(-d $actual_path){

            #install_watcher
            $inotify->watch($actual_path, $flags, $callback);
        
            &install_watcher_in_dir_recursive($actual_path, $inotify, $flags, $callback);
        }
    
        
    }

}

sub load_from_cli{
    my $self = {};

    # Mergeamos CONF cas opcions pasadas desde a linea de comandos
    my $opts = {};
    
    getopts("d:p:b", $opts) or &main::HELP_MESSAGE();

    $self->{base_path} = $opts->{d};

    &main::HELP_MESSAGE() unless($self->{base_path});

    $self->{payload} = $opts->{p} || 'echo "Event detected in '.$self->{base_path}.'"';

    $self->{boot} = $opts->{b} || undef;

    return $self;

}


sub main::HELP_MESSAGE{
    
    my $msg = <<EOF  

    Usage: $0 -d <path> -p <payload> -b
    
    -d: Path to watch recursively
    -p: Payload to exec when event detected
    -b: Run payload as soon as possible, the first time, before install watchers that can trigger other payload executions

EOF
;
    print $msg;
    exit(0);

}

sub other_process_watching_dir {
    my ($path) = @_;

    opendir my $d, '/proc';

    while(my $proc = readdir($d)){

        next unless($proc =~ /(\d+)/);
        my $pid = $1;

        # evitamos o noso pid
        next if($pid == $$);

        open my $f, '/proc/'.$pid.'/cmdline' || next;
        my $content = <$f>;
        close $f;

        if($content =~ /$0/){

            my $quoted_path = quotemeta($path);

            # no cmdline se separan os campos da linea de comandos por null (\0)
            return 1 if($content =~ /\-d\0$quoted_path/);
        }
    }

    closedir($d);
    
    return undef;

}

1;

__DATA__
