Code Repository

I often have to code many many scripts for my daily work as a system administrator. In the (vain) hopes these might be useful to someone else, maybe I should release these into the public domain.

My style of coding hasn't really stabilised; still trying to find a style that allows secure coding and easy readability. If you have suggestions, please let me know. Smile

Of course, if there a bugs, please let me know. Thanks! Smile

Bash Scripting Tips

I went looking around for bash scripting tips, especially secure coding of bash. Can't find much information, so decided to consolidate whatever I found here. Smile

Basket: A free-style note taking KDE application

Basket 1.0 is out. And it looks better than ever. Instead of just to-do lists, clipboard stacks, and checklists, there's now a new freestyle basket!

KDE Basket 1.0

Some other new features include import (text, HTML, "basket", Knotes, Sticky Notes, etc) and export (HTML or "basket" formats), password protection, integration with Kontact, and a lot more. It's looking more full featured now. Yeah... Smile

[Screenshot hosted on Flickr]

DNS BIND Hardening

Wow! Found this gem when researching on BIND hardening: Rob Thomas' CYMRU Secure BIND Template.

O'Reilly also published an excerpt of their DNS book (DNS and BIND, by Paul Albitz and Cricket Liu), which detailed more security tips. Yes, this book can be found in the National Library too.

During a security scan, one of my DNS servers was fingerprinted by NESSUS, so I think I will disable all CHAOS queries from now on.... Grrr......

I sold-out! Bought a Microsoft product!!

Okay, okay, sorry for the sensationalist headline. Tongue But I really bought a Microsoft Comfort Curve Keyboard 2000. My wrist is starting to hurt, so decided to prevent carpal tunnel syndrome before it is too late.

But why Microsoft?

Well, from my head-spinning survey of the keyboards in Sim LIm Square, there are only 4 types of ergonomic keyboards available.

Of course, the first one is out.

I tried one of the A-Shaped keyboards, and it felt weird. Now, I can touch-type, so the key positioning does not feel out of place. However, the A-Shape doesn't feel ergonomic. My wrists were still skewed, though not as much as before.

That left the Microsoft keyboards. The Natural Ergonomic keyboard costs a hefty $80 dollars!! Typical Microsoft over-pricing, I guess. So that leaves the more affordable Comfort Curve keyboard selling at $28.

Review of the keyboard

The Comfort Curve keyboard basically elongates the middle keys and spreads the keyboard over a curve. This allows a touch-typist (both systematic and non-systematic typists) to keep his/her wrists straight, while allowing two-finger peckers to peck away as usual. Isn't that great? Compare this with the A-Shape keyboard or the Natural Ergonomic keyboard.

A soft tactile keyboard, the keys feel responsive, and they are relatively quiet. Just the kind of keyboard I like. Smile Think I will get another, one for use at home, the other in the linuxNUS clubroom. Tongue

The packaging is not environmentally friendly though. The keyboard is packed in 1) a plastic bag, 2) a carton box, 3) a box sleeve. Is there a need to use so much material just for the packaging?!?

Oh yes, not to mention the two product manuals, which states (among other irrelevant things like optical laser precautions (!?!), battery powered precautions (?!?), etc) the keyboard can lead to "serious injuries or disorders". IT also warns one not to "ignore these warning signs", "promptly see a qualified health professional" for these "musculoskeletal disorders". All in good sense, but isn't that why one is buying this keyboard in the first place?!?!

Okay, the other product manual is more applicable, showing how one plugs in the keyboard to a USB port.

Typical of a Microsoft product, the packaging states that:

I guess this means people who are using Windows 9x, Linux, Unix, BSD, Mac Tiger (10.4) should change their operating systems. Or the use of the keyboard on these systems invalidates the warranty? Sheesh...

Altogether, the keyboard is great, but the marketing leaves much to be desired. Sigh.

Some thoughts

As an open source advocate, it's very tempting to paint all and any Microsoft product as "evil", "monopolistic" or "over-priced". However, doing so puts us at the same level as them. Being responsible (and successful) advocates, we have to be very clear on the good and the bad of both camps, and recommend F/OSS when it makes sense to do so. Pushing F/OSS and Linux when it is clearly not suitable just sets up the case for failure, which would not help our cause.

At least, in this case, the Microsoft keyboards are clearly better choices, so why insist on the other products? That's what market competition is all about, isn't it?

KDE Kooldock: Mac OSX dock clone

Cool... Kev just introduced me to Kooldock, a fast clone of the Mac OSX dock.

kooldock

Adding of applications is by either adding the application, or copying the application's .desktop file to the kooldock preferences directory. Automatically hidden till the mouse is at the bottom edge too! Very intuitive, responsive and well, kewl... Smile

List of *nix commands

Now that I am doing cross platform system administration, it is getting critical to have lists of equivalent commands across the *nixes. Found 2 guides so far:

  1. Unix toolbox: http://cb.vu/unixtoolbox.xhtml
  2. Tom's Hardware Universal Command Guide: http://www.tomshardware.com/ucg/

Will be adding more as the time goes by. Smile

SSH logins using RSA key-pair authentication

As mentioned previously, I had to mirror a server for testing purposes across two networks through a computer in the middle. The best way I found was to do an rsync over ssh, but this requires a non-password authentication, hence I have to set up a RSA key-pair login.

Server setup

First, the ssh server must allow this authentication method. Make sure /etc/ssh/sshd_config has the following:


Protocol 2            # use ssh protocol 2!!

# Authentication

RSAAuthentication      yes
PubkeyAuthentication   yes
AuthorizedKeysFile      .ssh/authorized_keys

Restart your ssh server. You might want to start another ssh session first, in case there's something wrong and your ssh server can't restart.

Make sure you are the only user able to access ~/.ssh/authorized_keys by changing the permissions to 600.


[user@server] $ chmod 600 ~/.ssh/authorized_keys

Client side

The default location of the RSA key-pair is in ~/.ssh/id_rsa (private key) and ~/.ssh/id_rsa.pub (public key). This is set in /etc/ssh/ssh_config of your client computer, under IdentityFile.

In your own account, generate a RSA key-pair. You do not need a password passphrase. Store the keys in the location indicated in your ssh client configuration.


[user@client] $ ssh-keygen -t rsa

Change the permissions such that only you can read the files.


[user@client] $ chmod 600 ~/.ssh/id_rsa
[user@client] $ chmod 600 ~/.ssh/id_rsa.pub

Append your public key to the ssh server. Of course you can cut and paste, or you can do this:


[user@client] $ cat ~/.ssh/id_rsa.pub | ssh username@ssh.server "cat - >> ~/.ssh/authorized_keys"

Test your setup

If everything works, you should be able to login without typing in any passwords. Hooray!

Script: Check No Missing Files After Reorganisation of Directory Trees

Here's another script I did when I had to reorganised a folder hierarchy of years of data. Basically to ensure files are not missing, or corrupted.


#!/bin/bash                                                                                                                              

#########################
#                        
# checkNoMissingFiles    
# ===================    
#                        
# This script checks that no files are missing after folders are reorganised.
# Basic algorithm is to checksum all files in both old and new folders, then 
# checking through both lists of checksums to ensure all checksums are present
# in both lists.

#                                                                             
# Changelog                                                                   
# =========                                                                   
#                                                                             
# 18 Oct 2007 - Junhao                                                        
# * Initial commit                                                            
#                                                                             
# 11 Dec 2007 - Junhao                                                        
# * Tidied style                                                              
# * Fixed bug with spaces in filenames                                        
# * added option to save generated checksums                                  
# * changed md5sum to sha1sum                                                 
# * changed checksum to general algorithm                                     
#########################                                                     

PATH=/bin:/usr/bin
export PATH       

## Program Locations
awk=/bin/awk        
cat=/bin/cat        
echo=/bin/echo      
find=/usr/bin/find  
grep=/bin/grep      
checksum=/usr/bin/sha1sum
mktemp=/bin/mktemp       
rm=/bin/rm               
tee="/usr/bin/tee -a"    
touch=/bin/touch         
## End Program Locations 

## Start Script

## Script parameters
f_logFile=/dev/null 
d_orgLoc=/dev/null  
d_newLoc=/dev/null  
v_oldFileName=      
v_oldFileChksum=    
f_oldChksumLog=     
f_newChksumLog=     
v_missingFilesCount=0
v_missingFiles=""    
v_output=            
v_f1flag=1           
v_f2flag=1           
## End Script parameters

function print_usage () {
    ${echo} "            
$0                       
Usage: $0 [-L logfile] [-f1 filename] [-f2 filename] [oldDir] [newDir]
 or    $0 -h                                                          
Description: Checks that there are no missing files after reorganising a directory.
Options:                                                                           
  -L logfile    (Optional) Path to log file                                        
  -h            (Optional) This help text                                          
  -1            (Optional) Filename to save checksum for old directory             
  -2            (Optional) Filename to save checksum for new directory             
  oldDir        Location of old directory                                          
  newDir        Location of new directory                                          
"                                                                                  
}                                                                                  

if [ $# -lt 2 ]; then
    print_usage      
    exit 1           
else                 
    while getopts hL:1:2: options; do
        case "${options}" in         
            h)  print_usage          
                exit 1               
                ;;                   
            L)  f_logFile=${OPTARG}  
                ;;                   
            1)  f_oldChksumLog=${OPTARG}
                v_f1flag=0              
                ;;                      
            2)  f_newChksumLog=${OPTARG}
                v_f2flag=0              
                ;;                      
            *)  f_logFile=/dev/null     
                ;;                      
        esac                            
    done                                
    shift $((${OPTIND} - 1))            

    if [ -d "$1" ]; then
        d_orgLoc="$1"   
    else                
        ${echo} "Error: Original directory does not exist!"
        print_usage                                        
        exit 1                                             
    fi                                                     
                                                           
    if [ -d "$2" ]; then                                   
        d_newLoc="$2"                                      
    else                                                   
        ${echo} "Error: New directory does not exist!"     
        print_usage                                        
        exit 1                                             
    fi                                                     

    if [ -z ${f_oldChksumLog} ]; then
        f_oldChksumLog=`${mktemp}`   
    elif [ -f ${f_oldChksumLog} ]; then
        ${echo} "Error: File ${f_oldChksumLog} exists! Please give another filename."
        exit 2                                                                       
    else                                                                             
        ${touch} ${f_oldChksumLog}                                                   
        if [ ! -f ${f_oldChksumLog} ]; then                                          
            ${echo} "Error: ${f_oldChksumLog} cannot be created!"                    
            exit 4                                                                   
        fi                                                                           
    fi                                                                               

    if [ -z ${f_newChksumLog} ]; then
        f_newChksumLog=`${mktemp}`   
    elif [ -f ${f_newChksumLog} ]; then
        ${echo} "Error: File ${f_newChksumLog} exists! Please give another filename."
        exit 3                                                                       
    else                                                                             
        ${touch} ${f_newChksumLog}                                                   
        if [ ! -f ${f_newChksumLog} ]; then                                          
            ${echo} "Error: File ${f_newChksumLog} cannot be created!"               
            exit 5                                                                   
        fi                                                                           
    fi                                                                               
fi                                                                                   

${echo} "${find} \"${d_orgLoc}\" -type f -exec ${checksum} \"\\{\\}\" \\;" | ${tee} ${f_logFile}
${find} "${d_orgLoc}" -type f -exec ${checksum} "{}" \; | ${tee} ${f_oldChksumLog}              
${echo} "${find} \"${d_newLoc}\" -type f -exec ${checksum} \"\\{\\}\" \\;" | ${tee} ${f_logFile}
${find} "${d_newLoc}" -type f -exec ${checksum} "{}" \; | ${tee} ${f_newChksumLog}              


while read -r v_oldFileChksum v_oldFileName; do
    if [[ `${grep} ${v_oldFileChksum} ${f_newChksumLog}` ]]; then
        v_output="Okay:  ${v_oldFileName} -> "                   
        v_output="${v_output} `${grep} \"${v_oldFileChksum}\" \"${f_newChksumLog}\" | ${awk} '{print $2}'`"
    else                                                                                                   
        v_output="ERROR: ${v_oldFileName} is missing"                                                      
        v_missingFiles="${v_missingFiles} ${v_oldFileName}"                                                
        v_missingFilesCount=$((v_missingFilesCount+1))                                                     
    fi                                                                                                     
    ${echo} "${v_output}" | ${tee} ${f_logFile}
done < ${f_oldChksumLog}

#### cleanup ####
if [ "1" == ${v_f1flag} ]; then
    ${rm} ${f_oldChksumLog}
fi
if [ "1" == ${v_f2flag} ]; then
    ${rm} ${f_newChksumLog}
fi


if [ ${v_missingFilesCount} -gt 0 ]; then
    ${echo} "ERROR: ${v_missingFilesCount} files are missing:" | ${tee} ${f_logFile}
    ${echo} "ERROR:   ${v_missingFiles}" | ${tee} ${f_logFile}
    exit 99
else
    ${echo} "Success: ${v_missingFilesCount} files are missing" | ${tee} ${f_logFile}
    exit 0
fi

Script: Yum Check Update

I have been using this script to check for updates on my Redhat systems for quite some time. Put this into your cron.daily, and you have a daily nag to update your system. Smile


#!/bin/bash                                                                                       

#########
## Yum Check Update Script
##
## This script checks for system updates and sends email
## to sysmin team if there are any updates.
##
## Changelog
## ---------
## 24 Oct 2008 (Junhao)
## - Initial commit
##
#########

_CAT="/bin/cat"
_DATE="/bin/date"
_HOSTNAME="/bin/hostname"
_MAILX="/bin/mailx"
_RM="/bin/rm"
_TOUCH="/bin/touch"
_YUM="/usr/bin/yum"

HOSTNAME=`${_HOSTNAME}`
DATESTAMP=`${_DATE} +%Y%b%d-%H:%M:%S`
EMAIL=root
MAILSUB="RHEL Update Available for ${HOSTNAME} on ${DATESTAMP}"

TEMPLOG=/tmp/yum-check-update.tmp

${_TOUCH} ${TEMPLOG}
${_YUM} check-update 1> ${TEMPLOG} 2>&1

if [[ $? != 0 ]]; then
        ${_CAT} ${TEMPLOG} | ${_MAILX} -s "${MAILSUB}" ${EMAIL}
fi

${_RM} ${TEMPLOG}

Script: Zimbra Backup Script

I am currently migrating out of Zimbra to a 3rd-party host. Just for archival, here's my Zimbra backup script. Just run this script using a cronjob every day. There will be a short downtime where Zimbra is shutdown to synchronise the last bit of emails, but that should be okay if you have a backup MX server.

This script creates a working live copy of the Zimbra directory, then shutdown Zimbra to sync the directory. The directory is then passed through star, into a small(er) file.


#!/bin/bash                                                                                      
########                                                                                         
## Zimbra backup script                                                                          
##                                                                                               
## See              
##                                                                                               
## Requires star, rsync, bash, gzip                                                              
## Does full backups of /opt/zimbra only. Tries to                                               
## minimise zimbra shutdown time with a live rsync,                                              
## then a offline rsync                                                                          
##                                                                                               
## Changelog                                                                                     
## ---------                                                                                     
## 15 Feb 2009 (Junhao)                                                                          
## - BUGFIX: deletes leftover archive.tgz tarball before                                         
##   creating new tar                                                                            
## 21 Oct 2008 (Junhao)                                                                          
## - added disk size to log                                                                      
## - added tarball -t test                                                                       
## 19 Oct 2008 (Junhao)                                                                          
## - Initial commit                                                                              
##                                                                                               
########                                                                                         

## config
_AWK=`which awk`
_CAT=`which cat`
_CD=cd                          #bash builtin
_CHECKSUM=`which sha1sum`                    
_DATE=`which date`                           
_DF=`which df`                               
_DU=`which du`                               
_ECHO=`which echo`                           
_MV=`which mv`                               
_MAIL=`which mail`                           
_RSYNC=`which rsync`                         
_RM=`which rm`                               
_SLEEP=`which sleep`                         
_SU=`which su`                               
_TAR=`which star`                            
_TOUCH=`which touch`                         

DATESTAMP=`date +%Y%b%d-%T`
RELEASE=`${_SU} - zimbra -c"zmcontrol -v"`
RELEASE=`${_ECHO} ${RELEASE} | ${_AWK} '{ print $2"-"$3"-"$4 }'`
TARBALL=zimbra-${RELEASE}-backup-full-${DATESTAMP}.tgz          
LOGFILE=zimbra-${RELEASE}-backup-full-${DATESTAMP}.log          
EMAIL=user@domain.co.m                                      
MAILSUB="ZCS Backup Report on ${DATESTAMP}"                     
BKUPRETRIES=5                                                   
RESTARTRETRIES=100                                              

ZIMBRADIR=/opt/zimbra
BASEDIR=/opt/zimbra-backups
WORKDIR=${BASEDIR}/working 
LOGDIR=${BASEDIR}/logs     
SAVEDIR=${BASEDIR}/saved   
LOG=${LOGDIR}/${LOGFILE}   
CHKSUMLOG=${SAVEDIR}/checksum
TARLOG=${WORKDIR}/tar.log    
LOCK=${BASEDIR}/zimbra-backup.lock
TEMPARCHIVE=archive.tgz           

function calc_downtime() {
        if [ -z "${STARTTIME3}" ]; then
                STARTTIME3=`date +%s`  
        fi                             
        if [ -z "${ENDTIME}" ]; then   
                ENDTIME=`date +%s`     
        fi                             
        TOTAL=$((${ENDTIME} - ${STARTTIME1}))
        OFFLINE=$((${STARTTIME3} - ${STARTTIME2}))
        ${_ECHO} "Time taken: $((${TOTAL} / 3600)) hours $((${TOTAL} % 3600 / 60)) minutes $((${TOTAL} % 3600 % 60)) seconds" >> ${LOG}                                                                         
        ${_ECHO} "Zimbra Offline: $((${OFFLINE} / 3600)) hours $((${OFFLINE} % 3600 / 60)) minutes $((${OFFLINE} % 3600 % 60)) seconds" >> ${LOG}                                                               

        return 0
}               
function force_restart() {
        for (( i=0; i<=${RESTARTRETRIES} ; i=$(($i+1)) )); do
                ${_SU} - zimbra -c"zmcontrol stop"           
                ${_SLEEP} 10                                 
                ${_SU} - zimbra -c"zmcontrol start"          
                ${_SLEEP} 10                                 
                ${_SU} - zimbra -c"zmcontrol status"         
                if [[ $? == 0 ]]; then                       
                        ${_ECHO} "Sucessfully restarted zimbra after $((${i}+1)) tries" >> ${LOG}
                        return 0                                                                 
                else                                                                             
                        ${_SLEEP} 10                                                             
                fi                                                                               
        done                                                                                     
        ${_ECHO} "Could not restart zimbra after ${RESTARTRETRIES} tries" >> ${LOG}              
        return 254                                                                               
}                                                                                                
function lock_set() {                                                                            
        ${_TOUCH} ${LOCK}                                                                        
}                                                                                                
function lock_remove() {                                                                         
        ${_RM} ${LOCK}                                                                           
}                                                                                                
function synchronise() {                                                                         
        for (( i=0; i<=${BKUPRETRIES} ; i=$(($i+1)) )); do                                       
                ${_RSYNC} -avHK --delete --exclude=*.pid ${ZIMBRADIR} ${WORKDIR}                 
                if [[ $? == 0 || $? == 24 ]]; then                                               
                        return $?                                                                
                fi                                                                               
                ${_SLEEP} 10                                                                     
        done                                                                                     
        return 254                                                                               
}                                                                                                
function send_mail() {                                                                           
        calc_downtime                                                                            
        ${_ECHO} "" >> ${LOG}                                                                    
        ${_SU} - zimbra -c"zmcontrol status" >> ${LOG}                                           

        ${_ECHO} "" >> ${LOG}
        ${_CAT} ${TARLOG} >> ${LOG}
        ${_RM} ${TARLOG}           

        ${_CAT} ${LOG} | ${_MAIL} -s "${MAILSUB}" ${EMAIL}
}                                                         
function send_error() {                                   
        MAILSUB="[Failed] ${MAILSUB}"                     
        send_mail                                         
}                                                         
function send_success() {                                 
        MAILSUB="[Success] ${MAILSUB}"                    
        send_mail                                         
}                                                         

${_TOUCH} ${LOG}
lock_set        
${_ECHO} "Server: `${_SU} - zimbra -c"zmhostname"`" >>${LOG}
${_ECHO} "Tarball: ${TARBALL}" >> ${LOG}                    
${_ECHO} "Logfile: ${LOG}" >> ${LOG}                        
${_ECHO} "Backup started at ${DATESTAMP}" >> ${LOG}         
${_ECHO} "" >> ${LOG}                                       

## Outputs time backup started for logging
STARTTIME1=`${_DATE} +%s`                 

## Online sync to working directory
${_ECHO} "Starting online rsync" >> ${LOG}
synchronise                               
if [[ $? == 254 ]]; then                  
        ${_ECHO} "Error in creating live copy" >> ${LOG}
        send_error                                      
        lock_remove                                     
        exit 1                                          
fi                                                      
STARTTIME2=`${_DATE} +%s`                               

## Shut down zimbra
${_SU} - zimbra -c"zmcontrol stop"
if [[ $? != 0 ]]; then            
        ${_ECHO} "Error stopping zimbra" >> ${LOG}
        ${_SU} - zimbra -c"zmcontrol status" >>${LOG}
        ${_ECHO} "Aborting backup, force restarting zimbra" >> ${LOG}
        force_restart                                                
        if [[ $? == 255 ]]; then                                     
                ${_ECHO} "Trying again" >> ${LOG}                    
                force_restart                                        
        fi                                                           
        send_error                                                   
        lock_remove                                                  
        exit 2                                                       
fi                                                                   
${_SLEEP} 10                                                         

## Offline sync to working directory
${_ECHO} "Starting offline rsync" >>${LOG}
synchronise                               
if [[ $? == 0 ]]; then                    
        ${_ECHO} "Offline rsync completed successfully" >> ${LOG}
elif [[ $? == 24 ]]; then                                        
        ## some files disappeared, meaning some open process running
        ## wait a while, rerun rsync, and assume okay               
        ${_SLEEP} 60                                                
        synchronise                                                 
else                                                                
        ${_ECHO} "Error in creating offline copy" >> ${LOG}         
        ${_ECHO} "Aborting backup, force restarting zimbra" >> ${LOG}
        force_restart                                                
        send_error                                                   
        lock_remove                                                  
        exit 3                                                       
fi                                                                   

## Start zimbra
force_restart  
if [[ $? == 254 ]]; then
        ${_ECHO} "Trying again" >> ${LOG}
        force_restart                    
        send_error                       
        lock_remove                      
        exit 4                           
fi                                       
STARTTIME3=`date +%s`                    

## Synchronization sucessful, create archive
${_CD} ${WORKDIR}                           
if [ -f ${TARLOG} ]; then                   
        ${_RM} ${TARLOG}                    
else                                        
        ${_TOUCH} ${TARLOG}                 
fi                                          

if [ -f ${TEMPARCHIVE} ]; then
        ${_RM} ${TEMPARCHIVE} 
fi                            

${_TAR} czf ${TEMPARCHIVE} ./ 1>> ${TARLOG} 2>&1
if [[ $? != 0 && $? != 254 ]]; then             
        ${_ECHO} "Error creating tarball. Error Code: $?" >> ${LOG}
        ${_ECHO} "Aborting backup" >> ${LOG}                       
        ${_ECHO} "Tarball location: ${WORKDIR}/${TEMPARCHIVE}" >> ${LOG}
        send_error                                                      
        lock_remove                                                     
        exit 5                                                          
fi                                                                      
${_TAR} ztf ${TEMPARCHIVE}                                              
if [[ $? == 0 ]]; then                                                  
        ${_MV} ${TEMPARCHIVE} ${SAVEDIR}/${TARBALL}                     
        ${_ECHO} "" >> ${LOG}                                           
        ${_DU} -sh ${SAVEDIR}/${TARBALL} >> ${LOG}                      
        ${_DF} -h ${SAVEDIR}/${TARBALL} >> ${LOG}                       
else                                                                    
        ${_ECHO} "Error validating tarball. Error Code: $?" >> ${LOG}   
        ${_ECHO} "Aborting backup" >> ${LOG}
        ${_ECHO} "Tarball location: ${WORKDIR}/${TEMPARCHIVE}" >> ${LOG}
        send_error
        lock_remove
        exit 6
fi

## Creating checksum
${_CD} ${SAVEDIR}
CHKSUM=`${_CHECKSUM} ${TARBALL}`
${_ECHO} "${CHKSUM}" >> ${CHKSUMLOG}
${_ECHO} "" >> ${LOG}
${_ECHO} "Checksum using ${_CHECKSUM}">> ${LOG}
${_ECHO} "${CHKSUM}" >> ${LOG}

## Backup done!
ENDTIME=`date +%s`
send_success
lock_remove
exit 0

Script: check for missing files in a directory after reorganisation

*Updated: 11 Dec 2007

I'm wondering where I should store the scripts I'm writing. Out of pure laziness, I'll just dump them as my blog entry for now. Tongue

Here's a script to check for missing files after a directory has been re-organised. Basically, it compares the md5sum of the files in the old directory and the new directory.

Please let me know if there are any bugs. Tongue

#!/bin/bash

#########################
#
# checkNoMissingFiles
# ===================
#
# This script checks that no files are missing after folders are reorganised.
# Basic algorithm is to checksum all files in both old and new folders, then
# checking through both lists of checksums to ensure all checksums are present
# in both lists.
#
# Changelog
# =========
#
# 18 Oct 2007 - Junhao
# * Initial commit
#
# 11 Dec 2007 - Junhao
# * Tidied style
# * Fixed bug with spaces in filenames
# * added option to save generated checksums
# * changed md5sum to sha1sum
# * changed checksum to general algorithm
#########################

PATH=/bin:/usr/bin;

## Program Locations
awk=/usr/bin/awk
cat=/usr/bin/cat
echo=/usr/bin/echo
find=/usr/bin/find
grep=/bin/grep
checksum="/usr/bin/sha1sum"
mktemp=/bin/mktemp
rm=/usr/bin/rm
tee="/usr/bin/tee -a"
touch="/bin/touch"
## End Program Locations

## Start Script

## Script parameters
f_logFile=/dev/null
d_orgLoc=/dev/null
d_newLoc=/dev/null
v_oldFileName=
v_oldFileChksum=
f_oldChksumLog=
f_newChksumLog=
v_missingFilesCount=0
v_missingFiles=""
v_output=
v_f1flag=1
v_f2flag=1
## End Script parameters

function print_usage () {
    ${echo} "
$0
Usage: $0 [-L logfile] [-f1 filename] [-f2 filename] [oldDir] [newDir]
 or    $0 -h
Description: Checks that there are no missing files after reorganising a directory.
Options:
  -L logfile    (Optional) Path to log file
  -h            (Optional) This help text
  -1           (Optional) Filename to save checksum for old directory
  -2           (OPtional) Filename to save checksum for new directory
  oldDir        Location of old directory
  newDir        Location of new directory
"
}

if [ $# -lt 2 ]; then
    print_usage
    exit 1
else
    while getopts hL:1:2: options; do
        case "${options}" in
            h)  print_usage
                exit 1
                ;;
            L)  f_logFile=${OPTARG}
                ;;
            1)  f_oldChksumLog=${OPTARG}
                v_f1flag=0
                ;;
            2)  f_newChksumLog=${OPTARG}
                v_f2flag=0
                ;;
            *)  f_logFile=/dev/null
                ;;
        esac
    done
    shift $((${OPTIND} - 1))

    if [ -d $1 ]; then
        d_orgLoc=$1
    else
        ${echo} "Error: Original directory does not exist!"
        print_usage
        exit 1
    fi

    if [ -d $2 ]; then
        d_newLoc=$2
    else
        ${echo} "Error: New directory does not exist!"
        print_usage
        exit 1
    fi

    if [ -z ${f_oldChksumLog} ]; then
        f_oldChksumLog=${mktemp}
    elif [ -f ${f_oldChksumLog} ]; then
        ${echo} "Error: File ${f_oldChksumLog} exists! Please give another filename."
        exit 2
    else
        ${touch} ${f_oldChksumLog}
        if [ ! -f ${f_oldChksumLog} ]; then
            ${echo} "Error: ${f_oldChksumLog} cannot be created!"
            exit 4
        fi
    fi

    if [ -z ${f_newChksumLog} ]; then
        f_oldChksumLog=${mktemp}
    elif [ -f ${f_newChksumLog} ]; then
        ${echo} "Error: File ${f_newChksumLog} exists! Please give another filename."
        exit 3
    else
        ${touch} ${f_newChksumLog}
        if [ ! -f ${f_newChksumLog} ]; then
            ${echo} "Error: File ${f_newChksumLog} cannot be created!"
            exit 5
        fi
    fi
fi

${echo} "${find} \"${d_orgLoc}\" -type f -exec ${checksum} \\"\{\}\\" \;" | ${tee} ${f_logFile}
${find} "${d_orgLoc}" -type f -exec ${checksum} \"\{\}\" \; | ${tee} ${f_oldChksumLog}
${find} "${find} \"${d_newLoc}\" -type f -exec ${checksum} \\"\{\}\\" \;" | ${tee} ${f_logFile}
${find} "${d_newLoc}" -type f -exec ${checksum} \"\{\}\" \; | ${tee} ${f_newChksumLog}


while read -r v_oldFileChksum v_oldFileName; do
    if [[ `${grep} ${v_oldFileChksum} ${f_newChksumLog}` ]]; then
        v_output="Okay:  ${v_oldFileName} -> "
        v_output="${v_output} `${grep} \"${v_oldFileChksum}\" \"${f_newChksumLog}\" | ${awk} '{print $2}'`"
    else
        v_output="ERROR: ${v_oldFileName} is missing"
        v_missingFiles="${v_missingFiles} ${v_oldFileName}"
        v_missingFilesCount=$((v_missingFilesCount+1))
    fi
    ${echo} "${v_output}" | ${tee} ${f_logFile}
done < ${f_oldChksumLog}

#### cleanup ####
if [ "1" == ${v_f1flag} ]; then
    ${rm} ${f_oldChksumLot}
fi
if [ "1" == ${v_f2flag} ]; then
    ${rm} ${f_newChksumLog}
fi


if [ ${v_missingFilesCount} -gt 0 ]; then
    ${echo} "ERROR: ${v_missingFilesCount} files are missing:" | ${tee} ${f_logFile}
    ${echo} "ERROR:   ${v_missingFiles}" | ${tee} ${f_logFile}
    exit 99
else
    ${echo} "Success: ${v_missingFilesCount} files are missing" | ${tee} ${f_logFile}
    exit 0
fi

Using unlink to delete files

Encountered a problem deleting a file with the name "--option=backup" (without the quotes). Don't ask me how it got there, I have no idea. It can't be deleted using rm or renamed using mv.


[user@system /]# rm '--owner=backup'
rm: unrecognized option `--owner=backup'
Try `rm --help' for more information.
[user@system /]# mv '--owner=backup' nothing
mv: unrecognized option `--owner=backup'
Try `mv --help' for more information.

Solution? Shyam Mani suggested unlink '--option=backup'. Apparently, it directly uses the Linux unlink system call to delete files, so does not encounter the same problem as rm

Update 1: Some people suggested alternatives like rm -- '--option=backup', or rm ./--option=backup. Both worked nicely.

Update 2: And yes, Konqueror can delete that file too. Why didn't I think of that? Tongue

airo350 + wpa_supplicant + ndiswrapper

After struggling to get my PCMCIA airo350 wireless card to work with PEAP in NUS for years, I finally caved in and used ndiswrapper. I know, I know, kernel 2.6.17 and above is supposed to support it, but that is for cards with firmware version 5.30.17 "or later". My card is 5.60.x, which is too much later, I guess.

Grabbing the files from Windows XP

So, no choice but to use ndiswrapper. So I boot to Windows, and checked out the files used by the Aironet 350 device (driver properties -> files), and grabbed the files c:\windows\inf\netx500.inf and c:\windows\system32\drivers\pcx500.sys. Just FYI, pcx500mp.sys is for the Aironet 350 Mini-PCI card.

Installing the Windows drivers

Then I boot into Linux, and installed ndiswrapper(1.33), and wpa_supplicant (v0.5.7).

Next, I have to install the drivers. This was easy. First make sure the 2 files are in the same directory, then install the driver.


# install the driver
root $ /sbin/ndiswrapper -i /path/to/netx500.inf

# check the driver is installed
root $ /sbin/ndiswrapper -l
netx500 : driver installed

# insert the pcmcia card, then load ndiswrapper
root $ modprobe ndiswrapper

# check ndiswrapper is loaded
root $ lsmod
Module                  Size  Used by
ndiswrapper           195284  0

root $ dmesg
[...]
ndiswrapper version 1.33 loaded
usbcore: registered new interface driver ndiswrapper

There's a problem. Ndiswrapper is not able to automatically create the PCMCIA configuration. I have to do this by manually. I wrote one using the configuration it has already generated (14B9:0350.5.conf) as a guide.


# save this as /path/to/ndiswrapper/netx500/14B9:929B.8.conf (e.g /etc/ndiswrapper/netx500/14B9\:929B.8.conf)
# the '8' in the filename is because CardBus is device type 8.

NdisVersion|0x50001
Environment|1
class_guid|4d36e972-e325-11ce-bfc1-08002be10318
NetworkAddress|XX:XX:XX:XX:XX:XX
driver_version|,07/01/2001,7.29.0.0
BusType|8

FormFactor|PCMCIA
InfrastructureMode|1
LowerRange|ethernet
MediaDisconnectDamper|10
NodeName|
PowerSaveMode|0
RadioName|PC3500
Service|PCX500
SSID1|
SupportedRates|0
UpperRange|ndis5

Now we have to link the Cardbus PCMCIA slot to this configuration. As Cardbus slots are detected as PCI devices, we can use lspci.


# this is the CardBus device we have to take note of
root $ lspci
[...]
01:0b.0 CardBus bridge: Toshiba America Info Systems ToPIC95 (rev 07)

# Get the manufacturer's device ID (xxxx:xxxx)
root $ lspci -n
[...]
01:0b.0 0607: 1179:060a (rev 07)

# create a symlink for the configuration to this device
# Note the capital letters
root $ cd /directory/of/14B9:929B.8.conf
root $ ln -s 14B9:929B.8.conf 1179\:060A.5.conf
root $ ls -al
1179:060A.5.conf -> 14B9:929B.8.conf
14B9:0340.5.conf
14B9:0350.5.conf
14B9:4800.5.conf
14B9:929B.8.conf
netx500.inf
pcx500.sys

The kernel might already have the opensource driver running. To prevent it from loading these modules (aka drivers) instead, we need to black list them in /etc/modprobe.conf


# blacklist the modules
blacklist airo_cs
blacklist airo

# alias the interface to eth1 (or wlan0 if that's your 
alias eth1 ndiswrapper

So now we have installed and setup ndiswrapper for this PCMCIA card.

Starting the card

Restart the system!!! Okay, there's no need to do that, but I'm lazy... Tongue Besides, I just upgraded a ton of other stuff too...


root $ modprobe ndiswrapper
root $ lsmod

Using WPA Supplicant

Since I'm using PEAP (yes, I know it's not secure. NUS doesn't care.... It's their newly installed "secure" wireless system.), I have to get wpa_supplicant to work too.

Hmm... I'm getting tired of typing. I'll just plonk my wpa_supplicant configuration here then... Tongue


ctrl_interface=/var/run/wpa_supplicant
ctrl_interface_group=wheel
ap_scan=2    # seem to work only if set to 2
eapol_version=2

network={
        disabled=0
        priority=1
        ssid="NUS"
        scan_ssid=1
        key_mgmt=IEEE8021X
        eap=PEAP
        phase2="auth=MSCHAPV2"
        identity="user@nus.edu.sg"
        password="password"
        ca_cert="/path/to/ase1.pem"
}
network={
        disabled=1
        ssid="NUSOPEN"
        scan_ssid=1
        key_mgmt=NONE
        priority=2
}
network={
        disabled=1
        key_mgmt=NONE
        priority=-9999999
}

Okok... I won't leave you in the berth. Here's a howto on NUS PEAP configuration