#!/bin/sh
# fetch-footprint.sh
#
# This script identifies all the files that were accessed/modified between a start time and an end time
# Check out the options to see how this script selects start and end times
#
# Usage:
#       $ fetch-footprint.sh [-a|-b|-f]  <Options> <Display Format> <Start & End time options> <Directory>
#
# Copywrite (c) 2009 Wind River Systems, Alameda

version=1.0

total_blocks=0
total_files=0
total_size=0
curr_path=.
include_dirs=0
is_busybox=false

# Flag that indicates that statistics should be captured
stats_flag=0

# Flags for display_flag
all_files=1
boot_time_touched_files=2
display_flag=0

# Options for TIME_FORMAT
general=0
input_format=1
total_file_xml=2
all_times=3

time_format=$general

s_flag=false
e_flag=false

before_execution=$(date +%s)
start_time=0
end_time=$(date +"%s")
   
############## checks to see if it is a busybox file system or not
checkBusyBox(){
    ST=$(getDat $(grep btime /proc/stat|cut -d' ' -f 2) 2>/dev/null)
    if [ "$ST" = "" ] ; then
	is_busybox=true
    fi
}
   
############## Function that displays the command line options
showUsage(){
    checkBusyBox
    echo
    echo " fetch-footprint assists in identifying files that were accessed/modified during a given time interval"
    echo " Usage: fetch-footprint [OPTION] [FORMAT] <directory>"
    echo 
    echo " Options:"
    echo "   [Mandatory - use one]"
    echo "      -a     Display all files"
    echo "      -b     Select files accessed/Modified since last boot time"
    echo "      -f     Display all files whose accessed/modified timestamps is in the future"
    echo "   [Optional]"
    echo "      -s     Print disk usage statistics"
    echo "      -d     Include directories also"
    if [ $is_busybox = false ] ; then
	echo "      -S     Start date should follow this option <yyyy-mm-dd@HH:MM:SS>" 
	echo "      -E     End date should follow this option <yyyy-mm-dd@HH:MM:SS>"
	echo "             Note: Start Date can not be greater than the end date"
    fi
    echo 
    echo " Format:     [Optional]"  
    echo "      -I     Prints files in a format that fetch_host_footprint understands [Execute from the target's /]"
    echo "      -R     Displays accessed & modified timestamps "
    echo "      -X     Displays the data in an XML format"
    if [ $is_busybox = false ] ; then
	echo "      -T     Display all timestamps in seconds from Epoch [Busybox mode]"
    fi
    echo " Usage Scenarios:"
    echo 
    echo "       Display timestamps of files ONLY                   \`fetch-footprint.sh -a\`"
    echo
    echo "       Display timestamps of files & directories          \`fetch-footprint.sh -a -d\`"
    echo 
    echo "       Display Files AND Directories inside               \`fetch-footprint.sh -b -d ../tempDir/ -R\`"
    echo "       ../tempDir/ directory that were accessed"
    echo "       or modified since boottime" 
    echo 
    echo "       List all files in the input mode                   \`fetch-footprint.sh -a -d ../../ -s -I\`"
    echo "          [Execute this from target's /]"
    echo 
    if [ $is_busybox = false ] ; then
	echo "       Display files between timestamps                   \`fetch-footprint.sh -a -S 2009-05-29@21:49:01 -E 2009-05-30@02:50:01 -s\`"
    fi
    echo 
    echo " (c) WindRiver Systems "
    echo 
}

############## Verify the arguments
verifyArgumentUsage() {
    if [ $stats_flag -gt 1 ] ; then
       	echo Invalid arguments
	showUsage
	exit
    fi

    if [ $include_dirs -gt 1 ] ; then
       	echo Invalid arguments
	showUsage
	exit
    fi

    if [ $start_time -gt $end_time ] ; then
    	echo ERROR: Start time is greater than end time
	exit
    fi
}

############## Displays the preamble 
preamble() {
    echo 
    echo "fetch-footprint.sh [Version $version]"
    echo "(c) Wind River Systems"
    echo
}

############## Arguments $start_time $end_time $time_format
printFooter()
{
    if [ $3 = $total_file_xml ] ; then
	echo "     </change_list>"
	echo "</layout_change_list>"
	echo
	return
    fi 2>/dev/null
    
    if [ $3 = "$input_format" ] ; then 
	return
    fi
    
    after_execution=$(date +%s)
    elapsed_secs=$(expr $after_execution - $before_execution)
    if [ $is_busybox = false ] ; then
	echo Elapsed Time: $(date -d "1970-01-01 $elapsed_secs sec" +%H:%M:%S)
    else
	echo Elapsed Time: $elapsed_secs sec
    fi
    echo "File Count:   $total_files"
    
    if [ $stats_flag -eq 1 ]; then
	total_size=$(($total_size/1000))
	echo "Total size:   $total_size Kb     [Accumulated File Size]" 
	echo "Foot Print:   $total_blocks Kb     [Accumulated 1K Blocks]" 
    fi

}


############## Arguments $start_time $end_time $time_format
printHeader() {

    # Print the XML header
    if [ "$3" = "$total_file_xml" ] ; then
	echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
	echo "<layout_change_list version=\"1\">"
	echo "     <change_list>"
	return
    fi
    
    if [ $is_busybox = false ] ; then
	case $3 in
	    $general) preamble; echo -n "   Action          Timestamp          ";;
	    $all_times) preamble; echo -n "Accessed                   Modified           ";;
	    $input_format) echo -n "Accessed|Modified|";;
	esac
    
	if [ $stats_flag -eq 1 ] ; then
	    if [ $3 -eq $input_format ] ; then
		echo -n "File Size|1K-Blocks|"
	    else
		    echo -n "  File Size   1K-Blks  "
	    fi
	fi
    
	if [ $3 != $input_format ] ; then
	    echo -n "   "
	fi
    
	if [ $include_dirs -eq 1 ] ; then
	    echo "Files/Directories"
	else
	    echo "Files"
	fi
    else
	case $3 in
	    $general) preamble; echo -n "   Action          Timestamp ";;
	    $all_times) preamble; echo -n "Accessed          Modified  ";;
	    $input_format) echo -n "Accessed|Modified|";;
	esac
    
	if [ $stats_flag -eq 1 ] ; then
	    if [ $3 -eq $input_format ] ; then
		echo -n "File Size|1K-Blocks|"
	    else
		    echo -n "  File Size   1K-Blks  "
	    fi
	fi
    
	if [ $3 != $input_format ] ; then
	    echo -n "   "
	fi
    
	if [ $include_dirs -eq 1 ] ; then
	    echo "Files/Directories"
	else
	    echo "Files"
	fi
    fi
}

getDat()
{
    echo $(date --date=@$1 -u +"%Y-%m-%d %T") 2>/dev/null
}
############## Converts the date from sections to the specified format
convertDate()
{
    dat=
    if [ $is_busybox = false ] ; then
    	echo $(getDat $1)
	return
    fi

    echo $1
}

############## Prints the string in the default format
generalFormat(){
    if [ "$1" -gt "$2" ] ; then
	    echo -n "   Accessed        $(convertDate $1)" 
    else
	    echo -n "   Modified        $(convertDate $2)"  
    fi
}

############## Function that executes the stat command
boottime(){
    filename=
    #find_str="find -P $curr_path -type f"
    find_str="find $curr_path -type f"
    
    if [ $include_dirs -eq 1 ] ; then
    #    find_str="find -P $curr_path "
	find_str="find $curr_path "
    fi
    
    for line in $($find_str); do
	    slash=$(echo $line|cut -c0,1)

	    # The logic works this way:
	    #     Every file/directory starts with a '.' 
	    # 	-> all words that start with a . are used to 'stat'
	    #
	    #     Files that have spaces in their names (such as "c e"->c\ e/) 
	    #     do not follow this rule. For such names, this function gets
	    #     twice (1-> for .c & 2 -> for e). In such cases, we accumulate 
	    #     all the words together, check for their existance and 
	    #     execute stat only when they are physically present
	    #     Use ${filename%/*} to extract directory
	    if [ $slash == "/" ] ; then
		    filename=$line
	    else
		    filename="$filename $line" #We simply accululate the words here
	    fi
	    firstDir=$(echo $filename|cut -c0-5)

	    if [ $firstDir == "/proc" ] || [ $firstDir == "/sys/" ] ; then
    		continue
	    fi

	    # Stat only if the file exists. This filters out files that have multiple spaces in their names (e.g., file-> "c c c c.txt")
	    if [ -f "$filename" ]; then # && [ $include_dirs -eq 0 ] ; then
		    statFiles $1 $2 "$filename" $3 # we stat only when the word is a complete file name
		    filename=
	    elif [ -a "$filename" ] && [ $include_dirs -eq 1 ] ; then
		    statFiles $1 $2 "$filename" $3 $curr_path # we stat only when the word is a complete file name
		    filename=
	    fi 2>/dev/null
    
	    #Otherwise, keep accumulating
    done
}

############## Stat each file
statFiles(){
    mod_flag=false
    acc_flag=false
    flag_size=0
    blocks=0
    
    # DO NOT CHANGE THE SPACING BETWEEN %X %Y. [One space ONLY] 
    #str=$(eval "stat  \"--printf=%X %Y %s %b %B\" \"$3\"" 2>/dev/null)
    
    str=$(eval "stat  -t \"$3\"" 2>/dev/null)
    length=$(echo $3|wc -c)
    len=$(echo $str|wc -c)
    str=$(echo $str|cut -c$length-$len)
    acc_t=$(echo "$str"|awk '{print $12}')
    mod_t=$(echo "$str"|awk '{print $13}')
    
    # Check the Last accessed time
    if [ $1 -le "$acc_t" ] && [ $2 -ge "$acc_t" ] ; then
	    acc_flag=true
    fi 2>/dev/null
    
    # Check the Last modified time
    if [ $1 -le "$mod_t" ] && [ $2 -ge "$mod_t" ]; then
	    mod_flag=true
    fi 2>/dev/null
    
    # Display the file information if accessed/modified
    if [ $acc_flag = true ] || [ $mod_flag = true ] ; then
		    
	    # Just to make things efficient
	    if [ $stats_flag -eq 1 ] ; then
		flag_size=$(echo "$str"|awk '{print $1}')
		blocks=$(($(echo "$str"|awk '{print $2}')/2));
		total_size=$(($total_size+$flag_size))
		total_blocks=$(($total_blocks+$blocks))
	    fi
    
	    # Accumulate the file count
	    total_files=$(($total_files+1))
    
	    case $4 in
	    $input_format) echo -n "$acc_t|$mod_t|";;
	    $total_file_xml) echo "          <cl action=\"touched\" name=\"$3\"/>"; return;;
	    $all_times) echo -n "$(convertDate $acc_t)        $(convertDate $mod_t)";;
	    $general) generalFormat $acc_t $mod_t;;
	    *) echo ERROR
	    esac
	    
	    if [ $stats_flag -eq 1 ] && [ $4 -ne $input_format ] ; then
		printf "%11s %9s  " "$flag_size"  "$blocks"
	    elif [ $stats_flag -eq 1 ] && [ $4 -eq $input_format ] ; then
		printf "$flag_size|$blocks|"
	    fi
    
	    if [ $4 -ne $input_format ] ; then
		echo "   $3"
	    else
		pathlen=$(($(echo $curr_path|wc -c)+1))
		echo $(echo $3|cut -c$pathlen-$(echo $3|wc -c))
	    fi
    fi 
}

############## Makes sure that two different time format options are not specified
checkFormat()
{
    if [ $time_format -gt 0 ] ; then
	echo "Illegal time format parameters"
    	showUsage
	exit
    fi
}

############## Validate the 'other' arguments
validateInputArgument(){

    if [ -d "$1" ] ; then
	curr_path=$1
	return
    fi

    echo fetch-footprint: Invalid Arguments;
    showUsage
    exit 
}

############## Validate the date
validateDate(){

    test_date=
    tempfile=.123WRS_tempfile
    test_date=$(echo $1|cut -d"@" -f1)
    test_date=$test_date" "$(echo $1|cut -d"@" -f2)
    rm -f tempfile
    test_date=$(date --date="$test_date" +%s 2>tempfile) 

    if [ $test_date -z ]; then
	    cat tempfile
	    showUsage
	    rm -f tempfile
	    exit
    fi 2>/dev/null
    rm -f tempfile

    if [ $2 -eq 1 ] ; then
    	start_time=$test_date
    else
	end_time=$test_date
    fi
}

############## main entry point into the script
main(){
    # Test if fetch-footprint is executing on a busybox
    
    checkBusyBox

    for i in $*
    do
    
	if [ $s_flag = true ] ; then
    	    validateDate $i 1
	    s_flag=false
	    continue
	elif [ $e_flag = true ] ; then
    	    validateDate $i 2
	    e_flag=false
	    continue
	fi

	if [ $i = "-S" ] ; then
    	    s_flag=true
	    if [ $is_busybox = true ] ; then
    		echo "ERROR: -S Option is not supported in BusyBox"
		exit
	    fi
    	    continue
	elif [ $i = "-E" ] ; then
    	    e_flag=true
	    if [ $is_busybox = true ] ; then
    		echo "ERROR: -E Option is not supported in BusyBox"
		exit
	    fi
    	    continue
	fi

	case $i in
	   "-h") showUsage; exit;;
	   "-a") display_flag=$all_files;;
	   "-b") display_flag=$boot_time_touched_files; start_time=$(grep btime /proc/stat|cut -d' ' -f 2);;
	   "-f") display_flag=$all_files; start_time=$(date +"%s"); ENDTIME=$(($start_time*2));;
	   "-I") checkFormat; time_format=$input_format;;
	   "-R") checkFormat; time_format=$all_times;;
	   "-X") checkFormat; time_format=$total_file_xml;;
	   "-s") stats_flag=$(($stats_flag+1));;
	   "-d") include_dirs=$(($include_dirs+1));;
	   "-T") is_busybox=true;;
	   *) validateInputArgument $i;;
	esac
    done
    
    if [ $display_flag -eq 0 ] ; then
	showUsage
	exit
    fi

    curr_path=$(echo $(cd $curr_path; pwd))

    # Make sure that all the arguments are used correctly
    verifyArgumentUsage    

    # Initialize the output by printing the header
    printHeader $start_time $end_time $time_format
    
    # Run the analysis
    boottime $start_time $end_time $time_format

    # Print the footer    
    printFooter $start_time $end_time $time_format
}

############## Call main and let the script start execution

main $*

