#!/bin/sh # this many seconds we will wait after sending signal terminate_timeout="20" kill_timeout="5" # awk regexp for filesystems to remount readonly (you may, for example, add '|ntfs' or smth. other) # you may also exclude smth., for example, if you have some external FS mounted r/w on "/", # then filesystems_regexp='/^(ext3|vfat)$/ && $2 != "/"' (otherwise this script will try to terminate almost everything, including itself) filesystems_regexp='/^(ext3|vfat)$/' saved_PATH="$PATH" PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin" # Problems not solved in this script. # # 1. Overlapping mounts. # # If we mount one FS on, say, "/path/to/dir" and then mount another FS on the same "/path/to/dir" or on "/path/to", or on "/path", # then `fuser -m "/path/to/dir"` won't detect open files on first FS. We could use device name instead of mount point, # but `fuser` from busybox doesn't treat device files specially, just shows processes, that have open files on "/dev". # But one can use `fuser` from "psmisc" package, # which treats devices as expected, showing processes that have files open on filesystem of that device. # Here comes the second part of this problem: `mount -o remount,ro ...` will remount only second FS # (with any combinations of parameters (device + mountpath)). # Solution is to use `umount "/path[/to[/dir]]"` to unmount second FS and then do anything with the first FS. Or to avoid such configurations. # # 2. Unusual swap files. # # If we use swap file (not partition) with newline char in name, then script will not be able to deactivate such swap file, # which means that it will probably fail to remount-readonly filesystem containing that file. # Solution: deactivate that file manually (or add that command to this script near other `swapoff` commands). Or, again, avoid such configurations. # # usage `get_pids escapes [mounts ...]` # param: escapes = {true|false} - if mounts have octal escapes # param: mounts ... - optional list of mountpoints # returns: (prints to stdout) PIDs of processes, that have open files on any of the mounts get_pids() { [ "$#" -gt 1 ] || return 0 escapes="$1" shift if $escapes ; then for path in "$@" ; do # convert octal escapes (\0xxx) to actual symbols path=`echo -en "$path"` fuser -m "$path" 2>/dev/null done | tr ' ' '\n' | sort -u else fuser -m "$@" 2>/dev/null fi } # include `signal_and_wait` function . /usr/local/sbin/signal_and_wait.inc.sh # stop services if [ -x "/opt/etc/init.d/rc.unslung" ] ; then echo "Stopping services" PATH="${saved_PATH}" /opt/etc/init.d/rc.unslung "stop" fi # create mountpoints list mounts="" for path in `cat "/proc/mounts" | awk '$3 ~ '"${filesystems_regexp}"' && $4 !~ /(^|,)ro(,|$)/ {print($2)}'` do # reverse order mounts="${path} ${mounts}" done # flush all data to disks echo "Flushing file system buffers" sync if [ -n "$mounts" ] ; then # convert strings like /foo/bar\0401 to /foo/bar\00401 (which means "/foo/bar 1" - those are octal escapes (\xxx)) has_escapes=false if echo "$mounts" | grep -q '\\' ; then has_escapes=true mounts=`echo "$mounts" | sed -r 's/\\\\/\\\\0/g'` fi # whatever dir we are run from, change to / (or we may terminate ourself) cd / # in case terminal closes trap '[ -t 0 ] && exec 0/dev/null ; [ -t 2 ] && exec 2>/dev/null' HUP # in case we are being redirected trap 'exec 1>/dev/null 2>&1' PIPE # in case we have open files (fd 0..9), redirect 0..2 to /dev/null and close everything else trap 'cd /proc/self/fd/ for fd in * ; do [ -e "./$fd" ] || continue case "$fd" in 0 ) exec 0/dev/null" ;; ? ) eval "exec $fd<&-" ;; esac done cd /' TERM # terminate all remaining processes echo "Asking all remaining processes to terminate" fate="ended" pids=`get_pids ${has_escapes} $mounts` start_time=`date '+%s'` if ! signal_and_wait "TERM" "${terminate_timeout}" $pids ; then # kill all remaining processes echo "Killing all remaining processes" fate="killed" pids=`get_pids ${has_escapes} $mounts` signal_and_wait "KILL" "${kill_timeout}" $pids || fate="(probably) ${fate}" fi end_time=`date '+%s'` echo "All processes ${fate} within $((${end_time} - ${start_time})) second(s)." fi # deactivating swap before remounting filesystems readonly - swap may use swapfile, not swap partition echo "Deactivating swap" swapoff -a # if there are any swaps not listed in /etc/fstab cat "/proc/swaps" | tail -n "+2" | sed -r 's/ +[^ ]+$//' | \ while read path ; do swapoff "$path" done if [ -n "$mounts" ] ; then # remount readonly if ${has_escapes} ; then # this is all because of mount(1) bug, so we can't just call `mount -o remount,ro "$path"` # output "/proc/mounts" in reversed order, # convert strings like /foo/bar\0401 to /foo/bar\00401 (which means "/foo/bar 1" - those are octal escapes (\xxx)), # add "remount,ro" to options, # print $1, $2, $4 fields (device, mountpath, options) cat "/proc/mounts" | awk '{printf("%d ", NR); print($0)}' | sort -nr | cut -f "2-" -s -d " " | \ awk '$3 ~ '"${filesystems_regexp}"' && $4 !~ /(^|,)ro(,|$)/ { gsub(/\\/, "\\0", $0); $4 = "remount," $4 ",ro"; gsub(/,rw,/, ",", $4); gsub(/,,+/, ",", $4); print($1, $2, $4)}' | \ while read -r dev path opts ; do # convert octal escapes (\0xxx) to actual symbols dev=`echo -en "$dev"` path=`echo -en "$path"` opts=`echo -en "$opts"` echo -n "Remounting ${path} readonly... " err=`mount -o "$opts" "$dev" "$path" 2>&1` && echo "success" || echo "fail: $err" done else for path in $mounts ; do echo -n "Remounting ${path} readonly... " err=`mount -o "remount,ro" "$path" 2>&1` && echo "success" || echo "fail: $err" done fi fi # just in case PATH="${saved_PATH}"