|
#!/bin/bash |
|
|
|
# NFSv3 File Locking Test Script |
|
# Usage: nfs-lock.sh <command> <file> [timeout] |
|
# Commands: lock, unlock, check, test |
|
|
|
set -e |
|
|
|
SHARED_DIR="${SHARED_DIR:-/shared}" |
|
LOCK_DIR="$SHARED_DIR/locks" |
|
LOG_FILE="$SHARED_DIR/lock.log" |
|
|
|
# Ensure lock directory exists |
|
mkdir -p "$LOCK_DIR" |
|
|
|
usage() { |
|
echo "Usage: $0 <command> <file> [timeout]" |
|
echo "Commands:" |
|
echo " lock <file> [timeout] - Acquire exclusive lock on file (default timeout: 10s, use 'forever' or '0' for persistent lock)" |
|
echo " unlock <file> - Release lock on file" |
|
echo " check <file> - Check if file is locked" |
|
echo " test <file> - Interactive test mode" |
|
echo " list - List all active locks" |
|
echo " cleanup - Clean up stale locks" |
|
echo "" |
|
echo "Examples:" |
|
echo " $0 lock myfile.txt 30" |
|
echo " $0 check myfile.txt" |
|
echo " $0 unlock myfile.txt" |
|
exit 1 |
|
} |
|
|
|
log_message() { |
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$(hostname)] $*" | tee -a "$LOG_FILE" |
|
} |
|
|
|
acquire_lock() { |
|
local file="$1" |
|
local timeout="${2:-10}" |
|
local lock_file="$LOCK_DIR/${file}.lock" |
|
local pid_file="$LOCK_DIR/${file}.pid" |
|
|
|
if [ "$timeout" = "forever" ] || [ "$timeout" = "0" ]; then |
|
log_message "Attempting to acquire persistent lock on '$file' (no timeout)" |
|
|
|
# Create a subshell that will hold the lock even after script exits |
|
( |
|
if flock -x 200; then |
|
echo $$ >"$pid_file" |
|
echo "$(hostname)" >"$lock_file" |
|
log_message "Successfully acquired persistent lock on '$file', background PID: $$" |
|
echo "Persistent lock acquired on '$file'. Background PID: $$, Host: $(hostname)" |
|
echo "Lock will persist until process is killed or system reboot" |
|
|
|
# Sleep forever to keep the lock |
|
while true; do |
|
sleep 3600 |
|
done |
|
else |
|
log_message "Failed to acquire persistent lock on '$file'" |
|
echo "Failed to acquire persistent lock on '$file'" |
|
exit 1 |
|
fi 200>"$lock_file" |
|
) & |
|
|
|
local bg_pid=$! |
|
echo "Background lock process started with PID: $bg_pid" |
|
echo "To release lock manually: kill $bg_pid" |
|
else |
|
log_message "Attempting to acquire lock on '$file' (timeout: ${timeout}s)" |
|
# Use flock with timeout |
|
if timeout "$timeout" flock -x 200; then |
|
echo $$ >"$pid_file" |
|
echo "$(hostname)" >"$lock_file" |
|
log_message "Successfully acquired lock on '$file'" |
|
echo "Lock acquired on '$file'. PID: $$, Host: $(hostname)" |
|
|
|
# Keep the lock open by reading from stdin |
|
echo "Lock is active. Press ENTER to release lock..." |
|
read -r |
|
release_lock "$file" |
|
else |
|
log_message "Failed to acquire lock on '$file' (timeout or already locked)" |
|
echo "Failed to acquire lock on '$file'" |
|
return 1 |
|
fi 200>"$lock_file" |
|
fi |
|
} |
|
|
|
release_lock() { |
|
local file="$1" |
|
local lock_file="$LOCK_DIR/${file}.lock" |
|
local pid_file="$LOCK_DIR/${file}.pid" |
|
|
|
if [ -f "$lock_file" ]; then |
|
rm -f "$lock_file" "$pid_file" |
|
log_message "Released lock on '$file'" |
|
echo "Lock released on '$file'" |
|
else |
|
echo "No lock found for '$file'" |
|
fi |
|
} |
|
|
|
check_lock() { |
|
local file="$1" |
|
local lock_file="$LOCK_DIR/${file}.lock" |
|
local pid_file="$LOCK_DIR/${file}.pid" |
|
|
|
# Try to acquire a non-blocking exclusive lock |
|
if flock -n -x 200; then |
|
echo "File '$file' is NOT locked" |
|
return 1 |
|
else |
|
# File is locked, try to get metadata |
|
if [ -f "$lock_file" ] && [ -f "$pid_file" ]; then |
|
local lock_host=$(cat "$lock_file" 2>/dev/null || echo "unknown") |
|
local lock_pid=$(cat "$pid_file" 2>/dev/null || echo "unknown") |
|
echo "File '$file' is LOCKED by host '$lock_host', PID '$lock_pid'" |
|
else |
|
echo "File '$file' is LOCKED (no metadata available)" |
|
fi |
|
return 0 |
|
fi 200>"$lock_file" |
|
} |
|
|
|
list_locks() { |
|
echo "Active locks in $LOCK_DIR:" |
|
if [ -n "$(ls "$LOCK_DIR"/*.lock 2>/dev/null)" ]; then |
|
for lock_file in "$LOCK_DIR"/*.lock; do |
|
if [ -f "$lock_file" ]; then |
|
local base_name=$(basename "$lock_file" .lock) |
|
local lock_host=$(cat "$lock_file" 2>/dev/null || echo "unknown") |
|
local pid_file="$LOCK_DIR/${base_name}.pid" |
|
local lock_pid=$(cat "$pid_file" 2>/dev/null || echo "unknown") |
|
echo " $base_name -> Host: $lock_host, PID: $lock_pid" |
|
fi |
|
done |
|
else |
|
echo " No active locks" |
|
fi |
|
} |
|
|
|
cleanup_locks() { |
|
echo "Cleaning up stale locks..." |
|
local cleaned=0 |
|
for lock_file in "$LOCK_DIR"/*.lock; do |
|
if [ -f "$lock_file" ]; then |
|
local base_name=$(basename "$lock_file" .lock) |
|
local pid_file="$LOCK_DIR/${base_name}.pid" |
|
local lock_pid=$(cat "$pid_file" 2>/dev/null || echo "") |
|
|
|
if [ -n "$lock_pid" ] && ! kill -0 "$lock_pid" 2>/dev/null; then |
|
rm -f "$lock_file" "$pid_file" |
|
echo " Removed stale lock for '$base_name' (PID $lock_pid no longer exists)" |
|
cleaned=$((cleaned + 1)) |
|
fi |
|
fi |
|
done 2>/dev/null |
|
echo "Cleaned up $cleaned stale locks" |
|
} |
|
|
|
interactive_test() { |
|
local file="$1" |
|
|
|
echo "=== NFS Lock Interactive Test ===" |
|
echo "File: $file" |
|
echo "Host: $(hostname)" |
|
echo "PID: $$" |
|
echo "" |
|
|
|
while true; do |
|
echo "Choose an action:" |
|
echo "1) Check lock status" |
|
echo "2) Acquire lock (10s timeout)" |
|
echo "3) Acquire lock (30s timeout)" |
|
echo "4) Acquire persistent lock (forever)" |
|
echo "5) Release lock" |
|
echo "6) List all locks" |
|
echo "7) View lock log" |
|
echo "8) Exit" |
|
echo -n "Enter choice [1-8]: " |
|
read -r choice |
|
|
|
case $choice in |
|
1) check_lock "$file" || true ;; |
|
2) acquire_lock "$file" 10 ;; |
|
3) acquire_lock "$file" 30 ;; |
|
4) acquire_lock "$file" forever ;; |
|
5) release_lock "$file" ;; |
|
6) list_locks ;; |
|
7) |
|
echo "=== Lock Log ===" |
|
tail -20 "$LOG_FILE" 2>/dev/null || echo "No log file found" |
|
;; |
|
8) |
|
echo "Exiting..." |
|
break |
|
;; |
|
*) echo "Invalid choice" ;; |
|
esac |
|
echo "" |
|
done |
|
} |
|
|
|
# Main script logic |
|
if [ $# -lt 1 ]; then |
|
usage |
|
fi |
|
|
|
command="$1" |
|
file="$2" |
|
timeout="$3" |
|
|
|
case "$command" in |
|
"lock") |
|
if [ -z "$file" ]; then |
|
echo "Error: file name required" |
|
usage |
|
fi |
|
acquire_lock "$file" "$timeout" |
|
;; |
|
"unlock") |
|
if [ -z "$file" ]; then |
|
echo "Error: file name required" |
|
usage |
|
fi |
|
release_lock "$file" |
|
;; |
|
"check") |
|
if [ -z "$file" ]; then |
|
echo "Error: file name required" |
|
usage |
|
fi |
|
check_lock "$file" |
|
;; |
|
"test") |
|
if [ -z "$file" ]; then |
|
echo "Error: file name required" |
|
usage |
|
fi |
|
interactive_test "$file" |
|
;; |
|
"list") |
|
list_locks |
|
;; |
|
"cleanup") |
|
cleanup_locks |
|
;; |
|
*) |
|
echo "Error: unknown command '$command'" |
|
usage |
|
;; |
|
esac |