3 # Copyright (c) 2014 The DragonFly Project. All rights reserved.
5 # This code is derived from software contributed to The DragonFly Project
6 # by Antonio Huete <tuxillo@quantumachine.net>
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
12 # 1. Redistributions of source code must retain the above copyright
13 # notice, this list of conditions and the following disclaimer.
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in
16 # the documentation and/or other materials provided with the
18 # 3. Neither the name of The DragonFly Project nor the names of its
19 # contributors may be used to endorse or promote products derived
20 # from this software without specific, prior written permission.
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 # COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 # INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32 # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 # This script operats HAMMER PFSes and dumps its contents for backup
49 backup_type=0 # Type of backup
50 incr_full_file="" # Full backup file for the incremental
51 input_file="" # Full backup filename
52 output_file="" # Output data file
53 metadata_file="" # Output metadata fiole
54 pfs_path="" # PFS path to be backed up
55 backup_dir="" # Target directory for backups
56 compress=0 # Compress output file?
57 comp_rate=6 # Compression rate
58 verbose=0 # Verbosity on/off
59 list_opt=0 # List backups
60 checksum_opt=0 # Perfom a checksum of all backups
61 find_last=0 # Find last full backup
62 timestamp=$(date +'%Y%m%d%H%M%S')
67 [ ${verbose} -eq 1 ] && echo "INFO: $1"
72 # Display an error and exit
79 echo 1>&2 "$0: ERROR: $*"
86 echo "Usage: ${SCRIPTNAME} [-hlvfk] [-i <full-backup-file|auto>]" \
87 "[-c <compress-rate>] -d [<backup-dir>] [pfs path]"
93 info "Validating PFS ${pfs_path}"
95 # Backup directory must exist
96 if [ -z "${pfs_path}" ]; then
100 # Make sure we are working on a HAMMER PFS
101 hammer pfs-status ${pfs_path} > /dev/null 2>&1
102 if [ $? -ne 0 ]; then
103 err 2 "${pfs} is not a HAMMER PFS"
111 awk -F "[=: ]" -vRS="\r" '{
121 # Get the shared UUID for the PFS
122 hammer pfs-status ${pfs_path} | awk -F'[ =]+' '
123 $2 == "shared-uuid" {
135 filename=$(basename $1)
136 filedate=$(echo ${filename} | cut -d "_" -f1)
138 date -j -f '%Y%m%d%H%M%S' ${filedate} +"%B %d, %Y %H:%M:%S %Z"
144 local filename=$(basename ${output_file})
145 local uuid=$(get_uuid)
147 local md5sum=$(md5 -q ${output_file} 2> /dev/null)
149 if [ -z "${endtid}" ]; then
151 err 1 "Couldn't update the metadata file! Deleting ${output_file}"
153 # XXX - Sanity checks missing?!!
154 if [ ${dryrun} -eq 0 ]; then
155 printf "%s,,,%d,%s,%s,%s\n" ${filename} ${backup_type} ${uuid} \
156 ${endtid} ${md5sum} >> ${metadata_file}
163 local compress_opts=""
166 # Calculate the compression options
167 if [ ${compress} -eq 1 ]; then
168 compress_opts=" | xz -c -${comp_rate}"
169 output_file="${output_file}.xz"
172 # Generate the datafile according to the options specified
173 cmd="hammer -y -v mirror-read ${pfs_path} ${begtid} 2> ${tmplog} \
174 ${compress_opts} > ${output_file}"
176 info "Launching: ${cmd}."
177 if [ ${dryrun} -eq 0 ]; then
178 # Sync to disk before mirror-read
179 hammer synctid ${pfs_path} > /dev/null 2>&1
181 if [ $? -eq 0 ]; then
182 info "Backup completed."
186 err 1 "Failed to created backup data file!"
189 info "Dry-run execution."
195 local tmplog=$(mktemp)
199 # Full backup (no param specified)
200 info "Initiating full backup."
203 # Generate the metadata file itself
204 metadata_file="${output_file}.bkp"
205 endtid=$(get_endtid ${tmplog})
207 update_mdata ${endtid}
219 if [ ! -r ${metadata_file} ]; then
220 err 1 "Could not find ${metadata_file}"
223 f1=$(basename ${metadata_file})
224 f2=$(head -1 ${metadata_file} | cut -d "," -f1)
226 if [ "${f1}" != "${f2}.bkp" ]; then
227 err 2 "Bad metadata file ${metadata_file}"
231 detect_latest_backup()
237 # Find latest metadata backup file if needed. Right now the timestamp
238 # in the filename will let them be sorted by ls. But this could actually
240 if [ ${find_last} -eq 1 ]; then
241 pattern=$(echo ${pfs_path} | tr "/" "_").xz.bkp
242 latest=$(ls -1 ${backup_dir}/*${pattern} 2> /dev/null | tail -1)
243 if [ -z "${latest}" ]; then
244 err 1 "Failed to detect the latest full backup file."
246 incr_full_file=${latest}
252 local tmplog=$(mktemp)
262 # Make sure the file exists and it can be read
263 if [ ! -r ${incr_full_file} ]; then
264 err 1 "Specified file ${incr_full_file} does not exist."
266 metadata_file=${incr_full_file}
268 # Verify we were passed a real metadata file
271 # The first backup of the metadata file must be a full one
272 line=$(head -1 ${incr_full_file})
273 btype=$(echo ${line} | cut -d ',' -f4)
274 if [ ${btype} -ne 1 ]; then
275 err 1 "No full backup in ${incr_full_file}. Cannot do incremental ones."
278 # Read metadata info for the last backup performed
279 line=$(tail -1 ${incr_full_file})
280 srcuuid=$(echo $line| cut -d ',' -f 5)
281 begtid=$(echo $line| cut -d ',' -f 6)
283 # Verify shared uuid are the same
285 if [ "${srcuuid}" != "${tgtuuid}" ]; then
286 err 255 "Shared UUIDs do not match! ${srcuuid} -> ${tgtuuid}"
289 # Do an incremental backup
290 info "Initiating incremental backup."
291 do_backup ${tmplog} 0x${begtid}
293 # Store the metadata in the full backup file
294 endtid=$(get_endtid ${tmplog})
297 # Handle the case where the hammer mirror-read command did not retrieve
298 # any data because the PFS was not modified at all. In that case we keep
299 # TID of the previous backup.
301 if [ -z "${endtid}" ]; then
304 update_mdata ${endtid}
314 for bkp in ${backup_dir}/*.bkp
316 # Skip files that don't exist
317 if [ ! -f ${bkp} ]; then
320 # Show incremental backups related to the full backup above
328 printf("%s endtid: 0x%s md5: %s\n", $1, $6, $7);
333 if [ ${nofiles} -eq 1 ]; then
334 err 255 "No backup files found in ${backup_dir}"
347 for bkp in ${backup_dir}/*.bkp
349 # Skip files that don't exist
350 if [ ! -f ${bkp} ]; then
353 # Perform a checksum test
356 tmp=$(echo $line | cut -d "," -f1)
357 fname=${backup_dir}/${tmp}
358 storedck=$(echo $line | cut -d "," -f7)
359 fileck=$(md5 -q ${fname} 2> /dev/null)
360 echo -n "${fname} : "
361 if [ ! -f ${fname} ]; then
364 elif [ "${storedck}" == "${fileck}" ]; then
373 if [ ${nofiles} -eq 1 ]; then
374 err 255 "No backup files found in ${backup_dir}"
379 # -------------------------------------------------------------
384 # Only can be run by root
385 if [ $(id -u) -ne 0 ]; then
386 err 255 "Only root can run this script."
389 # Checks hammer program
390 if [ ! -x /sbin/hammer ]; then
391 err 1 'Could not find find hammer(8) program.'
395 while getopts d:i:c:fvhnlk op
402 if [ ${backup_type} -eq 2 ]; then
403 err 1 "-f and -i are mutually exclusive."
408 if [ ${backup_type} -eq 2 ]; then
409 err 1 "-f and -i are mutually exclusive."
412 if [ "${OPTARG}" == "auto" ]; then
415 incr_full_file=$OPTARG
426 err 1 "Bad compression level specified."
451 shift $(($OPTIND - 1))
453 info "hammer-backup version ${VERSION}"
456 # If list option is selected
459 # Backup directory must exist
460 if [ -z "${backup_dir}" ]; then
462 elif [ ! -d "${backup_dir}" ]; then
463 err 1 "Backup directory does not exist!"
465 info "Backup dir is ${backup_dir}"
467 # Output file format is YYYYmmddHHMMSS
468 tmp=$(echo ${pfs_path} | tr '/' '_')
469 output_file="${backup_dir}/${timestamp}${tmp}"
471 # List backups if needed
472 if [ ${list_opt} -eq 1 ]; then
473 info "Listing backups."
478 if [ ${checksum_opt} -eq 1 ]; then
479 info "Checksum test for all backup files."
483 # Only work on a HAMMER fs
486 # Actually launch the backup itself
487 if [ ${backup_type} -eq 1 ]; then
490 elif [ ${backup_type} -eq 2 ]; then
491 info "Incremental backup."
492 incr_full_file=${backup_dir}/${incr_full_file}
495 err 255 "Impossible backup type."