-
Notifications
You must be signed in to change notification settings - Fork 34
/
temp-patch
executable file
·91 lines (79 loc) · 2.71 KB
/
temp-patch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/bin/bash
set -eu
bin=$(basename "$0")
usage() {
echo >&2 "Usage: $bin [-wtm] file patch [duration]"
echo >&2 "Example: $bin /etc/hosts ~/add_local_net_hosts.patch 2h"
echo >&2
echo >&2 'Temp-mount a patched file over the original'
echo >&2 'one for the specified duration (or until reboot).'
echo >&2
echo >&2 'Time should be specified as "\d+[dhms]?" (pcre),'
echo >&2 'were last letter is a unit spec (days, hours, mins. secs),'
echo >&2 'with seconds assumed if it is omitted.'
echo >&2
echo >&2 '"-w" option temp-mounts the file read/write instead'
echo >&2 'of default "ro" mode (because any there changes will be lost!).'
echo >&2 'Since "-o ro" doesnt seem to work on linux, "chmod a-w" is done instead.'
echo >&2
echo >&2 '"-t" option preserves all timestamps during the process'
echo >&2 'and "-m" inverts this behavior wrt timestamp on file after unmount.'
echo >&2 'By default, timestamps of mounted-over file are not copied'
echo >&2 'from original one and file gets mtime-bumped afterwards.'
exit "${1:-1}"
}
rw= touch_before= touch_after=t
while getopts ":hwtm" opt
do case "$opt" in
h) usage 0;;
w) rw=t;;
t) touch_before=t touch_after=;;
m) [[ -z "$touch_after" ]] && touch_after=t || touch_after=;;
*) echo >&2 -e "ERROR: Invalid option: -$OPTARG\n";;
esac; done
shift $((OPTIND-1))
[[ "$#" -ge 2 && "$#" -le 3 ]] || { echo >&2 "ERROR: Invalid number of args ($#)"; exit 1; }
[[ "$#" -eq 3 ]] && time=$3 || time=
dst=$1 patch=$2
[[ "$UID" -eq 0 ]] || {
echo >&2 "ERROR: Can't work as non-root (need to mount stuff)"
exit 1
}
[[ -z "$time" ]] || {
declare -A units=( ['d']=$((2600*24)) ['h']=3600 ['m']=60 ['s']=1 )
if [[ "$time" =~ ^([0-9.]+)([dhms]?)$ ]]
then
[[ -z "${BASH_REMATCH[2]}" ]] && u=1 || u=${units[${BASH_REMATCH[2]}]}
time=$(bc -l <<< "scale=2; ${BASH_REMATCH[1]} * $u")
else
echo >&2 "ERROR: Invalid duration format - '$time' (see --help)"
exit 1
fi
}
patch_opts=( --batch )
mount_opts=( --bind )
[[ -n "$rw" ]] || mount_opts+=( -o ro )
tempfile=$(mktemp /tmp/temp_patch.src.XXXXXX)
trap '[[ -n "$tempfile" ]] && rm -f "$tempfile"' EXIT
cat "$dst" >"$tempfile"
patch -s --dry-run "${patch_opts[@]}" "$tempfile" "$patch"\
&& patch -s "${patch_opts[@]}" "$tempfile" "$patch"\
|| { echo >&2 "Patch failed"; exit 1; }
chown --reference "$dst" "$tempfile"
chmod --reference "$dst" "$tempfile"
[[ -n "$rw" ]] || chmod a-w "$tempfile"
[[ -z "$touch_before" ]] || touch --reference "$dst" "$tempfile"
[[ -z "$touch_after" ]] || touch "$dst"
# set -x
mount "${mount_opts[@]}" "$tempfile" "$dst"
[[ -z "$time" ]] || {
(
sleep "$time"
if [[ -n "$touch_after" ]]; then
umount "$dst" 2>/dev/null || { umount -l "$dst"; sleep 3; }
touch "$dst"
else umount -l "$dst"
fi
) &
disown
}