Check if NFS Directory Mounted without Large Hangs on Failure - linux

I'm writing a shell script to check whether or not certain nfs mounts can be seen by nodes in a cluster.
The script works by doing an ls /nfs/"machine" |wc -l and if it's greater than 0 it will pass the test. My main concern with this solution is how long ls will hang if a disk is not mounted.
I had a go at the solution in this question "bash checking directory existence hanging when NFS mount goes down" but the results did not correspond with what was actually mounted.
I also tried doing a df -h /nfs/"machine" but that has a large hang if the disk isn't mounted.
Basically, is there an alternate way which can let me know if a disk is mounted or not without large hangs?
Alternatively, is there a way of restricting the time that a command can be executed for?
Thanks in advance!

Ok I managed to solve this using the timeout command, I checked back here to see that BroSlow updated his answer with a very similar solution. Thank you BroSlow for your help.
To solve the problem, the code I used is:
if [[ `timeout 5s ls /nfs/machine |wc -l` -gt 0 ]] ; then
echo "can see machine"
else
echo "cannot see machine"
fi
I then reduced this to a single line command so that it could be run through ssh and put inside of a loop (to loop through hosts and execute this command).

Couple of possibilities:
1)
find /nfs/machine -maxdepth 0 -empty should be a lot faster than ls /nfs/machine, though I'm not sure that's the problem in this case (also not sure sleep is needed, but might be some offset.
if [[ $(find /nfs/machine -maxdepth 0 -empty 2> /dev/null) == "" ]]; then
sleep 1 && [[ $(mount) == *"/nfs/machine"* ]] && echo "mounted" || echo "not mounted"
else
echo "mounted"
fi
2)
timeout 10 ls -A /nfs/machine | wc -l
if [[ $? > 0 ]]; then
echo "mounted"
else
echo "not mounted"
fi

Related

Bash Script - Check mounted devices

What I'm trying to do -
In the end, I want a script that checks for 3 devices, an SD card, Backup1 & Backup2. They've all been set to auto-mount at their respective mountpoints. The script should check for SD card first, if this fails, then a warning should be sent, and nothing more. If SD is okay, but only one backup is mounted, then ask for confirmation to go ahead with rsync to mounted backup. If all devices are mounted, then rsync from SD card to both backups.
Currently I'm just trying to get the device check nailed, using echo commands. Here's what I have (after multiple attempts) -
if ! mount | grep /media/card >/dev/null
then
echo "ERROR: SD card not mounted, aborting"
else
if ! mount | grep /media/backup >/dev/null
then
if ! mount | grep /media/backup2 >/dev/null
then
echo "ERROR: No backup devices"
else
echo "CAUTION: Backup_2 missing, Backup_1 OKAY"
fi
else
if ! mount | grep /media/backup2 /dev/null
then
echo "CAUTION: Backup_1 missing, Backup_2 OKAY"
else
echo "SUCCESS: All devices OKAY"
fi
fi
fi
Which isn't working. I'm getting confused by all the nested 'if's and surely there's a simpler way? Perhaps something that checks each device independently, then returns values that equate to the various combinations (0=no devices, 1=sd only, 2=sd & backup1, 3=sd & backup 2, 4 = all okay) which then is read and decides next part of script to run? If not, where has this way gone wrong?
Any help is appreciated
Use mountpoint, which returns success (0) if the path is a mounted device, and 1 otherwise:
#!/bin/bash
check_mountpoint(){
if mountpoint -q $1
then
printf "%s\n" "$1 is mounted"
return 0
else
printf "%s\n" "$1 is not mounted"
return 1
fi
}
check_mountpoint /media/sd
if [ $? -gt 0 ]
then
printf "%s\n" "aborting"
exit 0
fi
check_mountpoint /media/backup1
check_mountpoint /media/backup2
this is your code corrected, i hope it will help.
if mountpoint -q /media/sd
then
if mountpoint -q /media/backup
then
if mountpoint -q /media/backup2
then
echo "SUCCESS: All devices OKAY"
# run your command here
else
echo "CAUTION: Backup_2 missing, Backup_1 OKAY, SD OKAY"
# run your command here
fi
else
if mountpoint -q /media/backup2
then
echo "CAUTION: Backup_1 missing, Backup_2 OKAY, SD OKAY"
# run your command here
else
echo "SD OKAY , BACKUP Devices missing"
fi
fi
else
echo "SD is missing , Exit"
fi
Solved! Part of the problem was using mount | grep /media/backup > /dev/null as there's an issue with similar names, so /media/backup2 being mounted meant it was counting both devices as mounted.
Using grep -q /media/backup2\ /proc/mounts works in a similar way, or at least produces the same outcome, just without the naming issue.
I've also changed the way the if statements work, it now checks for SD card, if that fails, then it aborts. It then checks for which devices are mounted with a fairly simple flow.
Here's the code:
GNU nano 2.2.6 File: backupSD2.sh Modified
#!/bin/bash
#Check for SD
if ! grep -q /media/card\ /proc/mounts
then
echo "ERROR: NO SD CARD"
exit 0
else
echo "SD OKAY..."
fi
#Check for Backup Devices
if (( grep -q /media/backup\ /proc/mounts ) && ( grep -q /media/backup2\ /proc/mounts ))
then
echo "Both Backups mounted"
elif grep -q /media/backup\ /proc/mounts
then
echo "CAUTION: Backup_2 missing, Backup_1 OKAY"
elif grep -q /media/backup2\ /proc/mounts
then
echo "CAUTION: Backup_1 missing, Backup_2 OKAY"
else
echo "ERROR: NO BACKUP DEVICES"
exit 0
fi
After the 'echo' commands is where I'll be putting the rsync commands.
The final exit 0 is probably unnecessary, but I don't think it's doing any harm and reassures that it's not going to try any more commands.
Check for 1 required mounted source AND at least 1 required mounted target
The best aproach for this is to build an array of mounted devices, then evaluate de count of elements in array.
Introduction
1. Care! Using grep -q "$dev" /proc/mounts will fail to found spaced mount points!
/media/spaced target
in /proc/mounts will appear as
/media/spaced\040target
command printf -v varname '%b' $varname could help!
2. Using bash array and associative array to avoid fork
Instead of doing many fork to grep, (browsing same file many time), you could use bash for the job.
3. Parallelizing rsync on many targets
Using backgrounded jobs and wait -n -p <VARNAME>, you could do rsync on many targets simultaneously.
4. Using /proc/self/mounts instead of /proc/mounts
After having a look at man 5 proc.
Here is a script, within:
you could configure as many backup targets you want.
if all targets are mounted, script go on
if no targets are mounted, script stop and
if at least one target is mounted but not all, user is asked for continuing.
(this could be avoided by using -y/-n optional flag)
On second part, all rsync are run as background job (parallelized),
then script wait for all rsync to be done,
then print each result code with corresponding target,
before finish
The script:
#!/bin/bash
source='/media/card'
backups=(
/media/backup
/media/backup2
/media/other\ backup
)
die() {
echo "$0[$$] ERROR: $#" >&2
exit 1
}
warn() {
echo "$0[$$] WARNING: $#" >&2
}
declare -A tobemounted="(['$source']='source')"
for target in "${backups[#]}";do
tobemounted["$target"]=backup
done
mountedbk=()
while read -r _ mpnt _;do
printf -v mpnt %b $mpnt
if [[ -v tobemounted["$mpnt"] ]] ;then
[[ ${tobemounted["$mpnt"]} == backup ]] && mountedbk+=("$mpnt")
unset tobemounted["$mpnt"]
fi
done </proc/self/mounts
[[ -v tobemounted["$source"] ]] && die "Source '$source' not mounted."
for missing in "${!tobemounted[#]}";do
warn "Backup '$missing' not mounted."
done
(( ${#mountedbk[#]} == 0 )) && die "No backup present."
if (( ${#mountedbk[#]} < ${#backups[#]} ));then
printf -v msg 'Only %d / %d backups targets are present.' \
${#mountedbk[#]} ${#backups[#]}
warn "$msg"
case $* in
*-n* ) die Exit now. ;;
*-y* ) warn Process anyway. ;;
* ) read -rsn 1 -p 'Process anyway? (N/y): ' ans
[[ "${ans//[yY]}" ]] && { echo No; die 'User abort.';}
echo Yes ;;
esac
fi
pids=() resultmsg=(success)
for target in "${mountedbk[#]}";do
echo rsync "$source/." "$target/." &
pids[$!]="$target"
done
while ((${#pids[#]}));do
wait -np pid
printf 'backup to "%s" end with %s.\n' \
"${pids[pid]}" "${resultmsg[$?]:-error}"
unset 'pids[pid]'
done
Replace line:
echo rsync "$source/." "$target/." &
by somethine like:
rsync <your rsync options> "$source/SourcePath/." "$target/TargetPath/." &
Outputs could look like:
When source not ready:
$ ./backups.sh
./backups.sh[3721068] ERROR: Source '/media/card' not mounted.
When none of the targets are ready.
$ ./backups.sh
./backups.sh[3729071] WARNING: Backup '/media/backup' not mounted.
./backups.sh[3729071] WARNING: Backup '/media/backup2' not mounted.
./backups.sh[3729071] WARNING: Backup '/media/other backup' not mounted.
./backups.sh[3729071] ERROR: No backup present.
When some but not all targets are ready.
$ ./backups.sh
./backups.sh[3725569] WARNING: Backup '/media/backup2' not mounted.
./backups.sh[3725569] WARNING: Backup '/media/other backup' not mounted.
./backups.sh[3725569] WARNING: Only 1 / 3 backups targets are present.
Process anyway? (N/y): Yes
backup to "/media/backup" end with success.
$ ./backups.sh
./backups.sh[3729355] WARNING: Backup '/media/backup' not mounted.
./backups.sh[3729355] WARNING: Backup '/media/other backup' not mounted.
./backups.sh[3729355] WARNING: Only 1 / 3 backups targets are present.
Process anyway? (N/y): Yes
backup to "/media/backup2" end with success.
$ ./backups.sh
./backups.sh[3854708] WARNING: Backup '/media/backup2' not mounted.
./backups.sh[3854708] WARNING: Backup '/media/other backup' not mounted.
./backups.sh[3854708] WARNING: Only 1 / 3 backups targets are present.
Process anyway? (N/y): No
./backups.sh[3854708] ERROR: User abort.
$ ./backups.sh -y
./backups.sh[3734321] WARNING: Backup '/media/backup' not mounted.
./backups.sh[3734321] WARNING: Backup '/media/other backup' not mounted.
./backups.sh[3734321] WARNING: Only 1 / 3 backups targets are present.
./backups.sh[3734321] Process anyway.
backup to "/media/backup2" end with success.
$ ./backups.sh -n
./backups.sh[3854855] WARNING: Backup '/media/backup2' not mounted.
./backups.sh[3854855] WARNING: Backup '/media/other backup' not mounted.
./backups.sh[3854855] WARNING: Only 1 / 3 backups targets are present.
./backups.sh[3854855] ERROR: Exit now.
When all targets are ready.
$ ./backups.sh
backup to "/media/backup" end with success.
backup to "/media/backup2" end with success.
backup to "/media/other\ backup" end with success.

Linux - Directory Check

I am trying to write a script to check whether a directory exists and it has the adequate file size. So far I've managed to get a directory check but I've not had much luck checking for the size as well.
BASEDIR needs to be 8GB and WORKDIR 4GB
#!/bin/sh
BASEDIR=/opt/app
WORKDIR=$BASEDIR/program/work
if [[ -d "${BASEDIR}" && ! -L "${BASEDIR}" ]] ; then
echo "Confirmed ${BASEDIR}"
fi
if [[ -d "${WORKDIR}" && ! -L "${WORKDIR}" ]] ; then
echo "Confirmed ${WORKDIR}"
fi
R,
I'm assuming you want to ensure 8G (4G) of free space is available in the chosen directory, not that it is already populated with 8G (4G) worth of files. (See Inian's answer for the latter case).
You can use df to return the available space for a given folder.
avail = `df "$BASEDIR" -k --output=avail | tail -1`
if (($avail >= 8*1024*1024)); then echo "Good"; else echo "Not Good"; fi

Linux Shell Script: How to detect NFS Mount-point (or the Server) is dead?

Generally on NFS Client, how to detect the Mounted-Point is no more available or DEAD from Server-end, by using the Bash Shell Script?
Normally i do:
if ls '/var/data' 2>&1 | grep 'Stale file handle';
then
echo "failing";
else
echo "ok";
fi
But the problem is, when especially the NFS Server is totally dead or stopped, even the, ls command, into that directory, at Client-side is hanged or died. Means, the script above is no more usable.
Is there any way to detect this again please?
"stat" command is a somewhat cleaner way:
statresult=`stat /my/mountpoint 2>&1 | grep -i "stale"`
if [ "${statresult}" != "" ]; then
#result not empty: mountpoint is stale; remove it
umount -f /my/mountpoint
fi
Additionally, you can use rpcinfo to detect whether the remote nfs share is available:
rpcinfo -t remote.system.net nfs > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo Remote NFS share available.
fi
Added 2013-07-15T14:31:18-05:00:
I looked into this further as I am also working on a script that needs to recognize stale mountpoints. Inspired by one of the replies to "Is there a good way to detect a stale NFS mount", I think the following may be the most reliable way to check for staleness of a specific mountpoint in bash:
read -t1 < <(stat -t "/my/mountpoint")
if [ $? -eq 1 ]; then
echo NFS mount stale. Removing...
umount -f -l /my/mountpoint
fi
"read -t1" construct reliably times out the subshell if stat command hangs for some reason.
Added 2013-07-17T12:03:23-05:00:
Although read -t1 < <(stat -t "/my/mountpoint") works, there doesn't seem to be a way to mute its error output when the mountpoint is stale. Adding > /dev/null 2>&1 either within the subshell, or in the end of the command line breaks it. Using a simple test: if [ -d /path/to/mountpoint ] ; then ... fi also works, and may preferable in scripts. After much testing it is what I ended up using.
Added 2013-07-19T13:51:27-05:00:
A reply to my question "How can I use read timeouts with stat?" provided additional detail about muting the output of stat (or rpcinfo) when the target is not available and the command hangs for a few minutes before it would time out on its own. While [ -d /some/mountpoint ] can be used to detect a stale mountpoint, there is no similar alternative for rpcinfo, and hence use of read -t1 redirection is the best option. The output from the subshell can be muted with 2>&-. Here is an example from CodeMonkey's response:
mountpoint="/my/mountpoint"
read -t1 < <(stat -t "$mountpoint" 2>&-)
if [[ -n "$REPLY" ]]; then
echo "NFS mount stale. Removing..."
umount -f -l "$mountpoint"
fi
Perhaps now this question is fully answered :).
The final answers give by Ville and CodeMonkey are almost correct. I'm not sure how no one noticed this, but a $REPLY string having content is a success, not a failure. Thus, an empty $REPLY string means the mount is stale. Thus, the conditional should use -z, not -n:
mountpoint="/my/mountpoint"
read -t1 < <(stat -t "$mountpoint" 2>&-)
if [ -z "$REPLY" ] ; then
echo "NFS mount stale. Removing..."
umount -f -l "$mountpoint"
fi
I have ran this multiple times with a valid and invalid mount point and it works. The -n check gave me reverse results, echoing the mount was stale when it was absolutely valid.
Also, the double bracket isn't necessary for a simple string check.
Building off the answers here, I found some issues in testing that would output bad data due to how the $REPLY var would get updated (or not, if the result was empty), and the inconsistency of the stat command as provided in the answers.
This uses the stat command to check the FS type which responds to changes pretty fast or instant, and checks the contents of $REPLY to make sure the fs is NFS [ ref: https://unix.stackexchange.com/questions/20523/how-to-determine-what-filesystem-a-directory-exists-on ]
read -t1 < <(timeout 1 stat -f -c %T "/mnt/nfsshare/" 2>&-);if [[ ! "${REPLY}" =~ "nfs" ]];then echo "NFS mount NOT WORKING...";fi

verifing some arguments in bash

I'm doing a verification in BASH
if [ !( ( -e $f ) || ($# -lt 2) || ( -d $f ) ) ]; then
exit 0
fi
I'm trying to see if the file existes or if it's a directory or enough args are passed via terminal. Can I do this or must it be done in another way?
I gather from your question you want to reject anything that is not an ordinary file. In that case, the test for a directory is redundant if you use -f instead of -e.
if [[ ! -f "$f" || $# -lt 2 ]]; then
exit 0
fi
By the way, if this is an error exit, you should exit with something other than 0 - 0 indicates success in most cases; exit 1 would be better.
It looks like you have at least a few problems here.
The syntax for your test will require the more modern double [[ command instead of the legacy single [ command.
You're missing a fi at the end. (I edited this one in for you, with thanks to whoever modded it up.)
You may have the sense of your test reversed, but I'm not sure. It depends on what the rest of your script looks like.

Linux Single Instance Kill if running too long

I am using the following to keep a single instance of a script running on my server. I have a cronjob to run this every minute.
How do I daemonize an arbitrary script in unix?
#!/bin/bash
if [[ $# < 1 ]]; then
echo "Name of pid file not given."
exit
fi
# Get the pid file's name.
PIDFILE=$1
shift
if [[ $# < 1 ]]; then
echo "No command given."
exit
fi
echo "Checking pid in file $PIDFILE."
#Check to see if process running.
PID=$(cat $PIDFILE 2>/dev/null)
if [[ $? = 0 ]]; then
ps -p $PID >/dev/null 2>&1
if [[ $? = 0 ]]; then
echo "Command $1 already running."
exit
fi
fi
# Write our pid to file.
echo $$ >$PIDFILE
# Get command.
COMMAND=$1
shift
# Run command
$COMMAND "$*"
Now I found out that my script had hung for some reason and therefore it was stuck. I'd like a way to check if the $PIDFILE is "old" and if so, kill the process. I know that's possible (check the timestamp on the file) but I don't know the syntax or if this is even a good idea. Also, when this script is running, the CPU should be pretty heavily used. If it hangs (rare but it happened at least once so far), the CPU usage drops to 0%. It would be nice if I could check that the process is really hung/not active, but I don't know if there's an easy way to do that (and I don't want to have many false positives where it gets killed but it's running fine).
To answer the question in your title, which seems quite different from your problem, use timeout.
Now, for your problem, I don't see where it could hang, unless you gave it a fifo queue for the pid file. Now, to run and respawn, you can just run this script once, on startup:
#!/bin/bash
while /bin/true; do
"$#"
wait
done
Which brings up another bug in the code you got from the other question: "$*" will pass all the arguments to the script as a single argument; without the quotes it'll split arguments with white space. "$#" will pass them individually and handling white space properly.
Call with /path/to/script command [argument]....

Resources