This discussion is archived
0 Replies Latest reply: Sep 16, 2010 4:33 PM by 807559 RSS

Track dir removal by any means

807559 Newbie
Currently Being Moderated
Hi Guys,

I'm new (& self-taught) to DTrace and needed to write a program to track a specified dir and find out who/when/how etc if it got removed/renamed etc.

As you can see from the below code, I've been caught by 1 or 2 gotchas during my testing. This is a serious prog, going into production asap, so any comments towards making it better/more robust would be appreciated.

Cheers
Chris
# Desc    : Track dir deletions of specified dir in specified zone.
#           Attempts to handle path issues on cmd and/or dir/dir.
#           Tries to catch any form of removal eg shell cmds:
#           rm, rmdir, unlink, mv and 'internal' code cmds inside Perl, C etc.
#           Note that normally this prog is controlled by
#           dt_dir_removal_mgr.pl, which reads the stdout & stderr,
#           filters false positives etc & logs and emails any alerts.
#           We do not allow the path of the tgt dir to be used,
#           as this may not be specified by the offending user/app...
#           thus we may get some false positives eg a file of the same name.
#           Local zonename is avail from DTrace, but filesystem and inode
#           are not avail from psinfo struct.
#           Not matching on zone because tgt dir can be deleted from global,
#           although the users should not be able to get in there.
usage()
{
    echo "USAGE: dt_dir_removal.sh -d dirname -z zonename

            -d dirname      # dirname to track : must NOT inc path
        eg,
            dt_dir_removal.sh -d testdir
        "
    exit 1
}

# --- Process Arguments ---
#

# Arg supplied ?
if [[ $# -eq 0 ]]
then
    usage
fi

# Check switch value & arg value : see usage()
while getopts d: name
do
    case $name in
    d)  dirname=$(basename $OPTARG) ;;
    *)  usage ;;
    esac
done


#################################
# --- DTrace ---
#
# NB: seem to need the single quotes around the DTrace code ...
# This also means the even the contents of comment blocks CANNOT have single quotes
# in them eg don't, won't etc... (sigh...)
/usr/sbin/dtrace -n  '

 /* Params from shell input */
 inline string DIRNAME  = "'$dirname'";

 #pragma D option quiet
 #pragma D option switchrate=10hz

 /*
  * Print header
  */
 dtrace:::BEGIN
 {
    /* print main headers: We cannot line up final arg hdr exactly
     * because the cmd len varies
     */
    printf("%-20s %-12s %5s  %5s  %6s  %6s  %s -> %s\n",
           "TIME", "ZONE", "GID", "UID", "PID", "PPID", "CMD", "TARGET") ;
 }

 /*
  *  Check exec event type
  */

syscall::unlink:entry
{
    /* Grab the dirname in qn to test later: remove any preceding path */
    /* Experiment seems to indicate unlink will not have this value in the return state ;
     * contrast with rmdir below which may not have it in entry state
     */
    tgt = basename(copyinstr(arg0));
}

/* http://docs.sun.com/app/docs/doc/817-6223/6mlkidlrg?l=en&a=view#indexterm-458 :
 * Avoiding Errors
 * The copyin() and copyinstr() subroutines cannot read from user addresses which have not yet
 * been touched so even a valid address may cause an error if the page containing that address
 * has not yet been faulted in by being accessed.
 * To resolve this issue, wait for kernel or application to use the data before tracing it.
 * For example, you might wait until the system call returns to apply copyinstr()
 */
syscall::rmdir:entry, syscall::rename:entry
{
    /* Try saving a ptr to the relevant value for later, otherwise it gives invalid addr error
     * in return section below
     */
    self->file = arg0;
}

syscall::rmdir:return, syscall::rename:return
{
    /* Grab the dirname in qn to test later: remove any preceding path */
    tgt = basename(copyinstr(self->file));
}

/* Not matching on zone because tgt dir can be deleted from global,
 * although the users should not be able to get in there.
 */
syscall::rmdir:return, syscall::rename:return, syscall::unlink:return
/ DIRNAME == tgt /
{
    /* Print the field values. The TARGET tends not to line up as we
       print the cmd and the target name for completeness. For a shell level cmd,
       we will get the target name in the CMD field as well. For an "internal" cmd,
       eg rmdir() from within perl, the CMD field does not contain the target value.
    */
    printf("%-20Y %-12s %5d  %5d  %6d  %6d  %s -> %s\n",
            walltimestamp, zonename, gid, uid, pid, ppid,
            curpsinfo->pr_psargs, tgt ) ;

    /* Clear the self->file ptr to avoid dynamic variable drop errors */
    self->file = 0;
}

'