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
79 backup_type=0 # Type of backup
80 incr_full_file="" # Full backup file for the incremental
81 input_file="" # Full backup filename
82 output_file="" # Output data file
83 metadata_file="" # Output metadata fiole
84 pfs_path="" # PFS path to be backed up
85 backup_dir="" # Target directory for backups
86 compress=0 # Compress output file?
87 comp_rate=6 # Compression rate
88 verbose=0 # Verbosity on/off
89 timestamp=`date +'%Y%m%d%H%M%S'`
94 [ ${verbose} -eq 1 ] && echo "INFO: $1"
99 # Display an error and exit
106 echo 1>&2 "$0: ERROR: $*"
112 echo "Usage: $(basename $0) [-h] [-v] [-i <full-backup-file>]" \
113 "[-f] [-c <compress-rate>] -d [<backup-dir>] <path-to-PFS>"
119 info "Validating PFS ${pfs}"
121 # Backup directory must exist
122 if [ "${pfs_path}" == "" ]; then
126 # Make sure we are working on a HAMMER PFS
127 hammer pfs-status ${pfs_path} > /dev/null 2>&1
128 if [ $? -ne 0 ]; then
129 err 2 "${pfs} is not a HAMMER PFS"
137 awk -F "[=: ]" -vRS="\r" '{
147 md5 ${output_file} | awk '{print $4}'
152 # Get the shared UUID for the PFS
153 hammer pfs-status ${pfs_path} | \
154 awk -F= -v prop="shared-uuid" '{
167 filename=`basename $1`
168 filedate=`echo ${filename} | cut -d "_" -f1`
170 date -j -f '%Y%m%d%H%M%S' ${filedate} +"%B %d, %Y %H:%M:%S %Z"
176 local filename=`basename ${output_file}`
177 local uuid=$(get_uuid)
179 local md5sum=$(get_md5)
181 # XXX - Sanity checks missing?!!
182 printf "%s,,,%d,%s,%s,%s\n" $filename $backup_type $uuid $endtid $md5sum \
189 local compress_opts=""
192 # Calculate the compression options
193 if [ ${compress} -eq 1 ]; then
194 compress_opts=" | xz -c -${comp_rate}"
195 output_file="${output_file}.xz"
198 # Generate the datafile according to the options specified
199 cmd="hammer -y -v mirror-read ${pfs_path} ${begtid} 2> ${tmplog} \
200 ${compress_opts} > ${output_file}"
202 info "Launching: ${cmd}"
203 if [ ${dryrun} -eq 0 ]; then
204 # Sync to disk before mirror-read
205 hammer synctid ${pfs_path} > /dev/null 2>&1
207 if [ $? -eq 0 ]; then
208 info "Backup completed."
212 err 1 "Failed to created backup data file!"
219 local tmplog=`mktemp`
223 # Full backup (no param specified)
224 info "Initiating full backup"
227 # Generate the metadata file itself
228 metadata_file="${output_file}.bkp"
229 endtid=$(get_endtid ${tmplog})
231 update_mdata ${endtid}
243 if [ ! -r ${metadata_file} ]; then
244 err 1 "Could not find ${metadata_file}"
247 f1=`basename ${metadata_file}`
248 f2=`head -1 ${metadata_file} | cut -d "," -f1`
250 if [ "${f1}" != "${f2}.bkp" ]; then
251 err 2 "Bad metadata file ${metadata_file}"
257 local tmplog=`mktemp`
263 # Make sure the file exists and it can be read
264 if [ ! -r ${incr_full_file} ]; then
265 err 1 "Specified file ${incr_full_file} does not exist."
267 metadata_file=${incr_full_file}
269 # Verify we were passed a real metadata file
272 # The first backup of the metadata file must be a full one
273 line=`head -1 ${incr_full_file}`
274 btype=`echo ${line} | cut -d ',' -f4`
275 if [ ${btype} -ne 1 ]; then
276 err 1 "No full backup in ${incr_full_file}. Cannot do incremental ones."
279 # Read metadata info for the last backup performed
280 line=`tail -1 ${incr_full_file}`
281 srcuuid=`echo $line| cut -d ',' -f 5`
282 endtid=`echo $line| cut -d ',' -f 6`
284 # Verify shared uuid are the same
286 if [ "${srcuuid}" != "${tgtuuid}" ]; then
287 err 255 "Shared UUIDs do not match! ${srcuuid} -> ${tgtuuid}"
290 # Do an incremental backup
291 info "Initiating incremental backup"
292 do_backup ${tmplog} 0x${endtid}
294 # Store the metadata in the full backup file
295 endtid=$(get_endtid ${tmplog})
296 update_mdata ${endtid}
307 for bkp in `ls -1 ${backup_dir}/*.bkp 2> /dev/null`
309 # Extract the date from file
310 filedate=$(file2date ${bkp})
311 # Show incremental backups related to the full backup above
312 awk -F "," -v fd="${filedate}" '{
319 printf("%s endtid: 0x%s md5: %s\n", $1, $6, $7);
324 if [ ${nofiles} -eq 1 ]; then
325 err 255 "No backup files found in ${backup_dir}"
330 # -------------------------------------------------------------
335 # Only can be run by root
336 if [ $UID -ne 0 ]; then
337 err 255 "Only root can run this script."
340 # Checks hammer program
341 if [ ! -x /sbin/hammer ]; then
342 err 1 'Could not find find hammer(8) program.'
345 info "hammer-backup version ${VERSION}"
348 while getopts d:i:c:fvhnl op
353 info "Backup directory is ${backup_dir}."
356 if [ ${backup_type} -eq 2 ]; then
357 err 1 "-f and -i are mutually exclusive."
364 if [ ${backup_type} -eq 2 ]; then
365 err 1 "-f and -i are mutually exclusive."
368 info "Incremental backup."
370 incr_full_file=$OPTARG
377 comp_rate=`expr $OPTARG`
380 err 1 "Bad compression level specified."
384 info "XZ compression level ${comp_rate}."
387 info "Dry-run execution."
405 shift $(($OPTIND - 1))
408 # If list option is selected
411 # Backup directory must exist
412 if [ "${backup_dir}" == "" ]; then
414 elif [ ! -d "${backup_dir}" ]; then
415 err 1 "Backup directory does not exist!"
418 # Output file format is YYYYmmdd-HHMMSS
419 tmp=`echo ${pfs_path} | sed 's/\//_/g'`
420 output_file="${backup_dir}/${timestamp}${tmp}"
422 # List backups if needed
423 if [ "${list_opt}" == "1" ]; then
424 info "Listing backups in ${backup_dir}"
428 # Only work on a HAMMER fs
431 # Actually launch the backup itself
432 if [ ${backup_type} -eq 1 ]; then
434 elif [ ${backup_type} -eq 2 ]; then
435 incr_full_file=${backup_dir}/${incr_full_file}
438 err 255 "Impossible backup type."