#!/bin/tcsh -f
# JLdL 25Sep12.
#
# Copyright (C) 2006-2012 by Jorge L. deLyra <delyra@fma.if.usp.br>.
# This program may be copied and/or distributed freely. See the
# _ terms and conditions in /usr/share/doc/<package>/copyright.
#
# This program updates the binary quota file of a filesystem, based
# _ on the data in the corresponding text file; it does this very
# _ surgically, only for the users that had some quota data changed,
# _ and only for the pieces of data that have been changed.
#
# Record the name this script was called with.
set name = `basename $0`
#
# Define a variable with the tab character.
set tab = "`echo -n '\t'`"
#
# Initialize variables for the configuration file.
set conflag = 0
#
# Set the default configuration file.
set confile = "/etc/quotas.conf"
#
# Set a flag for the single argument needed.
set argflag = 0
#
# Set a flag for the use of the temporary text file.
set tmpflag = 0
#
# Set a flag for the progress-report output, default is up.
set proflag = 1
#
# Process the command-line arguments.
foreach cla ( $* )
    #
    # Detect options.
    if ( "`echo -n $cla | cut -c 1`" == "-" ) then
	#
	# If we got here with conflag up, there is an error.
	if ( $conflag == 1 ) then
	    echo "${name}: ERROR: option -C requires an argument"
	    exit 1
	endif
	#
	# Now process the options.
	switch ( $cla )
	case "-h":
	case "--help":
	    #
	    # Print a usage message.
	    echo -n "usage: $name [-C <config>] [-u|-g] [-t] [-q]"
	    echo    " </path/to/fs/root>"
	    echo "       -C: use alternate configuration file <config>"
	    echo "       -u: manipulate user quotas"
	    echo "       -g: manipulate group quotas"
	    echo "       -t: use the temporary text quota file"
	    echo "       -q: run quietly, no progress reporting"
	    exit 0
	    breaksw
	case "-C":
	case "--Config-file":
	    #
	    # Raise the flag.
	    set conflag = 1
	    breaksw
	case "-u":
	case "--usrquota":
	    #
	    # Set the type of quota.
	    set qtype = u
	    #
	    # Set the file extension.
	    set fext = user
	    breaksw
	case "-g":
	case "--grpquota":
	    #
	    # Set the type of quota.
	    set qtype = g
	    #
	    # Set the file extension.
	    set fext = group
	    breaksw
	case "-t":
	case "--temporary-file":
	    #
	    # Set the temporary flag.
	    set tmpflag = 1
	    breaksw
	case "-q":
	case "--quiet":
	    #
	    # Unset the progress-report flag.
	    set proflag = 0
	    breaksw
	default:
	    #
	    # Print an error message.
	    echo "${name}: ERROR: unknown option $cla"
	    exit 1
	    breaksw
	endsw
    #
    # Process non-option arguments.
    else
	#
	# Get the arguments of options.
	if ( $conflag == 1 ) then
	    #
	    # Set the configuration file.
	    set confile = $cla
	    #
	    # Lower the flag.
	    set conflag = 0
	else
	    #
	    # Get the filesystem from the command line argument.
	    if ( $argflag ) then
		echo "${name}: ERROR: too many arguments"
		exit 1
	    else
		set fs = $cla
		#
		# Lower the flag.
		set argflag = 1
	    endif
	endif
    endif
end
#
# If we got here with conflag up, there is an error.
if ( $conflag == 1 ) then
    echo "${name}: ERROR: option -C requires an argument"
    exit 1
endif
#
# Check for the single argument needed.
if ( ! $argflag ) then
    echo "${name}: ERROR: needs 1 argument: the filesystem"
    exit 1
endif
#
# Source the configuration file; this must define the following variables:
# _ quotas_path; default_type.
if ( -r $confile ) then
    source $confile
else
    echo "${name}: ERROR: cannot read configuration file $confile"
    exit 1
endif
#
# Set the location of the text quota files.
set qpath = "$quotas_path"
#
# If no quota-type options were entered, set the default type
# _ of quota, as defined in the configuration file.
if ( ! $?qtype ) then
    #
    # Set the type of quota.
    set qtype = `echo "$default_type" | cut -c 1`
    #
    # Set the file extension.
    set fext = "$default_type"
endif    
#
# Strip away any "/"'s at the extremes of the filesystem path.
set fs = `echo $fs | sed -e 's|/\+$||g' -e 's|^/\+||g'`
#
# Get the device containing the backup-home filesystem from
# _ the fstab file; one must exclude comment lines.
set fsdev = `grep "[ $tab]\+/${fs}[ $tab]\+" /etc/fstab \
	    | grep -v "^[ $tab]*#" | tr "$tab" ' ' \
	    | sed -e 's|^ *||g' | cut -d' ' -f1`
#
# This is needed in case the fstab file has been migrated to
# _ the new UUID-based device identification format.
#
# Check for a UUID entry in place of the device node.
if ( "`echo $fsdev| grep UUID=`" != "" ) then
    #
    # Extract the UUID number, without the keyword
    # _ "UUID=" and the punctuation marks "-".
    set uidev = `echo $fsdev | sed -e 's|\-||g' | cut -d= -f2`
    #
    # Get the device node from the output of blkid.
    set fsdev = `blkid | sed -e 's|\-||g' | grep "$uidev" | cut -d: -f1`
    #
endif
#
# Define the basic name of the text quota file.
set qname = `echo $fs | tr "/" "_"`
#
# Construct the quota filename.
if ( $tmpflag ) then
    set fname = $qname.$fext.TMP
else
    set fname = $qname.$fext
endif
set pname = $qpath/$fname
#
# Check for the existence of the quota file.
if ( ! -f $pname ) then
    echo "${name}: ERROR: no file $pname"
    exit 1
endif
#
# Define a lock file.
set lockfile = /var/lock/$name-$fname
#
# Check for a lock file.
if ( -f $lockfile ) then
    echo "${name}: this script is already running"
    exit 0
endif
#
# Make a lock file.
touch $lockfile
#
# Alias defining a macro for exiting cleanly.
alias cleanexit "\rm -f $lockfile >& /dev/null ; exit \!*"
#
# What to do on interrupts: do a clean exit.
onintr normalexit
#
# Put the whole text quota file, except headers,
# _ on a single record.
set frec = `cat $pname | grep -v "^[ $tab]*#"`
#
# The number of elements on the record.
set nc = $#frec
#
# The number of data lines in the quota file.
@ nl = $#frec / 5
#
# Print out an informative message.
if ( $proflag ) then
    if ( $nl == 1 ) then
	echo -n "/${fs}: there is $nl $fext to verify for update: "
    else
	echo -n "/${fs}: there are $nl ${fext}s to verify for update: "
    endif
endif
#
# Start a counter; inm is always the index of a name.
set inm = 1
#
# Loop over the elements.
while ( $inm <= $nc )
    #
    # Check whether the user or group exists in the system.
    #
    # The user case.
    if ( $qtype == u ) then
	#
	# The local case.
	grep "^${frec[$inm]}:" /etc/passwd >& /dev/null
	set stat1 = $status
	#
	# The NIS case.
	#
	# Check whether NIS is available.
	if ( -x /usr/bin/ypmatch ) then
	    ypmatch $frec[$inm] passwd >& /dev/null
	    set stat2 = $status
	else
	    set stat2 = 1
	endif
    #
    # The group case.
    else
	#
	# The local case.
	grep "^${frec[$inm]}:" /etc/group >& /dev/null
	set stat1 = $status
	#
	# The NIS case.
	#
	# Check whether NIS is available.
	if ( -x /usr/bin/ypmatch ) then
	    ypmatch $frec[$inm] group >& /dev/null
	    set stat2 = $status
	else
	    set stat2 = 1
	endif
    endif
    #
    # The user or group must be found somewhere; if not, then
    # _ write out a warning message and skip the entry.
    if ( $stat1 != 0 && $stat2 != 0 ) then 
	#
	# The user case.
	if ( $qtype == u ) then
	    echo ""
	    echo "${name}: warning: user $frec[$inm] does not exist in system"
	#
	# The group case.
	else
	    echo ""
	    echo "${name}: warning: group $frec[$inm] does not exist in system"
	endif
    else
	#
	# The soft block quota is the first element after the name.
	@ isb = $inm + 1
	#
	# The hard block quota is the second element after the name.
	@ ihb = $inm + 2
	#
	# The soft inode quota is the third element after the name.
	@ isi = $inm + 3
	#
	# The hard inode quota is the fourth element after the name.
	@ ihi = $inm + 4
	#
	# Get the quota entries for the user at the device containing
	# _ the filesystem.
	set entries = `quota -v -l -$qtype $frec[$inm] \
			    | grep $fsdev | tr '*' ' '`
	#
	# The third and fourth entries are the soft and hard block limits.
	set bsoft = $entries[3]
	set bhard = $entries[4]
	#
	# Verify whether the fifth entry has any letters within it.
	echo $entries[5] | grep -q '[a-z]'
	set thestatus = $status
	#
	# If it does not, then there is no grace-period entry and hence the
	# _ sixth and seventh entries are the soft and hard inode limits.
	if ( $thestatus ) then
	    set isoft = $entries[6]
	    set ihard = $entries[7]
	#
	# If it does, then there is a grace-period entry and in this case the
	# _ seventh and eigth entries are the soft and hard inode limits.
	else
	    set isoft = $entries[7]
	    set ihard = $entries[8]
	endif
	#
	# Verify whether any quota data was changed, and update only the data
	# _ which was in fact changed; first set an output control flag.
	set outflag = 1
	#
	# Soft blocks.
	if ( $bsoft != $frec[$isb] ) then
	    if ( $proflag ) then
		echo -n " $frec[$inm] (soft block) "
	    endif
	    quotatool -$qtype $frec[$inm] -b -q $frec[$isb] /$fs
	    #
	    # If there is an error, exit cleanly.
	    if ( $status != 0 ) then
		cleanexit $status
	    endif
	    #
	    set outflag = 0
	endif
	#
	# Hard blocks.
	if ( $bhard != $frec[$ihb] ) then
	    if ( $proflag ) then
		echo -n " $frec[$inm] (hard block) "
	    endif
	    quotatool -$qtype $frec[$inm] -b -l $frec[$ihb] /$fs
	    #
	    # If there is an error, exit cleanly.
	    if ( $status != 0 ) then
		cleanexit $status
	    endif
	    #
	    set outflag = 0
	endif
	#
	# Soft inodes.
	if ( $isoft != $frec[$isi] ) then
	    if ( $proflag ) then
		echo -n " $frec[$inm] (soft inode) "
	    endif
	    quotatool -$qtype $frec[$inm] -i -q $frec[$isi] /$fs
	    #
	    # If there is an error, exit cleanly.
	    if ( $status != 0 ) then
		cleanexit $status
	    endif
	    #
	    set outflag = 0
	endif
	#
	# Hard inodes.
	if ( $ihard != $frec[$ihi] ) then
	    if ( $proflag ) then
		echo -n " $frec[$inm] (hard inode) "
	    endif
	    quotatool -$qtype $frec[$inm] -i -l $frec[$ihi] /$fs
	    #
	    # If there is an error, exit cleanly.
	    if ( $status != 0 ) then
		cleanexit $status
	    endif
	    #
	    set outflag = 0
	endif
	#
	# Print out some progress report.
	if ( $proflag && $outflag ) then
	    echo -n "."
	endif
	#
    endif
    #
    # Increment the counter to the next username.
    @ inm = $inm + 5
end
#
# Finish the progress-report output.
if ( $proflag ) then
    echo " done."
endif
#
# Normal exit: remove the lock file.
normalexit:
cleanexit 0
