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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
|
#!/bin/bash
source /etc/squashfu.conf
# Informational output w/ happy colors
debug () {
[[ $DEBUG ]] && echo -e '\033[1;33mDEBUG ::\033[1;m ' $*
}
info () {
echo -e '\033[1;34m::\033[1;m ' $*
}
die () {
echo -e '\033[1;31mFATAL ::\033[1;m ' $* >&2
exit 1
}
mount_squash () {
# Arguments: none
# Returns: return code of mount command
mount -t loop,ro "$SEED" "${BKUP_ROOT}/ro"
return $?
}
mount_union_with_bins () {
# Arguments: numbers of bins to be mounted (variable number)
# Returns: 0 on successful mount, non-zero on failure
# Mount first as rw, shift, and mount the rest ro
branches="br=${BINS_DIR}/$1=rw:"; shift
for bin in $*; do
branches="${branches}/bins/$bin=ro:"
done
branches="${branches}${BKUP_ROOT}/ro=ro"
debug "mount -t aufs none "${BKUP_ROOT}/rw" -o udba=reval,$branches"
mount -t aufs none "${BKUP_ROOT}/rw" -o udba=reval,$branches
return $?
}
get_next_available_bin () {
# Arguments: none
# Returns: Numeric value of the next unused bin
return $[ $(cut -d: -f1 "$BINVENTORY" | sort -n | tail -1) + 1 ]
}
sweep_bins () {
# Arguments: none
# Returns: none
count=1
# Make sure bins are numbered in order, clean up if not. In other words,
# if we have 10 bins, make sure they're ordered 1 through 10.
for bin in "${BINS_DIR}/*"; do
if [[ ! -d "${BINS_DIR}/$count" ]]; then
high_bin=$(ls "${BINS_DIR}" | sort -n | tail -1)
mv "${BINS_DIR}/$high_bin" "${BINS_DIR}/$count"
sed -i "/^$high_bin:/s/^$high_bin:/$count:/" "$BINVENTORY"
fi
count=$[ $count + 1 ]
done
}
call_rsync () {
# Arguments: none
# Returns: return code from rsync
# Parse includes and excludes from heredocs in config
INCLUDES=($(sed -n '/^<<INCLUDES$/,/^INCLUDES$/p' $CONFIG | grep -vE "^<*INCLUDES$"))
EXCLUDES=($(sed -n '/^<<EXCLUDES$/,/^EXCLUDES$/p' $CONFIG | \
grep -vE "^<*EXCLUDES$" | \
sed -n 's/\(.*\)/--exclude "\1"/p'))
# rsync source to $BKUP_ROOT/rw
debug "Rsync executing with:"
debug ": Options: ${RSYNC_OPTS[@]}"
debug ": Includes: ${INCLUDES[@]}"
debug ": Excludes: ${EXCLUDES[@]}"
rsync ${RSYNC_OPTS[@]} ${INCLUDES[@]} ${EXCLUDES[@]} "${BKUP_ROOT}/rw"
return $?
}
create_new_bin () {
# Arguments: 1, the number of the bin to create
# Returns: 0 on success, non-zero on error
# Create new directory, fail if it exists (something's wrong)
mkdir "${BINS_DIR}/$1"
if [[ $? -ne 0 ]]; then
return $?
fi
# Update binventory with new bin name and timestamp
echo "${1}:$(stat -c %u ${BINS_DIR}/$1)" >> "$BINVENTORY"
# If write to bin list fails, remove diretory and exit
if [[ $? -ne 0 ]]; then
rmdir "${BINS_DIR}/${1}"
die "Error writing to '$BINVENTORY'"
fi
}
# Unmounting functions
unmount_union () {
# Args: none
# Returns: return code from umount
umount "${BKUP_ROOT}/rw"
return $?
}
unmount_seed () {
# Args: none
# Returns: return code from umount
umount "${BKUP_ROOT}/ro"
return $?
}
unmount_all () {
# Args: none
# Returns: none
# Union MUST be unmounted first
unmount_union
unmoun_seed
}
check_for_resquash () {
# Args: none
# Returns: number of bins needing to be merged
local number_of_bins=$(wc -l "$BINVENTORY")
if [[ $number_of_bins -gt $MAX_BINS ]]; then
return $[ $number_of_bins - $MIN_BINS ]
else
return 0
fi
}
create_new_squash () {
# Args: number of bins to be squashed (as determined by check_for_resquash), -1 on initial creation
# Returns: 0 on success, non-zero on failure
# If making first seed, create it empty and return
if [[ $1 -eq -1 ]]; then
mksquashfs "${BKUP_ROOT}/rw" "$SEED" -b 65536
return $?
fi
# Determine oldest $1 bins and mount them with the current squash
local old_bins=($(sort -n -r -t: -k2 "$BINVENTORY" | tail -$1 | cut -d: -f1))
mount_union_with_bins ${old_bins[@]}
# Create new squash with temp name
mksquashfs "${BKUP_ROOT}/rw" "$SEED.replace" -b 65536
# If the squash wasn't made correctly, we don't want to continue
if [[ $? -ne 0 ]]; then
return 1
fi
unmount_all
# Replace old squash
mv "${SEED}.replace" "$SEED"
# Delete old bins, and remove entry from binventory
for bin in $old_bins; do
rm -rf "${BKUP_ROOT}/bins/$bin"
sed -i "/^$bin:/d" "$BINVENTORY"
done
}
create_new_incremental () {
# Args: none
# Returns: 0 on success, non-zero on error
# Make a new bin for this incremenetal
get_next_available_bin
create_new_bin $?
# Determine the mount order via binventory
bin_order=($(sort -n -r -t:2 -k2 "$BINVENTORY" | cut -d: -f1))
mount_union_with_bins ${BIN_ORDER[@]}
# Die with error on mount, else start rsync
if [[ $? -ne 0 ]]; then
return 1;
fi
call_rsync
return $?
}
action_backup () {
# Args: options array squashfu was invoked with, shifted 1
# Returns: none
# Does the binventory exist? If not, prompt to make sure this is an initialization
if [[ ! -f "$BINVENTORY" || ! -f "$SEED" ]]; then
read -n "Looks like this is your first time running SquashFu. Is this correct? (y/n) " ans
while [[ true ]]; do
case $ans in
[yY]) break ;;
[nN]) die "Your bin inventory and/or seed seem to be missing. Please fix this before continuing." ;;
*) ;;
esac
done
# If we got here, the user answered yes, so initialize a new structure
mkdir -p "${BKUP_ROOT}/rw"
mkdir -p "${BKUP_ROOT}/ro"
mkdir -p "${BINS_DIR}"
touch "$BINVENTORY"
create_new_squash -1
FIRST_RUN=1
fi
create_new_incremental
check_for_resquash
if [[ val=$? -gt 0 ]]; then
create_new_squash $val
elif [[ $FIRST_RUN -eq 1 ]]; then
create_new_squash 1
fi
# TODO: Report if requested
unmount_all
}
action_rollback () {
debug "IOU: one rollback";exit 0
# Validate input
# call mount_squash
# call mount_union_with_bins
}
action_report () {
debug "IOU: one status report"; exit 0
# Enumerate bins, sort by order, provide size and convert timestamp to human readable
# use $(date --date="1970-01-01 $TIMESTAMP sec GMT")
}
action_backup
|