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
42 # It uses mirror-read directive (see 'man 8 hammer') to perform a
43 # dump to stdout that is redirected to a file with or without
46 # It can take two types of backup:
48 # a) Full: Where ALL the data of the PFS is sent to a file.
49 # b) Inremental: It requires a previous full backup.
51 # Additionally to the backup data itself, it creates a .bkp file
52 # which contains metadata relative to the full and/or incremental
55 # The format is the following
57 # filename,rsv01,rsv02,backup type,shared uuid,last TID,md5 hash
59 # filename : Backup data file file.
60 # rsv01,rsv02: Reserved fields
61 # backup type: 1 or 2 (full or incremental, respectively)
62 # shared uuid: PFS shared UUID for mirror ops
63 # last TID : Last transaction ID. Next incr. backup starting TID
64 # md5 hash : For restoring purposes
67 # $ head -1 20140305222026_pfs_t1_full.xz.bkp
68 # 20140305222026_pfs_t1_full.xz.bkp,,,f,e8decfc5-a4ab-11e3-942b-f56d04d293e0,000000011b36be30,05482d26644bd1e76e69d83002e08258
77 backup_type=0 # Type of backup
78 incr_full_file="" # Full backup file for the incremental
79 input_file="" # Full backup filename
80 output_file="" # Output data file
81 metadata_file="" # Output metadata fiole
82 pfs_path="" # PFS path to be backed up
83 backup_dir="" # Target directory for backups
84 compress=0 # Compress output file?
85 comp_rate=6 # Compression rate
86 verbose=0 # Verbosity on/off
87 timestamp=$(date +'%Y%m%d%H%M%S')
93 [ ${verbose} -eq 1 ] && echo "INFO: $1"
98 # Display an error and exit
105 echo 1>&2 "$0: ERROR: $*"
111 echo "Usage: ${SCRIPTNAME} [-h] [-l] [-v] [-i <full-backup-file>]" \
112 "[-f] [-c <compress-rate>] -d [<backup-dir>] <path-to-PFS>"
118 info "Validating PFS ${pfs}"
120 # Backup directory must exist
121 if [ -z "${pfs_path}" ]; then
125 # Make sure we are working on a HAMMER PFS
126 hammer pfs-status ${pfs_path} > /dev/null 2>&1
127 if [ $? -ne 0 ]; then
128 err 2 "${pfs} is not a HAMMER PFS"
136 awk -F "[=: ]" -vRS="\r" '{
146 # Get the shared UUID for the PFS
147 hammer pfs-status ${pfs_path} | awk -F'[ =]+' '
148 $2 == "shared-uuid" {
160 filename=$(basename $1)
161 filedate=$(echo ${filename} | cut -d "_" -f1)
163 date -j -f '%Y%m%d%H%M%S' ${filedate} +"%B %d, %Y %H:%M:%S %Z"
169 local filename=$(basename ${output_file})
170 local uuid=$(get_uuid)
172 local md5sum=$(md5 -q ${output_file})
174 # XXX - Sanity checks missing?!!
175 printf "%s,,,%d,%s,%s,%s\n" $filename $backup_type $uuid $endtid $md5sum \
182 local compress_opts=""
185 # Calculate the compression options
186 if [ ${compress} -eq 1 ]; then
187 compress_opts=" | xz -c -${comp_rate}"
188 output_file="${output_file}.xz"
191 # Generate the datafile according to the options specified
192 cmd="hammer -y -v mirror-read ${pfs_path} ${begtid} 2> ${tmplog} \
193 ${compress_opts} > ${output_file}"
195 info "Launching: ${cmd}"
196 if [ ${dryrun} -eq 0 ]; then
197 # Sync to disk before mirror-read
198 hammer synctid ${pfs_path} > /dev/null 2>&1
200 if [ $? -eq 0 ]; then
201 info "Backup completed."
205 err 1 "Failed to created backup data file!"
212 local tmplog=$(mktemp)
216 # Full backup (no param specified)
217 info "Initiating full backup"
220 # Generate the metadata file itself
221 metadata_file="${output_file}.bkp"
222 endtid=$(get_endtid ${tmplog})
224 update_mdata ${endtid}
236 if [ ! -r ${metadata_file} ]; then
237 err 1 "Could not find ${metadata_file}"
240 f1=$(basename ${metadata_file})
241 f2=$(head -1 ${metadata_file} | cut -d "," -f1)
243 if [ "${f1}" != "${f2}.bkp" ]; then
244 err 2 "Bad metadata file ${metadata_file}"
250 local tmplog=$(mktemp)
256 # Make sure the file exists and it can be read
257 if [ ! -r ${incr_full_file} ]; then
258 err 1 "Specified file ${incr_full_file} does not exist."
260 metadata_file=${incr_full_file}
262 # Verify we were passed a real metadata file
265 # The first backup of the metadata file must be a full one
266 line=$(head -1 ${incr_full_file})
267 btype=$(echo ${line} | cut -d ',' -f4)
268 if [ ${btype} -ne 1 ]; then
269 err 1 "No full backup in ${incr_full_file}. Cannot do incremental ones."
272 # Read metadata info for the last backup performed
273 line=$(tail -1 ${incr_full_file})
274 srcuuid=$(echo $line| cut -d ',' -f 5)
275 endtid=$(echo $line| cut -d ',' -f 6)
277 # Verify shared uuid are the same
279 if [ "${srcuuid}" != "${tgtuuid}" ]; then
280 err 255 "Shared UUIDs do not match! ${srcuuid} -> ${tgtuuid}"
283 # Do an incremental backup
284 info "Initiating incremental backup"
285 do_backup ${tmplog} 0x${endtid}
287 # Store the metadata in the full backup file
288 endtid=$(get_endtid ${tmplog})
289 update_mdata ${endtid}
299 for bkp in ${backup_dir}/*.bkp
301 # Skip files that don't exist
302 if [ ! -f ${bkp} ]; then
305 # Show incremental backups related to the full backup above
313 printf("%s endtid: 0x%s md5: %s\n", $1, $6, $7);
318 if [ ${nofiles} -eq 1 ]; then
319 err 255 "No backup files found in ${backup_dir}"
324 # -------------------------------------------------------------
329 # Only can be run by root
330 if [ $(id -u) -ne 0 ]; then
331 err 255 "Only root can run this script."
334 # Checks hammer program
335 if [ ! -x /sbin/hammer ]; then
336 err 1 'Could not find find hammer(8) program.'
339 info "hammer-backup version ${VERSION}"
342 while getopts d:i:c:fvhnl op
347 info "Backup directory is ${backup_dir}."
350 if [ ${backup_type} -eq 2 ]; then
351 err 1 "-f and -i are mutually exclusive."
358 if [ ${backup_type} -eq 2 ]; then
359 err 1 "-f and -i are mutually exclusive."
362 info "Incremental backup."
364 incr_full_file=$OPTARG
374 err 1 "Bad compression level specified."
378 info "XZ compression level ${comp_rate}."
381 info "Dry-run execution."
399 shift $(($OPTIND - 1))
402 # If list option is selected
405 # Backup directory must exist
406 if [ -z "${backup_dir}" ]; then
408 elif [ ! -d "${backup_dir}" ]; then
409 err 1 "Backup directory does not exist!"
412 # Output file format is YYYYmmddHHMMSS
413 tmp=$(echo ${pfs_path} | tr '/' '_')
414 output_file="${backup_dir}/${timestamp}${tmp}"
416 # List backups if needed
417 if [ ${list_opt} == 1 ]; then
418 info "Listing backups in ${backup_dir}"
422 # Only work on a HAMMER fs
425 # Actually launch the backup itself
426 if [ ${backup_type} -eq 1 ]; then
428 elif [ ${backup_type} -eq 2 ]; then
429 incr_full_file=${backup_dir}/${incr_full_file}
432 err 255 "Impossible backup type."