summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Gilbert <mgilbert@debian.org>2012-11-02 17:46:44 +0000
committerMichael Gilbert <mgilbert@debian.org>2012-11-02 17:46:44 +0000
commited8cee1b61f957d05dcd223e6c954821ea1294e4 (patch)
tree3c499a9186dde0212ddb425de501864878c183fd
flexbackup (1.2.1-6.2) unstable; urgency=medium
* Non-maintainer upload. * Fix placement of afio alternative (closes: #691860). # imported from the archive
-rw-r--r--CHANGES750
-rw-r--r--COPYING340
-rw-r--r--CREDITS40
-rw-r--r--INSTALL16
-rw-r--r--Makefile37
-rw-r--r--README227
-rw-r--r--TODO157
-rwxr-xr-xcontrib/flexbackup.cgi424
-rw-r--r--contrib/flexbackup.cgi.README8
-rw-r--r--debian/changelog88
-rw-r--r--debian/compat1
-rw-r--r--debian/control27
-rw-r--r--debian/copyright28
-rw-r--r--debian/dirs8
-rw-r--r--debian/docs4
-rw-r--r--debian/examples1
-rw-r--r--debian/flexbackup.lintian2
-rw-r--r--debian/patches/00list10
-rw-r--r--debian/patches/01_tempfile.dpatch84
-rw-r--r--debian/patches/02_buffer_test.dpatch44
-rw-r--r--debian/patches/10_Makefile_destdir.dpatch26
-rw-r--r--debian/patches/20_flexbackup.conf.5_hyphens.dpatch28
-rw-r--r--debian/patches/22_flexbackup.1.dpatch53
-rw-r--r--debian/patches/30_basic_debian_tape.dpatch43
-rw-r--r--debian/patches/40_dump_path.dpatch24
-rw-r--r--debian/patches/42_afio_cpio_incompatible_ok.dpatch21
-rw-r--r--debian/patches/50_lzma_support.dpatch113
-rw-r--r--debian/patches/60_use_afio_default_nocompression.dpatch75
-rwxr-xr-xdebian/rules76
-rw-r--r--debian/sample-cron24
-rw-r--r--faq.html444
-rwxr-xr-xflexbackup5688
-rw-r--r--flexbackup.1161
-rw-r--r--flexbackup.conf236
-rw-r--r--flexbackup.conf.5304
-rw-r--r--flexbackup.spec96
36 files changed, 9708 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..a4233dd
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,750 @@
+$Id: CHANGES,v 1.159 2003/10/10 15:42:41 edwinh Exp $
+$Name: v1_2_1 $
+
+----------------------------------------------------------------------
+Version 1.2.1 (20031010)
+
+Changes:
+ - Added "-ignore-errors" flag to allow backups to continue even if
+ commands return non-zero exit status. Use at your own risk.
+ - Added contributed manpages
+ - Add 'rsync' type that acts like 'copy' but using rsync instead of piped
+ cpio's. You need rsync >= 2.5.6 for this to work!
+ - Added lzop compression support
+ - Spinning bar indicator for long-running pkg delta operations if
+ output is a terminal
+ - Recognize .deb files as ar archives
+ - Skip proc/devpts/devfs/tmpfs for traverse_fs=local or all
+ - Add hooks for mbuffer's multivolume support (experimental)
+
+Bug fixes/cleanup:
+ - FreeBSD pkgdelta support fixed up
+ - Cleanup better if error detected
+ - Fix quoting in check_remote_progs if your remote shell is csh
+ - Make sure we don't generate conflicting index keys (as long as -newtape
+ is not run in parallel)
+ - Cleanup a bit regarding block devices
+
+----------------------------------------------------------------------
+Version 1.2.0 (20030707)
+
+Changes:
+ - Merge devel -> stable
+ - Added -pipe option to archive single directory to stdout,
+ or list/extract/compare archive from stdin
+ - No null padding necessary for reads, even with $pad_blocks true - only
+ needed for writes. Should take harmless warnings way from gzip,tar,
+ and others when reading
+ - Added -nodefaults option for debug
+ - Allow compression for filelist type
+
+Bugfixes/cleanup:
+ - Fix FreeBSD find w/ prune regex
+ - Truncate tar label to 99 chars
+ - Error if -num flag with backup write mode or using file
+ - Error if -erase with archive read mode or using file
+ - Fix afio volume header print filehandle
+ - Fix if on-disk backups, and "-rmfile all", the index for the keyfile
+ was removed but not the keyfile itself
+ - use $:: rather than $main:: for globals, its shorter...
+
+----------------------------------------------------------------------
+Version 1.1.8 (20030621)
+
+Changes:
+ - Detect old config file
+ - Config file parsing not so rigid - if values not found, it now uses
+ defaults (w/ message printed so you know it)
+ - Can configure buffer percent full and write sleep time options
+ - Compare works for copy type (just diff -r -q)
+
+Bugfixes/cleanup:
+ - Doh! index key creation broken for to-disk backups as a result of 1.1.7
+ changes. Fixed.
+ - Relative links for $device get chased correctly
+ - $pkgdelta_archive_changed = false didn't work.
+ - README updated; example for filesystem setup & cron.
+ - find -depth w/ -prune isn't supposed to work, no bug. (see single unix
+ spec and various manpages).
+
+----------------------------------------------------------------------
+Version 1.1.7 (20030618)
+
+Changes:
+ - Added "copy" type that just mirrors the files into a directory tree
+ if archiving to disk
+ - Initial support of "-pkgdelta" with FreeBSD packages
+ - On FreeBSD, use mt to set 1-filemark behavior globally
+ - Globally ignore sockets
+ - Use "find -prune" for subtree pruning. (When this happens, we have to
+ turn off "-depth" switch, as with GNU find 4.1.7 they don't work
+ together. Need to check into findutils more.)
+
+Bugfixes/cleanup:
+ - Don't use --same-order for tar. -extract -flist would fail if you had
+ more than one file and they weren't in the expected order
+ - Use --ignore-failed-read for tar
+ - Remote tape drive device didn't create index key correctly
+ - Typo/update star flags setup
+ - Tweak quiet flags
+
+----------------------------------------------------------------------
+Version 1.1.6 (20030313)
+
+Changes beyond stable 1.0.3 release:
+
+ - Different way to spec the filesystems. This lets us have arbitrarily
+ named backup "sets".
+
+ Config file:
+ $set{'tag1'} = "/dir1 /dir2 ...";
+ $set{'tag2'} = "/dir3 host:/dir4 ...";
+
+ Flags:
+ "-dir <dir>" backs up one directory tree (old "-fs <dir>")
+ "-set <tag>" backs up a named set. Useful new feature.
+ "-set all" acts like old "-fs all". If using tapes & level 0, each
+ set is a different tape (acts like the old numeric index to @filesystems)
+
+ - Per-filesystem subtree pruning
+ $prune{'/'} = "tmp proc mnt";
+ $prune{'host:/'} = "disk.?";
+
+ - Added packaging system delta capability.
+
+ If you give "-pkgdelta rpm", after the level/timestamp checks, we
+ further cut down the backup file list by only archiving files that are
+ either (a) not owned by a package, or (b) owned by a package but
+ modified from the distributed version.
+
+ New config variables to tweak behavior:
+ $pkgdelta_archive_list $pkgdelta_archive_unowned $pkgdelta_archive_changed
+
+ - Added 'mbuffer' program as alternative to 'buffer'
+
+ - Added 'filelist' type that doesn't back up anything, but prints/saves
+ the list of files that would have been archived. Good for debugging
+ timestamps, levels, and file exclusion.
+
+ - Added "-extract -onefile <file>" option; if you are extracting a single
+ file just spec it on the command line, no list file needed
+
+ - Changed boolean mt_var_blksize into mt_blocksize integer (in bytes).
+ Set to 0 for variable blocksize (old "true" value), comment out or
+ set to "$blksize * 1024" for old "false" value.
+
+ - If staticfiles or staticlogs true, don't timestamp list/extract/compare
+ log
+
+ - Added -wday option (helps with once-a-month, on a certain day of the
+ week only cron setups)
+
+ - test-tape-drive now diffs 3 files, one in the middle is different
+ (will catch people using rewind-on-close device)
+
+ - "-extract -files" -> "-extract -flist" to hopefully be more clear
+
+ - db/file rm flags tweaked:
+ Redid rmindex semantics again, see help
+ -rmfile can be specified multiple times
+ -rmindex can be specified multiple times
+
+ - Added support for lha, shar, and ar archives just to be stupid
+
+Cleanup:
+ - Allow dirs/prune paths with spaces in them - to use, enclose all
+ items in the lists in quotes.
+ - When level > 9 and using files, -toc sorts better now
+ - Config file with spaces in path works
+ - Don't check for writeable stampdir for non-backup
+ - Don't check for writeable logdir for non-backup
+ - Don't tie to db for non-backup
+
+----------------------------------------------------------------------
+Version 1.0.3 (20030313)
+
+Bug fixes:
+ - Only look for mt if we are using tape device
+ - If using files, "-fs all", and and multiple array entries for $filesystems,
+ index was getting hosed up.
+ - newtape/erase when using dirs was deleting keyfile entry from index db
+ - remove bogus OS_ERROR string from failed tie
+
+----------------------------------------------------------------------
+Version 1.0.2 (20030219)
+
+Changes:
+ - Added '-test-tape-drive' switch that just makes sure blocks, filemarks,
+ and padding work as expected and we can read,write,compare. If people
+ can't get that running, then the problem is somewhere else...
+
+ - Allow mt command override in the config file: mt{'old'} = 'new'. Useful
+ if there is a new platform-specific combination, you want 'status'
+ vs. 'tell' output in the log, etc...
+
+Bug fixes:
+ - If remote tape device, don't check for writable local device
+ - Fix pattern-match so "sudo -u username command" works
+ (Tweak sudo comment in config file and faq as well)
+
+Cleanup:
+ - Update mt op used for hardware compression (if anyone uses it)
+ - Cleanup more uses of $_
+
+----------------------------------------------------------------------
+Version 1.0.1 (20030202)
+
+Bug fixes:
+ - Quote the directory when doing initial "cd" (logfiles, stamps, filenames too).
+ Also needed to fix a few regexps that matched against the backup dir.
+ - afio + null fix in pre6 didn't work in FreeBSD due to a problem with its
+ printf(1). Switch back to newline-separated for afio types for now.
+ Linux, HPUX, Solaris, AIX printf(1) utilities did do nulls.
+
+----------------------------------------------------------------------
+Version 1.0pre6 (20030126)
+
+Bug fixes:
+ - Fix problem with afio volume headers and null-terminated file lists.
+ The first file in afio archives was being missed (actually showed up
+ in the label instead of the archive)
+
+Cleanup:
+ - "-rmfile" yells if no argument
+ - Move shell/remote/buffer check to its own function after option
+ checking. (So commandline/config errors and these pre-tests are
+ displayed separately)
+
+----------------------------------------------------------------------
+Version 1.0pre5 (20030124)
+
+Bug fixes:
+ - Fix sh status collector for the front of remote commands
+ - Fix it for commands in ( .. ) subshells also (echo + find for afio
+ volume header input for example)
+
+Cleanup:
+ - Don't do status collection work for non-pipelined remote commands
+
+----------------------------------------------------------------------
+Version 1.0pre4 (20030121)
+
+Bug Fixes:
+ - Try harder to catch pipeline errors in different environments:
+ o zsh has a pipestatus array variable similar to bash, use it
+ o Tweaks to shell detection to detect remote vanilla csh
+ o Add exit-status recording shell trick for local sh
+ - If we get an error, rm index & botched archive file if using files
+ - If we get an error, mark problem in the index if using tapes
+
+Cleanup:
+ - Replaced manual line-wrapper with Text::Wrap (other was buggy, missed
+ single-char "words")
+
+----------------------------------------------------------------------
+Version 1.0pre3 (20030119)
+
+Bug Fixes:
+ - Fix PIPESTATUS trick to not require extra 'seq' utility, just a while loop
+ - Using "-toc" gave uninitialized var warning, obvious problem, fixed.
+ Eradicated some other spots of code that used $_ too.
+ - "-rmindex" yells if you don't give an argument
+
+Cleanup:
+ - Cosmetic - fix missing line in output if no remote and buffer=false
+ - Remove duplicated index delete code
+
+----------------------------------------------------------------------
+Version 1.0pre2 (20030118)
+
+Bug fixes:
+ - atime preserve option to tar/star/afio modifies the ctime of the
+ files. Using ctime checks then says everything is newer, no matter
+ what the level. Make atime preserve a global option defaulting to
+ false; if true turn off ctime checks
+ - When reading from given filename, set device to dir so that we don't
+ pad blocks by accident if default device is tape
+ - Detect shell type for local and any remote hosts
+ Only use bash PIPESTATUS trick if we find bash 2.x
+
+
+----------------------------------------------------------------------
+Version 1.0pre1 (20030118)
+
+Changes:
+ - Allow non-root execution along with "sudo". Set
+ path{'command'} = "sudo command" or "sudo -u username command" in
+ the config file. Idea from Alex Aiminoff's patch to 0.9.8
+ - Added $remoteuser to config for rsh/ssh
+ - Check that 'buffer' can run with set $megs.
+ From John Reynolds, brought forward from 0.9.8. Remote as well.
+ - Check for existence of remote executables before doing remote backups
+ Based on John Reynolds' modifications
+ - A "no blocking at all" option. Set $blksize=0 in the config file.
+ - Added support for "zip" as valid archive compression type
+ - Added support for "zip" as valid log compression type
+ - "-device <dev> to override config file (tired of "-d device=/path/to/dir"
+ during testing...)
+ - "-rmfile all"' when using files will rm all files/db entries for that dir
+
+Bug Fixes:
+ - $erase_rewind_only wasn't working right if we didn't call
+ &mt('rewind') right before or after &mt('erase') (I think all spots
+ are covered now, but anyway...). Now we might call rewind twice if
+ this is set, but it won't hurt. (from e-smith patches)
+
+Cleanup:
+ - Redid the way buffer/read/write strings were constructed to make more sense
+ - Don't set tape blocksize with -toc unless we want current tape (from John)
+ - Interactive restore creates log file (was in e-smith stuff)
+ - Extract/list/compare/restore use date-stamped logfiles (e-smith)
+ - Simplified rmfile() to call existing rmindex() routine
+ - Symlink latest log even if staticlogs = true
+
+----------------------------------------------------------------------
+Version 0.9.9.7 (20030115)
+
+Changes: (just so we can say it supports *everything* for archive formats)
+ - Initial support for "star" archiver. Like tar but does acl's, has its own
+ fifo buffering mechanism, and more. see:
+ http://www.fokus.gmd.de/research/cc/glone/employees/joerg.schilling/private/star.html
+ - Initial support for "pax" archiver. Really just tar or cpio, but might
+ be good for portability across unices...
+ - Put .zip back in, same way as in old releases (kinda hokey with the tmpfiles,
+ but there is no cleaner way to do it with info-zip utils like I wanted)
+
+Bug fixes:
+ - Fixed problem with other releases trying to use find w/ -cnewer. Desired
+ so we get files that change perms, not content. ctime can't be set
+ backward with touch -t (or any mechanism), so we can't back-date a
+ reference file.
+
+ Now fixed so at least for local filesystems this works. Remote backups
+ still won't catch that type of change. Code changes:
+ -don't use generated timestamp files for local fs's; use the original stampfile
+ -refactor remote fs variable
+ -get_last_date function changes
+ -no stamps at all for level 0
+
+Cleanup:
+ - '-rmindex all' says it will wait 5 seconds
+ - '-incremental|full|differential' same as '-level <type>'
+ - Fix warning with no args
+ - touch -t the same for everyone. buggy touch in pre-4.0 fileutils is old by now..
+ - Clean up date routines
+
+----------------------------------------------------------------------
+Version 0.9.9.6 (20030112)
+
+Changes:
+ - Can now backup to / read from remote tape drives.
+ Set $device = "host:/dev/tape"
+ Uses ssh pipelines like our remote fs backups - not rmt.
+ Old mail idea from Caleb Crome <caleb@crome.org>, reworked.
+
+Bug fixes:
+ - Some commands still weren't using path overrides (dd,mt)
+
+Cleanup:
+ - Refactored some code due to the above.
+ - afio volume header had extra newline
+ - File list routine to nuke duplicated code
+ - Echo list/extract log filename.
+ - Ffind -depth is recommended.
+ - Add .tbz auto file extension.
+ - Fix incorrect tapedevice test in read setup function. (cosmetic result)
+
+----------------------------------------------------------------------
+Version 0.9.9.5 (20030111)
+
+Changes:
+ - Static filenames option when using disk (same fs, same level overwrite)
+ based on 0.9.8 patch from Michael Matsumura <michael@limit.org>
+ split into $staticlogs / $staticfiles config options
+
+Bug fixes:
+ - Old bug: stamp files from the various parts of 'all' were not getting
+ marked as 'old' correctly for removal (we were looking for "all.<level>")
+ - Fixed '-level incremental' with fs=all. Had to refactor some code and
+ not use a global level variable. logs filenames a little different
+ for 'all' in this case.
+
+Cleanup:
+ - Make path overrides also search $PATH (say if we just changed the name
+ tar == gtar but still want to do normal path-search)
+ - $nocompress_types -> $afio_nocompress_types since that's the only one
+ that uses it
+ - A few cosmetic fixes:
+ fix double dash-line on last line of logfile
+ make logging routines suable for screen/log/both
+ fix so -n won't nuke old files either
+
+----------------------------------------------------------------------
+Version 0.9.9.4 (20030110)
+
+Bug fixes:
+ - DOH! we *were* doing levels slightly wrong.
+ use < rather than <= for comparing levels.
+ popped out of investigating incremental and differential requests
+ Now we act like dumplevels should
+ - This also changes what we regard as "old" logs/stamps as a result
+
+Changes:
+ - Several requests were in old email for differential/incremental...
+ Just a misunderstanding I think. Check these links:
+ http://docs.sun.com/db/doc/805-7228/6j6q7uf18?a=view
+ http://www.arkeia.com/archives_indexed/2000/10/msg00006.html
+ http://www.sans.org/rr/backup/central.php
+
+ To sum up, no changes are *needed* if dump behavior mimiced (we had a
+ bug here BTW).
+
+ We can do what people call "incremental" as long as we increase the
+ backup level each day.
+
+ We can do what people call "differential" just by keeping the number the
+ same for consecutive backups
+
+ - Implemented support for -level [full|differential|incremental]
+ as aliased dump levels (for people not as familiar with dump-style levels)
+ full = 0
+ differential = 1
+ incremental = <latest backup> + 1 [broken for fs=all at this stage]
+
+ - Support for > 9 levels (for all types but 'dump')
+
+----------------------------------------------------------------------
+Version 0.9.9.3 (20030109)
+
+Bug fixes:
+ - BSD find wants "!" not "-not"
+
+----------------------------------------------------------------------
+Version 0.9.9.2 (20030107)
+
+Bug fixes:
+
+ - 'Argument "200301072208|0" isn't numeric in numeric comparison'
+ when using tapes... redid toc routine to use two hashes for disk/tape
+ files, sort them differently
+ - Existing tape indexes weren't found to a missed edit during the past few
+ days' cleanup
+
+----------------------------------------------------------------------
+Version 0.9.9.1 (20030107)
+
+Changes:
+ - use -print0 with find and null-terminated list flags for the archive
+ programs. Now any weird filenames should be valid. This necessitated
+ using find -regex rather than an egrep pipeline. (FreeBSD find now has
+ "-regex" so the old argument is gone).
+ - also took out the sed of the leading "./" since the null-terminated
+ file list messed it up too. Was just for looks anyway.
+ - cpio flags use the short options so more compatible with other cpio's
+ - cleaned out tape-only messages when using files
+
+Bug fixes:
+
+ - Pipeline error status - if something went wrong with a shell pipeline, and
+ it was not the _last_ command, you can't catch it! I never knew this
+ was impossible...
+
+ bash has a PIPESTATUS array variable for this, so I took care of it for
+ systems where SHELL is bash... tcsh seems to set $? if anything fails,
+ so it's ok too.
+
+ I don't know if there is a way to fix it generically.
+
+ - Actually log output. doh! (related to above, actually)
+
+ - afio compress programs honor the path overrides
+
+
+----------------------------------------------------------------------
+Version 0.9.9 (20030106)
+
+Wonder of wonders... I'm actually using this again. The guy who registered
+flexbackup.sourceforge.net never picked up the files or updates his page so I'm
+flipping pointers back to sourceforge.net I guess.
+
+I'm mainly just using on-disk files now - will try to keep tape operation
+tested.
+
+Changes:
+ - changed the index delete flags worked, it was too confusing overloading
+ the -toc flag (WTF was I smoking 2 years ago?).
+ Now toc only lists stuff, and db-fixing can be done with:
+ flexbackup -rmindex all
+ flexbackup -rmindex <key>
+ flexbackup -rmindex <key> -f <file>
+ - added "-rmfile <file>" option if we are backing up to disk. nukes
+ archive and its related index entry
+ - take the seconds field out of the archive + log names... it was just
+ too long to interact with, and who really does more than 1 backup in a
+ minute?
+ - removed zip archive support. They just don't like to work with
+ stdout/stdin like other unix tools and are a pain to special case.
+ - "-newtape" when using files will rm the files in the dir
+ - support non $PATH or hardcoded locations to commands in config file:
+ $path{'program'} = /path/to/program;
+
+Bug Fixes:
+ - doh - can't use find -cnewer & a reference file with touch -d together
+ since the change time is still "now". I remembered the suggestion but
+ the long lapse in devel time made me forget to see if it worked.
+ REMOVED cnewer mistake.
+ - default config uses /var/lib rather than /var/state (FHS spec on this
+ went away long ago)
+ - don't pad blocks when archiving to files on disk
+ - was using numeric sort when listing alphanumeric files
+ - "-num" invalid when using files
+ - newer cpio didn't like sparse in --create mode
+ - errors in command chain weren't detected. now separate some of the
+ steps; remote still hosed up
+
+
+----------------------------------------------------------------------
+Version 0.9.8.3 (20020824)
+
+Revive the tree
+Move support info/web site so this isn't stagnant anymore
+Check in what I had pending
+
+New Features:
+ - works for block devices (like floppies). Turns the indexing off since
+ you can't do multiple files, but otherwise it works fine
+
+Bug fixes:
+ - use "&&" rather than ";" in between commands
+ - use "-cnewer -or -newer" rather than "-newer"
+ - set locale so touch works
+ - tar - take off "! -type d" since we have --no recursion
+ - log/backup files now use yyyymmddhhmm.ss times in the
+ filenames. Previously you could get messed up if you ran two backups
+ on the same minute; files would clobber themselves
+ - OpenBSD didn't have "mt rdhpos". Split the BSD flavors up.
+ - add a rewind before erase when deleting old index
+ - "-n" was touching the stamp file, now it won't
+
+
+
+----------------------------------------------------------------------
+Version 0.9.8 (19991102)
+
+New Features:
+ - add -newtape option to initialize index, but no backup
+ - add -num <n> option for reads that will position the tape at the
+ specified file number before reading
+ - added config file option for whether "erase" actually calls "mt erase"
+ or just rewinds - for some drives, mt erase takes hours...
+ (variable is $erase_rewind_only)
+ - tried to add intelligence for various mt flavors on different Unix variants
+ - made block padding a config file option ($pad_blocks). conv=sync was
+ causing problems for Clemmit on OSF1.
+
+Bug fixes:
+ - sort the toc output correctly
+ - rewind after printing toc
+ - fix bug where tape indexes always started at 10 after awhile
+ (similar sorting problem with setting $main::nextfile)
+
+Cleanup:
+ - web site moved
+ - other minor cleanups to tape key handling
+ - "-n" bypasses index operations as well
+ - fix some messages/formatting
+
+----------------------------------------------------------------------
+Version 0.9.7 (19991019)
+
+New Features:
+ - table-of-contents support - tells you which backups are on which tapes.
+ uses a simple "key" on the tape plus on-disk database files
+ - added "traverse mountpoints" config option for non-dump types
+ can skip nfs mounts or really do everything
+ - added config option to turn off assumption that level zero "all"
+ should erase the tape
+
+Bug fixes:
+ - add "conv=sync" to dd flags so it will pad blocks
+ some tape drivers complain if writes are not padded
+ "buffer" already did this since I used the B flag by default
+ - don't use `date ..`, but POSIX::strftime() instead
+ incremental backups on FreeBSD were busted (no date -r)
+ - using $ in exclude expressions broke remote backups if the user's
+ login shell was csh/tcsh due to it's insane syntax quirks. Worked
+ around it putting a trailing $ outside the second level quotes
+ - works on OSF/1 boxes
+
+Cleanup:
+ - remove ftape table of contents now that I have a generic one
+ - autodetect Linux ftape device, eliminates configuration
+ - change time format slightly for consistency
+ - cleaned up handling of reten/erase/rewind options
+ - tape position display under FreeBSD is just "mt rdhpos",
+ under others is "mt tell"
+ ("mt status" cluttered things w/ lots of useless info)
+
+----------------------------------------------------------------------
+Version 0.9.6 (19991003)
+
+New Features:
+ - buffing is optional
+ - log files for list/extract/compare (flexbackup.<operation>.log in cwd)
+ - can now have more than one exclude expression defined
+ - add -norewind flag
+ - make symlink to latest log file
+ - use our own timestamps for "dump", with -T
+ config file variable to use /etc/dumpdates
+ - flag in configuration file for mt variable block size
+ - do buffering on archive read if enabled
+ - add "-extract -files <file>" to extract a list of files from an archive
+ (will need this later for interactive restore shell)
+ - minimal Makefile that lets you set config path, bin path, etc. for install
+ - can use "compress" now in addition to gzip/bzip2
+ - added zip type
+ - var in configuration file for afio compression cache size
+ - flag in configuration file for tar atime preserve
+ - flag in configuration file for tar record number printing
+ - flag in configuration file for sparse file handling
+ - add -compress flag to override config file setting
+
+Bug fixes/cleanup:
+ - afio nocompress types bug - have to list them all if we give the
+ -E flag.. (I added mp3 and it ignored the builtin defaults)
+ made generic \$nocompress_types since zip uses it too
+ - find -regex doesn't exist on BSD, redo with egrep to be more
+ generic
+ - GPL clause in program itself not readme
+ - redid remote commands to use only 1-way rsh/ssh
+ archive command building done differently now as a result
+ - redid command echo wrapping
+ - dates back to string format (easier to read), use numeric only on BSD
+ will work with pre-4.0 fileutils that have touch -t bug
+ - tar exclude mess is fixed, using --files-from again as well.
+ problem before was I was not stripping directory names
+ - make better afio control file handling for volume labels
+ - use -B (--read-full-blocks/--read-full-records) for tar
+ - use $verbose to set restore -v for list/extract
+ - log files and archives to disk use same filename pattern now
+ - do obsolete timestamp removal at end of job not beginning
+ - forgot to remove tar_verbose and cpio_verbose from conf file
+ - debug shows real mt command
+ - clean up flag handling some more, use long flags
+ - sleep at end of backup not done if file or debug
+ - if -noerase, move to eod before starting
+
+----------------------------------------------------------------------
+Version 0.9.5 (19990927)
+
+New features:
+ - buffering for all backup types
+ now requires the "buffer" utility, everything piped through it
+ if remote backup, buffers on both sides
+ - every backup type now has an embedded "title" (level,date,host,etc)
+ dump has had this info. by default
+ for afio we make a tiny control script
+ for tar we use -V for a volume label
+ for cpio we touch a filename wih the info before backing up
+ - config file regular expression for excluding files from backup
+ doesn't work for dump, of course
+ - config option for afio file size compression threshold
+ - config option for afio nocompress file extension list
+ - compression for all backup types
+ - if reading from a file, auto-set compression & archive type
+ whee we can also list & extract rpm files :)
+
+Bug fixes/cleanup:
+ - nuke trailing slashes on given dirs
+ - typo! mt defblksize, not mt defsetblk. also, that whole routine
+ was just broken. fixed.
+ - tar was archiving things twice. "find . | tar -c -T -" gives double
+ result. revert back to non-find mode for tar, works fine
+ - remote tar didn't quite work (needed full path for ssh/rsh)
+ - remote broken if using filenames (and they were long)
+ - error if remote host and this host are the same
+ - error if device is dir and its relative
+ - stderr output from new pipeline commands wasn't in log file
+ - fix dirs going on top of each other when mult. dumps. + files
+ - log file removal at end of job; undef logfile after compressing
+ - compress, compr_level, verbose, buffer are generic options
+ - commands are an array; command build cleanup; mt takes multiple commands
+
+
+---------------------------------------------------------------
+Version 0.9.4 (19990924)
+
+New features:
+ - bzip2 option for afio and tar archives, as well as log files
+ - configurable compression level for afio
+ - multiple tapes for an "all" level 0. This just lets you split up
+ backups onto different tapes, not end-of-media type of
+ multivolume problems (that's on the todo list)
+ - support for remote dump over ssh - use dd over an ssh pipe
+ - support ssh for cpio remote backups - use dd over an ssh pipe
+ - add -reten switch to retension before backup or any other operation
+ - print "date of this level" sort of like dump does
+
+Bug fixes/cleanup:
+ - test if mt setblksize or mt defblksize is the right command to use
+ - dump -a (autosize) works now. Found my problem was a known bug.
+ >>> NOTE for Linux w/dump, you MUST install dump-0.4b4-10
+ (redhat lorax) or newer, or equivalent patches if you want
+ dump -a to work
+ - afio doublebuffer only if local backup
+ - dump length bug for "-fs all"
+ - get last timestamp was off-by-one
+ - output of archive commands didn't go to log file
+ - with label "host:path-dir"; didn't work w/ remote + files
+ labels are now "host-path-dir"
+ - sanity checks in the setup, flag variables
+ - check for programs in your path
+
+---------------------------------------------------------------
+Version 0.9.3 (for testing only)
+
+New features:
+ - can now archive to files instead of devices
+ just set $device to a directory for backup
+ compare/extract/list/restore can now take a filename argument
+ - add -n option that doesn't run mt or the actual backup
+ - timestamp changes:
+ use "touch -t" not "touch -d" so FreeBSD works
+ all dates now in YYYYMMDDhhmm.SS format
+ use timestamps of the epoch for level 0 (simplifies code)
+ - add "-d var=val" for overriding config values
+ - tar mode uses "find" just like afio & cpio
+ - afio doublebuffering option
+
+Bug fixes/cleanup:
+ - FreeBSD: if $dump_length set to 0 use dump -a
+ - FreeBSD: fix for mt setblk
+ - error if level > 0 and no level 0 stamp exists
+ - fix label string for remote paths
+
+---------------------------------------------------------------
+Version 0.9.2 (19990920)
+
+ - initial release to the public
+ - gzip log files option
+ - fix afio buffer to be in units of k in cfg file
+
+Version 0.9.1 (1999019)
+
+ - incrementals w/ afio cpio tar done (remote fs too!)
+ - timestamp only after sucessful backup, but use the pre-backup time
+ - noerase option
+
+Version 0.8
+
+ - redone with "use strict"
+ - incremental for non-dump types almost done
+ - automated pacakging
+ - clean some duplicated code
+ - rpm build, lsm file
+ - previously just a local hack I had done for myself
+
+
+Local Variables:
+mode: flyspell
+end:
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d60c31a
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/CREDITS b/CREDITS
new file mode 100644
index 0000000..c1bf9b6
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,40 @@
+
+Mainly written by Edwin Huffstutler <edwinh@computer.org>
+
+Thanks to Paul Holcomb for trying to keep the project alive while I was
+ignoring it...
+
+Other people who have helped me debug/fix things, send patches, or report
+bugs:
+
+ John Reynolds <johnjen at reynoldsnet.org>
+ Paul Holcomb <pholcomb at cpoint.net>
+ Max Kalika <max at lsit.ucsb.edu>
+ Alex Aminoff <alex at basespace.net>
+ Michael Matsumura <michael at limit.org>
+ Caleb Crome <caleb at crome.org>
+ Perry Gilfillan <perrye at linuxmail.org>
+ Ben Chapman <benjamin-chapman at utulsa.edu>
+ Jochen Erwied <jochen at erwied.de>
+ George A. Theall <theall at tifaware.com>
+ E-smith / Mitel folks - Charlie Brady et al.
+ Andrew McRory <amacc at iron-bridge.net>
+ Markus Schaub <mailinglisten at markus-schaub.de>
+ Mike Packard <mike at packardshome.net>
+ Douglas Bollinger <dcb at sdf.lonestar.org>
+ raphael deimel <raphael at russe.ods.org>
+ Nicholas Paufler <npaufler at incentre.net>
+ Simon Matter <simon.matter at ch.sauter-bc.com>
+
+Many people in the long development time lapse, I'm sorry if your
+suggestions/fixes got dropped...
+
+older releases:
+
+ Clemmitt Sigler <siglercm at alphamb2.phys.vt.edu>
+ George Young <gry at ll.mit.edu>
+ sethalx <alx at taylor.org>
+ Rick Gaudette <rjg at ECE.NEU.EDU>
+ Rafal Maszkowski <rzm at icm.edu.pl>
+ Michael Bartosh <bartosh at apple.tamu.edu>
+
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..95dd1a7
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,16 @@
+
+Installation instructions
+
+1. If you don't like the default paths for the config file, the bin dir for
+ programs, or where perl lives, edit the Makefile, and set
+ them. Defaults are CONFFILE=/etc/flexbackup.conf, BINPATH=/usr/bin,
+ PERLPATH=/usr/bin/perl
+
+2. Run "make", then "make install"
+
+3. Edit /etc/flexbackup.conf (or wherever you put it) as desired.
+ See the comments for details.
+
+
+$Id: INSTALL,v 1.3 2003/07/03 18:16:47 edwinh Exp $
+$Name: v1_2_1 $
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..12e2b32
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+# Hey emacs, use -*-Makefile-*- mode
+######################################################################
+# $Id: Makefile.dist,v 1.8 2003/07/29 17:00:07 edwinh Exp $
+# $Name: v1_2_1 $
+######################################################################
+
+PREFIX=/usr
+
+# Where the script binary should go
+BINPATH = $(PREFIX)/bin
+# Where the manpage should go
+MANPATH = $(PREFIX)/share/man
+
+# Where flexbackup.conf should be stored
+CONFFILE = /etc/flexbackup.conf
+
+# Where perl lives
+PERLPATH = /usr/bin/perl
+
+######################################################################
+
+all: fb.install
+
+install: all
+ install -m 0644 flexbackup.conf $(CONFFILE)
+ install -m 0755 fb.install $(BINPATH)/flexbackup
+ install -m 0644 flexbackup.1 $(MANPATH)/man1/flexbackup.1
+ install -m 0644 flexbackup.conf.5 $(MANPATH)/man5/flexbackup.conf.5
+
+fb.install:
+ cp flexbackup fb.install
+ cp fb.install fb.tmp; sed -e 's%/etc/flexbackup.conf%$(CONFFILE)%g;' fb.tmp > fb.install
+ cp fb.install fb.tmp; sed -e 's%/usr/bin/perl%$(PERLPATH)%g' fb.tmp > fb.install
+ rm -f fb.tmp
+
+clean:
+ rm -f fb.install
diff --git a/README b/README
new file mode 100644
index 0000000..e8131e9
--- /dev/null
+++ b/README
@@ -0,0 +1,227 @@
+ _____ _ _ _
+ | ___| | _____ _| |__ __ _ ___| | ___ _ _ __
+ | |_ | |/ _ \ \/ / '_ \ / _` |/ __| |/ / | | | '_ \
+ | _| | | __/> <| |_) | (_| | (__| <| |_| | |_) |
+ |_| |_|\___/_/\_\_.__/ \__,_|\___|_|\_\\__,_| .__/
+ |_|
+ http://www.flexbackup.org
+
+----------------------------------------------------------------------
+A flexible backup tool
+
+Features:
+ o Easy to configure
+ o Uses dump, afio, GNU tar, cpio, star, pax, or zip archivers
+ o Full and numbered levels of incremental backup (acts like "dump")
+ o Compression and buffering options for all backup types
+ o Does remote filesystems (over rsh/ssh; no special service)
+ o Package delta mode can backup files not owned by rpm, or owned+changed files.
+ o Writes to tapes, on-disk archive files, or on-disk directory trees
+ o Keeps a table of contents so you know what archives are on each tape
+ o Nice log files
+
+Software needed:
+ o perl
+ o fileutils, findutils (a "find" capable of using the -regex switch)
+ o one or more of
+ dump/restore, afio, GNU tar, star, pax, cpio, zip
+ o mt (if using tapes)
+ o gzip, bzip2, lzop, or compress (optional)
+ o buffer (optional) [needs patches or recompile for 2+GB file support]
+
+See the INSTALL file for installation instructions.
+
+I looked at many other packages and didn't really find anything that
+worked the way I wanted it to. For my needs I felt the existing
+packages were any combination of:
+ o Too complex to setup/use
+ o Lacked documentation
+ o Nonstandard archiving formats
+ o Overkill for a small network of 2 or 3 machines
+ o Too simplistic, or hard-coded assumptions
+ o No scripts helped drive "dump/restore"
+ o Didn't have features I wanted
+
+I also wanted to take the standard backup tools and make a generic
+interface to them.
+
+----------------------------------------------------------------------
+USAGE EXAMPLES
+
+--> Please see "flexbackup -help" and the config file comments, or the
+flexbackup(1) and flexbackup.conf(5) manpages. There are many useful
+options that aren't appropriate to describe here.
+
+To do a full backup of /home:
+
+ flexbackup -dir /home
+
+For nightly incremental backups, run something like this via cron:
+
+ flexbackup -set all -level 9
+
+To do a full backup of /usr from machine "otherhost", forcing cpio type:
+
+ flexbackup -dir otherhost:/usr -type cpio
+
+To do an level 5 incremental backup of a named backup set:
+
+ flexbackup -set backupset2 -level 5
+
+Extract all files from file number 1 on the tape into current directory:
+
+ flexbackup -extract -num 1
+
+Compare afio archive at current tape position with the current
+directory:
+
+ flexbackup -compare -type afio
+
+Extract files listed in "restorelist" from an on-disk file:
+
+ flexbackup -extract home.0.19990930.afio -flist restorelist
+
+List what backups are on this tape:
+
+ flexbackup -toc
+
+----------------------------------------------------------------------
+NOTES - GENERAL
+
+ Remote: rsh or ssh is used for remote backups. Backups of remote machines
+ are to be run from the machine with the tape drive or archive disk space.
+
+ Archiving to files - just set $device to a directory. Backups will be
+ called <filesystem>.<level>.<yyyymmdd>.<type>. If you use type "copy" or
+ "rsync", instead of an archive file you'll get a directory tree with the
+ files mirrored in it.
+
+ Log files are of the format <fs>.<level>.<yyyymmdd> Lower-level backups
+ will wipe out all higher-level log files in the log directory for that
+ filesystem. A symlink is made to the "latest" log for a certain
+ filesystem.
+
+ Extracting only lists of files: dump/restore and tar will recurse
+ into subdirectories if they are in the list. afio and cpio will not
+ (you must list all the files you want to extract them in that case)
+
+ When comparing "dump" archives, and if you only backed up a
+ subdirectory of a filesystem, you will get errors for the "other"
+ parts of the filesystem. Similarly, if you extract a dump archive,
+ remember that it keeps the subdirectory path, the "top" of the
+ archive is not necessarily the directory you told it to dump, but
+ rather the mount point of the filesystem
+
+----------------------------------------------------------------------
+NOTES - WHEN USING TAPES
+
+ The first file on a tape is the "index key" (see "flexbackup -toc").
+ Therefore, use the -num switch to read specific archives. e.g, to read the
+ first backup archive on a tape: "flexbackup -list -num 1". Otherwise you
+ can position it yourself with "mt fsf 1 ; flexbackup -list ... "
+
+ Level 0 backups of "all" assume a new tape - will trigger
+ tape retension and erasure. Other backups move tape to
+ current end of data and start the backup there.
+
+ Backups rewind the tape when done. Other operations leave it where it is.
+
+ If you split the "all" level 0 backup into sets, tape numbers start at 0.
+
+ If you set device to "host:/dev/tapedevice", you can use remote tape
+ drives (via rsh/ssh)
+
+ I suggest using "afio" for tapes, especially if you want compression. It
+ compresses one file at a time, so an error will not corrupt your entire
+ archive like with other types. If you are using on-disk backups, the
+ archive type doesn't matter as much.
+
+----------------------------------------------------------------------
+SAMPLE FILESYSTEM SETUP
+
+Here's a snippet of a config file that I'm using, so you can see how the
+filesystem "sets" and pruning options work.
+
+# Configure backup "sets".
+# Not needed if you use "-dir <dir>" to backup one tree at a time.
+# Each set is a simple space-separated list of filesystems
+# Remote filesystems should denoted as 'host:dir'
+# You can use anything (other than 'all') as set names
+#
+# Example:
+# $set{'set1'} = "/home /usr";
+# $set{'set2'} = "/dir3 machine2:/dir4 machine3:/dir5";
+#
+# "-set all" will back up all defined sets. If you are doing a full backup
+# using tapes, each "set" will go onto a different tape and you will be
+# prompted for tape change in between.
+#
+$set{'homes'} = "/home/user1 /home/user2 /home/user3";
+$set{'alletc'} = "/etc hercules:/etc thor:/etc gazelle:/etc";
+$set{'other'} = "/var/named /net/local /net/www";
+$set{'os'} = "/ hercules:/ thor:/ gazelle:/";
+$set{'laptop'} = "laptop:/etc laptop:/home/edwinh";
+
+# Subtree pruning
+# A space-separated list of directories to prune from each backup.
+# Key is a filesystem or host:dir spec as outlined above
+# regular expressions allowed (not shell-type wildcards!)
+$prune{'/'} = "tmp proc var/spool/news var/spool/squid disk. mnt.? misc net";
+$prune{'hercules:/'} = "tmp proc disk. mnt.? misc net";
+$prune{'thor:/'} = "tmp proc disk. mnt.? misc net";
+$prune{'gazelle:/'} = "tmp proc disk. mnt.? misc net";
+$prune{'/net/local'} = "games";
+
+----------------------------------------------------------------------
+SAMPLE CRONTAB
+
+This goes with the above. Shows how the sets are arranged and how the
+-wday flag could be used. (You will not want the "root" field for a normal
+user crontab... this is in /etc/crontab type format.)
+
+# Home directories at 3:31am
+# Daily incremental backups weekdays (catch day-to-day changes)
+# Weekly differential backups Saturday (catch all changes since full)
+# Full backup - once a month on Sunday only
+31 3 * * 1-5 root flexbackup -set homes -incremental
+31 3 * * 6 root flexbackup -set homes -differential
+31 3 1-7 * * root flexbackup -wday 7 -set homes -full -type afio
+
+# All /etc dirs at 3:01
+# Daily incremental backups weekdays (catch day-to-day changes)
+# Weekly differential backups Saturday (catch all changes since full)
+# Full backup - once a month on Sunday only
+1 3 * * 1-5 root flexbackup -set alletc -incremental
+1 3 * * 6 root flexbackup -set alletc -differential
+1 3 1-7 * * root flexbackup -wday 7 -set alletc -full -type afio
+
+# Other filesystems at 2:31am
+# Daily incremental backups weekdays (catch day-to-day changes)
+# Weekly differential backups Saturday (catch all changes since full)
+# Full backup - once a month on Sunday only
+31 2 * * 1-5 root flexbackup -set other -incremental
+31 2 * * 6 root flexbackup -set other -differential
+31 2 1-7 * * root flexbackup -wday 7 -set other -full -type afio
+
+# Unowned/changed package stuff on each machine at 1:01am
+# Only once a month
+1 1 1-7 * * root flexbackup -wday 7 -set os -pkgdelta rpm -full
+
+
+----------------------------------------------------------------------
+MISCELLANEOUS
+
+ I originally did this to to help automate my life.
+ See the TODO list for what we might be planning to do next.
+
+ Send problems/suggestions to:
+ flexbackup-help@lists.sourceforge.net
+ Please check the FAQ!
+ See http://www.flexbackup.org for contact information.
+
+ Thanks,
+ Edwin Huffstutler <edwinh+flexbackup@edwinh.org>
+
+Local Variables:
+mode: flyspell
+end:
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..1b353cd
--- /dev/null
+++ b/TODO
@@ -0,0 +1,157 @@
+$Id: TODO,v 1.171 2003/10/10 05:41:56 edwinh Exp $
+$Name: v1_2_1 $
+
+Flexbackup to-do list
+Some of these might not happen
+
+Cleanup/fix:
+ - Consolidate use_file, use_pipe, etc to single string var.
+ - Fix remote tape drive + mt uname conditionals are local
+ - Clean up more $main:: vars, split into types or refactor code
+ - When level > 9 and rm old logs/stamps, order is printed wrong (cosmetic nit)
+ - If comp_log is changed, we don't nuke old logs that normally would be
+ overwritten with ourselves if comp_log stayed the same. (cosmetic nit)
+ - Fix afio w/null (FreeBSD printf can't spit out null char)
+ - Multiple backups launched within the same second clobber each other's
+ logs/filenames
+ - rpm-delta mode - prelink messages clutter stderr
+ - Whole class of cross-platform command switch stuff:
+ - Use dump combined flags (for older dumps that want abc not -a -b -c)
+ - Not all finds have the -fstype flag (from $traverse_fs)
+ - Use lowest common denominator flags? Sort of what's tried now w/ cpio
+ (This is why pax exists...)
+
+Misc:
+ - Do .debs?
+ - Try against torture-test list:
+ http://ftp.berlios.de/pub/star/testscripts/zwicky/testdump.doc.html
+ - Update mt manpages collection I keep in cvs
+ - Pondering moving everything to savannah. Sourceforge is annoying me.
+ mail list archives hardly work, and I hate the file release system thing
+ being totally un-automatable.
+
+Features to add:
+
+ - Remote directores for to-disk backups
+
+ - Remote device via Net::FTP ?
+
+ - Handle multi-volume backups (original idea w/ 'multibuf' never panned out)
+
+ Found "multivol" program.
+ Ack. still not workable. need edits to multivol...
+
+ mbuffer can do this? Bah. Won't work over remote, needs terminal
+ attached. Testing hooks in now for local.
+
+ - Make package delta work with debian pkgs or others
+
+ - A shorter summary status that's better than doing
+ egrep "(error|of set|Backup of|written)"
+ of logfile. Needs to handle all configurations....
+
+ - CD-ROM burning (or just .iso type?) for full backups? any level?
+ just act like to-file archives, and take those files & write to CD?
+ "iso" type with a dirtree copy? wasted space this way.
+ be able to split archives into 650MB chunks?
+ lots of questions
+
+ - If we detect an error and are using tapes, do something intelligent
+ to reposition tape, add another filemark, etc to get tape to known state
+ and avoid the maybe-error in the index & what 'nextfile' is on the tape
+
+ - Log tape sizes (output of buffer or dd or mt tell can be used?)
+
+ - Use AppConfig module or something for config file, so it doesn't have to
+ be in perl syntax (I don't mind perl syntax at all, not sure how joe
+ user feels)
+
+ - smbtar?
+ doable - use fs spec of something like user:pass@host/share:dir
+ smbtar has "files newer than" option, test
+ don't want password echoed
+ no exclude from find
+ top level of shares only?
+
+ - Make -extract -files for afio/cpio/zip recurse dirs
+ dump/tar already to this. Actually just check that all act the same.
+
+ - Interactive restore-like shell for types besides dump
+ see notes below
+
+ - Destdir option for compare / extract
+
+ - Umask spec for on-disk archives/logs/stamps/index. check tmpfiles too.
+
+ - Spiff up the contributed CGI program?
+
+ - Autoloader/mtx support? Need somonewho has one to do this
+
+ - Do we want to count on perl (or flexbackup for that matter) being
+ installed on remote machines? We could do some built-in replacements
+ for the 'find' for instance and make things more standardized. although
+ keeping it simply built out of standard commandline utils is a big plus
+ as well...
+
+ - Make per-host path overrides possible
+
+ - Make -set or -dir able to be given multiple times on the commandline
+ (then what do we call the log file?
+
+ - Stor lists of files backed up in a on-disk db (to help find what to
+ restore from?). Actually now you can just 'zgrep filename
+ /var/log/flexbackup/*' and that works pretty well.
+
+ - Extract options to not overwrite existing files
+
+ - Store the index db on the tapes?
+ But we don't know the size ahead of time unless hard-limited resperved size.
+ Updating db at the beginning of the tape will add tape-travel overhead for every backup.
+ rewind->write loses the rest of the contents past the first file?
+
+ - gnuplot graphs of backup size/duration vs time (parse logfiles)
+
+ - Encryption with gnupg?
+
+ - Make file-based backups able to use split for smaller archives?
+
+ - Along the same lines of "-toc all" there should be facilities in
+ flexbackup to query when the last level N backup of a filesystem was done and
+ what tape it was on.
+ -showindex /u -level 0
+ would print you out when the last level 0 backup was done and what the key
+ index was.
+
+
+-------------------------------------------------------------
+
+Notes for cloning restore shell for afio/tar/cpio types)
+Use perl readline module
+(Maybe a seperate program or module that runs off a list fed to it)
+
+1) do a listing
+2) parse & put into data structures
+3) mt bsf 1
+4) then give a shell w/ all commands from regular dump
+5) mark things for extraction
+6) reopen archive & extract that list
+
+Help from restore to jog my brain...
+
+ ls [arg] - list directory
+ cd arg - change directory
+ pwd - print current directory
+ add [arg] - add `arg' to list of files to be extracted
+ delete [arg] - delete `arg' from list of files to be extracted
+ extract - extract requested files
+ setmodes - set modes of requested directories
+ quit - immediately exit program
+ what - list dump header information
+ verbose - toggle verbose flag (useful with ``ls'')
+ help or `?' - print this list
+If no `arg' is supplied, the current directory is used
+
+
+Local Variables:
+mode: flyspell
+end:
diff --git a/contrib/flexbackup.cgi b/contrib/flexbackup.cgi
new file mode 100755
index 0000000..12a8d2f
--- /dev/null
+++ b/contrib/flexbackup.cgi
@@ -0,0 +1,424 @@
+#!/usr/bin/perl -w
+#
+# flexbackup.cgi
+# cgi frontend for the flexbackup tape
+# archival program
+# Copyright 2002, "Linux Systems Engineers / PC Doctor SE, Inc"
+# Released under GPL 2.0 By Andrew McRory <amacc@linuxsys.com>
+# Written by Brad Andrews <brad@...>
+# version 0.9a
+# Wed Oct 9 2002
+######################################################################
+
+use strict;
+use CGI;
+use CGI::Carp "fatalsToBrowser";
+use POSIX;
+use NDBM_File;
+
+my( $CGI ) = new CGI;
+my( $home_directory ) = "/home/flexbackupweb";
+my( $base );
+my( $script ) = $CGI -> script_name;
+my( $directory );
+my( $tape_id );
+my( %dir_hash );
+my( $passed_file );
+my( $tape_device ) = "/dev/st0";
+my( $non_rw_tape_device ) = "/dev/nst0";
+my( $flexbackup_conf ) = "/etc/flexbackup.conf";
+
+
+if ( $ARGV[ 0 ] eq 'blind' ) {
+# this is so you can run a backup from cron with
+# the advanced database features, just run
+# 'flexbackup.cgi blind'
+ &run_backup;
+ exit 0;
+}
+
+# This is the dispatch table to determine program state
+my( %dispatch_table ) = (
+ 'file' => \&extract_file,
+ 'tape_id' => \&pre_db_crawler,
+ 'Backup' => \&run_backup,
+ 'Change Schedule' => \&change_schedule,
+ 'edit_config_page' => \&edit_config_page,
+ 'filesystems' => \&change_conf,
+ 'add_filesystem' => \&change_conf,
+ 'Change Options' => \&change_conf,
+ );
+my( $param_test ) = 0;
+foreach my $keys ( keys( %dispatch_table ) ) {
+ if ( $CGI -> param( "$keys" ) ) {
+ $param_test++;
+ $dispatch_table{ "$keys" } -> ( $CGI -> param( "$keys" ) );
+ }
+}
+if ( $param_test < 1) {
+ &browse_archives;
+}
+
+sub pre_db_crawler {
+ $tape_id = $_[ 0 ];
+ &db_crawler;
+}
+
+sub browse_archives {
+# Main screen and backup initiator
+ &html_header;
+ my( %temp_archive );
+ opendir( DBDIR , "/var/lib/flexbackup" );
+ my( @archives_list ) = readdir( DBDIR );
+ print qq{<h3>Select a backup to browse</h3>};
+ foreach ( @archives_list ) {
+ if ( m/^([\d\.]+\.master)\.db$/ ) {
+ tie( %temp_archive, "NDBM_File", "/var/lib/flexbackup/$1", O_RDONLY , 0600 ) || die "can't tie $1, $!";
+ print qq{<blockquote>};
+ foreach ( sort( keys( %temp_archive ) ) ) {
+ print qq{Friendly name: <a href=$script?tape_id=$_>} , $temp_archive{ $_ } , qq{</a><br>\n};
+ }
+ print qq{</blockquote>};
+ untie( %temp_archive );
+ }
+ }
+ print qq{<h3>Or...</h3>Enter a name to backup to or leave blank for date format.<br>},
+ $CGI -> start_form( -action => $script ),
+ $CGI -> textfield( -name => "friendly_name" ),
+ $CGI -> submit( -name => "Backup" ),
+ $CGI -> endform,
+ qq{<h3>Or...</h3>click <a href=$script?edit_config_page=1>here</a> to edit configuration for backups};
+}
+
+sub db_crawler {
+# open the db file and read in directories/files from it
+ &html_header;
+ my( %hash );
+ tie( %hash , "NDBM_File" , $CGI -> param( 'tape_id' ) , O_RDONLY , 0600 ) || die "can't tie, $!";
+ my( $i , $full_rootlink );
+ my( %dir_hash, %file_hash );
+ if ( $CGI -> param( 'directory' ) ) {
+ $passed_file = $CGI -> param( 'directory' );
+ }
+ else {
+ $passed_file = $hash{ "<base_directory>" };
+ }
+ my( @rootlink ) = split( m/\// , $passed_file );
+ print qq{<table width=100% border=1><tr><td valign=top><h3>Files:</h3>};
+ #$passed_file =~ s/(.*)\/$/$1/;
+ foreach ( sort( keys( %hash ) ) ) {
+ if ( $hash{ $_ } =~ m/^$passed_file([^\/]+\/)$/ ) {
+ $dir_hash{ $hash{ $_ } } = $1;
+ }
+ #elsif ( $hash{ $_ } =~ m/^$passed_file$/ ) {
+ elsif ( $hash{ $_ } =~ m/^$passed_file$/ ) {
+ unless ( $_ eq "<base_directory>" ) {
+ my( $tempfile ) = $_;
+ $tempfile =~ s/.*\/(.*)/$1/;
+ $file_hash{ $_ } = $tempfile;
+ }
+ }
+ }
+ foreach( sort( keys( %file_hash ) ) ) {
+ print qq{<a href="$script/} , $file_hash{ $_ } , qq{?tape_id=$tape_id&file=$_">} , $file_hash{ $_ } , qq{</a><br>\n};
+ }
+ print qq{<p>Click <a href=$script?tape_id=$tape_id&tarfile=$passed_file>here</a> to download directory as a .tar file</td><td valign=top><h3>Subdirectories of } , ( $CGI -> param( 'directory' ) || $passed_file ) , qq{:</h3>};
+ foreach ( sort( keys( %dir_hash ) ) ) {
+ print "[<a href=$script?tape_id=$tape_id&directory=$_>$dir_hash{ $_ }</a>]<br>\n";
+ }
+ print qq{</td></tr><tr><td colspan=2><h3>Current Directory:</h3>};
+ for ( $i = 0 ; $i < ( scalar( @rootlink ) - 1 ) ; $i++ ) {
+ $full_rootlink .= "/$rootlink[ $i ]/";
+ $full_rootlink =~ s/\/\/*/\//;
+ print qq{<a href=$script?tape_id=$tape_id&directory=$full_rootlink>} , $rootlink[ $i ] , qq{</a>/};
+ }
+ print pop( @rootlink ),
+ qq{<br>\n<a href=$script>Return to archive selector</a></td></tr></table>};
+}
+
+sub run_backup {
+# run the actual backup (calling flexbackup)
+ &html_header;
+ print qq{Your backup has begun. It may take up to several hourse to complete, at which time access will be available.<br>};
+ my( $start_read , $base_directory , $tape_key , $file_num );
+ my( %hash_file , %master_file );
+ my( $friendly_name ) = $CGI -> param( 'friendly_name' ) || scalar( localtime() );
+
+ $ENV{'PATH'} = "/usr/bin:/bin";
+ $ENV{'BASH_ENV'} = undef;
+ $< = $>;
+ my( @old_key ) = `flexbackup -toc`;
+ my( $old_key ) = '';
+ foreach ( @old_key ) {
+ if ( $_ =~ m/(\d{12}\.\d\d)/ ) {
+ $old_key = $1;
+ }
+ }
+ if ( $old_key ne '' ) {
+ print "$old_key<br>";
+ opendir( KILL_DIRECTORY , "/var/lib/flexbackup" );
+ my( @kill_files ) = readdir( KILL_DIRECTORY );
+ close( KILL_DIRECTORY );
+ foreach ( @kill_files ) {
+ m/(.*)/;
+ $_ = $1;
+ print "$_<br>";
+ unless ( index( $_ , $old_key ) == -1 ) {
+ unlink( "/var/lib/flexbackup/$_" ) || die "can't unlink $_";
+ }
+ }
+ `mt -f $tape_device erase`;
+ `flexbackup -rmindex $old_key`;
+ }
+ open( FILE , "flexbackup -fs all|" ) || die "Can't open flexbackup, $!";
+
+ while ( <FILE> ) {
+ if ( m/File number (\d)+, index key ([\d\.]+)/ ){
+ $tape_key = $2;
+ $file_num = $1;
+ tie( %master_file,
+ "NDBM_File",
+ "/var/lib/flexbackup/$tape_key.master",
+ O_RDWR|O_CREAT,
+ 0660 )
+ || die "Can't tie, $!";
+ print "backing up $tape_key.$file_num";
+ $master_file{ "/var/lib/flexbackup/$tape_key.$file_num" } = "$friendly_name";
+ untie( %master_file );
+ tie( %hash_file,
+ "NDBM_File",
+ "/var/lib/flexbackup/$tape_key.$file_num",
+ O_RDWR|O_CREAT,
+ 0660 )
+ || die "Can't tie, $!";
+ }
+ if ( m/Backup of\: (.*)\s*$/ ) {
+ $base_directory = $1;
+ }
+ if ( m/([\w\.].*) \-\- / ) {
+ my( $full_name ) = "$base_directory/$1";
+ my( $file_name ) = $1;
+ $full_name =~ m/(.*\/)(.*)/;
+ my( $directory_name ) = $1;
+ #$hash_file{ $full_name } = $directory_name;
+ print "$file_name stored in $directory_name<br>\n";
+ $hash_file{ $file_name } = $directory_name;
+ }
+ }
+ close( FILE );
+
+ $hash_file{ "<base_directory>" } = "$base_directory/";
+ untie( %hash_file );
+ exit( 0 );
+}
+
+sub extract_file {
+# extract file from tape and send (single file restore)
+ $ENV{'PATH'} = "/usr/bin:/bin";
+ $ENV{'BASH_ENV'} = undef;
+ $< = $>;
+ $_[ 0 ] =~ m/(.*)/;
+ chdir( "$home_directory" );
+ `mt -t $tape_device rewind`;
+ `mt -t $non_rw_tape_device fsf 1`;
+ `buffer -m 3m -s 64k -u 100 -t -p 75 -B -i $tape_device | afio -i -y $1 -z -x -D /usr/bin/flexbackup -P gzip -Q -d -Q -q -Z -v -b 64k -`;
+ print $CGI -> header( $CGI -> meta( { -http_equiv => 'Content-Type',
+ -content => 'application/x-Binary' } ) );
+ open( SEND_FILE , "$home_directory/$1" );
+ while ( <SEND_FILE> ) {
+ print;
+ }
+ exit 0;
+}
+
+sub html_header {
+ print $CGI -> header,
+ $CGI -> start_html,"HTML interface to flexbackup<p>\n";
+}
+
+sub edit_config_page {
+# mess with the flexbackup config file in etc
+ &html_header;
+ my( $type , $compress , $compr_level , $blksize , $fs_ref ) = &read_flexbackup_conf;
+ my( @filesystems ) = @{ $fs_ref };
+ my( $hour , $m_tens , $m_ones , $day , $half_day ) = &read_cron;
+ print $CGI -> startform( -action => $script ),
+ qq{<table border="1"><tr><td bgcolor="#FFEEEE">Scheduled backup time<p>},
+ $CGI -> popup_menu( -name => 'schedule_hour',
+ -values => [1..12],
+ -default => $hour ),
+ qq{<b>:</b>},
+ $CGI -> popup_menu( -name => 'schedule_minutes_tens',
+ -values => [0..5],
+ -default => $m_tens ),
+ $CGI -> popup_menu( -name => 'schedule_minutes_ones',
+ -values => [0..9],
+ -default => $m_ones ),
+ $CGI -> popup_menu( -name => 'schedule_halfday',
+ -values => [ "am" , "pm" ],
+ -default => $half_day ),
+ qq{</p>every<br>},
+ $CGI -> popup_menu( -name => 'schedule_day_of_week',
+ -values => [ "Sunday" , "Monday" , "Tuesday",
+ "Wednesday" , "Thursday" , "Friday",
+ "Saturday" , "Weekday" , "Day" ],
+ -default => $day ),
+ qq{<br>},
+ $CGI -> submit( -name => 'Change Schedule' ),
+ qq{</td></tr></table><p>},
+ qq{<table border="1"><tr><td bgcolor="#EEFFEE">Filesystems to backup<br>\n},
+ $CGI -> scrolling_list( -name => 'filesystems',
+ -values => @filesystems,
+ -size=> 5 ),
+ $CGI -> textfield( -name => 'add_filesystem' ),
+ qq{<br>},
+ $CGI -> submit( -name => 'Remove Filesystem' ),
+ $CGI -> submit( -name => 'Add Filesystem' ),
+ qq{</td>},
+ qq{</td></tr></table><p>},
+ qq{<b>WARNING:</b> changing these options is not recommended},
+ qq{<table border="1" cellpadding="3"><tr>},
+ qq{<td bgcolor="#FFFFEE">Backup type<br>\n},
+ $CGI -> popup_menu( -name =>'type',
+ -values => [ "afio" , "dump" , "tar",
+ "cpio" , "zip" ],
+ -default => $type ),
+ qq{</td>},
+ qq{<td bgcolor="#EEFFFF">Compress type<br>\n},
+ $CGI -> popup_menu( -name => 'compress',
+ -values => [ "false" , "gzip" , "bzip2",
+ "compress" , "hardware" ],
+ -default => $compress ),
+ qq{</td></tr>},
+ qq{<tr>},
+ qq{<td bgcolor="#EEEEEE">Block size<br>\n},
+ $CGI -> textfield( -name => 'blksize',
+ -default => $blksize ),
+ qq{</td>},
+ qq{<td bgcolor="#FFEEFF">Compress level<br>\n},
+ $CGI -> popup_menu( -name => 'compress',
+ -values => [ 1..9 ],
+ -default => $compr_level ),
+ qq{</td></tr><tr><td colspan="2" align="center">},
+ $CGI -> submit( -name => 'Change Options' ),
+ qq{</td</tr></table>},
+ $CGI -> endform;
+}
+
+sub change_schedule {
+# edit flexbackup cron jobs
+ my( $hour ) = $CGI -> param( 'schedule_hour' );
+ my( $minutes ) = $CGI -> param( 'schedule_minutes_tens' ) . $CGI -> param( 'schedule_minutes_ones' );
+ my( $ampm ) = $CGI -> param( 'schedule_halfday' ),
+ my( $day )= $CGI -> param( 'schedule_day_of_week' );
+ if ( $day eq 'Day' ) { $day = '*' };
+ if ( $day eq 'Weekday' ) { $day = '1-5' };
+ $hour += 12;
+ if ( $ampm eq 'am' ) {
+ $hour %= 12;
+ }
+ my( $cron_line ) = "$minutes $hour * * $day /var/www/cgi-bin/flexbackup.cgi ?Backup=Backup\n";
+ open( CRON , "/var/spool/cron/root" ) || die "Can't open cron, $!";
+ my( $found ) = 0;
+ my( @cron_file );
+ while ( <CRON> ) {
+ unless ( m/^\#/ ) {
+ if ( m/flexbackup/ ) {
+ $_ = $cron_line;
+ $found++;
+ }
+ }
+ push( @cron_file , $_ );
+ }
+ if ( $found == 0 ) {
+ push( @cron_file , $cron_line );
+ }
+ close( CRON );
+ open( CRON , ">/var/spool/cron/root" ) || die "Can't open cron, $!";
+ foreach ( @cron_file ) {
+ print CRON $_;
+ }
+ close( CRON );
+ &edit_config_page;
+}
+
+sub read_flexbackup_conf {
+# read in config file for flexbackup
+ my( $rtype , @rfilesystems , $rcompress , $rcompr_level , $rblksize );
+ open( CONF_FILE , "$flexbackup_conf" ) || die "Can't open $flexbackup_conf, $!";
+ while ( <CONF_FILE> ) {
+ if ( $_ =~ m/^[^\#]*\$type\s*\=\s*[\'\"](.*)[\'\"]/ ) {
+ $rtype = $1;
+ }
+ elsif ( $_ =~ m/^[^\#]*\$filesystems\[\s*(\d+)\s*\]\s*\=\s*[\'\"](.*)[\'\"]/ ) {
+ $rfilesystems[ $1 ] = $2;
+ }
+ elsif ( $_ =~ m/^[^\#]*\$compress\s*\=\s*[\'\"](.*)[\'\"]/ ) {
+ $rcompress = $1;
+ }
+ elsif ( $_ =~ m/^[^\#]*\$compr_level\s*\=\s*[\'\"](.*)[\'\"]/ ) {
+ $rcompr_level = $1;
+ }
+ elsif ( $_ =~ m/^[^\#]*\$blksize\s*\=\s*[\'\"](.*)[\'\"]/ ) {
+ $rblksize = $1;
+ }
+ }
+ close( CONF_FILE );
+ return( $rtype , $rcompress , $rcompr_level , $rblksize , \@rfilesystems );
+}
+
+sub read_cron {
+# read in cron to determine flexbackup's current schedule
+ my( $fb_line , @fb_components , @return_vals );
+ open( CRON , "/var/spool/cron/root" );
+ while ( <CRON> ) {
+ unless ( m/^#/ ) {
+ if ( index( $_ , 'flexbackup' ) != -1 ) {
+ $fb_line = $_;
+ }
+ }
+ }
+ @fb_components = split( m/\s+/ , $fb_line , 6 );
+ $return_vals[ 0 ] = $fb_components[ 1 ] % 12;
+ $return_vals[ 1 ] = int( $fb_components[ 0 ] / 10 );
+ $return_vals[ 2 ] = $fb_components[ 0 ] % 10;
+ if ( $fb_components[ 4 ] eq '*' ) {
+ $return_vals[ 3 ] = 'Day';
+ }
+ elsif ( $fb_components[ 4 ] eq '1-5' ) {
+ $return_vals[ 3 ] = 'Weekday';
+ }
+ else {
+ $return_vals[ 3 ] = $fb_components[ 4 ];
+ }
+ if ( $fb_components[ 0 ] > 11 ) {
+ $return_vals[ 4 ] = 'pm';
+ }
+ else {
+ $return_vals[ 4 ] = 'am';
+ }
+ return( @return_vals );
+}
+
+sub change_conf {
+ if ( $CGI -> param( 'add_filesystem' ) ) {
+ add_fs( $_[ 0 ] );
+ }
+ elsif ( $CGI -> param( 'filesystems' ) ) {
+ remove_fs( $_[ 0 ] );
+ }
+}
+
+sub add_fs {
+# take on new directory for backup
+ open( FB_CONF , "$flexbackup_conf" );
+ my( $started , $fs_counter , @filesystems_list , @conf_file , $i );
+ while ( <FB_CONF> ) {
+ push( @conf_file , $_ );
+ }
+ for ( $i = 0 ; $i < scalar( @conf_file ) ; $i++ ) {
+ if ( $conf_file[ $i ] =~ m/^[^\#]*\$filesystems\[\s*(\d+)\s*\]\s*\=\s*[\'\"](.*)[\'\"]/ ) {
+ $rfilesystems[ $1 ] = $2;
+ }
+ }
+}
diff --git a/contrib/flexbackup.cgi.README b/contrib/flexbackup.cgi.README
new file mode 100644
index 0000000..c185da7
--- /dev/null
+++ b/contrib/flexbackup.cgi.README
@@ -0,0 +1,8 @@
+
+[ Sent in by Andrew McRory <amacc@iron-bridge.net>
+ meant as a web-frontend for hosting/ISP/appliance/firewall use ]
+
+flexbackup.cgi is in a "mostly finished" state
+It will run backups manually, and will schedule backups in cron but I wouldn't
+reccommend using it to change flexbackup's configuration. I don't think that
+functionality is fully functional.
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..cec97bc
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,88 @@
+flexbackup (1.2.1-6.2) unstable; urgency=medium
+
+ * Non-maintainer upload.
+ * Fix placement of afio alternative (closes: #691860).
+
+ -- Michael Gilbert <mgilbert@debian.org> Fri, 02 Nov 2012 17:46:44 +0000
+
+flexbackup (1.2.1-6.1) unstable; urgency=medium
+
+ * Non-maintainer upload.
+ * Move non-free afio package to the last of the alternative recommendations
+ (closes: #691860).
+
+ -- Michael Gilbert <mgilbert@debian.org> Tue, 30 Oct 2012 20:45:03 +0000
+
+flexbackup (1.2.1-6) unstable; urgency=low
+
+ * Acknowledge NMU. Thanks, Marco Rodrigues and Lucas Nussbaum!
+ * Move to Standards Version 3.7.3
+ * Add DM-Upload-Allowed to debian/control
+ * Implement lzma compression (John Dong <jdong@ubuntu.com>)
+ * afio nocompress file types: append to the afio default instead of
+ replacing it. (Closes: #264847)
+
+ -- Kurt B. Kaiser <kbk@shore.net> Sat, 23 Feb 2008 14:15:04 -0500
+
+flexbackup (1.2.1-5.1) unstable; urgency=low
+
+ * Non-maintainer upload.
+ * debian/rules:
+ Fix bashism (Closes: #459067). [KBK: also, don't ignore clean failure]
+ * debian/control:
+ Add Homepage field.
+
+ -- Marco Rodrigues <gothicx@sapo.pt> Fri, 25 Jan 2008 09:36:23 +0000
+
+flexbackup (1.2.1-5) unstable; urgency=low
+
+ * Use dpatch to track changes to upstream.
+ * Update compat to 5.
+ * Move lha (non-free) to Suggests:
+ * 'mt' can come from cpio or mt-st packages.
+ * Add upstream CREDITS.
+ * Fix hyphens in .../man5/flexbackup.conf.5.
+ * flexbackup.conf now supports backup operations without requiring
+ 'buffer', which is not necessary for backup to disk.
+ * test_bufferprog: don't rely on 'tempfile' which may not be present
+ on some remote systems. Create and delete the /tmp subdirectory on
+ remote systems during this test.
+ * flexbackup log and working directories now writable by backup group.
+ (Permissions similar to the amanda backup utility, needed override.)
+ * Remove unused targets and commands in debian/rules
+ * New Maintainer (Closes: #384184).
+
+ -- Kurt B. Kaiser <kbk@shore.net> Sun, 10 Sep 2006 09:14:54 -0700
+
+flexbackup (1.2.1-3) unstable; urgency=high
+
+ * QA upload.
+ * Fixed "CVE-2005-4802: default config insecure temporary file creation".
+ Patch by Alec Berryman <alec@thened.net>. Closes: #334350.
+ * Fixed "sub backup_dump does not use %path hash for dump". Patch by
+ Artem Chuprina <ran@ran.pp.ru>. Closes: #293884.
+ * Fixed "flexbackup unable to complete a backup". Patch by
+ Jose Luis Fernandez Barros <jlinform@worldonline.es>. Closes: #273750.
+ * Fixed man page errors. Closes: #250615, #312259.
+ * Fixed the following lintian messages:
+ W: out-of-date-standards-version 3.6.1 (current is 3.7.2)
+ E: build-depends-indep-should-be-build-depends debhelper
+ W: old-fsf-address-in-copyright-file
+ E: depends-on-essential-package-without-using-version recommends: tar
+
+ -- Anibal Monsalve Salazar <anibal@debian.org> Sun, 10 Sep 2006 11:23:47 +1000
+
+flexbackup (1.2.1-2) unstable; urgency=low
+
+ * Add suitable versions of rsync to the Recommends line, since flexbackup
+ can use `rsync --include-from=-` as a storage type. (Closes: #238510)
+ * Revamp sample cron script to do incremental backup levels.
+
+ -- Brian Bassett <brianb@debian.org> Fri, 2 Apr 2004 15:52:52 -0800
+
+flexbackup (1.2.1-1) unstable; urgency=low
+
+ * Initial Release.
+
+ -- Brian Bassett <brianb@debian.org> Fri, 16 Jan 2004 16:31:29 -0800
+
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..7ed6ff8
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+5
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..2627eff
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,27 @@
+Source: flexbackup
+Section: admin
+Priority: optional
+Maintainer: Kurt B. Kaiser <kbk@shore.net>
+DM-Upload-Allowed: yes
+Build-Depends: debhelper (>= 5.0.0), dpatch
+Standards-Version: 3.7.3
+Homepage: http://www.edwinh.org/flexbackup/
+
+Package: flexbackup
+Architecture: all
+Depends: ${perl:Depends}, cpio | mt-st
+Recommends: dump | star | pax | zip | sharutils | binutils | lzma (>= 4.43-2) | rsync | afio, buffer
+Suggests: ssh, lha
+Description: Flexible backup tool for small to medium sized installations
+ Flexbackup is a flexible backup tool that works well for small to medium
+ sized tasks for which solutions like amanda are overkill.
+ .
+ It features:
+ * Easy configuration
+ * Uses afio, dump, GNU tar, cpio, star, pax, lha, or zip archivers
+ * Full and numbered levels of incremental backup (similar to "dump")
+ * Compression and buffering options for all backup types
+ * Handles remote filesystems with ssh/rsh; no special services required
+ * Writes to tapes, on-disk archive files, or on-disk directory trees
+ * Keeps a table of contents so you know what archives are on each tape
+ * Extensive logging options
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..084295c
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,28 @@
+This package was debianized by Brian Bassett <brianb@debian.org> on
+Fri, 16 Jan 2004 16:31:29 -0800.
+
+It was downloaded from http://flexbackup.sourceforge.net/
+
+Upstream Author: Edwin Huffstutler
+
+Copyright:
+
+ Copyright 1999, 2003 Edwin Huffstutler
+
+ flexbackup is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ flexbackup is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with flexbackup; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+On Debian systems, the complete text of the GNU General Public License,
+version 2, can be in /usr/share/common-licenses/GPL-2.
diff --git a/debian/dirs b/debian/dirs
new file mode 100644
index 0000000..c0cd6c3
--- /dev/null
+++ b/debian/dirs
@@ -0,0 +1,8 @@
+usr/bin
+usr/share/lintian/overrides
+usr/share/man/man1
+usr/share/man/man5
+etc
+var/log/flexbackup
+var/lib/flexbackup
+
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..d557f7b
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,4 @@
+CREDITS
+README
+TODO
+faq.html
diff --git a/debian/examples b/debian/examples
new file mode 100644
index 0000000..9bdf5de
--- /dev/null
+++ b/debian/examples
@@ -0,0 +1 @@
+debian/sample-cron
diff --git a/debian/flexbackup.lintian b/debian/flexbackup.lintian
new file mode 100644
index 0000000..fba14c1
--- /dev/null
+++ b/debian/flexbackup.lintian
@@ -0,0 +1,2 @@
+flexbackup: non-standard-dir-perm var/log/flexbackup/ 0770 != 0755
+flexbackup: non-standard-dir-perm var/lib/flexbackup/ 0770 != 0755
diff --git a/debian/patches/00list b/debian/patches/00list
new file mode 100644
index 0000000..a8f42c2
--- /dev/null
+++ b/debian/patches/00list
@@ -0,0 +1,10 @@
+01_tempfile
+02_buffer_test
+10_Makefile_destdir
+20_flexbackup.conf.5_hyphens
+22_flexbackup.1
+30_basic_debian_tape
+40_dump_path
+42_afio_cpio_incompatible_ok
+50_lzma_support
+60_use_afio_default_nocompression
diff --git a/debian/patches/01_tempfile.dpatch b/debian/patches/01_tempfile.dpatch
new file mode 100644
index 0000000..092d84f
--- /dev/null
+++ b/debian/patches/01_tempfile.dpatch
@@ -0,0 +1,84 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## 01_tempfile.dpatch by <kbk@shore.net>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+## DP: 1.2.1-3: Fix RC bug: insecure use of temporary files.
+## DP: http://bugs.gentoo.org/show_bug.cgi?id=116510
+## DP: original patch from Martin Schulze, further developed at Gentoo
+## DP: to handle creation of remote /tmp subdirectory.
+## DP: CVE-2005-4802
+
+@DPATCH@
+diff -urNad flexbackup-1.2.1~/flexbackup flexbackup-1.2.1/flexbackup
+--- flexbackup-1.2.1~/flexbackup 2003-10-10 07:12:09.000000000 -0700
++++ flexbackup-1.2.1/flexbackup 2006-08-31 16:09:16.000000000 -0700
+@@ -269,6 +269,7 @@
+ untie(%::index);
+ }
+
++system ('rm', '-rf', $cfg::tmpdir);
+ exit(0);
+
+ ######################################################################
+@@ -811,6 +812,11 @@
+ ($remove, @cmds) = &backup_filelist($label, $localdir, $title, $level, $remote);
+ }
+
++ if(defined($remote)) {
++ # create our temporary directory as first remote command
++ unshift(@cmds, &maybe_remote_cmd("$::path{mkdir} -p $cfg::tmpdir", $remote));
++ }
++
+ # Nuke any tmp files used in the above routines
+ if ($remove ne '') {
+ push(@cmds, &maybe_remote_cmd("$::path{rm} -f $remove", $remote));
+@@ -827,6 +833,11 @@
+ push(@cmds, &maybe_remote_cmd("$::path{rm} -f $pkglist", $remote));
+ }
+ }
++
++ if(defined($remote)) {
++ # remove temporary directory as our last remote command
++ push(@cmds, &maybe_remote_cmd("$::path{rm} -rf $cfg::tmpdir", $remote));
++ }
+
+ # Strip multiple spaces
+ foreach my $cmd (@cmds) {
+@@ -2750,8 +2761,9 @@
+ $::path{'find'} = &checkinpath('find');
+ $::path{'dd'} = &checkinpath('dd');
+ $::path{'printf'} = &checkinpath('printf');
++ $::path{'mkdir'} = &checkinpath('mkdir');
+
+- push(@::remoteprogs,($::path{'touch'},$::path{'rm'},$::path{'find'},$::path{'printf'}));
++ push(@::remoteprogs,($::path{'touch'},$::path{'rm'},$::path{'find'},$::path{'printf'},$::path{'mkdir'}));
+
+ # Check device (or dir)
+ $::ftape = 0;
+@@ -3442,6 +3454,15 @@
+ push(@::errors,"\$tmpdir $cfg::tmpdir is not writable");
+ }
+
++ $cfg::hostname = `hostname`;
++ chomp($cfg::hostname);
++
++ # Use a subdirectory of the user-specified directory as our tmpdir
++ # Also note that we make it closer to globally unique as we sometimes
++ # use this variable for remote systems, so PID isn't enough
++ $cfg::tmpdir = $cfg::tmpdir .'/flexbackup.'.$$.'.'.$cfg::hostname;
++ mkdir ($cfg::tmpdir) || die "Can't create temporary directory, $!";
++
+ # Levels
+ if (defined($::opt{'level'}) and
+ (defined($::opt{'incremental'}) or
+@@ -5236,8 +5257,8 @@
+ # Create a script which tests the buffer program
+ open(SCR,"> $tmp_script") || die;
+ print SCR "#!/bin/sh\n";
+- print SCR "tmp_data=/tmp/bufftest\$\$.txt\n";
+- print SCR "tmp_err=/tmp/bufftest\$\$.err\n";
++ print SCR "tmp_data=\`tempfile\`\n";
++ print SCR "tmp_err=\`tempfile\`\n";
+ print SCR "echo testme > \$tmp_data\n";
+ print SCR "$buffer_cmd > /dev/null 2> \$tmp_err < \$tmp_data\n";
+ print SCR "res=\$?\n";
diff --git a/debian/patches/02_buffer_test.dpatch b/debian/patches/02_buffer_test.dpatch
new file mode 100644
index 0000000..a929838
--- /dev/null
+++ b/debian/patches/02_buffer_test.dpatch
@@ -0,0 +1,44 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## 02_buffer_test.dpatch by <"Kurt B. Kaiser" <kbk@shore.net>>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+## DP: Safe tmpfiles in test_bufferprog which don't use 'tempfile'; this
+## DP: allows remote backups on systems which don't support 'tempfile'.
+## DP: Create/remove the remote temporary directory in test_bufferprog.
+## DP: Also displays the test script in debug mode.
+## DP: To be applied after 01_tempfile.dpatch
+
+@DPATCH@
+
+--- flexbackup-1.2.1/flexbackup 2006-09-05 22:46:54.000000000 -0700
++++ flexbackup.mod 2006-09-06 21:55:44.000000000 -0700
+@@ -5257,8 +5257,8 @@
+ # Create a script which tests the buffer program
+ open(SCR,"> $tmp_script") || die;
+ print SCR "#!/bin/sh\n";
+- print SCR "tmp_data=\`tempfile\`\n";
+- print SCR "tmp_err=\`tempfile\`\n";
++ print SCR "tmp_data=$cfg::tmpdir/buftest.$host.$$.txt\n";
++ print SCR "tmp_err=$cfg::tmpdir/buftest.$host.$$.err\n";
+ print SCR "echo testme > \$tmp_data\n";
+ print SCR "$buffer_cmd > /dev/null 2> \$tmp_err < \$tmp_data\n";
+ print SCR "res=\$?\n";
+@@ -5276,7 +5276,8 @@
+ $pipecmd = "sh $tmp_script ";
+ } else {
+ print $::msg "| Checking '$cfg::buffer' on host $host... ";
+- $pipecmd = "cat $tmp_script | ($::remoteshell $host 'cat > $tmp_script; sh $tmp_script; rm -f $tmp_script')";
++ $pipecmd = "cat $tmp_script | ($::remoteshell $host 'mkdir -p $cfg::tmpdir;
++ cat - > $tmp_script; sh $tmp_script; rm -rf $cfg::tmpdir')";
+ }
+
+ if (!defined($::debug)) {
+@@ -5308,6 +5309,8 @@
+ close (PIPE);
+
+ } else {
++ print $::msg "\n(debug) 'buffer' test script:\n($tmp_script)\n\n"
++ . `cat $tmp_script`;
+ print $::msg "\n(debug) $pipecmd\n";
+ }
+
diff --git a/debian/patches/10_Makefile_destdir.dpatch b/debian/patches/10_Makefile_destdir.dpatch
new file mode 100644
index 0000000..92ab601
--- /dev/null
+++ b/debian/patches/10_Makefile_destdir.dpatch
@@ -0,0 +1,26 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## 10_Makefile_destdir.dpatch by <"Kurt B. Kaiser" <kbk@shore.net>>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+## DP: add DESTDIR to paths
+
+@DPATCH@
+diff -urNad flexbackup~/Makefile flexbackup/Makefile
+--- flexbackup~/Makefile 2003-07-29 10:00:07.000000000 -0700
++++ flexbackup/Makefile 2006-09-05 13:52:42.000000000 -0700
+@@ -7,12 +7,12 @@
+ PREFIX=/usr
+
+ # Where the script binary should go
+-BINPATH = $(PREFIX)/bin
++BINPATH = $(DESTDIR)$(PREFIX)/bin
+ # Where the manpage should go
+-MANPATH = $(PREFIX)/share/man
++MANPATH = $(DESTDIR)$(PREFIX)/share/man
+
+ # Where flexbackup.conf should be stored
+-CONFFILE = /etc/flexbackup.conf
++CONFFILE = $(DESTDIR)/etc/flexbackup.conf
+
+ # Where perl lives
+ PERLPATH = /usr/bin/perl
diff --git a/debian/patches/20_flexbackup.conf.5_hyphens.dpatch b/debian/patches/20_flexbackup.conf.5_hyphens.dpatch
new file mode 100644
index 0000000..afeffbb
--- /dev/null
+++ b/debian/patches/20_flexbackup.conf.5_hyphens.dpatch
@@ -0,0 +1,28 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## 20_flexbackup.conf_hyphens.5.dpatch by <"Kurt B. Kaiser" <kbk@shore.net>>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+## DP: Minus signs need to be escaped.
+
+@DPATCH@
+diff -urNad flexbackup~/flexbackup.conf.5 flexbackup/flexbackup.conf.5
+--- flexbackup~/flexbackup.conf.5 2003-10-04 08:52:00.000000000 -0700
++++ flexbackup/flexbackup.conf.5 2006-09-05 15:24:19.000000000 -0700
+@@ -131,7 +131,7 @@
+ .TP
+ \fB$atime_preserve\fR = \fI'true|false'\fR;
+ Set to true to try and preserve file access times during backup, if the
+-selected archive program can do so. Note that if this is true, -cnewer checks
++selected archive program can do so. Note that if this is true, \-cnewer checks
+ (file permission/status changes only, not content) are turned off when deciding
+ which files to archive on the local system.
+ .TP
+@@ -212,7 +212,7 @@
+ .br
+ \fB$path{\fI'dump'\fR}\fR = \fI'sudo dump'\fR;
+ .br
+-\fB$path{\fI'afio'\fR}\fR = \fI'sudo -u nonrootuser afio'\fR;
++\fB$path{\fI'afio'\fR}\fR = \fI'sudo \-u nonrootuser afio'\fR;
+ .RE
+ .RE
+ .TP
diff --git a/debian/patches/22_flexbackup.1.dpatch b/debian/patches/22_flexbackup.1.dpatch
new file mode 100644
index 0000000..98d5dc3
--- /dev/null
+++ b/debian/patches/22_flexbackup.1.dpatch
@@ -0,0 +1,53 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## 22_flexbackup.1.dpatch by <"Kurt B. Kaiser" <kbk@shore.net>>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+
+## DP: Fix #312259 Wrong option for onefile extract in manpage
+## DP: Fix #250615 man page errors
+## DP: Correct a couple of typos.
+
+@DPATCH@
+diff -urNad flexbackup~/flexbackup.1 flexbackup/flexbackup.1
+--- flexbackup~/flexbackup.1 2003-10-04 08:52:00.000000000 -0700
++++ flexbackup/flexbackup.1 2006-09-10 11:29:30.000000000 -0700
+@@ -25,11 +25,10 @@
+ (previous backup level + 1).
+ .TP
+ \fBflexbackup\fR [...] \fI-pkgdelta\fR <\fIrpm\fR | \fIfreebsd\fR>
+-prune backup to files not part of a package or changed from distributed version.
++Prune backup to files not part of a package or changed from distributed version.
+ .TP
+ \fBflexbackup\fR [...] \fI-wday\fR <\fI0-7\fR>
+-Prune backup to files not part of a package or changed from distributed version.
+-backup only if the week day matches the input number. Sunday is 0 or 7.
++Backup only if the week day matches the input number. Sunday is 0 or 7.
+ .TP
+ \fBflexbackup\fR [...] \fI-pipe\fR
+ Write backup data to stdout rather than file/device.
+@@ -48,7 +47,7 @@
+ Extract (restore) the files listed in text file \(dqfilelist\(dq into your
+ current working directory.
+ .TP
+-\fBflexbackup\fR \fI-extract\fR \fI-flist\fR <\fIfilename\fR>
++\fBflexbackup\fR \fI-extract\fR \fI-onefile\fR <\fIfilename\fR>
+ Extract (restore) the single file named \(dqfilename\(dq into your current
+ working directory.
+ .TP
+@@ -81,13 +80,13 @@
+ \fBflexbackup\fR \fI-rmindex\fR \fIall\fR
+ Force delete all database index info.
+ .TP
+-\fBflexbackup\fR \fI-toc\fR <\fIkey\fR>
++\fBflexbackup\fR \fI-rmindex\fR <\fIkey\fR>
+ Force delete specified database tape/dir index key named \(dqkey\(dq.
+ .TP
+-\fBflexbackup\fR \fI-toc\fR <\fIkey\fR:\fIfile\fR>
++\fBflexbackup\fR \fI-rmindex\fR <\fIkey\fR:\fIfile\fR>
+ Force delete specified database tape/dir index key named \(dqkey\(dq,
+ file number \(dqfile\(dq.
+-.SH "MISCELLENEOUS OPTIONS"
++.SH "MISCELLANEOUS OPTIONS"
+ .TP
+ \fBflexbackup\fR \fI-newtape\fR
+ Erase and create new index key (but don't do any backups).
diff --git a/debian/patches/30_basic_debian_tape.dpatch b/debian/patches/30_basic_debian_tape.dpatch
new file mode 100644
index 0000000..407f417
--- /dev/null
+++ b/debian/patches/30_basic_debian_tape.dpatch
@@ -0,0 +1,43 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## 30_basic_debian_tape.dpatch by <"Kurt B. Kaiser" <kbk@shore.net>>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+## DP: Modify config to support basic Debian tape operations using tar.
+## DP: Note that streaming can be greatly improved by using 'buffer'.
+## DP: However, many will use disk backup these days instead of tape
+## DP: and 'buffer' isn't required.
+
+@DPATCH@
+diff -urNad flexbackup~/flexbackup.conf flexbackup/flexbackup.conf
+--- flexbackup~/flexbackup.conf 2003-09-21 15:59:58.000000000 -0700
++++ flexbackup/flexbackup.conf 2006-09-09 15:27:35.000000000 -0700
+@@ -8,7 +8,7 @@
+ # Archive type? afio, dump, tar, cpio, star, pax, zip, lha, ar, shar
+ # 'copy' or 'rsync' are extra options if running in archive-to-disk mode.
+ # 'filelist' dumps a list of files in your cwd - for debugging setup/exclusion
+-$type = 'afio';
++$type = 'tar';
+
+ # Configure backup "sets".
+ # Not needed if you use "-dir <dir>" to backup one tree at a time.
+@@ -36,8 +36,9 @@
+ $compress = 'gzip'; # one of false/gzip/bzip2/lzop/zip/compress/hardware
+ $compr_level = '4'; # compression level (1-9) (for gzip/bzip2/lzop/zip)
+
+-# Buffering program - to help streaming
+-$buffer = 'buffer'; # one of false/buffer/mbuffer
++# Buffering program - to help streaming. The use of buffer or mbuffer
++# when writing to tape is recommended to avoid excessive drive wear.
++$buffer = 'false'; # one of false/buffer/mbuffer
+ $buffer_megs = '10'; # buffer memory size (in megabytes)
+ $buffer_fill_pct = '75'; # start writing when buffer this percent full
+ $buffer_pause_usec = '100'; # pause after write (tape devices only)
+@@ -52,7 +53,7 @@
+ # If a directory, will archive to files in that directory rather than a device
+ # If "host:/dev/tapedevice", will use remote tape drive via rsh/ssh
+ #
+-$device = '/dev/tape';
++$device = '/dev/nst0';
+
+ # Block size (in kilobytes!) to use for archive programs and dd. Default is
+ # 10 for most things. Some tape drives need 32 or 64. Set to '0' to
diff --git a/debian/patches/40_dump_path.dpatch b/debian/patches/40_dump_path.dpatch
new file mode 100644
index 0000000..24a5b5f
--- /dev/null
+++ b/debian/patches/40_dump_path.dpatch
@@ -0,0 +1,24 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## 40_dump_path.dpatch by <"Kurt B. Kaiser" <kbk@shore.net>>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+## DP: Debian Bug #293884
+## DP: flexbackup: sub backup_dump does not use %path hash for dump
+## DP:
+## DP: sub backup_dump does not use %path hash for dump, so if
+## DP: backup user does not have /sbin in his $PATH (it is so for
+## DP: Debian 'backup' system user), flexbackup cannot use dump.
+
+@DPATCH@
+diff -urNad flexbackup~/flexbackup flexbackup/flexbackup
+--- flexbackup~/flexbackup 2006-09-07 20:19:38.000000000 -0700
++++ flexbackup/flexbackup 2006-09-07 20:21:06.000000000 -0700
+@@ -1068,7 +1068,7 @@
+ }
+
+ $cmd = '';
+- $cmd .= "dump -$level ";
++ $cmd .= "$::path{dump} -$level ";
+ $cmd .= "$::dump_blk_flag ";
+ if ($cfg::dump_use_dumpdates eq "true") {
+ $cmd .= "-u ";
diff --git a/debian/patches/42_afio_cpio_incompatible_ok.dpatch b/debian/patches/42_afio_cpio_incompatible_ok.dpatch
new file mode 100644
index 0000000..7a5de38
--- /dev/null
+++ b/debian/patches/42_afio_cpio_incompatible_ok.dpatch
@@ -0,0 +1,21 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## 42_afio_cpio_incompatible_ok.dpatch by <"Kurt B. Kaiser" <kbk@shore.net>>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+## DP: Debian Bug #273750: flexbackup unable to complete a backup !
+## DP: afio is not configured to be cpio compatible; should not exit here
+## DP: on a warning.
+
+@DPATCH@
+diff -urNad flexbackup~/flexbackup flexbackup/flexbackup
+--- flexbackup~/flexbackup 2006-09-08 20:17:32.000000000 -0700
++++ flexbackup/flexbackup 2006-09-08 20:18:28.000000000 -0700
+@@ -1151,7 +1151,7 @@
+ $cmd .= "$::path{afio} -o ";
+ $cmd .= "$no_compress ";
+ $cmd .= "-z ";
+- $cmd .= "-1 m ";
++ $cmd .= "-1 mc ";
+ $cmd .= "$::afio_z_flag ";
+ $cmd .= "$::afio_verb_flag ";
+ $cmd .= "$::afio_sparse_flag ";
diff --git a/debian/patches/50_lzma_support.dpatch b/debian/patches/50_lzma_support.dpatch
new file mode 100644
index 0000000..3b6aa44
--- /dev/null
+++ b/debian/patches/50_lzma_support.dpatch
@@ -0,0 +1,113 @@
+#! /bin/sh /usr/share/dpatch/dpatch-run
+## 50_lzma_support.dpatch by John Dong <jdong@ubuntu.com>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+## DP: Add lzma compression
+
+@DPATCH@
+--- flexbackup-1.2.1.orig/flexbackup
++++ flexbackup-1.2.1/flexbackup
+@@ -687,6 +687,8 @@
+ $filename .= ".zip";
+ } elsif ($cfg::compress eq "compress") {
+ $filename .= ".Z";
++ } elsif ($cfg::compress eq "lzma") {
++ $filename .= ".lzma";
+ }
+ } elsif ($cfg::type eq "afio") {
+ # tag these a little different, the archive file itself isn't a
+@@ -701,6 +703,8 @@
+ $filename .= "-zip";
+ } elsif ($cfg::compress eq "compress") {
+ $filename .= "-Z";
++ } elsif ($cfg::compress eq "lzma") {
++ $filename .= "-lzma";
+ }
+ }
+
+@@ -2700,7 +2704,7 @@
+ # First check if things are defined in the config file
+ # Checks exist, true/false, or one of options
+ &checkvar(\$cfg::type,'type','dump afio cpio tar star pax zip ar shar lha copy rsync filelist','tar');
+- &checkvar(\$cfg::compress,'compress','gzip bzip2 lzop compress zip false hardware','gzip');
++ &checkvar(\$cfg::compress,'compress','gzip bzip2 lzop compress zip false hardware lzma','gzip');
+ &checkvar(\$cfg::compr_level,'compr_level','exist','4');
+ &checkvar(\$cfg::verbose,'verbose','bool','true');
+ &checkvar(\$cfg::sparse,'sparse','bool','true');
+@@ -3001,6 +3005,16 @@
+ $::z = " | $::path{zip} -$cfg::compr_level - -";
+ $::unz = "$::path{funzip} | ";
+ }
++ } elsif ($cfg::compress eq "lzma") {
++ $::path{'lzma'} = &checkinpath($cfg::compress);
++ push(@::remoteprogs, $::path{$cfg::compress});
++ if ($cfg::compr_level !~ m/^[123456789]$/) {
++ push(@::errors,"\$compr_level must be set to 1-9");
++ } else {
++ $::z = " | $::path{$cfg::compress} -$cfg::compr_level ";
++ }
++ $::unz = "$::path{$cfg::compress} -d | ";
++
+ } else {
+ $::z = "";
+ $::unz = "";
+@@ -3252,6 +3266,10 @@
+ $::afio_z_flag = "-P $::path{$cfg::compress} -Q -c -Z";
+ $::afio_unz_flag = "-P $::path{$cfg::compress} -Q -d -Q -c -Z";
+
++ } elsif ($cfg::compress eq "lzma") {
++ $::afio_z_flag = "-P $::path{$cfg::compress} -Q -$cfg::compr_level -Z";
++ $::afio_unz_flag = "-P $::path{$cfg::compress} -Q -d -Z";
++
+ }
+ $::unz = ""; # Reset & just use this for reading the archive file.
+
+@@ -3415,7 +3433,7 @@
+ $::path{'lha'} = &checkinpath('lha');
+ push(@::remoteprogs, $::path{'lha'});
+
+- if ($cfg::compress =~ /^(gzip|bzip2|lzop|compress|zip)$/) {
++ if ($cfg::compress =~ /^(gzip|bzip2|lzop|compress|zip|lzma)$/) {
+ warn("Using type \"lha\" with compress=$cfg::compress makes no sense");
+ warn("Setting compression to false");
+ $::unz = "";
+@@ -3781,7 +3799,7 @@
+
+ # Try and guess file types and commpression scheme
+ # might as well since we are reading from a file in this case
+- if ($file =~ m/\.(dump|cpio|tar|star|pax|a|shar|filelist)\.(gz|bz2|lzo|Z|zip)$/) {
++ if ($file =~ m/\.(dump|cpio|tar|star|pax|a|shar|filelist)\.(gz|bz2|lzo|Z|zip|lzma)$/) {
+ $cfg::type = $1;
+ $cfg::compress = $2;
+ $cfg::type =~ s/^a$/ar/;
+@@ -3789,16 +3807,18 @@
+ $cfg::compress =~ s/bz2/bzip2/;
+ $cfg::compress =~ s/lzo/lzop/;
+ $cfg::compress =~ s/Z/compress/;
++ $cfg::compress =~ s/lzma/lzma/;
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+- } elsif ($file =~ m/\.afio-(gz|bz2|lzo|Z|zip)$/) {
++ } elsif ($file =~ m/\.afio-(gz|bz2|lzo|Z|zip|lzma)$/) {
+ $cfg::type = "afio";
+ $cfg::compress = $1;
+ $cfg::compress =~ s/gz/gzip/;
+ $cfg::compress =~ s/bz2/bzip2/;
+ $cfg::compress =~ s/lzo/lzop/;
+ $cfg::compress =~ s/Z/compress/;
++ $cfg::compress =~ s/lzma/lzma/;
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+--- flexbackup-1.2.1.orig/flexbackup.conf.5
++++ flexbackup-1.2.1/flexbackup.conf.5
+@@ -42,7 +42,7 @@
+ each backup. Key is a filesystem/directory or \(dqhost:directory\(dq spec as
+ outlined above regular expressions allowed (not shell-type wildcards!).
+ .TP
+-\fB$compress\fR = \fI'false|gzip|bzip2|lzop|zip|compress|hardware'\fR;
++\fB$compress\fR = \fI'false|gzip|bzip2|lzop|zip|compress|hardware|lzma'\fR;
+ .TQ
+ \fB$compr_level\fR = \fI'4'\fR;
+ Configure compression. Choose a type of compression to use and configure the
diff --git a/debian/patches/60_use_afio_default_nocompression.dpatch b/debian/patches/60_use_afio_default_nocompression.dpatch
new file mode 100644
index 0000000..cdd3ace
--- /dev/null
+++ b/debian/patches/60_use_afio_default_nocompression.dpatch
@@ -0,0 +1,75 @@
+#!/bin/sh /usr/share/dpatch/dpatch-run
+## 60_use_afio_default_nocompression.dpatch by Kurt B. Kaiser <kbk@shore.net>
+##
+## All lines beginning with `## DP:' are a description of the patch.
+## DP: Bug #264847 patch by Jose Luis Fernandez Barros <jlinform@worldonline.es>
+## DP: plus man page and flexbackup.conf changes by KBK.
+## DP:
+## DP: Add to the list of file types afio won't compress, instead of
+## DP: replacing that list.
+
+@DPATCH@
+diff -urNad flexbackup-svn~/flexbackup flexbackup-svn/flexbackup
+--- flexbackup-svn~/flexbackup 2008-02-23 13:54:28.000000000 -0500
++++ flexbackup-svn/flexbackup 2008-02-23 13:58:43.000000000 -0500
+@@ -1132,7 +1132,7 @@
+ if (($cfg::compress !~ /^(false|hardware)$/) and ($cfg::afio_nocompress_types ne "")) {
+ $cmd = "$::path{printf} \"$cfg::afio_nocompress_types\" > $tmpnocompress";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+- $no_compress = "-E $tmpnocompress";
++ $no_compress = "-E +$tmpnocompress";
+ $remove .= " $tmpnocompress";
+ }
+
+@@ -3250,7 +3250,7 @@
+ &checkvar(\$cfg::afio_echo_block,'afio_echo_block','bool','false');
+ &checkvar(\$cfg::afio_compress_cache_size,'afio_compress_cache_size','exist','2');
+ &checkvar(\$cfg::afio_compress_threshold,'afio_compress_threshold','exist','3');
+- &checkvar(\$cfg::afio_nocompress_types,'afio_nocompress_types','exist','mp3 MP3 Z z gz gif zip ZIP lha jpeg jpg JPG taz tgz deb rpm bz2 lzo');
++ &checkvar(\$cfg::afio_nocompress_types,'afio_nocompress_types','exist','.lzo');
+
+ $::path{'afio'} = &checkinpath('afio');
+ push(@::remoteprogs, $::path{'afio'});
+diff -urNad flexbackup-svn~/flexbackup.conf flexbackup-svn/flexbackup.conf
+--- flexbackup-svn~/flexbackup.conf 2008-02-23 13:54:28.000000000 -0500
++++ flexbackup-svn/flexbackup.conf 2008-02-23 14:05:33.000000000 -0500
+@@ -128,8 +128,9 @@
+ # ----------------------------------------------------------------------
+ # Parameters for 'afio' only
+
+-# File extensions that should not be compressed (seperate with spaces)
+-$afio_nocompress_types = 'mp3 MP3 Z z gz gif zip ZIP lha jpeg jpg JPG taz tgz deb rpm bz2 lzo';
++# Additional (besides the afio defaults) file extensions that should not
++# be compressed (separate with spaces)
++$afio_nocompress_types = '.lzo';
+
+ # True to show block numbers
+ $afio_echo_block = 'false';
+diff -urNad flexbackup-svn~/flexbackup.conf.5 flexbackup-svn/flexbackup.conf.5
+--- flexbackup-svn~/flexbackup.conf.5 2008-02-23 13:54:28.000000000 -0500
++++ flexbackup-svn/flexbackup.conf.5 2008-02-23 14:08:10.000000000 -0500
+@@ -226,15 +226,15 @@
+ \fB$afio_compress_threshold\fR = \fI'3'\fR;
+ .TQ
+ \fB$afio_compress_cache_size\fR = \fI'2'\fR;
+-These settings apply to the \fB'afio'\fR backup types only. Files with
+-extensions specified in \fB$afio_nocompress_types\fR will not be compressed.
+-Define whether or not echo block numbers in \fB$afio_echo_block\fR. Configure
+-the minimum file size (in kilobytes) required for compression in
+-\fB$afio_compress_threshold\fR. \fB$afio_compress_cache_size\fR setting is
+-used to specify the maximum amount of memory (megabytes) to use for temporary
+-storage of compression results. If a compressed file is bigger than this,
+-compression will have to run twice on the file. See the \fBafio\fR(1) manpage
+-for more information.
++These settings apply to the \fB'afio'\fR backup types only. In addition to the
++afio defaults, files with extensions specified in \fB$afio_nocompress_types\fR
++will not be compressed. Define whether or not echo block numbers in
++\fB$afio_echo_block\fR. Configure the minimum file size (in kilobytes) required
++for compression in \fB$afio_compress_threshold\fR.
++\fB$afio_compress_cache_size\fR setting is used to specify the maximum amount
++of memory (megabytes) to use for temporary storage of compression results. If a
++compressed file is bigger than this, compression will have to run twice on the
++file. See the \fBafio\fR(1) manpage for more information.
+ .TP
+ \fB$tar_echo_record_num\fR = \fI'true|false'\fR;
+ These settings apply to the \fB'tar'\fR backup types only. Define whether
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..7bda846
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,76 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+build: build-stamp
+build-stamp: patch-stamp
+ dh_testdir
+
+ # Add here commands to compile the package.
+ $(MAKE)
+
+ touch build-stamp
+
+clean: clean-patched unpatch
+clean-patched:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+
+ # Add here commands to clean up after the build process.
+ [ ! -f Makefile ] || $(MAKE) clean
+
+ dh_clean
+
+patch: patch-stamp
+patch-stamp:
+ dpatch apply-all
+ touch patch-stamp
+
+unpatch:
+ dpatch deapply-all
+ rm -rf patch-stamp debian/patched
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install the package into debian/flexbackup.
+ $(MAKE) install DESTDIR=$(CURDIR)/debian/flexbackup/
+
+
+# Build architecture-dependent files here.
+binary-arch: build install
+# We have nothing to do by default.
+
+# Build architecture-independent files here.
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs CHANGES
+ dh_installdocs
+ dh_installexamples
+ dh_installman
+ dh_compress
+ dh_fixperms
+ chown -R root:backup debian/flexbackup/var/lib/flexbackup/
+ chown -R root:backup debian/flexbackup/var/log/flexbackup/
+ chmod -R u=rwX,g=rwX,o-rwx debian/flexbackup/var/lib/flexbackup/
+ chmod -R u=rwX,g=rwX,o-rwx debian/flexbackup/var/log/flexbackup/
+ install -o root -g root -m 0644 debian/flexbackup.lintian \
+ debian/flexbackup/usr/share/lintian/overrides/flexbackup
+ dh_perl
+ dh_installdeb
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary: binary-indep binary-arch
+
+.PHONY: build clean binary-indep binary-arch binary install patch unpatch
diff --git a/debian/sample-cron b/debian/sample-cron
new file mode 100644
index 0000000..f621f98
--- /dev/null
+++ b/debian/sample-cron
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+#
+# Sample cron script for incrementing backup levels, designed for
+# placement into /etc/cron.daily
+#
+# Author: Brian Bassett <brianb@debian.org>
+#
+
+# Constants for days of the week
+SUN=0
+MON=1
+TUE=2
+WED=3
+THU=4
+FRI=5
+SAT=6
+
+# This is the day of the week that the Level 0 backup is made.
+LVL0DAY=$TUE
+
+# Backup!
+/usr/bin/flexbackup -set all -level $(expr \( $(date +%w) - $LVL0DAY \) % 7)
+
diff --git a/faq.html b/faq.html
new file mode 100644
index 0000000..40c767a
--- /dev/null
+++ b/faq.html
@@ -0,0 +1,444 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html401/transitional.dtd">
+<HTML> <HEAD>
+<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1">
+<TITLE>flexbackup FAQ</TITLE>
+<LINK TYPE="text/css" REL="stylesheet" HREF="style.css">
+</HEAD>
+
+<H1>flexbackup frequently-asked questions</H1>
+
+<P>Suggestions/additions welcome<BR>
+<p><i>FAQ Revised: Friday 10 October 2003 11:26:37</i></p>
+<hr><h2>Table of Contents</h2>
+<dl>
+<dt><b>1. Configuration</b></dt>
+<dd><ul>
+<li><a href="#Configuration0">1.1. How do I upgrade from 1.0.x to 1.2.x?</a></li>
+<li><a href="#Configuration1">1.2. Why won't my exclude or prune expressions work?</a></li>
+<li><a href="#Configuration2">1.3. How can I avoid running backups or remote shells as root?</a></li>
+<li><a href="#Configuration3">1.4. On my system, "tar" is not GNU tar but is the vendor's "tar". GNU tar is
+installed as "gtar". On my system, "dump" is "ufsdump".</a></li>
+<li><a href="#Configuration4">1.5. How can I back up Windows systems?</a></li>
+<li><a href="#Configuration5">1.6. Does the to-disk backup mode use single archive files or directory tree copies
+of files?</a></li>
+<li><a href="#Configuration6">1.7. How do I deal with spaces in set or prune directory specifications?</a></li>
+<li><a href="#Configuration7">1.8. Does flexbackup work with autoloaders / mtx?</a></li>
+</ul></dd>
+<dt><b>2. Backup</b></dt>
+<dd><ul>
+<li><a href="#Backup0">2.1. Can you explain numeric dump levels?</a></li>
+<li><a href="#Backup1">2.2. Do you support multi-tape archives?</a></li>
+<li><a href="#Backup2">2.3. I'm backing up to files on disk -- How can I get filenames to not have
+timestamps in them?</a></li>
+<li><a href="#Backup3">2.4. What is the maxiumum size of the archives flexbackup can produce?</a></li>
+</ul></dd>
+<dt><b>3. Recovery / Extraction</b></dt>
+<dd><ul>
+<li><a href="#Recovery / Extraction0">3.1. How do I extract only certain files in an archive rather than the
+whole thing?</a></li>
+<li><a href="#Recovery / Extraction1">3.2. How do I find out which archive(s) contain a certain file?</a></li>
+<li><a href="#Recovery / Extraction2">3.3. The disk totally died. How do I get flexbackup's archives off the
+tapes on a machine without flexbackup or even perl installed?</a></li>
+<li><a href="#Recovery / Extraction3">3.4. I don't have the index files! (disk died, different machine, etc.)</a></li>
+</ul></dd>
+<dt><b>4. Common problems</b></dt>
+<dd><ul>
+<li><a href="#Common problems0">4.1. I get I/O errors or other errors from my tape drive.</a></li>
+<li><a href="#Common problems1">4.2. Backups seem to work ok, but I can't get extracting/listing to work.</a></li>
+<li><a href="#Common problems2">4.3. How do I get remote backups over ssh to work without prompting for a password?</a></li>
+<li><a href="#Common problems3">4.4. I keep getting only one backup per tape when I do "-set all"!</a></li>
+<li><a href="#Common problems4">4.5. 'buffer' complains that it can't run successfully or that it doesn't
+have enough shared memory.</a></li>
+<li><a href="#Common problems5">4.6. When using -newtape or -erase, the tape drive sits there forever making
+noise and nothing happens on the screen</a></li>
+<li><a href="#Common problems6">4.7. My "mt" or tape drive doesn't support operation xxxx.</a></li>
+<li><a href="#Common problems7">4.8. Flexbackup caused my system to hang!</a></li>
+<li><a href="#Common problems8">4.9. The backup looks like it works ok, but flexbackup complains of an error
+at the end of the archive, then exits.</a></li>
+<li><a href="#Common problems9">4.10. I get an error from the backup due to files moving/changing/being deleted
+during the backup</a></li>
+</ul></dd>
+<dt><b>5. Misc</b></dt>
+<dd><ul>
+<li><a href="#Misc0">5.1. How does the "packaging system delta" (or "-pkgdelta") feature work?</a></li>
+<li><a href="#Misc1">5.2. What exactly is the "-wday" switch for?</a></li>
+<li><a href="#Misc2">5.3. How can I tell flexbackup to read from stdin or backup to stdout?</a></li>
+<li><a href="#Misc3">5.4. Why keep the table of contents db on disk rather than on the tape?</a></li>
+<li><a href="#Misc4">5.5. This index stuff is a useless mess/I can't get it to work!</a></li>
+</ul></dd>
+</dl>
+<hr><h2>1. Configuration</h2>
+<dl>
+<dt><b><a name="Configuration0">1.1. How do I upgrade from 1.0.x to 1.2.x?</a></b></dt>
+<dd><B>Use the new default config file, then tweak as needed</B>. Specific things
+that changed:
+
+<OL>
+<LI>Filesystems: the filesystem spec now allows an arbitrary "backup set"
+concept. In the config file, you can do things like:<PRE>
+ $set{'tag1'} = "/dir1 /dir2 ...";
+ $set{'tag2'} = "/dir3 host:/dir4 ...";</PRE>
+<LI>The commandline -fs switch became:
+<UL>
+<LI>"-dir &lt;dir&gt;" backs up one directory tree (old "-fs &lt;dir&gt;")
+<LI>"-set &lt;tag&gt;" backs up a named set. Useful new feature.
+<LI>"-set all" acts like old "-fs all". If using tapes & level 0, each set
+is a different tape (acts like the old numeric index to @filesystems)
+</UL>
+
+<LI>mt blocksize: old configuration boolean $mt_var_blksize is now an integer, $mt_blocksize (in bytes).
+<UL>
+<LI>Set to 0 for variable blocksize - old "true" value
+<LI>Comment out or set to "$blksize * 1024" for old "false" value.
+<LI>Or use some other value, like "512". This wasn't possible before.
+</UL>
+
+<LI>"-extract -files" turned into "-extract -onefile" or "-extract
+-flist" (functionality should be more clear now).
+
+</OL><br><br></dd>
+<dt><b><a name="Configuration1">1.2. Why won't my exclude or prune expressions work?</a></b></dt>
+<dd>Use regular expressions, not wild cards (aka globs). See a perl, sed,
+or grep reference, or "man 7 regex". Simplistically, you probably mean to
+say ".*" instead of "*", and ".?" instead of "?". Also, leave OFF the
+first part of a path that is the filesystem you are archiving; the exclude
+expressions only match sub-paths of the backup directory.
+
+<P>You can test exclusion, levels, pruning, and package deltas by
+using "-type filelist". This writes a text file listing all files that
+would have been backed up into your current working directory.
+
+<P>You can also test to see if your expression matches what you want to
+exclude by:<BR><BR>
+<CODE>cd &lt;topdir&gt;; find . -regex "&lt;expression&gt;"</CODE><br><br></dd>
+<dt><b><a name="Configuration2">1.3. How can I avoid running backups or remote shells as root?</a></b></dt>
+<dd>You can do this with 'sudo'.
+<OL>
+<LI>Set up a passphraseless ssh key for a user to run the backups.
+<LI>Set <CODE>$path{command} = 'sudo command';</CODE> in the config file.
+ Do this for all commands that will need privilege (find, afio/tar/etc)
+<LI>Set up an /etc/sudoers file with something like this:<BR><BR>
+<CODE>
+Cmnd_Alias BACKUP = /usr/bin/afio, /usr/bin/find<BR>
+backupuser ALL = NOPASSWD: BACKUP</CODE>
+</OL><br><br></dd>
+<dt><b><a name="Configuration3">1.4. On my system, "tar" is not GNU tar but is the vendor's "tar". GNU tar is
+installed as "gtar". On my system, "dump" is "ufsdump".</a></b></dt>
+<dd>Use the path overrides at the end of the config file. A couple examples
+are given there, such as: <CODE>$path{'dump'} = 'ufsdump';</CODE>. You can
+use this mechanism to override paths or just change command names.<br><br></dd>
+<dt><b><a name="Configuration4">1.5. How can I back up Windows systems?</a></b></dt>
+<dd>There are currently two ways:
+<UL>
+<LI>Use "smbmount" to mount the windows share on a directory and back that
+up just like any other directory
+<LI>flexbackup runs natively under cygwin
+</UL>
+
+<P>In the future we might add a way to set up "smbtar" or something similar...<br><br></dd>
+<dt><b><a name="Configuration5">1.6. Does the to-disk backup mode use single archive files or directory tree copies
+of files?</a></b></dt>
+<dd>You can do either. The "copy" or "rsync" types will do a tree mirror of the selected
+files, other types make single-file archives in the <CODE>$device</CODE> dir.
+
+<P>Note that archive-to-disk with remote directories (<CODE>$device =
+host:/path/to/dir</CODE>) is not currently possible. You need to either run the
+backup from the machine with the disk, or use NFS.<br><br></dd>
+<dt><b><a name="Configuration6">1.7. How do I deal with spaces in set or prune directory specifications?</a></b></dt>
+<dd>Good question, since these variables are set up as space-separated
+lists. Just enclose each item in single or double quotes:<BR><BR>
+<CODE>$set{'backup'} = "'/path/file system 1' '/path/file system 2'";</CODE><br><br></dd>
+<dt><b><a name="Configuration7">1.8. Does flexbackup work with autoloaders / mtx?</a></b></dt>
+<dd>Not currently. If you want help add it, let us know.
+See also 2.2 below.<br><br></dd>
+</dl>
+<hr><h2>2. Backup</h2>
+<dl>
+<dt><b><a name="Backup0">2.1. Can you explain numeric dump levels?</a></b></dt>
+<dd>Level 0 gets everything. For 1 or higher, files that have been changed
+since the last backup at a <B>lower</B> level are archived. For
+instance, if a level 2 backup was done on Monday, followed by a level 4
+backup on Tuesday, a subsequent level 3 backup on Wednesday would contain
+all files modified or added since the level 2 (Monday) backup.
+
+<P>See also <A HREF="http://docs.sun.com/db/doc/805-7228/6j6q7uf18?a=view">these sun
+docs</A> for an example with some pictures.
+
+<P>Traditional Unix 'dump' behavior only allows levels 0-9; flexbackup
+removes this limit if you are using any of the non-dump archive formats.
+
+<P>For those people coming from other platforms that are not used to
+dump-style numeric levels, the latest versions of flexbackup have added
+<CODE>-full</CODE>, <CODE>-differential</CODE>, <CODE>-incremental</CODE>
+that get translated into appropriate dump-style level numbers.
+Definitions:<UL>
+<LI>full: everything (duh!)
+<LI>differential: only files that have been changed or added since the last full backup
+<LI>incremental: only the files that have changed or been added to a system since doing the last backup (of any type)
+</UL>
+<P>These are simply aliased to numeric levels: full = 0, differential = 1, and
+incremental = (latest backup + 1)<br><br></dd>
+<dt><b><a name="Backup1">2.2. Do you support multi-tape archives?</a></b></dt>
+<dd>If you split filesystems/directories to be in different "sets", each set
+will go on a different tape for full backups (you'll be prompted).
+
+<P>As of now, having <B>one</B> directory archive span multiple tapes is not supported.
+True, some of the archiver programs can deal with end-of media if and
+only if <B>they</B> are writing directly to the device, but it can't be
+done in a shell pipeline, or in any kind of generic fashion. For now, split
+the directory up into workable portions, use larger media, or backup to
+disk...
+
+<P>We need a program like "buffer", but that also deals with the volume end
+detection &amp; media change prompting, has configuraable "new tape"
+scripts, etc.
+
+<P>Version 1.2.1 has basic support for using mbuffer with multivolumes.
+Please try it out and let me know.<br><br></dd>
+<dt><b><a name="Backup2">2.3. I'm backing up to files on disk -- How can I get filenames to not have
+timestamps in them?</a></b></dt>
+<dd>Set <CODE>$staticfiles</CODE> to true in the config file. Archives will
+then have names such as "etc.3.dump.gz" rather than something like
+"home.0.200301212305.afio-bz2". There is a similar parameter for the log
+files, by the way.<br><br></dd>
+<dt><b><a name="Backup3">2.4. What is the maxiumum size of the archives flexbackup can produce?</a></b></dt>
+<dd>This is a limitation of the archive program, tape driver, underlying
+filesystem, OS, or specific compilation of perl - nothing in flexbackup
+controls that.
+
+<P>Some older programs/systems barf at 2GB files -- anything recent should
+deal with large files no problem, using standard APIs.
+
+<P>If you are using "buffer" be aware that you need to compile with extra
+flags, or patch the 1.19 version to handle >2GB files. (The RPMS linked to
+from this site have been)
+
+<P>See also <A HREF="http://www.suse.de/~aj/linux_lfs.html">Large File
+Support in Linux</A> or <A
+HREF="http://ftp.sas.com/standards/large.file/x_open.20Mar96.html">Large
+file size changes to the single Unix spec</A> for more info and hints on
+how to fix things if you need to recompile something.<br><br></dd>
+</dl>
+<hr><h2>3. Recovery / Extraction</h2>
+<dl>
+<dt><b><a name="Recovery / Extraction0">3.1. How do I extract only certain files in an archive rather than the
+whole thing?</a></b></dt>
+<dd>Use the -flist or -onefile switches.
+
+<P>To quickly extract just a single file, use "-extract -onefile path/to/my/file",
+giving the path from the archive.
+
+<P>To extract a list of multiple files, put them into a text file, for
+instance "restorelist", then use "-extract -flist restorelist". The format
+is one line per pathname, using the path of the file in the archive. Note
+if you are using afio with compression you need to append ".z" to
+filenames for any compressed files (depends on threshold and exclusion patterns).<br><br></dd>
+<dt><b><a name="Recovery / Extraction1">3.2. How do I find out which archive(s) contain a certain file?</a></b></dt>
+<dd>If you don't know in which archive to find a certain file, look at the
+log files. As long as you have verbose turned on (default), you can just
+'zgrep filename /var/log/flexbackup/*.gz', and that works pretty well.<br><br></dd>
+<dt><b><a name="Recovery / Extraction2">3.3. The disk totally died. How do I get flexbackup's archives off the
+tapes on a machine without flexbackup or even perl installed?</a></b></dt>
+<dd>Something like this, if using compression with tar/cpio/dump:<BR><BR>
+<CODE>dd if=/dev/tapedevice bs=10k | gzip -dc | tar xvf -</CODE><BR><BR>
+replace "tar xvf -" with "restore -i -f -" or whatever. Skip the gzip pipe
+if you aren't using compression. afio is a bit more complex, you might
+need something like:<BR><BR><CODE>afio -i -k -x -P bzip2 -Q -d -Z -v</CODE>
+<BR><BR>Check the manpages for the archive utility you used...<br><br></dd>
+<dt><b><a name="Recovery / Extraction3">3.4. I don't have the index files! (disk died, different machine, etc.)</a></b></dt>
+<dd>This is OK, you can still extract/restore/etc. The index files are for
+human convenience only. Just skip the first file on the tape, it's a
+simple "tag" text file. The rest of the tape is your normal archives.<br><br></dd>
+</dl>
+<hr><h2>4. Common problems</h2>
+<dl>
+<dt><b><a name="Common problems0">4.1. I get I/O errors or other errors from my tape drive.</a></b></dt>
+<dd>Run this: <CODE>flexbackup -test-tape-drive</CODE>. It writes a couple
+small files to the tape, then reads & diffs them. This will flush out any
+problems with parameters like blocking, filemarks, and padding; or issues
+with the tape drive or driver itself.
+
+<P>If it fails, try these one at a time:<OL>
+<LI>If you are using buffering, turn it off (<CODE>$buffer='false'</CODE>) until you can get things working without it.
+<LI>Change the block size parameter (<CODE>$blksize</CODE>) in the configuration file. The default is 10k, but some tape drives like 32k or 64k much better.
+<LI>Try setting <CODE>$pad_blocks</CODE> to false
+<LI>Switch the setting of <CODE>$mt_blocksize</CODE>.
+<LI>Try with with <CODE>$indexes</CODE> set to false
+</OL>
+
+<P>If you are using an IDE tape drive under Linux, and are still having
+trouble, you might also try the 'ide-scsi' layer with the 'st' module, and
+treating the thing like a SCSI tape - I've had more success that way.
+
+<P>You can also just try writing/reading stuff straight from the drive with
+an archiver and dd (to factor flexbackup out of the loop), although that's what the
+<CODE>-test-tape-drive</CODE> switch tries to do. If you can get that working,
+but flexbackup still seems to malfunction, let us know. Try things like:
+(The example below uses afio to test, tar/cpio/others will be different)
+
+<PRE>
+ # Backup two dirs
+ mt -f /dev/tape setblk 32768
+ mt -f /dev/tape rewind
+ mt -f /dev/tape erase
+ find /dir1 -print | afio -o -z -v -b 32k - | dd ibs=32k obs=32k of=/dev/tape
+ find /dir2 -print | afio -o -z -v -b 32k - | dd ibs=32k obs=32k of=/dev/tape
+
+ # List
+ mt -f /dev/tape rewind
+ dd ibs=32k obs=32k if=/dev/tape | afio -t -z -v -b 32k -
+ dd ibs=32k obs=32k if=/dev/tape | afio -t -z -v -b 32k -
+</PRE><br><br></dd>
+<dt><b><a name="Common problems1">4.2. Backups seem to work ok, but I can't get extracting/listing to work.</a></b></dt>
+<dd>Usually a matter of tape positioning, or else is a tape drive parameter
+problem as talked about above. For positioning, use 'mt rewind; mt fsf
+&lt;n&gt;' to get to the right file number on the tape, or use the "-num"
+switch with "-list|extract|compare|restore" to have flexbackup do it for
+you. If you are using flexbackup's indexes, remember that file number 0 is
+a header used for the index itself.<br><br></dd>
+<dt><b><a name="Common problems2">4.3. How do I get remote backups over ssh to work without prompting for a password?</a></b></dt>
+<dd>For rsh, use .rhosts or hosts.equiv. But you really should NOT be using
+rsh.
+
+<P>If you are using ssh and are having a problem with it asking for a
+password, then you don't have a passphrase-less authorized key set up, your
+RSAAuthentication parameters are incorrect, or you have some other
+public/private key problem. Section 7.2.2 of the ssh FAQ might
+help:<BR><BR> <A
+HREF="http://www.onsight.com/faq/ssh/ssh-faq.html">http://www.onsight.com/faq/ssh/ssh-faq.html</A><BR><BR>
+In a nutshell the value of your <CODE>~/.ssh/identity.pub</CODE> needs to
+be present in your <CODE>~/.ssh/authorized_keys</CODE> file for RSA
+authentication to work. For root accounts you might also need to
+put<BR><BR> <CODE>PermitRootLogin yes</CODE><BR><BR> in your sshd_config
+file, as root is treated with more care than normal user accounts. Note:
+you might choose without-password or forced-commands-only instead of yes
+for more security. See your ssh(1) and sshd(1) man pages.
+
+<P>If you do not want to use passphrase-less ssh keys with root logins, See
+the section on use with "sudo". (Or else sit at the terminal and type
+passwords during the backups if you *really* want to...)<br><br></dd>
+<dt><b><a name="Common problems3">4.4. I keep getting only one backup per tape when I do "-set all"!</a></b></dt>
+<dd>Use the non-rewinding device.<br><br></dd>
+<dt><b><a name="Common problems4">4.5. 'buffer' complains that it can't run successfully or that it doesn't
+have enough shared memory.</a></b></dt>
+<dd>Set <CODE>$buffer_megs</CODE> to a lower value and try again. I usually
+run with it set somewhere between 5-20MB.
+
+<P>On FreeBSD, SysV shared memory can run low using the GENERIC kernel at
+its default value (especially if you are running something like GNOME). You
+can modify your kernel file and change the line<BR><BR>
+<CODE>options SHMMAXPGS=2048</CODE>
+<P>To something really big. You can also change this dynamically at startup by
+putting<BR><BR>
+<CODE>kern.ipc.shmmax={large integer}</CODE>
+<P>in /etc/sysctl.conf. Other BSD's will have very similar mechanisms.<br><br></dd>
+<dt><b><a name="Common problems5">4.6. When using -newtape or -erase, the tape drive sits there forever making
+noise and nothing happens on the screen</a></b></dt>
+<dd>Some types of tape drives/drivers take an extremely long time to do an
+"mt erase" operation, or its unnecessary. If you suspect this is the case,
+check to see if "mt erase" process is running. In these cases, you can set
+the config variable <CODE>$erase_rewind_only</CODE> to true - then an
+"erase" operation becomes just a rewind. If an archive is then written,
+filemarks and tape end-of-data should work fine.
+
+<P>Also, this hang can be caused by incorrect tape drive parameters on some
+systems. Try "flexbackup -test-tape-drive". If it hangs, and there is a dd
+or buffer process continuously running, kill it and adjust blocksizes and/or
+padding.<br><br></dd>
+<dt><b><a name="Common problems6">4.7. My "mt" or tape drive doesn't support operation xxxx.</a></b></dt>
+<dd>mt commands definitely vary greatly from platform to platform. We try
+to pick the best default, but it won't be right for everyone. To override mt operation
+'xxxx', just put $mt{'xxxx'} = 'yyyy' in the config (see the comments
+near the end). For instance, to make flexbackup use "mt rdspos" instead of
+"mt rdhpos, use: <BR><BR>
+<CODE> $mt{'rdhpos'} = 'rdspos';</CODE><br><br></dd>
+<dt><b><a name="Common problems7">4.8. Flexbackup caused my system to hang!</a></b></dt>
+<dd>No it didn't. Give me a break, it's a script. If you are asking this
+question most likely your tape driver lost its mind and hung the system
+somehow.<br><br></dd>
+<dt><b><a name="Common problems8">4.9. The backup looks like it works ok, but flexbackup complains of an error
+at the end of the archive, then exits.</a></b></dt>
+<dd>If flexbackup detects a non-zero exit status for any command in the
+pipeline, it will stop. Something probably happened further up in the log
+that caused the archive or buffering program to exit with an error status.
+Look over the log again, or run with "-d verbose=false" to turn off the
+printing of filenames to make it more obvious. See also the next question.<br><br></dd>
+<dt><b><a name="Common problems9">4.10. I get an error from the backup due to files moving/changing/being deleted
+during the backup</a></b></dt>
+<dd>You really shouldn't be doing that!
+
+<P>If flexbackup detects a non-zero exit status for any command in the
+pipeline, it will stop. You can override this with the "--ignore-errors"
+switch, but then it will just blindly continue on such cases. You have been
+warned.
+
+<P>Some archivers are more picky than others. You can try switching the
+archive type.
+
+<P>If it is something like a SQL database data file causing the problem, you
+should dump the database to a secondary location and back THAT data up.
+(See the mysqldump command, for instance).<br><br></dd>
+</dl>
+<hr><h2>5. Misc</h2>
+<dl>
+<dt><b><a name="Misc0">5.1. How does the "packaging system delta" (or "-pkgdelta") feature work?</a></b></dt>
+<dd>When you have OS CD's sitting around, and/or you know darn well that 90%
+of the non-user, non-data files on your system you can get off the 'net
+easily, there is no need to back up "stock" files.
+
+<P>Flexbackup can use a "package delta" mode that cuts down the number of files
+to be archived by basically doing the following:
+<OL>
+<LI>List the files in the directory/filesystem to be backed up, using the normal
+ level &amp; timstamp mechanism
+<LI>Subtract any files that are "owned" by packages.
+<LI>Add back any package files modified from the distributed version (optional)
+<LI>Backup the files in the list.
+</OL>
+
+<P>It was orginally intended for use with RPM-based systems ("-pkgdelta
+rpm"). Initial support for FreeBSD packages is included ("-pkgdelta
+freebsd"), although there the "base" files aren't part of package so they
+can't be excluded.
+
+<P>If someone knows how to do the equivalent things for
+.deb packages or others, let me know and we can add it.<br><br></dd>
+<dt><b><a name="Misc1">5.2. What exactly is the "-wday" switch for?</a></b></dt>
+<dd>To help cron do things like once-a-month, certain day of the week
+jobs. With crontab fields, you can easily set up something to run
+on certain day(s) of the week, or on certain day(s) of the month.
+
+<P>Now, tell me you how to set up a cron job to run once a month on the first
+Sunday only.... you can't do it.
+
+<P>The "-wday" flag tells flexbackup to just exit if the current day of the
+week does not match the flag. Now, to fulfill the above requirement, you
+add a cron entry like:
+<PRE>
+ 0 3 1-7 * * flexbackup -wday 7 -set all ....
+</PRE>
+Which will run at 3:00AM on the first Sunday of every month.<br><br></dd>
+<dt><b><a name="Misc2">5.3. How can I tell flexbackup to read from stdin or backup to stdout?</a></b></dt>
+<dd>See the "-pipe" switch. Only lets you do one directory at a time,
+obviously. You can use this to interact with other programs in a pipeline.<br><br></dd>
+<dt><b><a name="Misc3">5.4. Why keep the table of contents db on disk rather than on the tape?</a></b></dt>
+<dd><UL>
+<LI>We don't know the table size ahead of time unless we use a hard-limited reserved size.
+<LI>Updating the db at the beginning of the tape will add tape-travel overhead for every backup.
+<LI>a backup->rewind->write start of tape sequence will lose the rest of the contents past the first file unless we start to play filemark tricks.
+</UL><br><br></dd>
+<dt><b><a name="Misc4">5.5. This index stuff is a useless mess/I can't get it to work!</a></b></dt>
+<dd>It isn't necessary if you don't like it. Set <CODE>$indexes =
+'false';</CODE> in the config file, and just use written labels on the
+tapes...<br><br></dd>
+</dl>
+<HR>
+
+<ADDRESS><A HREF="mailto:flexbackup-help@lists.sourceforge.net">flexbackup-help at lists dot sourceforge dot net</A></ADDRESS>
+
+<P>
+<I>FAQ compilation thanks to <A HREF="http://www.makefaq.org">makefaq</A></I>
+
+</BODY>
+</HTML> \ No newline at end of file
diff --git a/flexbackup b/flexbackup
new file mode 100755
index 0000000..74add23
--- /dev/null
+++ b/flexbackup
@@ -0,0 +1,5688 @@
+#!/usr/bin/perl -w
+######################################################################
+#
+# Edwin Huffstutler, <edwinh@computer.org>
+# $Id: flexbackup,v 1.185 2003/10/10 14:12:09 edwinh Exp $
+# $Name: v1_2_1 $
+#
+# >>>> Also see the config file, README, manpages, & FAQ <<<<
+#
+# USAGE:
+# flexbackup -help : this message
+#
+# BACKUP:
+# flexbackup -dir <dir> : backup directory tree, level 0
+# flexbackup -set <tag> : backup set "tag" (def. in config file), level 0
+# flexbackup -set all : backup all sets, level 0
+# flexbackup [...] -level <n> : backup level, can be integer or
+# full/differential/incremental
+# flexbackup [...] -pkgdelta <x> : prune backup to files not part of a package
+# or changed from distributed version
+# <x> can be "rpm" or "freebsd" package systems
+# flexbackup [...] -wday <n> : backup only if the week day matches
+# the input number. Sunday is 0 or 7.
+# flexbackup [...] -pipe : write to stdout rather than file/device
+# flexbackup [...] -ignore-errors : continue backups even if commands return error
+# status
+# READING ARCHIVES:
+# flexbackup -list : list files in archive
+# flexbackup -extract : extract all files from archive into your
+# current working directory
+# flexbackup -extract -flist <f> : restore the files listed in text file <f>
+# into your current working directory
+# flexbackup -extract -onefile <f>: restore the single file specified by <f>
+# into your current working directory
+# flexbackup -compare : compare archive with the files in your
+# current directory
+# flexbackup -restore : interactive restore (dump type only for now)
+# flexbackup [...] -num <n> : read file number n from tape; if not given
+# uses current tape position
+# flexbackup [...] <file> : if archiving to files rather than a device,
+# list/extract/compare/restore options take
+# flexbackup [...] -pipe : read archive from stdin
+# flexbackup [...] -volumes <n> : # of volumes in input
+# (EXPERIMENTAL mbuffer multivolume support)
+# INDEX RELATED:
+# flexbackup -toc : list current device's table of contents
+# flexbackup -toc all : list all known table of contents
+# flexbackup -toc <key> : list table of contents for specific key
+# flexbackup -rmindex all : force db delete of all index info
+# flexbackup -rmindex <key> : force db delete of specified tape/dir index
+# flexbackup -rmindex <key>:<x> : force db delete of specified tape:file
+#
+# TESTING/DEBUG:
+# flexbackup -test-tape-drive : tries writing/reading files to make sure you
+# have tape driver & parameters set up right
+# flexbackup [...] -n : don't run actual dump or mt commands, just echo
+# flexbackup [...] -type filelist : special backup type that just saves list of
+# files that would have been archived
+# MISC:
+# flexbackup -newtape : erase & create new index key (but no backup)
+# flexbackup -rmfile <file> : if backups to disk, rm file & index info
+# flexbackup -rmfile all : if backups to disk, rm all files/index for dir
+# flexbackup [...] -c <file> : use <file> instead of /etc/flexbackup.conf
+# for configuration
+# flexbackup [...] -type <x> : override $type from config file
+# flexbackup [...] -compress <x> : override $compress from config file
+# flexbackup [...] -device <dev> : override $device from config file
+# flexbackup [...] -d 'var=val' : override config file setting of $var
+# flexbackup -dir <x> -erase : force a rewind/erase before backup
+# flexbackup -dir <x> -norewind : don't rewind tape after a single backup
+# flexbackup -set <x> -noreten : don't retension for level 0 set backups
+# flexbackup -set <x> -noerase : don't rewind/erase for level 0 set backups
+# flexbackup [...] -reten : force a retension before read
+# flexbackup [...] -nodefaults : don't use any default values for config variables
+# flexbackup -version : show version
+#
+######################################################################
+#
+# flexbackup is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# flexbackup is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with flexbackup; see the file COPYING. If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+######################################################################
+
+use POSIX;
+use AnyDBM_File;
+use Getopt::Long;
+use Text::Wrap;
+use File::Basename;
+use English;
+use strict;
+
+# No output buffering
+$OUTPUT_AUTOFLUSH = 1;
+
+# Set the traditional UNIX system locale behavior (touch doesn't read LANG)
+my $loc = POSIX::setlocale( &POSIX::LC_ALL, "C" );
+
+# See if afio is calling us as a control script
+if (defined($ARGV[0]) and ($ARGV[0] =~ /flexbackup.volume_header_info/)) {
+ &print_afio_volume_header();
+}
+
+# This is changed during "make install"
+$::CONFFILE="/etc/flexbackup.conf";
+
+# This took awhile to figure out. if the shell is capable of it, we use
+# this on the end of any pipelines to see if any of the commands in the
+# pipeline failed, rather than just the last one.
+#
+# If /bin/sh is really bash2 in disguise, or remote shell is bash2/zsh,
+# we can use their status array variables
+#
+# With plain sh, we don't know if the non-last command in the pipe fails
+# See exit-status collecting trick in the code.
+#
+# With tcsh/csh as a remote shell, you don't know which command, but
+# $? is still set if anything in the pipeline failed
+#
+$::bash_pipe_exit = '; x=(${PIPESTATUS[@]}); i=0; while [ $i -lt ${#x[@]} ]; do [ ${x[$i]} -eq 0 ] || exit ${x[$i]}; i=$(($i+1)); done';
+$::zsh_pipe_exit = '; x=(${pipestatus[@]}); i=1; while [ $i -le ${#x[@]} ]; do [ ${x[$i]} -eq 0 ] || exit ${x[$i]}; i=$(($i+1)); done';
+
+# tar has a limit of this many chars in its volume label
+$::tar_max_label = 99;
+
+# Get commandline flags
+%::opt = ();
+if (! &::GetOptions(\%::opt,
+ "c=s",
+ "compare:s",
+ "compress=s",
+ "d=s%",
+ "dir=s",
+ "pipe",
+ "pkgdelta=s",
+ "device=s",
+ "differential",
+ "erase!",
+ "extract:s",
+ "flist=s",
+ "full",
+ "help",
+ "incremental",
+ "ignore-errors",
+ "level=s",
+ "list:s",
+ "onefile=s",
+ "n",
+ "newtape",
+ "nodefaults",
+ "num:i",
+ "restore:s",
+ "reten!",
+ "rewind!",
+ "rmfile:s@",
+ "rmindex:s@",
+ "set=s",
+ "test-tape-drive",
+ "toc:s",
+ "type=s",
+ "version",
+ "volumes:i",
+ "wday=i"
+ )) {
+ exit(0);
+}
+
+# Default fd for messages (we might have stdout as archive output)
+if (defined($::opt{'pipe'})) {
+ $::msg = *STDERR;
+} else {
+ $::msg = *STDOUT;
+}
+
+# Give usage message
+if (defined($::opt{'help'})) {
+ &usage();
+ exit(0);
+}
+
+# Version
+if (defined($::opt{'version'})) {
+ print $::msg "flexbackup version " . &versionstring() . "\n";
+ print $::msg '$Id: flexbackup,v 1.185 2003/10/10 14:12:09 edwinh Exp $ ' . "\n";
+ exit(0);
+}
+
+# Exit if -wday given and it isn't that day of the week (see FAQ)
+&check_wday();
+
+# Get/read config file
+print $::msg "\nflexbackup version " . &versionstring() . "\n";
+&readconfigfile();
+print $::msg "\n";
+
+# Set OS type
+chomp($::uname = `uname -s`);
+
+# Sanity check commandline flags and config file options
+&optioncheck();
+&line('screen');
+
+# Check shells, buffer is runnable, remote progs...
+&test_before_run();
+
+# See about rewind/erase/reten flags
+&set_tape_operation_defaults();
+
+# Get current date string
+$::date = &current_time('numeric');
+
+# Decide what to do
+if (defined($::opt{'restore'})) {
+ &restore_routine();
+
+} elsif (defined($::opt{'extract'})) {
+ &extract_routine();
+
+} elsif (defined($::opt{'compare'})) {
+ &compare_routine();
+
+} elsif (defined($::opt{'list'})) {
+ &list_routine();
+
+} elsif (defined($::opt{'dir'}) or defined($::opt{'set'})) {
+ &backup_routine();
+
+} elsif (defined($::opt{'toc'})) {
+ &line();
+ # Only do this if we're going to grab current tape index
+ if ($::opt{'toc'} eq '') {
+ &mt("generic-blocksize $::mt_blksize");
+ }
+ &toc_routine();
+
+} elsif (defined($::opt{'rmindex'})) {
+ &line();
+ foreach my $arg (@{$::opt{'rmindex'}}) {
+ &rmindex($arg);
+ }
+
+} elsif (defined($::opt{'newtape'})) {
+ &line();
+ &mt("generic-blocksize $::mt_blksize");
+ &newtape();
+
+} elsif (defined($::opt{'rmfile'})) {
+ &line();
+ &rmfile();
+
+} elsif (defined($::opt{'test-tape-drive'})) {
+ &line();
+ &test_tape_drive();
+
+}
+
+if (($::mode !~ m/^(list|extract|restore|compare|test-tape-drive)$/) and
+ ($cfg::indexes eq "true")) {
+ untie(%::index);
+}
+
+exit(0);
+
+######################################################################
+# Backup
+######################################################################
+sub backup_routine {
+
+ my @files;
+ my $label;
+ my $tapecounter = 0;
+ my %oldlogs;
+ my $fs;
+ my $logfile;
+ my $symlink = '';;
+ my $logext = '';
+ my $comp_cmd;
+ my $tape_key;
+ my $logsuffix = '';
+ my $error = 0;
+
+ # Figure out log file name & empty log file
+ if (defined($::opt{'set'})) {
+ $label = &get_label($::opt{'set'});
+ } else {
+ $label = &get_label($::opt{'dir'});
+ }
+
+ if ($cfg::staticlogs eq 'false' ) {
+ $logsuffix = ".$::date";
+ }
+
+ if (!defined($::set_incremental)) {
+ $logfile = "$cfg::prefix$label.$::level" . $logsuffix;
+ } else {
+ $logfile = "$cfg::prefix$label.incremental" . $logsuffix;
+ }
+
+ $symlink = "$cfg::prefix$label.latest";
+ $::log = "$cfg::logdir/$logfile";
+ if (! open(LOG,">$::log")) {
+ die "Can't write to $::log: $OS_ERROR";
+ }
+ close(LOG);
+
+ &line();
+ &mt("generic-blocksize $::mt_blksize");
+
+
+ # Remember old log files (will remove at end of job)
+ # ("old" = any higher- or equal-numbered logs for this label)
+ if (!defined($::set_incremental)) {
+ opendir(DIR,"$cfg::logdir") or die("Can't open cfg::logdir: $OS_ERROR");
+ @files = readdir(DIR);
+ foreach my $lf (reverse sort @files) {
+
+ # Skip our own log
+ next if ($lf =~ m/^$logfile(\.gz|\.bz2|\.lzo|\.Z|\.zip)?$/);
+
+ # Find normal old logs
+ if ($lf =~ m/^$cfg::prefix$label\.(\d+)(\.(\d+))?(\.gz|\.bz2|\.lzo|\.Z|\.zip)?$/) {
+ if ($1 >= $::level) {
+ # Might be from $staticlogs=true or false
+ if (defined($3)) {
+ $oldlogs{"$cfg::logdir/$lf"} = $1 . "|" . $3;
+ } else {
+ $oldlogs{"$cfg::logdir/$lf"} = $1;
+ }
+ }
+ }
+
+ # If this is a level 0, we can nuke incremental logs
+ if (($::level == 0) and ($lf =~ m/^$cfg::prefix$label\.(incremental)(\.(\d+))?(\.gz|\.bz2|\.lzo|\.Z|\.zip)?$/)) {
+ # Might be from $staticlogs=true or false
+ if (defined($3)) {
+ $oldlogs{"$cfg::logdir/$lf"} = $1 . "|" . $3;
+ } else {
+ $oldlogs{"$cfg::logdir/$lf"} = $1;
+ }
+ }
+ }
+ close(DIR);
+ }
+
+
+ # Possibly populate package-file hashes if we are using
+ # -pkgdelta. This is so we only have to run through these operations
+ # once per machine if multiple fs's are being run
+ if (defined($::pkgdelta)) {
+ if (defined($::local)) {
+ &list_packages('localhost');
+ &find_packaged_files('localhost');
+ &find_changed_files('localhost');
+ }
+ foreach my $host (keys %::remotehosts) {
+ &list_packages($host);
+ &find_packaged_files($host);
+ &find_changed_files($host);
+ }
+ $::pkgdelta_filelist = "$cfg::tmpdir/pkgdelta.$PROCESS_ID";
+ &line();
+ }
+
+ ##########################
+ #
+ # Main backup routine
+ #
+ ##########################
+ if (defined($::opt{'set'})) {
+
+ if (!defined($::set_incremental)) {
+ &log("| Doing level $::level backup of set $::opt{set} using $cfg::type");
+ } else {
+ &log("| Doing incremental backup of $::opt{set} using $cfg::type");
+ }
+
+ # All sets or just one?
+ my @do_sets;
+ if ($::opt{'set'} eq 'all') {
+ @do_sets = keys(%cfg::set);
+ if (defined($::tapedevice)) {
+ $_ = scalar(@do_sets);
+ $_ = join(" ", @do_sets) . " ($_ tapes)";
+ } else {
+ $_ = join(" ", @do_sets);
+ }
+ &log("| All sets = $_");
+ } else {
+ @do_sets = ($::opt{'set'});
+ }
+
+ my $num_tapes = scalar(@do_sets) - 1;
+ foreach my $this_set (@do_sets) {
+
+ # Maybe retension
+ if (($::do_reten == 1) and defined($::tapedevice)) {
+ &log('| Retensioning tape...');
+ &mt('retension');
+ }
+
+ # Maybe rewind/erase
+ if ($::do_erase == 1) {
+ $tape_key = &newtape();
+ } else {
+ &mt('rewind');
+ $tape_key = &get_tape_key();
+ if(defined($::tapedevice)) {
+ &log('| Making sure tape is at end of data...');
+ }
+ &mt('generic-eod');
+ }
+
+ # Print what this set contains
+ &log("| Backup set \"$this_set\" ($cfg::set{$this_set})");
+
+ # Show tape position
+ if (defined($::tapedevice)) {
+ # Multiple tapes are only for level 0
+ if (!defined($::set_incremental) and ($::level == 0)) {
+ &log("| Tape \#$tapecounter");
+ }
+ &line();
+ &mt('generic-query');
+ }
+
+ # Iterate over the filesystems in the set and back 'em up
+ foreach my $dir (&split_list($cfg::set{$this_set})) {
+
+ my $level;
+
+ # Get rid of trailing /
+ $dir = &nuke_trailing_slash($dir);
+
+ # If level is icremental for the set, each dir might
+ # have a different numeric level
+ if (!defined($::set_incremental)) {
+ $level = $::level;
+ } else {
+ $level = &get_incremental_level($dir);
+ }
+
+ $error = &backup($dir, $tape_key, $level);
+ last if ($error != 0);
+
+ if ($cfg::indexes eq "true") {
+ $::nextfile++;
+ }
+ }
+
+ # Prompt for new tape if more than one set in list & level 0
+ if (!defined($::set_incremental) and ($::level == 0)) {
+ if ($tapecounter < $num_tapes) {
+
+ # Maybe rewind (usually true)
+ if ($::do_rewind_after == 1) {
+ if(defined($::tapedevice)) {
+ &log("| Rewinding...");
+ }
+ &mt('rewind');
+ &line();
+ }
+
+ if (defined($::tapedevice)) {
+ &toc_routine($tape_key);
+ }
+
+ $tapecounter++;
+ if (defined($::tapedevice)) {
+ print $::msg "\n";
+ while(1) {
+ print $::msg "---> Insert tape \#$tapecounter (enter y to continue) ";
+ chomp($_ = <STDIN>);
+ last if ($_ =~ m/^y/i);
+ }
+ print $::msg "\n";
+ &line();
+ }
+
+ } # end not at last tape
+ } # end if level == 0
+
+ } # end foreach set
+
+ } else {
+
+ # Just one filesystem, -dir given
+ &log("| Doing level $::level backup of $::opt{dir} using $cfg::type");
+
+ # Maybe retension
+ if ($::do_reten == 1) {
+ if (defined($::tapedevice)) {
+ &log('| Retensioning tape...');
+ }
+ &mt('retension');
+ }
+
+ # Maybe rewind/erase
+ if ($::do_erase == 1) {
+ $tape_key = &newtape();
+ } else {
+ &mt('rewind');
+ $tape_key = &get_tape_key();
+ if (defined($::tapedevice)) {
+ &log('| Making sure tape is at end of data...');
+ }
+ &mt('generic-eod');
+ }
+
+ if (defined($::tapedevice)) {
+ &line();
+ &mt('generic-query');
+ }
+
+ $error = &backup($::opt{'dir'}, $tape_key, $::level);
+
+ } # end set or single fs
+
+ if (defined($::tapedevice)) {
+ &line();
+ }
+
+ # Maybe rewind (usually true)
+ if (($::do_rewind_after == 1) and defined($::tapedevice)) {
+ &log("| Rewinding...");
+ &mt('rewind');
+ }
+
+ # Remove old log files now that we are done
+ if ($error == 0) {
+ my $rmlogs = 0;
+ foreach my $lf (sort keys %oldlogs) {
+ $rmlogs++;
+ my ($lev,$d) = split(/\|/,$oldlogs{$lf});
+ if (defined($d)) {
+ &log("| Removing old level $lev log of $label (dated $d)");
+ } else{
+ &log("| Removing old level $lev log of $label");
+ }
+ if (!defined($::debug)) {
+ unlink("$lf") or warn("Can't remove $lf: $OS_ERROR\n");
+ }
+ }
+ &line('log') if ($rmlogs > 0);
+ }
+
+ # Compress log file
+ if ($cfg::comp_log ne 'false') {
+ if ($cfg::comp_log eq "gzip") {
+ $logext = ".gz";
+ $comp_cmd = "$::path{gzip} -f \"$::log\"";
+ } elsif ($cfg::comp_log eq "bzip2") {
+ $logext = ".bz2";
+ $comp_cmd = "$::path{bzip2} -f \"$::log\"";
+ } elsif ($cfg::comp_log eq "lzop") {
+ $logext = ".lzo";
+ $comp_cmd = "$::path{lzop} -U -f \"$::log\"";
+ } elsif ($cfg::comp_log eq "zip") {
+ $logext = ".zip";
+ $comp_cmd = "$::path{cat} \"$::log\" | $::path{zip} -q - - > \"$::log" . $logext . "\"; $::path{rm} -f \"$::log\"";
+ } elsif ($cfg::comp_log eq "compress") {
+ $logext = ".Z";
+ $comp_cmd = "$::path{compress} -f \"$::log\"";
+ }
+ undef $::log;
+ &log("| Compressing log ($logfile" . "$logext)", 'screen');
+ system("$comp_cmd");
+ if ($CHILD_ERROR) {
+ warn("Error compressing log file\n");
+ }
+ }
+
+ # Symlink the "latest" log file for this level
+ unlink("$cfg::logdir/$symlink" . $logext);
+ &log("| Linking $symlink" . "$logext -> $logfile" . $logext, 'screen');
+ symlink("$logfile" . $logext,"$cfg::logdir/$symlink" . $logext);
+
+ &line('screen');
+
+ if ($error == 0) {
+ &toc_routine($tape_key);
+ }
+
+ exit($error);
+
+}
+
+######################################################################
+# Backup a filesystem
+######################################################################
+sub backup {
+
+ my $dir = shift(@_);
+ my $tape_key = shift(@_);
+ my $level = shift(@_);
+ my $title;
+ my $title_without_type;
+ my @cmds;
+ my @echo_cmds;
+ my $cmd;
+ my $localdir = $dir;
+ my $label = &get_label($dir);
+ my $host;
+ my @files;
+ my %oldstamps;
+ my $remote;
+ my $tapehost;
+ my $indexkey;
+ my $catchexit;
+ my $exitscript = "$cfg::tmpdir/collectexit.$PROCESS_ID.sh";
+ my $result = "$cfg::tmpdir/exitstatus.$PROCESS_ID";
+ my $pkglist;
+ my $error = 0;
+
+ &line();
+
+
+ if ($localdir =~ s/^(.+)://) {
+ $remote = $1;
+ chomp($tapehost = `hostname`);
+ if (($tapehost eq $remote)
+ or
+ ($remote =~ /^localhost/)) {
+ die("Remote host and this host are the same! No scooby snack for you!");
+ }
+
+ } else {
+ undef $remote;
+ }
+
+ # Remember old stamp files (will remove at end of job)
+ # "old" = any higher-numbered stamps for this label
+ # (we will be touching the one of equal level, so don't mark for removal)
+ opendir(DIR,"$cfg::stampdir") or die("Can't open $cfg::stampdir: $OS_ERROR");
+ @files = readdir(DIR);
+ foreach my $f (reverse sort @files) {
+ next if ($f !~ m/^$cfg::sprefix$label\.(\d+)$/);
+ if ($1 > $level) {
+ $oldstamps{"$cfg::stampdir/$f"} = $1
+ }
+ }
+ close(DIR);
+
+ # Create file name if writing to a file
+ # (config file's $device points to a dir in this case)
+ if (defined($::use_file)) {
+
+ my $filename = $level;
+
+ if (defined($::pkgdelta)) {
+ $filename .= $::pkgdelta;
+ }
+
+ if ($cfg::staticfiles eq 'true') {
+ $filename .= "." . $cfg::type;
+ } else {
+ $filename .= "." . $::date . "." . $cfg::type;
+ }
+
+ # Some types need the filename modified
+ if ($cfg::type eq 'ar') {
+ $filename =~ s/ar$/a/;
+ } elsif ($cfg::type eq 'copy') {
+ $filename =~ s/\.copy$//;
+ } elsif ($cfg::type eq 'rsync') {
+ $filename =~ s/\.rsync$//;
+ }
+
+ # Note compression setting in filename
+ if ($cfg::type =~ m/^(tar|dump|cpio|star|pax|ar|shar|filelist)$/) {
+ if ($cfg::compress eq "gzip") {
+ $filename .= ".gz";
+ } elsif ($cfg::compress eq "bzip2") {
+ $filename .= ".bz2";
+ } elsif ($cfg::compress eq "lzop") {
+ $filename .= ".lzo";
+ } elsif ($cfg::compress eq "zip") {
+ $filename .= ".zip";
+ } elsif ($cfg::compress eq "compress") {
+ $filename .= ".Z";
+ }
+ } elsif ($cfg::type eq "afio") {
+ # tag these a little different, the archive file itself isn't a
+ # .gz or .bz2, but the files in it are....
+ if ($cfg::compress eq "gzip") {
+ $filename .= "-gz";
+ } elsif ($cfg::compress eq "bzip2") {
+ $filename .= "-bz2";
+ } elsif ($cfg::compress eq "lzop") {
+ $filename .= "-lzo";
+ } elsif ($cfg::compress eq "zip") {
+ $filename .= "-zip";
+ } elsif ($cfg::compress eq "compress") {
+ $filename .= "-Z";
+ }
+ }
+
+ # Overwrite device var to be the archive filename
+ $::device = $cfg::device . "/" . $label . "." . $filename;
+
+ }
+
+ # Just get the date for now; don't write the timestamp
+ # Until after the backup has run
+ $::date_at_start = &current_time('ctime');
+ $::stamp_at_start = &current_time('numeric');
+
+ # Label for this archive
+ chomp($host = `hostname`);
+ $title = $cfg::type . "+" . $cfg::compress;
+ $title =~ s/\+false//;
+ if (!defined($::pkgdelta)) {
+ $title = "level $level $dir $::date_at_start $title from $host";
+ $title_without_type = "level $level $dir $::date_at_start from $host";
+ } else {
+ $pkglist = "flexbackup.$::pkgdelta.packagelist";
+ $title = "level $level+$::pkgdelta $dir $::date_at_start $title from $host";
+ $title_without_type = "level $level+$::pkgdelta $dir $::date_at_start from $host";
+ }
+
+ # Modify table of contents
+ if (($tape_key ne '')
+ and
+ ($cfg::indexes eq "true")) {
+ # If writing to files, store the filename
+ if (defined($::use_file)) {
+ @_ = split(/\//,$::device);
+ $_ = pop(@_);
+ $indexkey = "$tape_key|$_";
+ if (defined($::debug)) {
+ &log("(debug) \$::index{$indexkey} = $title_without_type");
+ } else {
+ $::index{$indexkey} = "$title_without_type";
+ }
+ } elsif (defined($::use_blockdevice)) {
+ # no indexes anyway
+ } else {
+ $indexkey = "$tape_key|$::nextfile";
+ if (defined($::debug)) {
+ &log("(debug) \$::index{$indexkey} = $title");
+ } else {
+ $::index{$indexkey} = $title;
+ }
+ &log("| File number $::nextfile, tape index $tape_key");
+ }
+ }
+
+ # Write list of packages
+ if (defined($::pkgdelta) and
+ (
+ ($cfg::pkgdelta_archive_list eq 'true') or
+ (($cfg::pkgdelta_archive_list eq 'rootonly') and ($localdir eq '/'))
+ )
+ ) {
+ $pkglist = "$localdir/$pkglist";
+ my $write = "> $pkglist";
+ my $h;
+
+ if(defined($remote)) {
+ $write = &maybe_remote_cmd("$::path{cat} $write", $remote);
+ $write = "| $write";
+ $h = $remote;
+ } else {
+ $h = 'localhost';
+ }
+ if (!defined($::debug)) {
+ open(LIST,"$write") || die;
+ foreach my $pkg (sort keys %{$::package_list{$h}}) {
+ print LIST "$pkg\n";
+ }
+ close(LIST);
+ }
+ }
+
+ &log("| Backup of: $dir");
+ my $remove = '';
+ if ($cfg::type eq 'dump') {
+ ($remove, @cmds) = &backup_dump($label, $localdir, $level, $remote);
+ } elsif ($cfg::type eq 'afio') {
+ ($remove, @cmds) = &backup_afio($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'cpio') {
+ ($remove, @cmds) = &backup_cpio($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'tar') {
+ ($remove, @cmds) = &backup_tar($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'star') {
+ ($remove, @cmds) = &backup_star($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'pax') {
+ ($remove, @cmds) = &backup_pax($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'zip') {
+ ($remove, @cmds) = &backup_zip($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'ar') {
+ ($remove, @cmds) = &backup_ar($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'shar') {
+ ($remove, @cmds) = &backup_shar($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'lha') {
+ ($remove, @cmds) = &backup_lha($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'copy') {
+ ($remove, @cmds) = &backup_copy_cpio($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'rsync') {
+ ($remove, @cmds) = &backup_copy_rsync($label, $localdir, $title, $level, $remote);
+ } elsif ($cfg::type eq 'filelist') {
+ ($remove, @cmds) = &backup_filelist($label, $localdir, $title, $level, $remote);
+ }
+
+ # Nuke any tmp files used in the above routines
+ if ($remove ne '') {
+ push(@cmds, &maybe_remote_cmd("$::path{rm} -f $remove", $remote));
+ }
+
+ # Create/nuke tmp file list if we did local package delta
+ if (defined($::pkgdelta)) {
+ if (
+ ($cfg::pkgdelta_archive_list eq 'true') or
+ (($cfg::pkgdelta_archive_list eq 'rootonly') and ($localdir eq '/'))
+ ) {
+ push(@cmds, &maybe_remote_cmd("$::path{rm} -f $::pkgdelta_filelist $pkglist", $remote));
+ } else {
+ push(@cmds, &maybe_remote_cmd("$::path{rm} -f $pkglist", $remote));
+ }
+ }
+
+ # Strip multiple spaces
+ foreach my $cmd (@cmds) {
+ $cmd =~ s/\s+/ /g;
+ }
+
+ # Use pipeline exitcode hook if /bin/sh can't report pipeline status
+ if ($::shelltype{'localhost'} =~ m/^(unknown|bash1|ksh)$/) {
+
+ $catchexit = 1;
+
+ unlink($result);
+ open(SCR, "> $exitscript") || die;
+ print SCR '#!/bin/sh' . "\n";
+ print SCR '"$@"' . "\n";;
+ print SCR '[ $? = 0 ] || echo $@ >> ' . $result . "\n";
+ close(SCR);
+ chmod(0755, $exitscript);
+
+ push(@cmds, "[ ! -e $result ]");
+ }
+
+ # Replace piped commands with exit status collector if we need to
+ foreach my $cmd (@cmds) {
+
+ if (defined($catchexit)) {
+
+ # Save ssh commands temporarily so we don't replace pipes inside them
+ my $saveremote;
+ if ($cmd =~ s/($cfg::remoteshell .* \'.*\')/XXXflexbackupXXX/) {
+ $saveremote = $1;
+ }
+
+ # Replace piped or anded commands with catch-script
+ # -Not if the command started a subshell ( .. )
+ if ($cmd =~ s:\s+(\||&&)\s+([^\(]): $1 $exitscript $2:g) {
+
+ # You would think we'd put it on the front of the pipe as
+ # well. Can't do this globally because the "cd <dir> &&"
+ # at the front makes the cd happen in a subshell. If
+ # its not "cd <something>, do it.
+ if ($cmd !~ m:^\s*cd\s+\"[^\"]+\"\s*(;|&&):) {
+ $cmd = "$exitscript $cmd";
+ }
+
+ # Take care of subshell
+ $cmd =~ s:\s+(\||&&)\s+(\()\s*: $1 \( $exitscript :g;
+
+ }
+
+ # Put any ssh stuff back
+ $cmd =~ s:XXXflexbackupXXX:$saveremote:;
+ }
+ }
+
+ # Format commands for nice printing
+ @echo_cmds = @cmds;
+ foreach my $line (@echo_cmds) {
+ &split_and_echo($line);
+ }
+ &line();
+
+ # Enough fooling around... run it.
+ if (!defined($::debug)) {
+ foreach $cmd (@cmds) {
+
+ if (defined($::use_pipe)) {
+ system("$cmd");
+ } else {
+ if ($::shelltype{'localhost'} eq 'bash2') {
+ # /bin/sh is really bash2 on this system
+ open(CMD,"($cmd " . $::bash_pipe_exit . ") 2>&1 |") || die;
+ } elsif ($::shelltype{'localhost'} eq 'zsh') {
+ # Does anybody make /bin/sh be zsh? probably not...
+ open(CMD,"($cmd " . $::zsh_pipe_exit . ") 2>&1 |") || die;
+ } else {
+ open(CMD,"($cmd) 2>&1 |") || die;
+ }
+ open(LOG,">>$::log") || die;
+ while(<CMD>) {
+ print $::msg $_;
+ print LOG $_;
+ }
+ close(LOG);
+ close(CMD);
+ }
+
+ if ($CHILD_ERROR) {
+ &log('');
+
+ # If using exit trick, cat the result file; otherwise use normal output
+ if (defined($catchexit)) {
+ my $out = `cat $result`;
+ &log("ERROR: non-zero exit from:\n$out");
+ } else {
+ &log("ERROR: non-zero exit from:\n$cmd");
+ }
+
+ if (defined($::opt{'ignore-errors'})) {
+
+ $error = 0;
+ &log('');
+ &log("ERROR: will continue anyway");
+
+ } else {
+
+ $error++;
+ &log('');
+ &log("ERROR: exiting");
+
+ # Put ERROR in the index if tapedevice, or nuke index if file
+ if (defined($indexkey)) {
+ if (defined($::use_file)) {
+ delete $::index{$indexkey};
+ } elsif (defined($::use_blockdevice)) {
+ # no indexes anyway
+ } else {
+ $::index{$indexkey} .= "\n\t---> ERROR during write, above may not be valid";
+ }
+ }
+
+ # If file, rm botched file regardless of index
+ if (defined($::use_file)) {
+ if ($cfg::type =~ m/^(copy|rsync)$/) {
+ system("rm -rf $::device");
+ } else {
+ unlink($::device);
+ }
+ }
+
+ } # ignore error defined
+
+ } # CHILD_ERROR
+
+ } # foreach cmd
+
+ } else {
+ &log("(debug) command output would be here");
+ }
+ &line();
+
+ # Actually remove the old stamp files now that we are done
+ if ($error == 0) {
+ foreach my $ts (sort keys %oldstamps) {
+ print $::msg "| Removing out of date level $oldstamps{$ts} timestamp for $dir\n";
+ if (!defined($::debug)) {
+ unlink("$ts") or warn("Can't remove $ts: $OS_ERROR\n");
+ }
+ }
+ }
+
+ # Create timestamp file, but use date from before the backup started
+ # so next time we will catch files that might have been touched during the run
+ my $t = &current_time('ctime');
+ &log("| Backup start: $::date_at_start");
+ &log("| Backup end: $t");
+ if (($error == 0) and !defined($::debug)) {
+ system("$::path{touch} -t \"$::stamp_at_start\" \"$cfg::stampdir/$cfg::sprefix$label.$level\"");
+ }
+
+ &line();
+
+ # Got errors unless I paused before trying to access the tape right way...
+ if ((!defined($::debug)) and defined($::tapedevice)) {
+ sleep 10;
+ }
+
+ # Show where we are on the tape
+ &mt('generic-query');
+
+ if (defined($catchexit)) {
+ unlink($result);
+ unlink($exitscript);
+ }
+
+ return($error);
+}
+
+######################################################################
+# Return command to backup a directory using dump
+######################################################################
+sub backup_dump {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $date_flag;
+ my $remove = '';
+
+ # Need this check here in case fs=all, level=incremental, and we go beyond 9
+ if ($level > 9) {
+ die("Can't use level > 9 and type=dump");
+ }
+
+ # Warnings about stuff dump can't do
+ if (defined($cfg::exclude_expr[0])) {
+ &log("| NOTE: \$exclude_expr is ignored for type=dump");
+ }
+
+ my $prunekey;
+ if (defined($remote)) {
+ $prunekey = "$remote:$dir";
+ } else {
+ $prunekey = $dir;
+ }
+ if (defined(%{$::prune{$prunekey}})) {
+ &log("| NOTE: \$prune is ignored for type=dump");
+ }
+
+ if ($cfg::traverse_fs ne 'false') {
+ &log("| NOTE: \$traverse_fs is always false for type=dump");
+ }
+
+ if (defined($::pkgdelta)) {
+ &log("| NOTE: packaging system delta ignored for for type=dump");
+ }
+
+ # With this one we don't have to put a stampfile on the remote system
+ # since we only need the date string
+ my $time = &get_last_date($label, $level, 'ctime');
+ if ($level == 0) {
+ $date_flag = "";
+ } else {
+ $date_flag = "-T \"$time\" ";
+ }
+
+ $cmd = '';
+ $cmd .= "dump -$level ";
+ $cmd .= "$::dump_blk_flag ";
+ if ($cfg::dump_use_dumpdates eq "true") {
+ $cmd .= "-u ";
+ } else {
+ $cmd .= $date_flag;
+ }
+ $cmd .= "$::dump_len_flag ";
+ $cmd .= "-f - ";
+ $cmd .= "$dir $::z";
+
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ return($remove, @cmds);
+
+
+}
+
+######################################################################
+# Return command to backup a directory using afio
+######################################################################
+sub backup_afio {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $tmplabel = "$cfg::tmpdir/label.$PROCESS_ID";
+ my $tmpnocompress = "$cfg::tmpdir/nocompress.$PROCESS_ID";
+ my $remove = '';
+ my $no_compress = '';
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ # list of file exenstions to not compress
+ if (($cfg::compress !~ /^(false|hardware)$/) and ($cfg::afio_nocompress_types ne "")) {
+ $cmd = "$::path{printf} \"$cfg::afio_nocompress_types\" > $tmpnocompress";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $no_compress = "-E $tmpnocompress";
+ $remove .= " $tmpnocompress";
+ }
+
+ if ($cfg::label ne 'false') {
+ $cmd = "$::path{printf} \"Volume Label:\\n$title\\n\\n\" > $tmplabel";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $tmplabel";
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ if ($cfg::label ne 'false') {
+ $cmd .= "($::path{printf} \"//--$tmplabel flexbackup.volume_header_info\\n\" && ";
+ }
+ $cmd .= &file_list_cmd($dir, $stamp, 'newline', $level, $remote);
+ if ($cfg::label ne 'false') {
+ $cmd .= ")";
+ }
+ $cmd .= " | ";
+
+ $cmd .= "$::path{afio} -o ";
+ $cmd .= "$no_compress ";
+ $cmd .= "-z ";
+ $cmd .= "-1 m ";
+ $cmd .= "$::afio_z_flag ";
+ $cmd .= "$::afio_verb_flag ";
+ $cmd .= "$::afio_sparse_flag ";
+ $cmd .= "$::afio_atime_flag ";
+ $cmd .= "$::afio_bnum_flag ";
+ $cmd .= "$::afio_blk_flag ";
+ $cmd .= "-";
+
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ return($remove, @cmds);
+
+}
+
+######################################################################
+# Return command to backup a directory using cpio
+######################################################################
+sub backup_cpio {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $remove = '';
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ if ($cfg::label ne 'false') {
+ # Kludge a title by replacing / with - in the title
+ # then touch a file in the dir we are going to back up.
+ $title =~ s%/%-%g;
+ $cmd = "$::path{touch} \"$dir/$title\"";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " \"$dir/$title\"";
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd($dir, $stamp, 'null', $level, $remote);
+ $cmd .= "| ";
+
+ $cmd .= "$::path{cpio} -o ";
+ $cmd .= "-0 ";
+ $cmd .= "-H $cfg::cpio_format ";
+ $cmd .= "$::cpio_verb_flag ";
+ $cmd .= "$::cpio_blk_flag ";
+ $cmd .= "$::z";
+
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ return($remove, @cmds);
+
+}
+
+######################################################################
+# Return command to copy directory tree
+######################################################################
+sub backup_copy_cpio {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $remove = '';
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd($dir, $stamp, 'null', $level, $remote);
+ $cmd .= "| ";
+
+ $cmd .= "$::path{cpio} -o ";
+ $cmd .= "-0 ";
+ $cmd .= "-H $cfg::cpio_format ";
+ $cmd .= "$::cpio_verb_flag ";
+ $cmd .= "$::cpio_blk_flag ";
+
+ # Buffer both sides / compress if remote
+ if (defined($remote)) {
+ $cmd .= "$::z";
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Yell if destination exists
+ if (-d "$::device") {
+ &log("| Existing destination directory $::device found!");
+ &log("| It will be *deleted*, unless you hit CTRL-C");
+ &log("| and abort within 10 seconds...");
+ &line();
+ sleep(10);
+ system("rm -rf $::device");
+ }
+
+ # Expand cpio archive on other side of pipe
+ $cmd .= " | ";
+ if (defined($remote)) {
+ $cmd .= "$::unz";
+ }
+ $cmd .= "(";
+ $cmd .= "mkdir -p $::device ; ";
+ $cmd .= "cd $::device ; ";
+ $cmd .= "$::path{cpio} -i ";
+ $cmd .= "-m ";
+ $cmd .= "-d ";
+ $cmd .= "$::cpio_blk_flag";
+ $cmd .= ")";
+
+ push(@cmds, $cmd);
+
+ return($remove, @cmds);
+
+}
+
+######################################################################
+# Return command to copy directory tree via rsync
+######################################################################
+sub backup_copy_rsync {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $remove = '';
+
+ if ($cfg::buffer ne 'false') {
+ &log("| NOTE: \$buffer is ignored for type=rsync");
+ }
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd($dir, $stamp, 'newline', $level, $remote);
+
+ # Just the find may run on the remote - rsync call will always be local
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Have to take leading './' off to make rsync's include/exclude work right
+ $cmd .= " | $::path{sed} -e \"s/\\.\\///g\" | ";
+
+ $cmd .= "$::path{rsync} ";
+ $cmd .= "--include-from=- --exclude=* ";
+ $cmd .= "--archive ";
+ $cmd .= "$::rsync_verb_flag ";
+ $cmd .= "--delete --delete-excluded ";
+ if ($cfg::compress ne 'false') {
+ $cmd .= "--compress ";
+ }
+ if (defined($remote)) {
+ $cmd .= "--rsh=$::path{$cfg::remoteshell} ";
+ if ($cfg::remoteuser ne '') {
+ $cmd .= "$cfg::remoteuser" . '@' . "$remote:";
+ } else {
+ $cmd .= "$remote:";
+ }
+ }
+ $cmd .= "$dir/ $::device";
+
+ push(@cmds, $cmd);
+
+ return($remove, @cmds);
+
+}
+
+######################################################################
+# Return command to backup a directory using tar
+######################################################################
+sub backup_tar {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $remove = '';
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd($dir, $stamp, 'null', $level, $remote);
+ $cmd .= "| ";
+
+ $cmd .= "$::path{tar} --create ";
+ $cmd .= "--null ";
+ $cmd .= "--files-from=- ";
+ $cmd .= "--ignore-failed-read ";
+ $cmd .= "--same-permissions ";
+ $cmd .= "--no-recursion ";
+ $cmd .= "--totals ";
+ if ($cfg::label ne 'false') {
+ if (length($title) > $::tar_max_label) {
+ &log("| NOTE: truncating tar label (> $::tar_max_label chars)");
+ $title = substr($title, 0, $::tar_max_label);
+ }
+ $cmd .= "--label \"$title\" ";
+ }
+ $cmd .= "$::tar_verb_flag ";
+ $cmd .= "$::tar_sparse_flag ";
+ $cmd .= "$::tar_atime_flag ";
+ $cmd .= "$::tar_recnum_flag ";
+ $cmd .= "$::tar_blk_flag ";
+ $cmd .= "--file - ";
+ $cmd .= "$::z";
+
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ return($remove, @cmds);
+
+}
+
+######################################################################
+# Return command to backup a directory using star
+######################################################################
+sub backup_star {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $remove = '';
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd($dir, $stamp, 'newline', $level, $remote);
+ $cmd .= "| ";
+
+ $cmd .= "$::path{star} -c ";
+ $cmd .= "list=- ";
+ $cmd .= "-p ";
+ $cmd .= "-l ";
+ $cmd .= "-D ";
+ $cmd .= "-B ";
+ $cmd .= "-dirmode ";
+ if ($cfg::label ne 'false') {
+ $cmd .= "VOLHDR=\"$title\" ";
+ }
+ $cmd .= "H=$cfg::star_format ";
+ $cmd .= "$::star_fifo_flag ";
+ $cmd .= "$::star_acl_flag ";
+ $cmd .= "$::star_verb_flag ";
+ $cmd .= "$::star_sparse_flag ";
+ $cmd .= "$::star_atime_flag ";
+ $cmd .= "$::star_blocknum_flag ";
+ $cmd .= "$::star_blk_flag ";
+ $cmd .= "file=- ";
+ $cmd .= "$::z";
+
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ return($remove, @cmds);
+
+}
+
+######################################################################
+# Return command to backup a directory using pax
+######################################################################
+sub backup_pax {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $remove = '';
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ if ($cfg::label ne 'false') {
+ # Kludge a title by replacing / with - in the title
+ # then touch a file in the dir we are going to back up.
+ $title =~ s%/%-%g;
+ $cmd = "$::path{touch} \"$dir/$title\"";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " \"$dir/$title\"";
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd( $dir, $stamp, 'newline', $level, $remote);
+ $cmd .= "| ";
+
+ $cmd .= "$::path{pax} -w ";
+ $cmd .= "-d ";
+ $cmd .= "-s %^./%% ";
+ $cmd .= "-x $cfg::pax_format ";
+ $cmd .= "$::pax_verb_flag ";
+ $cmd .= "$::pax_blk_flag ";
+ $cmd .= "$::z";
+
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ return($remove, @cmds);
+
+}
+
+######################################################################
+# Return command to backup a directory using zip
+######################################################################
+sub backup_zip {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $tmpzip = "$cfg::tmpdir/archive.$PROCESS_ID.zip";
+ my $remove = '';
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ if ($cfg::label ne 'false') {
+ # Kludge a title by replacing / with - in the title
+ # then touch a file in the dir we are going to back up.
+ $title =~ s%/%-%g;
+ $cmd = "$::path{touch} \"$dir/$title\"";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " \"$dir/$title\"";
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd($dir, $stamp, 'newline', $level, $remote);
+ $cmd .= "| ";
+
+ $cmd .= "$::path{zip} -@ ";
+ $cmd .= "-b $cfg::tmpdir "; # temp file path
+ $cmd .= "-y "; # store symlinks
+ $cmd .= "$::zip_compr_flag ";
+ $cmd .= "$::zip_noz_flag "; # nocompress list
+ $cmd .= "$::zip_verb_flag "; # verbose flag
+ $cmd .= "$tmpzip";
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+ push(@cmds,$cmd);
+
+ $cmd = "$::path{cat} $tmpzip ";
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ $remove .= " $tmpzip";
+
+ return($remove, @cmds);
+
+}
+
+
+
+######################################################################
+# Return command to backup a directory using ar
+######################################################################
+sub backup_ar {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $filelist = "$cfg::tmpdir/arlist.$PROCESS_ID";
+ my $tmpfile = "$cfg::tmpdir/ar.$PROCESS_ID";
+ my $remove = '';
+
+ &log("| NOTE: ar archives will not descend directories");
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ if ($cfg::label ne 'false') {
+ # Kludge a title by replacing / with - in the title
+ # then touch a file in the dir we are going to back up.
+ $title =~ s%/%-%g;
+ $title =~ s% %_%g;
+ $cmd = "$::path{touch} \"$dir/$title\"";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " \"$dir/$title\"";
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd( $dir, $stamp, 'newline', $level, $remote, '-maxdepth 1 ! -type d');
+ $cmd .= "> $filelist; ";
+
+ $cmd .= "$::path{ar} rc";
+ $cmd .= "$::ar_verb_flag ";
+ $cmd .= "$tmpfile ";
+ $cmd .= "`$::path{cat} $filelist`";
+ $cmd .= "; $::path{cat} $tmpfile $::z";
+
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ $remove .= " $filelist $tmpfile";
+
+ return($remove, @cmds);
+
+}
+
+######################################################################
+# Return command to backup a directory using shar
+######################################################################
+sub backup_shar {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $remove = '';
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd( $dir, $stamp, 'newline', $level, $remote, '! -type d');
+ $cmd .= " | ";
+
+ $cmd .= "$::path{shar} ";
+ $cmd .= "$::shar_verb_flag ";
+ if ($cfg::label ne 'false') {
+ $cmd .= "-n \"$title\" ";
+ }
+ $cmd .= "-S ";
+ $cmd .= "$::z";
+
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ return($remove, @cmds);
+
+}
+
+
+######################################################################
+# Return command to backup a directory using lha
+######################################################################
+sub backup_lha {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $filelist = "$cfg::tmpdir/lhalist.$PROCESS_ID";
+ my $tmpfile = "$cfg::tmpdir/lha.$PROCESS_ID";
+ my $remove = '';
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ if ($cfg::label ne 'false') {
+ # Kludge a title by replacing / with - in the title
+ # then touch a file in the dir we are going to back up.
+ $title =~ s%/%-%g;
+ $title =~ s% %_%g;
+ $cmd = "echo \"$title\" > \"$dir/$title\"";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " \"$dir/$title\"";
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd( $dir, $stamp, 'newline', $level, $remote);
+ $cmd .= "> $filelist; ";
+
+ $cmd .= "$::path{lha} a";
+ $cmd .= "$::lha_verb_flag ";
+ $cmd .= "$tmpfile ";
+ $cmd .= "`$::path{cat} $filelist`";
+ $cmd .= "; $::path{cat} $tmpfile $::z";
+
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ $remove .= " $filelist $tmpfile";
+
+ return($remove, @cmds);
+
+}
+
+######################################################################
+# Just back up the file listing (useful for debugging)
+######################################################################
+sub backup_filelist {
+
+ my $label = shift(@_);
+ my $dir = shift(@_);
+ my $title = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $cmd = '';
+ my @cmds;
+ my $stamp = "$cfg::tmpdir/refdate.$PROCESS_ID";
+ my $filelist = "$cfg::tmpdir/filelist.$PROCESS_ID";
+ my $remove = '';
+
+ if (defined($remote) and ($level != 0)) {
+ my $time = &get_last_date($label, $level, 'numeric');
+ $cmd = "$::path{touch} -t \"$time\" $stamp";
+ push(@cmds, &maybe_remote_cmd($cmd, $remote));
+ $remove .= " $stamp";
+ } else {
+ $stamp = &get_last_date($label, $level, 'filename');
+ }
+
+ if (defined $::use_pipe) {
+ &log("| NOTE: Writing list of files that would have been backed up to stdout");
+ } else {
+ &log("| NOTE: Writing list of files that would have been backed up to current directory");
+ }
+
+ $cmd = "cd \"$dir\" && ";
+ $cmd .= &file_list_cmd( $dir, $stamp, 'newline', $level, $remote);
+ $cmd .= "> $filelist; $::path{cat} $filelist 1>&2; $::path{cat} $filelist ";
+ $cmd .= "$::z";
+
+ # Buffer both sides if remote
+ if (defined($remote)) {
+ $cmd .= $::buffer_cmd;
+ }
+
+ # Wrap all that together
+ $cmd = &maybe_remote_cmd($cmd, $remote);
+
+ # Append writer stuff
+ $cmd = &append_writer_cmd($cmd);
+
+ push(@cmds, $cmd);
+
+ $remove .= " $filelist";
+
+ return($remove, @cmds);
+
+}
+
+######################################################################
+# List the files in an archive
+######################################################################
+sub list_routine {
+
+ my $cmd = &setup_before_read('list');
+
+ if ($cfg::type eq 'dump') {
+ $cmd .= "$::path{restore} -t ";
+ $cmd .= "$::dump_verb_flag ";
+ $cmd .= "$::dump_blk_flag ";
+ $cmd .= "-f -";
+
+ } elsif ($cfg::type eq 'afio') {
+ $cmd .= "$::path{afio} -t ";
+ $cmd .= "-z ";
+ # Don't use label reader if reading from pipe (needs stdin)
+ if (!defined($::use_pipe)) {
+ $cmd .= "-D $0 ";
+ }
+ $cmd .= "$::afio_unz_flag ";
+ $cmd .= "$::afio_verb_flag ";
+ $cmd .= "$::afio_sparse_flag ";
+ $cmd .= "$::afio_bnum_flag ";
+ $cmd .= "$::afio_blk_flag ";
+ $cmd .= "-";
+
+ } elsif ($cfg::type eq 'cpio') {
+ $cmd .= "$::path{cpio} -t ";
+ $cmd .= "$::cpio_verb_flag ";
+ $cmd .= "$::cpio_blk_flag";
+
+ } elsif ($cfg::type eq 'tar') {
+ $cmd .= "$::path{tar} --list ";
+ $cmd .= "--totals ";
+ $cmd .= "$::tar_verb_flag ";
+ $cmd .= "$::tar_sparse_flag ";
+ $cmd .= "$::tar_recnum_flag ";
+ $cmd .= "$::tar_blk_flag ";
+ $cmd .= "-B ";
+ $cmd .= "--file -";
+
+ } elsif ($cfg::type eq 'star') {
+ $cmd .= "$::path{star} -t ";
+ $cmd .= "$::star_fifo_flag ";
+ $cmd .= "$::star_verb_flag ";
+ $cmd .= "$::star_sparse_flag ";
+ $cmd .= "$::star_blocknum_flag ";
+ $cmd .= "$::star_blk_flag ";
+ $cmd .= "-B ";
+ $cmd .= "file=-";
+
+ } elsif ($cfg::type eq 'pax') {
+ $cmd .= "$::path{pax} ";
+ $cmd .= "$::pax_verb_flag ";
+
+ } elsif ($cfg::type eq 'zip') {
+ my $tmpfile = "$cfg::tmpdir/zip.$PROCESS_ID";
+ $cmd .= "$::path{cat} > $tmpfile ; ";
+ $cmd .= "$::path{unzip} -l ";
+ $cmd .= "$::zip_verb_flag ";
+ $cmd .= "$tmpfile ; ";
+ $cmd .= "$::path{rm} -f $tmpfile";
+
+ } elsif ($cfg::type eq 'ar') {
+ my $tmpfile = "$cfg::tmpdir/ar.$PROCESS_ID";
+ $cmd .= "$::path{cat} > $tmpfile; ";
+ $cmd .= "$::path{ar} t";
+ $cmd .= "$::ar_verb_flag ";
+ $cmd .= "$tmpfile; ";
+ $cmd .= "$::path{rm} -f $tmpfile";
+
+ } elsif ($cfg::type eq 'shar') {
+
+ $cmd .= "perl -pe 'last if (! m/^#/)'";
+
+ } elsif ($cfg::type =~ m/^(copy|rsync)$/) {
+
+ if ($cfg::verbose eq "true") {
+ $cmd = "ls -laR $::device";
+ } else {
+ $cmd = "ls -aR $::device";
+ }
+
+ } elsif ($cfg::type eq 'lha') {
+ my $tmpfile = "$cfg::tmpdir/lha.$PROCESS_ID";
+ $cmd .= "$::path{cat} > $tmpfile ; ";
+ $cmd .= "$::path{lha} l";
+ $cmd .= "$::lha_verb_flag ";
+ $cmd .= "$tmpfile ; ";
+ $cmd .= "$::path{rm} -f $tmpfile";
+
+ } elsif ($cfg::type eq 'filelist') {
+
+ $cmd .= "$::path{cat}";
+
+ }
+
+ &run_or_echo_then_query($cmd);
+
+}
+
+######################################################################
+# Extract files (maybe a list) to current directory
+######################################################################
+sub extract_routine {
+
+ my $restore_files = '';
+ my $newlist = "$cfg::tmpdir/extract.$PROCESS_ID";
+
+ my $cmd = &setup_before_read('extract');
+
+ if (defined($::opt{'flist'})) {
+ # Have to get a list of the files for restore to use
+ open(LIST,"$::opt{flist}") or die ("Can't open $::opt{flist}: $OS_ERROR");
+ open(NEWLIST,">$newlist") or die ("Can't open $newlist: $OS_ERROR");
+ while(<LIST>) {
+ chomp;
+ $_ =~ s%^/%%;
+ $_ =~ s%^\./%%;
+
+ # Some types need the leading ./ to extract the file list,
+ # since its stored that way
+ if ($cfg::type =~ m/^(tar|lha)$/) {
+ $_ = './' . $_;
+ }
+ print NEWLIST "$_\n";
+ $restore_files .= " $_";
+ }
+ close(LIST);
+ close(NEWLIST);
+ &log("| Extracting files listed in $::opt{flist}");
+ }
+
+ if (defined($::opt{'onefile'})) {
+ open(NEWLIST,">$newlist") or die ("Can't open $newlist: $OS_ERROR");
+ $_ = $::opt{'onefile'};
+ $_ =~ s%^/%%;
+ $_ =~ s%^\./%%;
+ # Some types need the leading ./ to extract the file list,
+ # since its stored that way
+ if ($cfg::type =~ m/^(tar|lha)$/) {
+ $_ = './' . $_;
+ }
+ print NEWLIST "$_\n";
+ $restore_files .= " $_";
+ close(NEWLIST);
+ &log("| Extracting single file" . $restore_files);
+ }
+
+ if ($cfg::type eq 'dump') {
+ $cmd .= "$::path{restore} -x ";
+ $cmd .= "$::dump_verb_flag ";
+ $cmd .= "$::dump_blk_flag ";
+ $cmd .= "-f -";
+ $cmd .= $restore_files;
+
+ } elsif ($cfg::type eq 'afio') {
+ $cmd .= "$::path{afio} -i ";
+ if ($restore_files ne '') {
+ $cmd .= "-w $newlist ";
+ }
+ $cmd .= "-z ";
+ $cmd .= "-x ";
+ # Don't use label reader if reading from pipe (needs stdin)
+ if (!defined($::use_pipe)) {
+ $cmd .= "-D $0 ";
+ }
+ $cmd .= "$::afio_unz_flag ";
+ $cmd .= "$::afio_verb_flag ";
+ $cmd .= "$::afio_sparse_flag ";
+ $cmd .= "$::afio_bnum_flag ";
+ $cmd .= "$::afio_blk_flag ";
+ $cmd .= "-";
+
+ } elsif ($cfg::type eq 'cpio') {
+ $cmd .= "$::path{cpio} -i ";
+ if ($restore_files ne '') {
+ $cmd .= "-E $newlist ";
+ }
+ $cmd .= "-m ";
+ $cmd .= "-d ";
+ $cmd .= "$::cpio_verb_flag ";
+ $cmd .= "$::cpio_blk_flag";
+
+ } elsif ($cfg::type eq 'tar') {
+ $cmd .= "$::path{tar} --extract ";
+ if ($restore_files ne '') {
+ $cmd .= "--files-from $newlist ";
+ }
+ $cmd .= "--totals ";
+ $cmd .= "--same-permissions ";
+ $cmd .= "$::tar_verb_flag ";
+ $cmd .= "$::tar_sparse_flag ";
+ $cmd .= "$::tar_recnum_flag ";
+ $cmd .= "$::tar_blk_flag ";
+ $cmd .= "-B ";
+ $cmd .= "--file -";
+
+ } elsif ($cfg::type eq 'star') {
+ $cmd .= "$::path{star} -x ";
+ if ($restore_files ne '') {
+ $cmd .= "list=$newlist ";
+ }
+ $cmd .= "-p ";
+ $cmd .= "$::star_fifo_flag ";
+ $cmd .= "$::star_verb_flag ";
+ $cmd .= "$::star_sparse_flag ";
+ $cmd .= "$::star_blocknum_flag ";
+ $cmd .= "$::star_blk_flag ";
+ $cmd .= "-B ";
+ $cmd .= "file=-";
+
+ } elsif ($cfg::type eq 'pax') {
+ $cmd .= "$::path{pax} -r ";
+ $cmd .= "$::pax_verb_flag ";
+ $cmd .= $restore_files;
+
+ } elsif ($cfg::type eq 'zip') {
+ my $tmpfile = "$cfg::tmpdir/zip.$PROCESS_ID";
+ $cmd .= "$::path{cat} > $tmpfile ; ";
+ $cmd .= "$::path{unzip} ";
+ $cmd .= "$tmpfile ";
+ $cmd .= $restore_files;
+ $cmd .= "; ";
+ $cmd .= "$::path{rm} -f $tmpfile";
+
+ } elsif ($cfg::type eq 'ar') {
+ my $tmpfile = "$cfg::tmpdir/ar.$PROCESS_ID";
+ $cmd .= "$::path{cat} > $tmpfile; ";
+ $cmd .= "$::path{ar} xo";
+ $cmd .= "$::ar_verb_flag ";
+ $cmd .= "$tmpfile ";
+ $cmd .= $restore_files;
+ $cmd .= "; ";
+ $cmd .= "$::path{rm} -f $tmpfile";
+
+ } elsif ($cfg::type eq 'shar') {
+ $cmd .= "sh ";
+ if ($restore_files ne '') {
+ &log("| NOTE: \"-flist/-onefile\" ignored for shar");
+ }
+
+ } elsif ($cfg::type =~ m/^(copy|rsync)$/) {
+
+ die("Ummm... just copy your files, you have the whole tree...");
+
+ } elsif ($cfg::type eq 'filelist') {
+
+ die("You can't extract the 'filelist' type, it's just for testing...");
+
+ } elsif ($cfg::type eq 'lha') {
+ my $tmpfile = "$cfg::tmpdir/lha.$PROCESS_ID";
+ $cmd .= "$::path{cat} > $tmpfile ; ";
+ $cmd .= "$::path{lha} x";
+ $cmd .= "$::lha_verb_flag ";
+ $cmd .= "$tmpfile ";
+ $cmd .= $restore_files;
+ $cmd .= "; ";
+ $cmd .= "$::path{rm} -f $tmpfile";
+
+ }
+
+ &run_or_echo_then_query($cmd);
+
+ if (defined($::opt{'flist'})) {
+ unlink("$newlist") or die ("Can't remove $newlist: $OS_ERROR");
+ }
+}
+
+######################################################################
+# Compare an archive to current directory
+######################################################################
+sub compare_routine {
+
+ my $cmd = &setup_before_read('compare');
+
+ if ($cfg::type eq 'dump') {
+ $cmd .= "$::path{restore} -C ";
+ $cmd .= "$::dump_blk_flag ";
+ $cmd .= "-f -";
+
+ } elsif ($cfg::type eq 'afio') {
+ $cmd .= "$::path{afio} -r ";
+ $cmd .= "-z ";
+ # Don't use label reader if reading from pipe (needs stdin)
+ if (!defined($::use_pipe)) {
+ $cmd .= "-D $0 ";
+ }
+ $cmd .= "$::afio_unz_flag ";
+ $cmd .= "$::afio_sparse_flag ";
+ $cmd .= "$::afio_blk_flag ";
+ $cmd .= "-";
+
+ } elsif ($cfg::type eq 'tar') {
+ $cmd .= "$::path{tar} --diff ";
+ $cmd .= "--totals ";
+ $cmd .= "$::tar_blk_flag ";
+ $cmd .= "$::tar_sparse_flag ";
+ $cmd .= "$::tar_recnum_flag ";
+ $cmd .= "-B ";
+ $cmd .= "--file -";
+
+ } elsif ($cfg::type eq 'star') {
+ $cmd .= "$::path{star} -diff ";
+ $cmd .= "$::star_fifo_flag ";
+ $cmd .= "$::star_blk_flag ";
+ $cmd .= "$::star_sparse_flag ";
+ $cmd .= "$::star_blocknum_flag ";
+ $cmd .= "-B ";
+ $cmd .= "file=-";
+
+ } elsif ($cfg::type =~ m/^(copy|rsync)$/) {
+
+ $::path{'diff'} = &checkinpath('diff');
+
+ $cmd = "$::path{diff} -r -q ";
+ $cmd .= ". $::device";
+
+ } else {
+ die("$cfg::type not capable of comparing files");
+ }
+
+ &run_or_echo_then_query($cmd);
+
+}
+
+######################################################################
+# Interactive restore
+######################################################################
+sub restore_routine {
+
+ my $cmd = &setup_before_read('restore');
+
+ if ($cfg::type eq 'dump') {
+ $cmd .= "$::path{restore} -i ";
+ $cmd .= "$::dump_verb_flag ";
+ $cmd .= "$::dump_blk_flag ";
+ $cmd .= "-f -";
+
+ } else {
+ die("Interactive restore for $cfg::type not implemented");
+ }
+
+ &run_or_echo_then_query($cmd);
+
+}
+
+######################################################################
+# Return the "label" name of the filesystem/dir
+######################################################################
+sub get_label {
+
+ my $path = shift(@_);
+ my $host = '';
+ my $label;
+
+ if ($path =~ s/(\S+)://) {
+ $host = $1 . "-";
+ $label = $path;
+ } else {
+ $label = $path;
+ }
+
+ $label =~ s%^/%%; # nuke leading slash
+ $label =~ s%/%-%g; # turn / into -
+ $label = 'root' if ($label eq '');
+
+ return($host . $label);
+
+}
+
+######################################################################
+# Return a date string of the timestamp file
+# from the last dump of lower level
+# in YYYYMMDDhhmm.ss format if arg 'numeric'
+# in ctime format if if arg 'ctime'
+# timestamp reference file if arg 'filename'
+######################################################################
+sub get_last_date {
+
+ my $label = shift(@_);
+ my $thislevel = shift(@_);
+ my $format = shift(@_);
+ my $lastlevel;
+ my $targetfile = '';
+ my $numeric_val;
+ my $string_val;
+ my $mtime;
+
+
+ # use the epoch for level 0
+ if ($thislevel == 0) {
+ $numeric_val = '197001010000.00';
+ $string_val = "Thu Jan 01 00:00:00 1970";
+
+ } else {
+
+ # Find last stamp file
+ opendir(DIR,"$cfg::stampdir") or die("Can't open $cfg::stampdir: $OS_ERROR");
+ close(DIR);
+ my $tmp = $thislevel - 1;
+ foreach my $lev (reverse (0..$tmp)) {
+ my $file = "$cfg::stampdir/$cfg::sprefix" . "$label.$lev";
+ if (-e "$file") {
+ $lastlevel = $lev;
+ $targetfile = $file;
+ last;
+ }
+ }
+
+ # get date from targetfile
+ # or complain if no timestamp
+ if ($targetfile ne '') {
+ $mtime = (stat($targetfile))[9];
+ $string_val = strftime("%a %b %d %H:%M:%S %Y", localtime($mtime));
+ $numeric_val = strftime("%Y%m%d%H%M.%S", localtime($mtime));
+ } else {
+ die("Can't do a level $thislevel backup - no level 0 timestamp found");
+ }
+
+ }
+
+ &log("| Date of this level $thislevel backup: $::date_at_start");
+ if ($thislevel == 0) {
+ &log("| Date of last level $thislevel backup: the epoch");
+ } else {
+ &log("| Date of last level $lastlevel backup: $string_val");
+ }
+ &line();
+
+ if (!defined($format)) {
+ $format = 'ctime';
+ }
+
+ if ($format eq 'numeric') {
+ return($numeric_val);
+ } elsif ($format eq 'ctime') {
+ return($string_val);
+ } elsif ($format eq 'filename') {
+ return($targetfile);
+ } else {
+ return($string_val);
+ }
+}
+
+######################################################################
+# Echo message to screen and log
+# optionally just one or the other
+######################################################################
+sub log {
+
+ my $msg = shift(@_);
+ my $only = shift(@_);
+ my $do_screen = 1;
+ my $do_log = 1;
+
+ if (!defined($only)) {
+ $do_screen = 1;
+ $do_log = 1;
+ } elsif ($only eq 'screen') {
+ $do_screen = 1;
+ $do_log = 0;
+ } elsif ($only eq 'log') {
+ $do_screen = 0;
+ $do_log = 1;
+ }
+
+ if ($do_screen == 1) {
+ print $::msg "$msg\n";
+ }
+
+ if (($do_log == 1) and defined($::log)) {
+ open(LOG,">>$::log") || warn("can't open logfile");
+ print LOG "$msg\n";
+ close(LOG);
+ }
+
+}
+
+######################################################################
+# Echo a line to both screen and log
+# optionally just one or the other
+######################################################################
+sub line {
+
+ my $only = shift(@_);
+ my $do_screen = 1;
+ my $do_log = 1;
+
+ my $length = 60;
+
+ if (!defined($only)) {
+ $do_screen = 1;
+ $do_log = 1;
+ } elsif ($only eq 'screen') {
+ $do_screen = 1;
+ $do_log = 0;
+ } elsif ($only eq 'log') {
+ $do_screen = 0;
+ $do_log = 1;
+ }
+
+ if ($do_screen == 1) {
+ print $::msg '|';
+ print $::msg '-' x $length;
+ print $::msg "\n";
+ }
+
+ if (($do_log == 1) and defined($::log)) {
+ open(LOG,">>$::log") || warn("can't open logfile");
+ print LOG '|';
+ print LOG '-' x $length;
+ print LOG "\n";
+ close(LOG);
+ }
+
+}
+
+######################################################################
+# Read configuration file
+######################################################################
+sub readconfigfile {
+
+ my $configfile;
+ my $var;
+ my $value;
+ my $defines = $::opt{'d'};
+
+ if (defined($::opt{'c'})) {
+ $configfile = $::opt{'c'};
+ } else {
+ $configfile = $::CONFFILE;
+ }
+ if (! -r "$configfile") {
+ die("config file $configfile: $OS_ERROR");
+ }
+ system("perl -c \"$configfile\"");
+ if ($CHILD_ERROR) {
+ die("syntax error in config file $configfile");
+ }
+
+ package cfg;
+ require "$configfile";
+ package main;
+
+ # Overrides
+ foreach $var (keys %$defines) {
+ $value = $$defines{$var};
+ &log("(override) $var = $value");
+ eval("\$cfg::$var=\"$value\"");
+ }
+
+}
+
+######################################################################
+# Do a tape operation
+######################################################################
+sub mt {
+
+ my (@operations) = (@_);
+
+ # Set hardware compression when we do the blocksize
+ if ($cfg::compress eq "hardware") {
+ foreach my $operation (@operations) {
+ if ($operation =~ m/generic-blocksize/) {
+ if ($::uname =~ /Linux/) {
+ push(@operations,'compression 1');
+ } elsif ($::uname =~ /FreeBSD/) {
+ push(@operations,'comp on');
+ } else {
+ push(@operations,'compression 1');
+ }
+ }
+ }
+ }
+
+ # We want 1-filemark behavior always
+ # Set if currently doing blocksize command
+ foreach my $operation (@operations) {
+ if ($operation =~ m/generic-blocksize/) {
+ if ($::uname =~ /FreeBSD/) {
+ push(@operations,'seteotmodel 1');
+ }
+ }
+ }
+
+ foreach my $operation (@operations) {
+
+ # mt flavors for block number
+ if ($operation eq 'generic-query') {
+ if ($::uname =~ /Linux/) {
+ $operation = 'tell';
+ if ($::ftape == 1) {
+ $operation = 'getsize';
+ }
+ } elsif ($::uname =~ /OpenBSD/) {
+ $operation = 'status';
+ } elsif ($::uname =~ /FreeBSD/) {
+ $operation = 'rdhpos';
+ } elsif ($::uname =~ /OSF1/) {
+ $operation = 'status';
+ } elsif ($::uname =~ /AIX/) {
+ $operation = 'status';
+ } elsif ($::uname =~ /HP-UX/) {
+ $operation = 'status';
+ } elsif ($::uname =~ /SunOS/) {
+ $operation = 'status';
+ } elsif ($::uname =~ /IRIX/) {
+ $operation = 'status';
+ } else {
+ $operation = 'status';
+ }
+ }
+
+ # mt flavors for eod
+ if ($operation eq 'generic-eod') {
+ if ($::uname =~ /Linux/) {
+ $operation = 'eod';
+ if ($::ftape == 1) {
+ $operation = 'eom';
+ }
+ } elsif ($::uname =~ /OpenBSD/) {
+ $operation = 'eod';
+ } elsif ($::uname =~ /FreeBSD/) {
+ $operation = 'eod';
+ } elsif ($::uname =~ /OSF1/) {
+ $operation = 'seod';
+ } elsif ($::uname =~ /AIX/) {
+ $operation = 'fsf 1000';
+ } elsif ($::uname =~ /HP-UX/) {
+ $operation = 'eod';
+ } elsif ($::uname =~ /SunOS/) {
+ $operation = 'eom';
+ } elsif ($::uname =~ /IRIX/) {
+ $operation = 'eod';
+ } else {
+ $operation = 'eod';
+ }
+ }
+
+ # mt flavors for erase
+ # (some mt's have no "erase", just rewind before starting...)
+ if ($operation eq 'generic-erase') {
+
+ if ($cfg::erase_rewind_only eq "true") {
+ $operation = 'rewind';
+ } elsif ($::uname =~ /Linux/) {
+ $operation = 'erase';
+ } elsif ($::uname =~ /OpenBSD/) {
+ $operation = 'erase';
+ } elsif ($::uname =~ /FreeBSD/) {
+ $operation = 'erase';
+ } elsif ($::uname =~ /OSF1/) {
+ $operation = 'erase';
+ } elsif ($::uname =~ /AIX/) {
+ $operation = 'erase';
+ } elsif ($::uname =~ /HP-UX/) {
+ $operation = 'erase';
+ } elsif ($::uname =~ /SunOS/) {
+ $operation = 'erase';
+ } elsif ($::uname =~ /IRIX/) {
+ $operation = 'erase';
+ } else {
+ $operation = 'erase';
+ }
+ }
+
+ # mt flavors for setblk
+ if ($operation =~ /generic-blocksize/) {
+ if ($::uname =~ /Linux/) {
+ $operation =~ s/generic-blocksize/setblk/;
+ } elsif ($::uname =~ /OpenBSD/) {
+ $operation =~ s/generic-blocksize/blocksize/;
+ } elsif ($::uname =~ /FreeBSD/) {
+ $operation =~ s/generic-blocksize/blocksize/;
+ } elsif ($::uname =~ /OSF1/) {
+ $operation =~ s/generic-blocksize/setblk/;
+ } elsif ($::uname =~ /AIX/) {
+ $operation =~ s/generic-blocksize/setblk/;
+ } elsif ($::uname =~ /HP-UX/) {
+ $operation =~ s/generic-blocksize/setblk/;
+ } elsif ($::uname =~ /SunOS/) {
+ $operation =~ s/generic-blocksize/setblk/;
+ } elsif ($::uname =~ /IRIX/) {
+ $operation =~ s/generic-blocksize/setblksz/;
+ } else {
+ $operation =~ s/generic-blocksize/setblk/;
+ }
+ }
+
+ if (defined($::use_file)) {
+ # mt ops skipped for files
+ } elsif (defined($::use_blockdevice)) {
+ # mt ops skipped for block device
+ } else {
+
+ my $command;
+
+ # Override mt operation so user can set for unknown flavors
+ # or for debugging info, like mt tell -> mt status
+ if(defined($cfg::mt{$operation})) {
+ $operation = $cfg::mt{$operation};
+ next if ($operation eq 'nop');
+ }
+
+ if ($operation =~ /setblk/) {
+ # Try and see which of setblk/defblksize will work
+ # This is kludgy, but doable
+ $command = "$::path{mt} -f $::device $operation > /dev/null 2>&1";
+ if (defined($::remotetapehost)) {
+ $command = &maybe_remote_cmd($command, $::remotetapehost);
+ }
+ if (defined($::debug)) {
+ &log("(debug) $command");
+ }
+ system($command);
+ if ($CHILD_ERROR) {
+ &log("| Trying \"mt defblksize\" instead of \"mt setblk\"");
+ my $oldoperation = $operation;
+ $operation =~ s/setblk/defblksize/;
+ $command = "$::path{mt} -f $::device $operation > /dev/null 2>&1";
+ if (defined($::remotetapehost)) {
+ $command = &maybe_remote_cmd($command, $::remotetapehost);
+ }
+ if (defined($::debug)) {
+ &log("(debug) $command");
+ }
+ system($command);
+ if ($CHILD_ERROR) {
+ &log("Error setting block size");
+ &log("Neither of these commands worked:");
+ &log(" $::path{mt} -f $::device $oldoperation");
+ &log(" $::path{mt} -f $::device $operation");
+ exit(1);
+ } # error on second guess
+ } # error on first guess
+ } # operation = setblk
+
+ $command = "$::path{mt} -f $::device $operation 2>&1 ";
+
+ if (defined($::remotetapehost)) {
+ $command = &maybe_remote_cmd($command, $::remotetapehost);
+ }
+
+ if (!defined($::debug)) {
+
+ open(CMD,"($command) 2>&1 |") || die;
+ if (defined($::log)) { open(LOG,">>$::log") || die; }
+ while(<CMD>) {
+ print $_;
+ if (defined($::log)) { print LOG $_; }
+ }
+ close(CMD);
+ if (defined($::log)) { close(LOG); }
+
+ } else {
+ &log("(debug) $command");
+ }
+
+ } # not a file
+
+ } # foreach operation
+
+}
+
+######################################################################
+# Option error checking & init stuff
+######################################################################
+sub optioncheck {
+
+ my $buffer_blk_flag;
+ my $buffer_write_pad_flag;
+ my $buffer_read_pad_flag;
+
+ my $mbuffer_blk_flag;
+ my $mbuffer_write_pad_flag;
+ my $mbuffer_read_pad_flag;
+
+ # Archive type on commandline
+ if (defined($::opt{'type'})) {
+ $cfg::type = $::opt{'type'};
+ }
+
+ # Compress flag on commandline
+ if (defined($::opt{'compress'})) {
+ $cfg::compress = $::opt{'compress'};
+ }
+
+ # Device flag on commandline
+ if (defined($::opt{'device'})) {
+ $cfg::device = $::opt{'device'};
+ if (defined($::opt{'stdout'})) {
+ push(@::errors,"Can't use -device and -pipe at the same time");
+ }
+ }
+
+ # Debug
+ if (defined($::opt{'n'})) {
+ $::debug = 1;
+ }
+
+ # Flag old config file
+ if (defined(@cfg::filesystems) or defined($cfg::mt_var_blksize)) {
+ # so strict shuts up
+ my $junk = @cfg::filesystems;
+ $junk = $cfg::mt_var_blksize;
+ push(@::errors,"You've got an old 1.0.x configuration file, please update it!");
+ }
+
+ # Mode
+ my (@modelist) = qw(set dir list extract compare restore toc newtape rmindex rmfile test-tape-drive);
+ my @modes;
+ my $modecount = 0;
+ $::mode = '';
+ foreach my $mode (@modelist) {
+ if (defined($::opt{$mode})) {
+ $modecount++;
+ $::mode = $mode;
+ push(@modes,$mode);
+ }
+ }
+ if ($modecount > 1) {
+ $_ = join(" -",@modes);
+ push(@::errors,"Can't specify more than one mode (given \"-$_\")");
+ }
+ if ($modecount == 0) {
+ push(@::errors,"Nothing to do (see -help)");
+ }
+
+ # First check if things are defined in the config file
+ # Checks exist, true/false, or one of options
+ &checkvar(\$cfg::type,'type','dump afio cpio tar star pax zip ar shar lha copy rsync filelist','tar');
+ &checkvar(\$cfg::compress,'compress','gzip bzip2 lzop compress zip false hardware','gzip');
+ &checkvar(\$cfg::compr_level,'compr_level','exist','4');
+ &checkvar(\$cfg::verbose,'verbose','bool','true');
+ &checkvar(\$cfg::sparse,'sparse','bool','true');
+ &checkvar(\$cfg::label,'label','bool','true');
+ &checkvar(\$cfg::atime_preserve,'atime_preserve','bool','false');
+ &checkvar(\$cfg::indexes,'indexes','bool','true');
+ &checkvar(\$cfg::staticfiles,'staticfiles','bool','false');
+ &checkvar(\$cfg::buffer,'buffer','false buffer mbuffer','false');
+ &checkvar(\$cfg::pad_blocks,'pad_blocks','bool','true');
+ &checkvar(\$cfg::device,'device','exist','/dev/tape');
+ &checkvar(\$cfg::remoteshell,'remoteshell','ssh ssh2 ssh1 rsh','ssh');
+ &checkvar(\$cfg::remoteuser,'remoteuser','exist','');
+ &checkvar(\$cfg::erase_tape_set_level_zero,'erase_tape_set_level_zero','bool','true');
+ &checkvar(\$cfg::erase_rewind_only,'erase_rewind_only','bool','false');
+ &checkvar(\$cfg::logdir,'logdir','exist','/var/log/flexbackup');
+ &checkvar(\$cfg::tmpdir,'tmpdir','exist','/tmp');
+ &checkvar(\$cfg::comp_log,'comp_log','gzip bzip2 lzop compress zip false','gzip');
+ &checkvar(\$cfg::stampdir,'stampdir','exist','/var/lib/flexbackup');
+ &checkvar(\$cfg::index,'index','exist','/var/lib/flexbackup/index');
+ &checkvar(\$cfg::keyfile,'keyfile','exist','00-index-key');
+ &checkvar(\$cfg::staticlogs,'staticlogs','bool','false');
+ &checkvar(\$cfg::prefix,'prefix','exist','');
+ &checkvar(\$cfg::sprefix,'sprefix','exist','');
+
+ if (@::errors) {
+ print $::msg "Errors:\n";
+ while(@::errors) {
+ print $::msg " " . shift(@::errors) . "\n";
+ }
+ exit(1);
+ }
+
+ # Check we can find rsh or ssh
+ $::path{$cfg::remoteshell} = &checkinpath($cfg::remoteshell);
+ if ($cfg::remoteuser ne '') {
+ $::remoteshell = "$::path{$cfg::remoteshell} -l $cfg::remoteuser";
+ } else {
+ $::remoteshell = $::path{$cfg::remoteshell};
+ }
+
+ # Check we can find common stuff
+ $::path{'touch'} = &checkinpath('touch');
+ $::path{'hostname'} = &checkinpath('hostname');
+ $::path{'cat'} = &checkinpath('cat');
+ $::path{'rm'} = &checkinpath('rm');
+ $::path{'tee'} = &checkinpath('tee');
+ $::path{'find'} = &checkinpath('find');
+ $::path{'dd'} = &checkinpath('dd');
+ $::path{'printf'} = &checkinpath('printf');
+
+ push(@::remoteprogs,($::path{'touch'},$::path{'rm'},$::path{'find'},$::path{'printf'}));
+
+ # Check device (or dir)
+ $::ftape = 0;
+ if (defined($::opt{'pipe'})) {
+
+ # Dump to stdout.
+ # Disable indexing, all messages to stderr
+ $::use_file = 1;
+ $::use_pipe = 1;
+ $cfg::indexes = 'false';
+ $cfg::device = '-';
+
+ } elsif ($cfg::type eq 'filelist') {
+
+ $::use_file = 1;
+ chomp($cfg::device = `pwd`);
+ $cfg::device =~ s:/$::;
+ $cfg::indexes = 'false';
+
+ # Can we write to cwd?
+ if (! -w $cfg::device) {
+ push(@::errors,"Can't write to $cfg::device");
+ }
+
+ } else {
+
+ # Chase device links
+ my $realdev = $cfg::device;
+ while (-l "$realdev") {
+
+ my @pathname = split('/',$realdev);
+ $realdev = readlink("$realdev");
+
+ # If a relative link we'll need the dir from the link
+ if ($realdev !~ m:^/:) {
+ pop(@pathname);
+ $realdev = join('/',@pathname) . "/$realdev";
+ }
+ }
+
+ if (-c $realdev) {
+
+ # Check for ftape driver
+ if ($realdev =~ /n?z?[qr]ft(\d+)/) {
+ $::ftape = 1;
+ }
+ $::tapedevice = 1;
+
+ } elsif (-b $realdev) {
+
+ # In case of floppy or similar.
+ # Can't do multiple files this way; turn indexing off
+ $::use_blockdevice = 1;
+ $cfg::indexes = 'false';
+
+ } elsif (-d "$cfg::device") {
+ if ($cfg::device !~ m:^/:) {
+ push(@::errors,"Please give full path, not relative (\$device=$cfg::device)");
+ } else {
+ $::use_file = 1;
+ $cfg::device =~ s:/$::; # nuke trailing slash if any
+ }
+
+ } elsif ($cfg::device =~ m%(\S+):(/dev/.*)%) {
+
+ $::remotetapehost = $1;
+ $cfg::device = $2;
+ $::tapedevice = 1;
+
+ } else {
+ push(@::errors,"\$device must be set to a directory, a local device, or a remote device");
+ }
+
+ # Can we write to it?
+ if ((! -w $cfg::device) and
+ !defined($::remotetapehost) and
+ ($::mode =~ m/^(set|dir|newtape)$/)) {
+ push(@::errors,"Can't write to $cfg::device");
+ }
+
+ }
+
+ $::device = $cfg::device;
+
+
+ # Set mt type
+ if (defined($::tapedevice)) {
+ if ($::ftape == 1) {
+ $::path{'mt'} = &checkinpath('ftmt');
+ } else {
+ $::path{'mt'} = &checkinpath('mt');
+ }
+ }
+
+ # Exclude regexp for find
+ $::exclude_expr = '';
+ if (defined($cfg::exclude_expr[0])) {
+ my @excl_array;
+ my $expr;
+ foreach $expr (@cfg::exclude_expr) {
+
+ # People just don't grok regex's.
+ #
+ # If the first character is a *, they obviously got it wrong,
+ # we can try to assume what they meant.
+ #
+ # If the user put "*.whatever" as an expression, turn this
+ # "glob" into a regex for them
+ # If the user put "*whatever" as an expression, turn this
+ # "glob" into a regex for them
+ if ($expr =~ m/^\*\./) {
+ $expr =~ s/^\*\./.\*\\./;
+ }
+ if ($expr =~ m/^\*/) {
+ $expr =~ s/^\*/.*/;
+ }
+
+ # AAAH! Csh should be banned from the face of the earth!
+ #
+ # If an expression contains $ at the end we need to be careful
+ # and leave it out of the quotes, or csh will yack if doing a
+ # remote backup. This happens only if the user's shell is
+ # csh/tcsh. Then the string is doublequoted inside single
+ # quotes and there is _no way_ for csh do deal with $ in that
+ # situation. This took a LONG time to figure out.
+ if ($expr =~ m/^(.+)\$$/) {
+ $expr = '"' . $1 . '"' . '$'; #' (comment to fool emacs 20.7
+ } else {
+ $expr = '"' . $expr . '"';
+ }
+
+ $::exclude_expr .= "! -regex $expr ";
+ }
+ }
+
+ # Traverse mountpoints?
+ &checkvar(\$cfg::traverse_fs,'traverse_fs','false local all','false');
+ if ($cfg::traverse_fs eq "local") {
+ $::mountpoint_flag = "! -fstype nfs ! -fstype smbfs ! -fstype bind ! -fstype proc ! -fstype devpts ! -fstype devfs ! -fstype tmpfs";
+ } elsif ($cfg::traverse_fs eq "all") {
+ $::mountpoint_flag = "! -fstype proc ! -fstype devpts ! -fstype devfs ! -fstype tmpfs";
+ } else {
+ $::mountpoint_flag = "-xdev";
+ }
+
+ # Block size
+ &checkvar(\$cfg::blksize,'blksize','exist','10');
+ # Isn't required; if commented out in config we use same as $blksize
+ #&checkvar(\$cfg::mt_blksize,'mt_blksize','exist');
+ if ($cfg::blksize !~ m/^\d+$/) {
+ push(@::errors,"\$blksize must be set to an integer");
+ }
+ if ($cfg::blksize ne '0') {
+ # buffer blocksize needs k appended
+ $buffer_blk_flag = "-s " . $cfg::blksize . "k";
+ # mbuffer blocksize in bytes
+ $mbuffer_blk_flag = "-s " . $cfg::blksize * 1024;
+ # dd blocksize needs k appended
+ $::dd_blk_flag = "ibs=" . $cfg::blksize . "k obs=" . $cfg::blksize . "k";
+ # dump blocksize just in k like the config file
+ $::dump_blk_flag = "-b $cfg::blksize";
+ # afio blocksize needs k appended
+ $::afio_blk_flag = "-b " . $cfg::blksize . "k";
+ # cpio blocks are in bytes
+ $::cpio_blk_flag = "-C " . $cfg::blksize * 1024;
+ # tar blocks are in 512-byte units
+ # long name is really --blocking-factor but changed from --block-size
+ # only in recent versions. just use the short flag.
+ $::tar_blk_flag = "-b " . $cfg::blksize * 2;
+ # star blocks are in 512-byte units
+ $::star_blk_flag = "blocks=" . $cfg::blksize * 2;
+ # pax blocksize needs k appended
+ $::pax_blk_flag = "-b " . $cfg::blksize . "k";
+ } else {
+ $buffer_blk_flag = "";
+ $mbuffer_blk_flag = "";
+ $::dd_blk_flag = "";
+ $::dump_blk_flag = "";
+ $::afio_blk_flag = "";
+ $::cpio_blk_flag = "";
+ $::tar_blk_flag = "";
+ $::star_blk_flag = "";
+ $::pax_blk_flag = "";
+ }
+
+ # mt block size (in bytes not k)
+ if (!defined($cfg::mt_blksize)) {
+ $cfg::mt_blksize = $cfg::blksize * 1024;
+ $::mt_blksize = $cfg::mt_blksize;
+ }
+ if ($cfg::mt_blksize !~ m/^\d+$/) {
+ push(@::errors,"\$mt_blksize must be set to an integer");
+ } else {
+ if ($cfg::mt_blksize != 0) {
+ my $tmp = $cfg::blksize * 1024;
+ if ($tmp%$cfg::mt_blksize != 0) {
+ push(@::errors,"\$mt_blksize ($cfg::mt_blksize) should be a factor of \$blksize ($tmp)");
+ }
+ }
+ $::mt_blksize = $cfg::mt_blksize;
+ }
+
+ # Generic compression (afio archives will do their own flags)
+ if ($cfg::compress eq "gzip") {
+ $::path{'gzip'} = &checkinpath($cfg::compress);
+ push(@::remoteprogs, $::path{$cfg::compress});
+ if ($cfg::compr_level !~ m/^[123456789]$/) {
+ push(@::errors,"\$compr_level must be set to 1-9");
+ } else {
+ $::z = " | $::path{$cfg::compress} -$cfg::compr_level";
+ }
+ $::unz = "$::path{$cfg::compress} -dq | ";
+
+ } elsif ($cfg::compress eq "bzip2") {
+ $::path{'bzip2'} = &checkinpath($cfg::compress);
+ push(@::remoteprogs, $::path{$cfg::compress});
+ if ($cfg::compr_level !~ m/^[123456789]$/) {
+ push(@::errors,"\$compr_level must be set to 1-9");
+ } else {
+ $::z = " | $::path{$cfg::compress} -$cfg::compr_level";
+ }
+ $::unz = "$::path{$cfg::compress} -d | ";
+
+ } elsif ($cfg::compress eq "lzop") {
+ $::path{'lzop'} = &checkinpath($cfg::compress);
+ push(@::remoteprogs, $::path{$cfg::compress});
+ if ($cfg::compr_level !~ m/^[123456789]$/) {
+ push(@::errors,"\$compr_level must be set to 1-9");
+ } else {
+ $::z = " | $::path{$cfg::compress} -$cfg::compr_level";
+ }
+ $::unz = "$::path{$cfg::compress} -d | ";
+
+ } elsif ($cfg::compress eq "compress") {
+ $::path{'compress'} = &checkinpath($cfg::compress);
+ push(@::remoteprogs, $::path{$cfg::compress});
+ $::z = " | $::path{$cfg::compress} -c";
+ $::unz = "$::path{$cfg::compress} -dc | ";
+
+ } elsif ($cfg::compress eq "zip") {
+ $::path{'zip'} = &checkinpath('zip');
+ push(@::remoteprogs, $::path{'zip'});
+ $::path{'funzip'} = &checkinpath('funzip');
+ if ($cfg::compr_level !~ m/^[123456789]$/) {
+ push(@::errors,"\$compr_level must be set to 1-9");
+ } else {
+ $::z = " | $::path{zip} -$cfg::compr_level - -";
+ $::unz = "$::path{funzip} | ";
+ }
+ } else {
+ $::z = "";
+ $::unz = "";
+ }
+
+ # Block padding
+ if (($cfg::pad_blocks eq "true") and defined($::tapedevice)) {
+ $::dd_write_pad_flag = "conv=noerror,sync";
+ $::dd_read_pad_flag = "conv=noerror";
+ $buffer_write_pad_flag = "-B";
+ $buffer_read_pad_flag = "";
+ $mbuffer_write_pad_flag = "";
+ $mbuffer_read_pad_flag = "";
+ } else {
+ $::dd_write_pad_flag = "conv=noerror";
+ $::dd_read_pad_flag = "conv=noerror";
+ $buffer_write_pad_flag = "";
+ $buffer_read_pad_flag = "";
+ $mbuffer_write_pad_flag = "";
+ $mbuffer_read_pad_flag = "";
+ }
+
+ # Buffer setup
+ if ($cfg::buffer ne 'false') {
+ &checkvar(\$cfg::buffer_megs,'buffer_megs','exist');
+ &checkvar(\$cfg::buffer_fill_pct,'buffer_fill_pct','exist','75');
+ &checkvar(\$cfg::buffer_pause_usec,'buffer_pause_usec','exist','100');
+ if ($cfg::buffer_megs !~ m/^\d+$/) {
+ push(@::errors,"\$buffer_megs must be set to integer number of megabytes");
+ }
+ if ($cfg::buffer_fill_pct !~ m/^\d+$/) {
+ push(@::errors,"\$buffer_fill_pct must be set to an integer");
+ }
+ if ($cfg::buffer_pause_usec !~ m/^\d+$/) {
+ push(@::errors,"\$buffer_pause_usec must be set to an integer");
+ }
+ if ($cfg::buffer eq "buffer") {
+
+ $::path{'buffer'} = &checkinpath('buffer');
+ push(@::remoteprogs, $::path{'buffer'});
+
+ my $write_flags;
+ my $read_flags;
+ my $megs = $cfg::buffer_megs . "m";
+ my $bufcmd = "$::path{buffer} -m $megs -p $cfg::buffer_fill_pct $buffer_blk_flag -t ";
+
+ if (defined($::tapedevice)) {
+ $write_flags = "-u $cfg::buffer_pause_usec $buffer_write_pad_flag -o ";
+ $read_flags = "-u $cfg::buffer_pause_usec $buffer_read_pad_flag -i ";
+ } else {
+ $write_flags = "$buffer_write_pad_flag -o ";
+ $read_flags = "$buffer_read_pad_flag -i ";
+ }
+ $::buffer_cmd = " | $bufcmd";
+ $::write_cmd = "$bufcmd $write_flags";
+ $::read_cmd = "$bufcmd $read_flags";
+
+ } elsif ($cfg::buffer eq "mbuffer") {
+
+ $::path{'mbuffer'} = &checkinpath('mbuffer');
+ push(@::remoteprogs, $::path{'mbuffer'});
+
+ my $megs = $cfg::buffer_megs . "M";
+ my $bufcmd = "$::path{mbuffer} -q -m $megs -p $cfg::buffer_fill_pct $mbuffer_blk_flag ";
+
+ $::buffer_cmd = " | $bufcmd";
+ $::write_cmd = "$bufcmd -f -o ";
+ if (defined($::opt{'volumes'})) {
+ $::read_cmd = "$bufcmd -f -n $::opt{volumes} -i ";
+ } else {
+ $::read_cmd = "$bufcmd -f -i ";
+ }
+ }
+ } else {
+
+ # If buffering disabled, use dd or cat depending on if blocking turned off on not
+ if ($cfg::blksize eq '0') {
+ $::buffer_cmd = "";
+ $::write_cmd = "$::path{cat} > ";
+ $::read_cmd = "$::path{cat} ";
+ } else {
+ $::buffer_cmd = "";
+ $::write_cmd = "$::path{dd} $::dd_blk_flag $::dd_write_pad_flag of=";
+ $::read_cmd = "$::path{dd} $::dd_blk_flag $::dd_read_pad_flag if=";
+ }
+ }
+
+ # Sets / filesystems
+ if (defined($::opt{'dir'})) {
+
+ # Single directory
+ if ($::opt{'dir'} =~ /^(\S+):/) {
+ $::remotehosts{$1} = 1;
+ } else {
+ $::local = 1;
+ }
+
+ # Get rid of trailing /
+ $::opt{'dir'} = &nuke_trailing_slash($::opt{'dir'});
+
+ } elsif (defined($::opt{'set'})) {
+
+ if (defined($::use_pipe)) {
+ push(@::errors,"can't use -set with -pipe option");
+ }
+
+ foreach my $set (keys %cfg::set) {
+ if ($set eq 'all') {
+ push(@::errors,"can't define a set named 'all'");
+ }
+ }
+
+ my @do_sets;
+ if ($::opt{'set'} eq 'all') {
+ @do_sets = keys(%cfg::set);
+ if (scalar(@do_sets) == 0) {
+ push(@::errors,"no backup sets defined");
+ }
+ } else {
+ @do_sets = ($::opt{'set'});
+ }
+
+ foreach my $this_set (@do_sets) {
+ if (!defined($cfg::set{$this_set})) {
+ push(@::errors,"set $this_set is not defined");
+ } else {
+ foreach my $dir (&split_list($cfg::set{$this_set})) {
+ if ($dir =~ /^(\S+):/g) {
+ $::remotehosts{$1} = 1;
+ } else {
+ $::local = 1;
+ }
+ }
+ }
+ }
+ }
+
+ # Subtree pruning
+ foreach my $fs (keys %cfg::prune) {
+ $fs = &nuke_trailing_slash($fs);
+ foreach my $expr (&split_list($cfg::prune{$fs})) {
+ $::prune{$fs}{$expr} = 1;
+ }
+ }
+
+ # Verbose flag
+ if ($cfg::verbose eq "true") {
+ $::dump_verb_flag = "-v";
+ $::afio_verb_flag = "-v";
+ $::cpio_verb_flag = "-v";
+ $::tar_verb_flag = "--verbose";
+ $::star_verb_flag = "-v";
+ $::pax_verb_flag = "-v";
+ $::zip_verb_flag = "-v";
+ $::ar_verb_flag = "v";
+ $::shar_verb_flag = "";
+ $::lha_verb_flag = "";
+ $::rsync_verb_flag = "--verbose";
+ } else {
+ $::dump_verb_flag = "";
+ $::afio_verb_flag = "";
+ $::cpio_verb_flag = "";
+ $::tar_verb_flag = "";
+ $::star_verb_flag = "-silent";
+ $::pax_verb_flag = "";
+ $::zip_verb_flag = "-q";
+ $::ar_verb_flag = "";
+ $::shar_verb_flag = "-q";
+ $::lha_verb_flag = "q";
+ $::rsync_verb_flag = "";
+ }
+
+ # Sparse flag
+ if ($cfg::sparse eq "true") {
+ $::afio_sparse_flag = "";
+ $::cpio_sparse_flag = "";
+ $::tar_sparse_flag = "--sparse";
+ $::star_sparse_flag = "-sparse";
+ } else {
+ $::afio_sparse_flag = "-j";
+ $::cpio_sparse_flag = "";
+ $::tar_sparse_flag = "";
+ $::star_sparse_flag = "";
+ }
+
+ # atime preserve flag
+ if ($cfg::atime_preserve eq "true") {
+ $::afio_atime_flag = "-a";
+ $::tar_atime_flag = "--atime-preserve";
+ $::star_atime_flag = "-atime";
+ } else {
+ $::afio_atime_flag = "";
+ $::tar_atime_flag = "";
+ $::star_atime_flag = "";
+ }
+
+ # Type-specific setup
+ if ($cfg::type eq 'dump') {
+
+ &checkvar(\$cfg::dump_length,'dump_length','exist','0');
+ &checkvar(\$cfg::dump_use_dumpdates,'dump_use_dumpdates','bool','false');
+
+ $::path{'dump'} = &checkinpath('dump');
+ $::path{'restore'} = &checkinpath('restore');
+ push(@::remoteprogs, $::path{'dump'});
+
+ # Length of tape
+ if ($cfg::dump_length !~ m/^\d+$/) {
+ push(@::errors,"\$dump_length must be set to integer number of kilobytes");
+ }
+
+ # If length set to 0 will will try autosize
+ if ($cfg::dump_length == 0) {
+ $::dump_len_flag = "-a";
+ } else {
+ $::dump_len_flag = "-B $cfg::dump_length";
+ }
+
+ } elsif ($cfg::type eq 'afio') {
+
+ &checkvar(\$cfg::afio_echo_block,'afio_echo_block','bool','false');
+ &checkvar(\$cfg::afio_compress_cache_size,'afio_compress_cache_size','exist','2');
+ &checkvar(\$cfg::afio_compress_threshold,'afio_compress_threshold','exist','3');
+ &checkvar(\$cfg::afio_nocompress_types,'afio_nocompress_types','exist','mp3 MP3 Z z gz gif zip ZIP lha jpeg jpg JPG taz tgz deb rpm bz2 lzo');
+
+ $::path{'afio'} = &checkinpath('afio');
+ push(@::remoteprogs, $::path{'afio'});
+
+ # Compress flag for afio must be handled differently
+ if ($cfg::compress =~ m/^(gzip|bzip2|lzop|compress|zip)$/) {
+
+ if ($cfg::compress eq "gzip") {
+ $::afio_z_flag = "-P $::path{$cfg::compress} -Q -$cfg::compr_level -Z";
+ $::afio_unz_flag = "-P $::path{$cfg::compress} -Q -d -Q -q -Z";
+
+ } elsif ($cfg::compress eq "bzip2") {
+ $::afio_z_flag = "-P $::path{$cfg::compress} -Q -$cfg::compr_level -Z";
+ $::afio_unz_flag = "-P $::path{$cfg::compress} -Q -d -Z";
+
+ } elsif ($cfg::compress eq "lzop") {
+ $::afio_z_flag = "-P $::path{$cfg::compress} -Q -$cfg::compr_level -Z";
+ $::afio_unz_flag = "-P $::path{$cfg::compress} -Q -d -Z";
+
+ } elsif ($cfg::compress eq "zip") {
+ $::afio_z_flag = "-P $::path{zip} -Q -$cfg::compr_level -Q - -Q - -Z";
+ $::afio_unz_flag = "-P $::path{funzip} -Q \"\" -Z";
+
+ } elsif ($cfg::compress eq "compress") {
+ $::afio_z_flag = "-P $::path{$cfg::compress} -Q -c -Z";
+ $::afio_unz_flag = "-P $::path{$cfg::compress} -Q -d -Q -c -Z";
+
+ }
+ $::unz = ""; # Reset & just use this for reading the archive file.
+
+ # Compression cache size
+ if ($cfg::afio_compress_cache_size !~ m/^\d+$/) {
+ push(@::errors,"\$afio_compress_cache_size must be set to an integer");
+ } else {
+ if ($cfg::afio_compress_cache_size != 0) {
+ $::afio_z_flag .= " -M " . $cfg::afio_compress_cache_size . "m";
+ }
+ }
+
+ # Compression threshold
+ if ($cfg::afio_compress_threshold !~ m/^\d+$/) {
+ push(@::errors,"\$afio_compress_threshold must be set to an integer");
+ } else {
+ if ($cfg::afio_compress_threshold != 0) {
+ $::afio_z_flag .= " -T " . $cfg::afio_compress_threshold . "k";
+ }
+ }
+
+ } else {
+ $::afio_z_flag = "";
+ $::afio_unz_flag = "";
+ }
+
+ # Echo block number
+ $::afio_bnum_flag = "";
+ if ($cfg::verbose eq "true") {
+ if ($cfg::afio_echo_block eq "true") {
+ $::afio_bnum_flag = "-B";
+ }
+ }
+
+ } elsif (($cfg::type eq 'cpio') or ($cfg::type eq 'copy')) {
+
+ &checkvar(\$cfg::cpio_format,'cpio_format','bin odc newc crc tar ustar hpbin hpodc','newc');
+
+ $::path{'cpio'} = &checkinpath('cpio');
+ push(@::remoteprogs, $::path{'cpio'});
+
+ if ($cfg::type eq 'copy') {
+ if (!defined($::use_file)) {
+ push(@::errors,"Can't use type \"copy\" unless archiving to disk!");
+ }
+ if (defined($::use_pipe)) {
+ push(@::errors,"Can't use type \"copy\" with -pipe!");
+ }
+ }
+
+ } elsif ($cfg::type eq 'rsync') {
+
+ $::path{'rsync'} = &checkinpath('rsync');
+ $::path{'sed'} = &checkinpath('sed');
+ push(@::remoteprogs, $::path{'rsync'});
+
+ if (!defined($::use_file)) {
+ push(@::errors,"Can't use type \"rsync\" unless archiving to disk!");
+ }
+ if (defined($::use_pipe)) {
+ push(@::errors,"Can't use type \"rsync\" with -pipe!");
+ }
+
+ } elsif ($cfg::type eq 'tar') {
+
+ &checkvar(\$cfg::tar_echo_record_num,'tar_echo_record_num','bool','false');
+
+ $::path{'tar'} = &checkinpath('tar');
+ push(@::remoteprogs, $::path{'tar'});
+
+ # Echo record number
+ $::tar_recnum_flag = "";
+ if ($cfg::verbose eq "true") {
+ if ($cfg::tar_echo_record_num eq "true") {
+ $::tar_recnum_flag = "-R";
+ }
+ }
+
+ } elsif ($cfg::type eq 'star') {
+
+ &checkvar(\$cfg::star_acl,'star_acl','bool','true');
+ &checkvar(\$cfg::star_fifo,'star_fifo','bool','true');
+ &checkvar(\$cfg::star_format,'star_format','tar star gnutar ustar pax xstar xustar exustar suntar','exustar');
+ &checkvar(\$cfg::star_echo_block_num,'star_echo_block_num','bool','false');
+
+ $::path{'star'} = &checkinpath('star');
+ push(@::remoteprogs, $::path{'star'});
+
+ # Echo block number
+ $::star_blocknum_flag = "";
+ if ($cfg::verbose eq "true") {
+ if ($cfg::star_echo_block_num eq "true") {
+ $::star_blocknum_flag = "-block-number";
+ }
+ }
+
+ # ACL flag
+ if ($cfg::star_acl eq "true") {
+ $::star_acl_flag = "-acl";
+ } else {
+ $::star_acl_flag = "";
+ }
+
+ # fifo
+ if ($cfg::star_fifo eq "true") {
+ $::star_fifo_flag = "-fifo";
+ if ($cfg::verbose eq "true") {
+ $::star_fifo_flag .= " -fifostats";
+ }
+ } else {
+ $::star_fifo_flag = "";
+ }
+
+ } elsif ($cfg::type eq 'pax') {
+
+ &checkvar(\$cfg::pax_format,'pax_format','cpio bcpio sv4cpio sv4crc tar ustar');
+
+ $::path{'pax'} = &checkinpath('pax');
+ push(@::remoteprogs, $::path{'pax'});
+
+ } elsif ($cfg::type eq 'zip') {
+
+ &checkvar(\$cfg::zip_nocompress_types,'zip_nocompress_types','exist','mp3 MP3 Z z gz gif zip ZIP lha jpeg jpg JPG taz tgz deb rpm bz2 lzo');
+
+ $::path{'zip'} = &checkinpath('zip');
+ push(@::remoteprogs, $::path{'zip'});
+ $::path{'unzip'} = &checkinpath('unzip');
+
+ $::zip_compr_flag = "-$cfg::compr_level";
+
+ if ($cfg::compress =~ /^(gzip|bzip2|lzop|compress|zip)$/) {
+ warn("Using type \"zip\" with compress=$cfg::compress makes no sense");
+ warn("Setting compression to false");
+ $::unz = "";
+ $::z = "";
+ $cfg::compress = "false";
+ }
+
+ $::zip_noz_flag = "";
+ if (defined($cfg::zip_nocompress_types) and $cfg::zip_nocompress_types ne "") {
+ # Add dots to file extensions, make -n flag
+ @_ = split(" ",$cfg::zip_nocompress_types);
+ foreach (@_) {
+ $_ = "." . $_;
+ }
+ $::zip_noz_flag = " -n " . join(":",@_);
+ }
+
+ } elsif ($cfg::type eq 'ar') {
+
+ $::path{'ar'} = &checkinpath('ar');
+ push(@::remoteprogs, $::path{'ar'});
+
+ } elsif ($cfg::type eq 'shar') {
+
+ $::path{'shar'} = &checkinpath('shar');
+ push(@::remoteprogs, $::path{'shar'});
+
+ } elsif ($cfg::type eq 'lha') {
+
+ $::path{'lha'} = &checkinpath('lha');
+ push(@::remoteprogs, $::path{'lha'});
+
+ if ($cfg::compress =~ /^(gzip|bzip2|lzop|compress|zip)$/) {
+ warn("Using type \"lha\" with compress=$cfg::compress makes no sense");
+ warn("Setting compression to false");
+ $::unz = "";
+ $::z = "";
+ $cfg::compress = "false";
+ }
+
+ } elsif ($cfg::type eq 'filelist') {
+
+ # Nothing specific to check
+
+ } # type-specific
+
+
+ # Tmp dir
+ $cfg::tmpdir = &nuke_trailing_slash($cfg::tmpdir);
+ if ($cfg::tmpdir !~ m:^/:) {
+ push(@::errors,"\$tmpdir must be absolute path: $cfg::tmpdir");
+ }
+ if (! -d "$cfg::tmpdir") {
+ push(@::errors,"\$tmpdir $cfg::tmpdir is not a directory");
+ }
+ if (! -w "$cfg::tmpdir") {
+ push(@::errors,"\$tmpdir $cfg::tmpdir is not writable");
+ }
+
+ # Levels
+ if (defined($::opt{'level'}) and
+ (defined($::opt{'incremental'}) or
+ defined($::opt{'differential'}) or
+ defined($::opt{'full'}))) {
+ push(@::errors,"Can't use -level AND -incremental/-differential/-full");
+ }
+
+ if (!defined($::opt{'level'})) {
+ if (defined($::opt{'incremental'})) {
+ $::opt{'level'} = 'incremental';
+ } elsif (defined($::opt{'differential'})) {
+ $::opt{'level'} = 'differential';
+ } elsif (defined($::opt{'full'})) {
+ $::opt{'level'} = 'full';
+ } else {
+ $::opt{'level'} = 0;
+ }
+ }
+
+ if (($::opt{'level'} !~ m/^\d+$/) and
+ ($::opt{'level'} !~ m/^(full|differential|incremental)$/)) {
+ push(@::errors,"-level must be numeric, or full/differential/incremental");
+ }
+
+ # Check for digits or change full/diff to level number
+ # Incremental + fs=all we have to handle later since it might be
+ # different for each fs
+ if ($::opt{'level'} =~ m/^\d+$/) {
+ # Make string variable numeric
+ $::level = POSIX::strtod($::opt{'level'});
+ if (($cfg::type eq 'dump') and ($::level > 9)) {
+ push(@::errors,"can't use level > 9 and type=dump");
+ }
+ } elsif ($::opt{'level'} eq "full") {
+ $::level = 0;
+ } elsif ($::opt{'level'} eq "differential") {
+ $::level = 1;
+ } elsif ($::opt{'level'} eq "incremental") {
+ # If incremental + one fs, we can find the level now.
+ if (defined($::opt{'dir'})) {
+ $::level = &get_incremental_level($::opt{'dir'});
+ if (($cfg::type eq 'dump') and ($::level > 9)) {
+ push(@::errors,"can't use level > 9 and type=dump");
+ }
+ } else {
+ # If we are doing a set have to postpone till later; each
+ # fs might have a different level...
+ undef $::level;
+ $::set_incremental = 1;
+ }
+ }
+
+ # Package delta option
+ if (defined($::opt{'pkgdelta'})) {
+
+ &checkvar(\$cfg::pkgdelta_archive_list,'pkgdelta_archive_list','true false rootonly','rootonly');
+ &checkvar(\$cfg::pkgdelta_archive_unowned,'pkgdelta_archive_unowned','bool','true');
+ &checkvar(\$cfg::pkgdelta_archive_changed,'pkgdelta_archive_changed','bool','true');
+
+ if ($::opt{'pkgdelta'} eq 'rpm') {
+ $::pkgdelta = 'rpm';
+ $::path{'rpm'} = &checkinpath('rpm');
+
+ } elsif ($::opt{'pkgdelta'} =~ /freebsd/i) {
+ $::pkgdelta = 'freebsd';
+ $::path{'pkg_info'} = &checkinpath('pkg_info');
+
+ } else {
+ push(@::errors,"$::opt{pkgdelta} not a valid option for -pkgdelta");
+ }
+ }
+
+ # Check toc/rmindex/rmfile flags
+ if (defined($::opt{'toc'}) or defined($::opt{'rmindex'})) {
+ if ($cfg::indexes eq "false") {
+ push(@::errors,"Can't do -toc/rmindex with \$indexes set to false");
+ }
+ }
+ if (defined($::opt{'rmindex'}) and (${$::opt{'rmindex'}}[0] eq '')) {
+ push(@::errors,"-rmindex requires 'key:filenum', 'key' or 'all'");
+ }
+ if (defined($::opt{'rmfile'}) and (${$::opt{'rmfile'}}[0] eq '')) {
+ push(@::errors,"-rmfile requires a filename or 'all'");
+ }
+
+ # Check log/stamp dirs (only if we are in a 'write' mode)
+ if ($::mode =~ m/^(set|dir|newtape)$/) {
+ $::path{$cfg::comp_log} = &checkinpath($cfg::comp_log) if ($cfg::comp_log ne "false");
+ $cfg::logdir = &nuke_trailing_slash($cfg::logdir);
+ $cfg::stampdir = &nuke_trailing_slash($cfg::stampdir);
+ if ($cfg::logdir !~ m:^/:) {
+ push(@::errors,"\$logdir must be absolute path: $cfg::logdir");
+ }
+ if ($cfg::stampdir !~ m:^/:) {
+ push(@::errors,"\$stampdir must be absolute path: $cfg::stampdir");
+ }
+ if (! -d "$cfg::logdir") {
+ mkdir("$cfg::logdir",0755) or push(@::errors,"Can't mkdir $cfg::logdir: $OS_ERROR");
+ }
+ if (! -w "$cfg::logdir") {
+ push(@::errors,"Can't write to $cfg::logdir");
+ }
+ if (! -d "$cfg::stampdir") {
+ mkdir("$cfg::stampdir",0755) or push(@::errors,"Can't mkdir $cfg::stampdir: $OS_ERROR");
+ }
+ if (! -w "$cfg::stampdir") {
+ push(@::errors,"Can't write to $cfg::stampdir: $OS_ERROR");
+ }
+ }
+
+ # Tie index database
+ if (($::mode !~ m/^(list|extract|restore|compare|test-tape-drive)$/) and
+ ($cfg::indexes eq "true")) {
+ tie(%::index,"AnyDBM_File",$cfg::index,O_CREAT|O_RDWR,0640) or
+ push(@::errors,"Can't tie DB $cfg::index");
+ }
+
+ # Sanity check some accessory tape flags
+ if (($::mode =~ m/^(list|extract|restore|compare)$/) and defined($::opt{'erase'})) {
+ push(@::errors,"-erase can't be used in -$::mode mode");
+ }
+ if (($::mode =~ m/^(set|dir|newtape)$/) and defined($::opt{'num'})) {
+ push(@::errors,"-num Can't be used in -$::mode mode");
+ }
+ if (defined($::use_file) or defined($::use_blockdevice)) {
+ if (defined($::opt{'num'})) {
+ push(@::errors,"Can't use -num unless reading from tape");
+ }
+ if (defined($::opt{'erase'}) or defined($::opt{'rewind'}) or defined($::opt{'reten'})) {
+ push(@::errors,"Can't use -erase/-rewind/-reten unless using a tape");
+ }
+ }
+
+ # Testing
+ if (defined($::debug)) {
+ &log('(debug) no backup or mt commands will be executed');
+ &log('(debug) no old stamps or old log files will be removed');
+ }
+
+ # Check extract list
+ if (defined($::opt{'flist'})) {
+ if (defined($::opt{'extract'})) {
+ if (! -r $::opt{'flist'}) {
+ push(@::errors,"list of files $::opt{flist} not readable: $OS_ERROR");
+ }
+ } else {
+ push(@::errors,"-flist can only be used with -extract");
+ }
+ }
+ if (defined($::opt{'onefile'}) and !defined($::opt{'extract'})) {
+ push(@::errors,"-onefile can only be used with -extract");
+ }
+
+ # Requirements for testing
+ if (defined($::opt{'test-tape-drive'})) {
+ if (defined($::use_file)) {
+ push(@::errors,"No use trying tape drive tests on directories!");
+ } elsif (defined($::use_blockdevice)) {
+ push(@::errors,"No use trying tape drive tests on block devices!");
+ }
+ $::path{'diff'} = &checkinpath('diff');
+ $::path{'tr'} = &checkinpath('tr');
+ }
+
+ if (@::errors) {
+ print $::msg "\nErrors:\n";
+ while(@::errors) {
+ print $::msg " " . shift(@::errors) . "\n";
+ }
+ exit(1);
+ }
+
+}
+
+######################################################################
+# Check buffer, shelltype, and any remote hosts for required programs
+######################################################################
+sub test_before_run {
+
+ if ($cfg::buffer ne 'false') {
+ &test_bufferprog($::buffer_cmd, 'localhost');
+ }
+
+ &check_shell('localhost');
+
+ &check_remote_progs(\%::remotehosts, \@::remoteprogs);
+
+ if (@::errors) {
+ print $::msg "\nErrors:\n";
+ while(@::errors) {
+ print $::msg " " . shift(@::errors) . "\n";
+ }
+ exit(1);
+ }
+
+}
+
+######################################################################
+# Print usage summary from the header
+######################################################################
+sub usage {
+
+ open(FILE,"$0") or die "Can't open $0: $OS_ERROR";
+ while(<FILE>) {
+ last if (m/^\#\s+USAGE:/);
+ }
+ while(<FILE>) {
+ last if (m/^\#\#\#\#\#\#\#/);
+ s/^\#//;
+ print;
+ }
+ close(FILE);
+
+}
+
+######################################################################
+# Return version string from CVS tag
+######################################################################
+sub versionstring {
+
+ my $ver = ' $Name: v1_2_1 $ ';
+ $ver =~ s/Name//g;
+ $ver =~ s/[:\$]//g;
+ $ver =~ s/\s+//g;
+ $ver =~ s/^v//g;
+ $ver =~ s/_/\./g;
+ if ($ver eq '') {
+ $ver = "devel";
+ }
+ return($ver . " (http://flexbackup.sourceforge.net)");
+
+}
+
+######################################################################
+# Return current time in ctime format if normal
+# in YYYYMMDDHHMM.SS format if 'numeric' is given
+######################################################################
+sub current_time {
+
+ my $format = shift(@_);
+ my $string;
+ my $current_time = time;
+
+ if (defined($format) and ($format eq 'numeric')) {
+ $string = strftime("%Y%m%d%H%M", localtime($current_time));
+ } elsif (defined($format) and ($format eq 'ctime')) {
+ $string = strftime("%a %b %d %H:%M:%S %Y", localtime($current_time));
+ } else {
+ $string = strftime("%a %b %d %H:%M:%S %Y", localtime($current_time));
+ }
+
+ return($string);
+
+}
+
+######################################################################
+# Possibly return a filename to use
+# if running list/extract/compare/restore
+######################################################################
+sub maybe_get_filename {
+
+ my @modes = qw(list extract compare restore);
+ my $arg;
+ my $file;
+ my $ftype;
+
+ # grab filename from option argument
+ # optionscheck already guarantees only one is set
+ foreach my $mode (@modes) {
+ if (defined($::opt{$mode})) {
+ $arg = $::opt{$mode};
+ }
+ }
+
+ # If reading from stdin
+ if (defined($::use_pipe)) {
+ # -pipe and file arg doesn't make sense, yell
+ if ($arg ne '') {
+ print STDERR "Error: when using -pipe, don't specify file name.\n";
+ die();
+ } else {
+ # Set file to "-" for stdin
+ return('-');
+ }
+ }
+
+ # If the flag given but null, and $device was not set to a dir, just return
+ if (($arg eq '') and (!defined($::use_file))) {
+ return($::device);
+ }
+
+ # If the flag given but null, and $device is a dir, spew
+ if (($arg eq '') and (defined($::use_file))) {
+ print STDERR "Error: when extracting from a file, you must specify file name.\n";
+ print STDERR "(like \"-list file.tar.bz2\")\n";
+ die();
+ }
+
+ # Look for file in current dir first (or full path given)
+ # Then in $device dir (if conf file set to backup to files)
+ if (-f "$arg") {
+ $file = $arg;
+ $::use_file = 1;
+ $cfg::device = $cfg::tmpdir; # Just so optioncheck doesn't assume tape
+ undef $::tapedevice;
+ undef $::remotetapehost;
+
+ } elsif (defined($::use_file) and (-f "$cfg::device/$arg")) {
+ $file = $cfg::device . "/" . $arg;
+ $cfg::device = $cfg::tmpdir; # Just so optioncheck doesn't assume tape
+ undef $::tapedevice;
+ undef $::remotetapehost;
+
+ } elsif (-d "$arg") {
+ $file = $arg;
+ $::use_file = 1;
+ $cfg::device = $cfg::tmpdir; # Just so optioncheck doesn't assume tape
+ undef $::tapedevice;
+ undef $::remotetapehost;
+
+ } elsif (defined($::use_file) and (-d "$cfg::device/$arg")) {
+ $file = $cfg::device . "/" . $arg;
+ $cfg::device = $cfg::tmpdir; # Just so optioncheck doesn't assume tape
+ undef $::tapedevice;
+ undef $::remotetapehost;
+
+ } else {
+ if (defined($::use_file)) {
+ print STDERR "Error: file \"$arg\" or \"$cfg::device/$arg\" not found\n";
+ print STDERR "(like \"-list file.tar.bz2\")\n";
+ die();
+ } else {
+ die("Error: file \"$arg\" not found");
+ }
+ }
+
+ # Try and guess file types and commpression scheme
+ # might as well since we are reading from a file in this case
+ if ($file =~ m/\.(dump|cpio|tar|star|pax|a|shar|filelist)\.(gz|bz2|lzo|Z|zip)$/) {
+ $cfg::type = $1;
+ $cfg::compress = $2;
+ $cfg::type =~ s/^a$/ar/;
+ $cfg::compress =~ s/gz/gzip/;
+ $cfg::compress =~ s/bz2/bzip2/;
+ $cfg::compress =~ s/lzo/lzop/;
+ $cfg::compress =~ s/Z/compress/;
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ } elsif ($file =~ m/\.afio-(gz|bz2|lzo|Z|zip)$/) {
+ $cfg::type = "afio";
+ $cfg::compress = $1;
+ $cfg::compress =~ s/gz/gzip/;
+ $cfg::compress =~ s/bz2/bzip2/;
+ $cfg::compress =~ s/lzo/lzop/;
+ $cfg::compress =~ s/Z/compress/;
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ } elsif ($file =~ m/\.(dump|afio|cpio|tar|star|pax|zip|a|shar|lha|filelist)$/) {
+ $cfg::type = $1;
+ $cfg::type =~ s/^a$/ar/;
+ $cfg::compress = "false";
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ } elsif (-d "$file") {
+ $cfg::type = "copy";
+ $cfg::compress = "false";
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ } elsif ($file =~ m/\.tgz$/) {
+ $cfg::type = "tar";
+ $cfg::compress = "gzip";
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ } elsif ($file =~ m/\.tbz2?$/) {
+ $cfg::type = "tar";
+ $cfg::compress = "bzip2";
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ } elsif ($file =~ m/\.taz$/) {
+ $cfg::type = "tar";
+ $cfg::compress = "compress";
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ } elsif ($file =~ m/\.rpm$/) {
+ $cfg::type = "cpio";
+ $cfg::compress = "false";
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ } elsif ($file =~ m/\.deb$/) {
+ $cfg::type = "ar";
+ $cfg::compress = "false";
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ } elsif ($file =~ m/\.jar$/i) {
+ $cfg::type = "zip";
+ $cfg::compress = "false";
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ } elsif ($file =~ m/\.lzh$/i) {
+ $cfg::type = "lha";
+ $cfg::compress = "false";
+ &log("| Auto-set to type=$cfg::type compress=$cfg::compress");
+ &optioncheck(); # redo to set a few variables over
+
+ }
+
+ return($file);
+
+}
+
+######################################################################
+# Check validity of a config option
+######################################################################
+sub checkvar {
+
+ my $ref = shift(@_); # ref to variable
+ my $varname = shift(@_); # name of variable
+ my $ok = shift(@_); # list of ok values, "bool", "exists"
+ my $default = shift(@_); # default to use if not set
+ my @ok;
+ my $found = 0;
+
+ if (!defined($ok)) {
+ die("checkvar called incorrectly");
+ }
+
+ if ($ok eq 'bool') {
+ @ok = ('true','false');
+ } else {
+ @ok = split(" ",$ok);
+ }
+
+ if (!defined($$ref)) {
+ if (!defined($::opt{'nodefaults'}) and defined($default)) {
+ print $::msg " \$$varname not found in config: default=$default\n";
+ $$ref = $default;
+ } else {
+ push(@::errors,"\$$varname not defined");
+ }
+ } else {
+ if ($ok[0] ne "exist") {
+ foreach (@ok) {
+ if ($_ eq $$ref) {
+ $found = 1;
+ }
+ }
+ if ($found == 0 ) {
+ $_ = join(", ",@ok);
+ push(@::errors,"\$$varname must be one of $_");
+ }
+ }
+ }
+
+}
+
+######################################################################
+# Check to see if a program is found in $PATH
+######################################################################
+sub checkinpath {
+
+ my $file = shift(@_);
+
+ if (defined($cfg::path{$file})) {
+
+ # Override in config file
+
+ if ($cfg::path{$file} =~ m:^/:) {
+
+ # Starts with /; full path override
+ if (-e $cfg::path{$file} && -x _) {
+ print $::msg "path $file = $cfg::path{$file}\n";
+ return "$cfg::path{$file}";
+ } else {
+ push(@::errors,"$cfg::path{$file} not found");
+ return(0);
+ }
+
+ } elsif (($cfg::path{$file} =~ m:^\s*sudo\s+-u\s+\S+\s+(\S+):) or
+ ($cfg::path{$file} =~ m:^\s*sudo\s+(\S+):)) {
+
+ # some sort of sudo...
+ my $prog = $1;
+
+ &checkinpath('sudo');
+
+ # sudo with full pathname
+ if (($prog =~ m:^/:) and (-e $prog) and (-x _)) {
+ print $::msg "path $file = $cfg::path{$file}\n";
+ return "$cfg::path{$file}";
+ }
+ # sudo with just command name
+ my @path = split(/:/,$ENV{'PATH'});
+ foreach my $dir (@path) {
+ if (-e "${dir}/$prog" && -x _) {
+ return "$cfg::path{$file}";
+ }
+ }
+
+ push(@::errors,"sudo $prog not found in \$PATH");
+ return(0);
+
+ } else {
+
+ # Didn't start with /; just overriding name of command
+ # search PATH for it
+ my @path = split(/:/,$ENV{'PATH'});
+ foreach my $dir (@path) {
+ if (-e "${dir}/$cfg::path{$file}" && -x _) {
+ return "$cfg::path{$file}";
+ }
+ }
+
+ push(@::errors,"$cfg::path{$file} not found in \$PATH");
+ return(0);
+
+ }
+
+ } else {
+
+ # Not spec'ed as an override in config file; search PATH
+ my @path = split(/:/,$ENV{'PATH'});
+ foreach my $dir (@path) {
+ if (-e "${dir}/$file" && -x _) {
+ return "$file";
+ }
+ }
+
+ push(@::errors,"$file not found in \$PATH");
+ return(0);
+ }
+
+}
+
+######################################################################
+# Run a command, or echo it depending on the -n flag
+# Then show tape drive position
+######################################################################
+sub run_or_echo_then_query {
+
+ my $cmd = shift(@_);
+
+ &split_and_echo($cmd);
+ &line();
+
+ if (!defined($::debug)) {
+ system("($cmd) 2>&1 | $::path{tee} -a $::log");
+ } else {
+ &log("(debug) command output would be here");
+ }
+
+ if (!defined($::use_file)) {
+ &line();
+ &mt('generic-query');
+ }
+
+ &line();
+
+ # Maybe rewind (usually false for reads)
+ if (($::do_rewind_after == 1) and !defined($::use_file)) {
+ &log("| Rewinding...");
+ &mt('rewind');
+ &line();
+ }
+
+}
+
+######################################################################
+# Return a command possibly wrapped in ssh/rsh
+######################################################################
+sub maybe_remote_cmd {
+
+ my $cmd = shift(@_);
+ my $host = shift(@_);
+ my $quote = shift(@_);
+ my $is_pipeline = 0;
+
+ if (!defined($quote)) {
+ $quote = "'";
+ }
+
+ if ($cmd =~ m:\s+(\||&&)\s+:) {
+ $is_pipeline = 1;
+ }
+
+ if (defined($host) and ($host ne '')) {
+
+ # If remote shell is smart enough use pipeline exit detectors
+ if (($is_pipeline == 1) and ($::shelltype{$host} eq 'bash2')) {
+ $cmd = "$::remoteshell $host " . $quote . $cmd . $::bash_pipe_exit . $quote;
+ } elsif (($is_pipeline == 1) and ($::shelltype{$host} eq 'zsh')) {
+ $cmd = "$::remoteshell $host " . $quote . $cmd . $::zsh_pipe_exit . $quote;
+ } else {
+ $cmd = "$::remoteshell $host " . $quote . $cmd . $quote;
+ }
+
+ } else {
+ $cmd = "$cmd";
+ }
+ return($cmd);
+
+}
+
+######################################################################
+# Append to the pipelins string appropriate commands to write archive
+######################################################################
+sub append_writer_cmd {
+
+ my $cmd = shift(@_);
+ my $dev = shift(@_);
+
+ # Possibly override device
+ if (!defined($dev)) {
+ $dev = $::device;
+ }
+
+ if (defined($::use_pipe)) {
+
+ $cmd .= $::buffer_cmd;
+
+ } elsif (!defined($::remotetapehost)) {
+
+ $cmd .= " | " . $::write_cmd . '"' . $dev . '"' ;
+
+ } else {
+
+ $cmd .= "$::buffer_cmd | ";
+ $cmd .= &maybe_remote_cmd($::write_cmd . '"' . $dev . '"', $::remotetapehost);
+ }
+
+ return($cmd);
+}
+
+######################################################################
+# Stuff to do before list/restore/extract/compare
+# return command to get archive on stdout
+######################################################################
+sub setup_before_read {
+
+ my $op = shift(@_);
+ my $cmd;
+
+ &line();
+
+ if (($cfg::staticlogs eq 'false') and ($cfg::staticfiles eq 'false')) {
+ $::log = "flexbackup.$op." . &current_time('numeric') . ".log";
+ } else {
+ $::log = "flexbackup.$op.log";
+ }
+
+ if (! open(LOG,">$::log")) {
+ $::log = "$cfg::tmpdir/$::log";
+ if (! open(LOG,">$::log")) {
+ die "Can't write to $::log: $OS_ERROR";
+ }
+ }
+ close(LOG);
+
+ &log("| Logging output to \"$::log\"");
+
+ $::device = &maybe_get_filename();
+
+ &mt("generic-blocksize $::mt_blksize");
+
+ # Maybe retension
+ if (($::do_reten == 1) and !defined($::use_file)) {
+ &log('| Retensioning tape...');
+ &mt('retension');
+ }
+
+ if (defined($::opt{'num'})) {
+ &log("| Positioning tape at file number $::opt{num}");
+ &mt("rewind","fsf $::opt{num}");
+ } else {
+ if (defined($::use_pipe)) {
+ &log("| Reading from stdin (type=$cfg::type compress=$cfg::compress)");
+ } elsif (defined($::use_file)) {
+ &log("| Reading from on-disk file $::device");
+ } elsif (defined($::use_blockdevice)) {
+ &log("| Reading from block device $::device");
+ } else {
+ &log("| Reading from CURRENT TAPE POSITION");
+ }
+ }
+
+ &line();
+
+ if (!defined($::use_file)) {
+ &mt('generic-query');
+ &line();
+ }
+
+ $cmd = &read_function($::device);
+
+ if (defined($::remotetapehost)) {
+ $cmd = &maybe_remote_cmd($cmd, $::remotetapehost);
+ # Buffer both sides if remote
+ $cmd .= $::buffer_cmd;
+ }
+
+ $cmd .= " | $::unz ";
+
+ if ($::device =~ m/\.rpm$/) {
+ $cmd .= "rpm2cpio | ";
+ }
+
+ $cmd =~ s/\s+/ /g;
+
+ return($cmd);
+
+}
+
+######################################################################
+# Read from file/device - in future buffer cmds might need a blocking
+# dd read ahead of them
+######################################################################
+sub read_function {
+
+ my $file = shift(@_);
+ my $cmd;
+
+ # If reading from stdin arg is '-'
+ if ($file eq '-') {
+
+ $cmd = $::buffer_cmd;
+ $cmd =~ s/^\s*\|\s*//; # Nuke leading " | " we normally use
+
+ } else {
+
+ $cmd = $::read_cmd . '"' . $file . '"';
+
+ }
+
+ return($cmd);
+
+}
+
+######################################################################
+# Get rid of trailing slash on path or host:/path specs
+######################################################################
+sub nuke_trailing_slash {
+
+ my $spec = shift(@_);
+ my $host;
+ my $path;
+
+ if ($spec =~ s/(\S+:)//) {
+ $host = $1;
+ $path = $spec;
+ } else {
+ $host = '';
+ $path = $spec;
+ }
+
+ if ($path ne "/") {
+ $path =~ s%/$%%;
+ }
+
+ return($host . $path);
+
+}
+
+######################################################################
+# Print the volume label from an afio control file
+######################################################################
+sub print_afio_volume_header {
+ # for now just echo our stdin
+ print STDOUT "\n";
+ while(<STDIN>) {
+ print;
+ }
+ exit(0);
+}
+
+######################################################################
+# Figure out which of rewind/erase/reten we are going to assume
+######################################################################
+sub set_tape_operation_defaults {
+
+ # Assume stuff based on how we are called first
+ if (defined($::opt{'set'})) {
+ if (!defined($::set_incremental) and
+ ($::level == 0) and
+ !defined($::use_file)) {
+ # Set level zero, using device. Retension & erase a new tape
+ # (config file may tell us not to erase)
+ if ($cfg::erase_tape_set_level_zero eq "true") {
+ $::do_reten = 1;
+ $::do_erase = 1;
+ } else {
+ $::do_reten = 0;
+ $::do_erase = 0;
+ }
+ $::do_rewind_after = 1;
+ } else {
+ # Using files, set incremental backup, or set non-zero
+ # don't erase + go to end of tape
+ $::do_reten = 0;
+ $::do_erase = 0;
+ $::do_rewind_after = 1;
+ }
+ } elsif (defined($::opt{'dir'})) {
+ # Just one filesystem - assume we append to tape
+ $::do_reten = 0;
+ $::do_erase = 0;
+ $::do_rewind_after = 1;
+ } else {
+ # We're doing a read of some sort
+ $::do_reten = 0;
+ $::do_erase = 0; # -erase has no effect anyway here
+ $::do_rewind_after = 0;
+ }
+
+ # Then see if commandline flags override anything
+ if (defined($::opt{'reten'})) {
+ $::do_reten = $::opt{'reten'};
+ }
+ if (defined($::opt{'erase'})) {
+ $::do_erase = $::opt{'erase'};
+ }
+ if (defined($::opt{'rewind'})) {
+ $::do_rewind_after = $::opt{'rewind'};
+ }
+}
+
+######################################################################
+# Split long lines for echoing
+######################################################################
+sub split_and_echo {
+
+ my $string = shift(@_);
+ my $initial_tab;
+ my $subsequent_tab;
+
+ local($Text::Wrap::columns) = 76;
+
+ # Older perl's don't have this var. Use twice to shut up
+ # -w in that case. Output almost the same...
+ local($Text::Wrap::separator) = " \\\n";
+ local($Text::Wrap::separator) = " \\\n";
+
+ # This make it easier to cut-n-paste for debugging commands manually
+ if (defined($::debug)) {
+ $initial_tab = " ";
+ $subsequent_tab = " ";
+ } else {
+ $initial_tab = "| ";
+ $subsequent_tab = "| ";
+ }
+
+ my @lines = wrap($initial_tab, $subsequent_tab, ($string));
+ foreach (@lines) {
+ &log($_);
+ }
+
+}
+
+######################################################################
+# Create new tape "key" and return it (YYYYMMDDHHMMSS)
+# Also sets ::nextfile
+######################################################################
+sub new_tape_key {
+
+ my $key;
+ my $dev = $cfg::device;
+ my $old;
+ my $string;
+
+ return('') if $cfg::indexes eq "false";
+
+ $key = &current_time('numeric');
+
+ # If writing to a file see if there is already an index key and use it
+ if (defined($::use_file)) {
+ $dev .= "/$cfg::keyfile";
+ if (-r $dev) {
+ open(KEY,$dev) or die("Can't open existing key $dev: $OS_ERROR");
+ chomp($key = <KEY>);
+ close(KEY);
+
+ &log("| Directory's existing key is $key");
+
+ # Make sure keyfile entry is there
+ if (!defined($::index{"$key|$cfg::keyfile"})) {
+ my $label = "<index keyfile, dir=$cfg::device>";
+ if (defined($::debug)) {
+ &log("(debug) \$::index{$key|$cfg::keyfile} = $label");
+ } else {
+ $::index{"$key|$cfg::keyfile"} = $label;
+ }
+ }
+
+ # Figure out the existing files
+ foreach (sort keys %::index) {
+ my ($tape,$filenum) = split(/\|/,$_);
+ if ($tape eq $key) {
+ $::nextfile = $filenum;
+ }
+ }
+ # Set for the next file
+ $::nextfile++;
+ return($key);
+ }
+ }
+
+ &log("| Creating index key $key");
+ $string = "$::path{printf} \'$key\\nThis is a flexbackup index key\\n\' ";
+ $string = &append_writer_cmd($string, $dev);
+ if (defined($::debug)) {
+ &log("(debug) $string");
+ } else {
+ `$string 2> /dev/null`;
+ }
+
+ $::nextfile = 1;
+
+ if (defined($::use_file)) {
+ my $label = "<index keyfile, dir=$cfg::device>";
+ if (defined($::debug)) {
+ &log("(debug) \$::index{$key|$cfg::keyfile} = $label");
+ } else {
+ $::index{"$key|$cfg::keyfile"} = $label;
+ }
+ } else {
+ my $label = "<tape index key>";
+ if (defined($::debug)) {
+ &log("(debug) \$::index{$key|0} = $label");
+ } else {
+ $::index{"$key|0"} = $label;
+ }
+ }
+
+ # So that we won't generate duplicate keys...
+ # (as long as two processes with -newtape aren't run in parallel)
+ sleep(1);
+
+ return($key);
+}
+
+######################################################################
+# Get existing index key
+# Also sets ::nextfile
+######################################################################
+sub get_tape_key {
+
+ my $quiet = shift(@_);
+ my $key;
+
+ return('') if $cfg::indexes eq "false";
+
+ # If writing to a file see if there is already an index key and use it
+ if (defined($::use_file)) {
+ my $dev = "$cfg::device/$cfg::keyfile";
+ if (-r $dev) {
+ open(KEY,$dev) or die("Can't open existing key $dev: $OS_ERROR");
+ chomp($key = <KEY>);
+ close(KEY);
+ } else {
+ return(&new_tape_key());
+ }
+
+ } else {
+
+ my $string = "$::path{dd} $::dd_blk_flag $::dd_write_pad_flag count=1 if=$::device";
+ if (defined($::remotetapehost)) {
+ $string = &maybe_remote_cmd($string, $::remotetapehost);
+ }
+
+ if (defined($::debug)) {
+ &log("(debug) $string");
+ $key = '';
+ } else {
+ $key = `$string 2> /dev/null`;
+ @_ = split(/\n/,$key);
+ $key = $_[0];
+ }
+
+ if (defined($key)) {
+ chomp($key);
+ if ($key !~ m/^\d+$/) {
+ if (!defined($quiet)) {
+ &log("| ERROR: Tape doesn't have an index! (use -newtape?)");
+ }
+ $::nextfile = 0;
+ return('');
+ }
+ } else {
+ if (!defined($quiet)) {
+ &log("| ERROR: Tape doesn't have an index! (use -newtape?)");
+ }
+ $::nextfile = 0;
+ return('');
+ }
+
+ }
+
+ # Find the number of existing files
+ $::nextfile = 0;
+
+ unless (defined($::use_file)) {
+ foreach (sort keys %::index) {
+ my ($tape,$filenum) = split(/\|/,$_);
+ if ($tape eq $key) {
+ if ($filenum > $::nextfile) {
+ $::nextfile = $filenum;
+ }
+ }
+ }
+ # Set for the next file
+ $::nextfile++;
+ &log("| Found index key $key, next file is $::nextfile");
+ } else {
+ &log("| Found directory index key $key");
+ }
+
+ return($key);
+
+}
+
+######################################################################
+# Print table of contents
+# Can give a specific key as argument
+# Or uses command flag (specific key, current tape/dir, or "all")
+######################################################################
+sub toc_routine {
+
+ my $arg = shift(@_);
+ my %desired_keys;
+ my $tape;
+ my $desired;
+ my $label;
+ my $dir;
+ my $file;
+ my %tape_files;
+ my %disk_files;
+
+ return if $cfg::indexes eq "false";
+
+ if (defined($arg)) {
+
+ # Print toc for current tape if given argument
+ $desired_keys{$arg} = 1;
+
+ } elsif ($::opt{'toc'} =~ m/^\d+$/) {
+
+ # Print toc for a specific tape
+ &log("| Listing specific index");
+ $desired_keys{"$::opt{toc}"} = 1;
+ &line();
+
+ } elsif ($::opt{'toc'} eq '') {
+
+ # Print toc for current tape/device
+ &mt('rewind');
+ my $key = &get_tape_key();
+ &mt('rewind');
+ if ($key ne '') {
+ $desired_keys{$key} = 1;
+ }
+ &line();
+
+ } elsif ($::opt{'toc'} eq "all") {
+
+ # Print everything we know about
+ &log("| Listing all in database");
+ foreach (keys %::index) {
+ ($tape,$file) = split(/\|/,$_);
+ $desired_keys{$tape} = 1;
+ }
+ &line();
+
+ } else {
+ die("Invalid key spec $::opt{toc}");
+ }
+
+ # Go through the index and fill hashes
+ foreach my $key (keys %::index) {
+ ($tape,$file) = split(/\|/,$key);
+ if ($file =~ m/^\d+$/) {
+ $tape_files{$tape}{$file} = $::index{$key};
+ } else {
+ $disk_files{$tape}{$file} = $::index{$key};
+ }
+ }
+
+ # Print the toc of each tape in our desired list
+ foreach $desired (sort bynumber keys %desired_keys) {
+
+ my $found = 0;
+ my $length = 45;
+
+ foreach $tape (sort bynumber keys %tape_files) {
+ if ($tape eq $desired) {
+ $found = 1;
+ &log('');
+ &log("File Contents (tape index $tape)");
+ &log("-" x $length);
+ foreach $file (sort bynumber keys %{$tape_files{$tape}}) {
+ $_ = sprintf("%-04s",$file);
+ &log($_ . " " . $tape_files{$tape}{$file});
+ }
+ }
+ }
+
+ foreach $dir (sort bynumber keys %disk_files) {
+ if ($dir eq $desired) {
+ my @array;
+ $found = 1;
+ foreach $file (sort keys %{$disk_files{$dir}}) {
+ if ((! -e "$cfg::device/$file") and
+ (!defined($::opt{'toc'}) or ($::opt{'toc'} eq ''))) {
+ &log("| Bogus index entry - $file does not exist");
+ &rmindex("$dir:$file");
+ delete $disk_files{$dir}{$file};
+ }
+ }
+ &log('');
+ &log("File Contents (dir index $dir)");
+ &log("-" x $length);
+ foreach $file (keys %{$disk_files{$dir}}) {
+ push(@array, $file . " " . $disk_files{$dir}{$file});
+ }
+ foreach (sort byfilename @array) {
+ &log($_);
+ }
+ }
+ }
+
+ if ($found == 0) {
+ &log("Key $desired not found in index");
+ }
+
+ &log('');
+
+ }
+
+}
+
+######################################################################
+# Nuke stuff from DB
+######################################################################
+sub rmindex {
+
+ my $arg = shift(@_);
+ my $key;
+ my $tape;
+ my $filenum;
+ my $file;
+ my $found = 0;
+
+ return if $cfg::indexes eq "false";
+
+ # Figure out if we delete all for one tape, single entry for one tape,
+ # or the entire db
+ if ($arg =~ m/^(\d+)(:all)?$/) {
+ $key = $1;
+ } elsif ($arg =~ m/^(\d+):(.+)$/) {
+ $key = $1;
+ $file = $2;
+ } elsif ($arg eq "all") {
+ &log("| Removing all in database!!!");
+ &log("| Hit CTRL-C to abort within 5 seconds..");
+ &line();
+ sleep(5);
+ foreach (keys %::index) {
+ delete $::index{$_};
+ }
+ return;
+ } else {
+ die("Invalid key or key:fileno spec $arg");
+ }
+
+
+ if ($key =~ m/^\d+$/) {
+
+ # This section deletes a whole index record, or maybe just
+ # individual file records
+ foreach (sort keys %::index) {
+ ($tape,$filenum) = split(/\|/,$_);
+
+ if (defined($file)) {
+ # One file entry
+ if (($tape eq $key)
+ and
+ (defined($::use_file) or ($filenum != 0))
+ and
+ ($filenum eq $file)) {
+ &log("| Deleting record for $tape file $filenum");
+ $found++;
+ if (defined($::debug)) {
+ &log("(debug) delete \$::index{$tape|$filenum}");
+ } else {
+ delete $::index{"$tape|$filenum"};
+ }
+ }
+
+ } else {
+
+ # Whole tape/dir entry
+ if ($tape eq $key) {
+ &log("| Deleting record for $tape file $filenum");
+ $found++;
+ if (defined($::debug)) {
+ &log("(debug) delete \$::index{$tape|$filenum}");
+ } else {
+ delete $::index{"$tape|$filenum"};
+ }
+ }
+ }
+ }
+
+ if ($found eq 0) {
+ &log("| Record for $arg not found");
+ }
+
+ &line();
+ return;
+ }
+}
+
+######################################################################
+# Nuke file from on disk, and stuff from DB
+######################################################################
+sub rmfile {
+
+ my $key;
+ my $tape;
+ my $filenum;
+
+ return if !defined($::use_file);
+
+ $key = &get_tape_key('quiet');
+
+ foreach my $arg (@{$::opt{'rmfile'}}) {
+
+ my $file = "$cfg::device/$arg";
+
+ if ($arg eq 'all') {
+ # Nuke all files in this dir
+ opendir(DIR,$cfg::device) or die ("Can't open dir $cfg::device: $OS_ERROR");
+ foreach my $f (readdir(DIR)) {
+ next if ($f =~ m:^\.\.?$:);
+ #next if ($f =~ m%^$cfg::keyfile$%);
+ if ( -f "$cfg::device/$f") {
+ &log("| Erasing archive $f");
+ unlink("$cfg::device/$f") or die ("Can't rm $cfg::device/$f: $OS_ERROR");
+ }
+ if ( -d "$cfg::device/$f") {
+ &log("| Erasing directory $f");
+ system("rm -rf $cfg::device/$f") and die ("Can't rm $cfg::device/$f: $OS_ERROR");
+ }
+ }
+ closedir(DIR);
+ # Nuke all db entries for this key
+ if ($key ne '') {
+ &rmindex("$key:all");
+ }
+ } elsif (-f $file) {
+ &log("| Deleting file $file");
+ unlink($file) or die ("Can't rm $file: $OS_ERROR");
+ if ($key ne '') {
+ # Nuke db entry for this file
+ &rmindex("$key:$arg");
+ }
+ } elsif (-d $file) {
+ &log("| Deleting directory $file");
+ system("rm -rf $file") and die ("Can't rm $file: $OS_ERROR");
+ if ($key ne '') {
+ # Nuke db entry for this file
+ &rmindex("$key:$arg");
+ }
+ } else {
+ warn("Error: $file doesn't exist");
+ }
+ }
+}
+
+######################################################################
+# Remove index records for a tape we are about to erase
+######################################################################
+sub maybe_delete_old_index {
+
+ my $key;
+
+ return if $cfg::indexes eq "false";
+
+ return if (defined($::use_file));
+
+ $key = &get_tape_key('quiet');
+ if ($key ne '') {
+ &rmindex("$key:all");
+ }
+
+}
+
+######################################################################
+# Sort by number
+######################################################################
+sub bynumber {
+ $a <=> $b;
+}
+
+
+######################################################################
+# Sort by archive filename
+######################################################################
+sub byfilename {
+
+ return 0 if ($a =~ m/^$cfg::keyfile/);
+ return 1 if ($b =~ m/^$cfg::keyfile/);
+
+ my $alabel;
+ my $alevel;
+ my $blabel;
+ my $blevel;
+
+ if ($a =~ m/^(.+?)\.(\d+)(\.(\d+))?\./) {
+ $alabel = $1;
+ $alevel = $2;
+ if ($b =~ m/^(.+?)\.(\d+)(\.(\d+))?\./) {
+ $blabel = $1;
+ $blevel = $2;
+
+ if ($alabel eq $blabel) {
+ return($alevel <=> $blevel);
+ }
+ }
+ }
+
+ return($a cmp $b);
+}
+
+
+######################################################################
+# Figure out numeric level for '-level incremental', for a certain fs.
+# Try to find last the stamp file, then add one to the level
+######################################################################
+sub get_incremental_level {
+
+ my $fs = shift(@_);
+
+ my $label = &get_label($fs);
+ my $highestlevel = 0;
+
+ opendir(DIR,"$cfg::stampdir") or die("Can't open $cfg::stampdir: $OS_ERROR");
+ foreach my $file (readdir(DIR)) {
+ next if ($file !~ m/^$cfg::sprefix$label\.(\d+)$/);
+ if ($1 > $highestlevel) {
+ $highestlevel = $1;
+ }
+ }
+ close(DIR);
+
+ $highestlevel++;
+
+ return($highestlevel);
+
+}
+
+######################################################################
+# Common commands to invoke 'find' & get a desired file list on stdout
+######################################################################
+sub file_list_cmd {
+
+ my $dir = shift(@_);
+ my $timestampfile = shift(@_);
+ my $separator = shift(@_);
+ my $level = shift(@_);
+ my $remote = shift(@_);
+ my $otherarg = shift(@_);
+
+ if (!defined($separator) or ($separator !~ m/^(null|newline)$/)) {
+ $separator = 'null';
+ }
+
+ my $cmd = '';
+ # FreeBSD wants -E to enable extended regex
+ if ($::uname =~ /FreeBSD/) {
+ $cmd .= "$::path{find} -E . ";
+ } else {
+ $cmd .= "$::path{find} . ";
+ }
+
+ my $prunekey;
+ if (defined($remote)) {
+ $prunekey = "$remote:$dir";
+ } else {
+ $prunekey = $dir;
+ }
+
+ if (defined(%{$::prune{$prunekey}})) {
+ # FreeBSD needs -E (above) and no backslashes around the (|) chars
+ if ($::uname =~ /FreeBSD/) {
+ $cmd .= '-regex "\./(';
+ $cmd .= join('|', keys %{$::prune{$prunekey}});
+ $cmd .= ')/.*" ';
+ } else {
+ $cmd .= '-regex "\./\(';
+ $cmd .= join('\|', keys %{$::prune{$prunekey}});
+ $cmd .= '\)/.*" ';
+ }
+ $cmd .= '-prune -o ';
+ } else {
+ # Can't use find -depth with -prune (see single unix spec etc)
+ # (not toally required anyway, only if you are archiving dirs you
+ # don't have permissions on and are running as non-root)
+ $cmd .= "-depth ";
+ }
+
+ $cmd .= "$::mountpoint_flag ";
+ $cmd .= "! -type s ";
+
+ if (defined($otherarg)) {
+ $cmd .= $otherarg . " ";
+ }
+
+ if ($level != 0) {
+
+ # If local, we can use the flexbackup timetamp native and ctime
+ # checks can be used. Remote, we'll be creating stamp with "touch
+ # -t"... but ctime can't be touched backwards. Turn it off.
+ #
+ # If atime preserve is set, can't use ctime checks anyway since
+ # preserving atime changes the ctime.
+
+ if (($cfg::atime_preserve eq 'false') and !defined($remote)) {
+ $cmd .= '\( ';
+ }
+
+ $cmd .= "-newer \"$timestampfile\" ";
+
+ if (($cfg::atime_preserve eq 'false') and !defined($remote)) {
+ $cmd .= "-or -cnewer \"$timestampfile\" " . '\) ';
+ }
+ }
+
+ $cmd .= "$::exclude_expr ";
+
+ if (!defined($::pkgdelta)) {
+ if ($separator eq 'newline') {
+ $cmd .= "-print ";
+ } else {
+ $cmd .= "-print0 ";
+ }
+
+ } else {
+
+ # Use the normal level & timestamp mechanism to get a list of files
+ # Then only keep unowned or owned+changed files
+
+ my $host;
+ my $find = &maybe_remote_cmd("cd \"$dir\"; $cmd -print", $remote);
+ my $write = "> $::pkgdelta_filelist";
+ if(defined($remote)) {
+ &log("| Listing level $level to-be-archived files for $remote:$dir");
+ $write = &maybe_remote_cmd("$::path{cat} $write", $remote);
+ $write = "| $write";
+ $host = $remote;
+ } else {
+ &log("| Listing level $level to-be-archived files for $dir");
+ $host = 'localhost';
+ }
+ &log("| Finding subset of files based on packaging system delta");
+ if (!defined($::debug)) {
+ open(LIST,"$find |") || die;
+ open(NEWLIST,"$write") || die;
+ while(<LIST>) {
+
+ my $key;
+ my $archive = 0;
+ chomp(my $file = $_);
+
+ # Strip leading ./
+ $file =~ s:^\./::g;
+
+ # Don't care about the backup dir itself
+ next if ($file eq '.');
+
+ if ($dir eq '/') {
+ $key = "/$file";
+ } else {
+ $key = "$dir/$file";
+ }
+
+ if (($cfg::pkgdelta_archive_unowned eq 'true') and
+ !defined($::packaged{$host}{$key})) {
+ $archive = 1;
+ }
+
+ if (($cfg::pkgdelta_archive_changed eq 'true') and
+ defined($::changed{$host}{$key})) {
+ $archive = 1;
+ }
+
+ if ($archive == 1) {
+ if ($separator eq 'null') {
+ print NEWLIST "./$file\0";
+ } else {
+ print NEWLIST "./$file\n";
+ }
+ }
+
+ }
+ close(LIST);
+ close(NEWLIST);
+ }
+
+ &line();
+
+ $cmd = "$::path{cat} $::pkgdelta_filelist ";
+ }
+
+ return($cmd);
+
+}
+
+######################################################################
+# List installed packages, fills %package_list hash
+######################################################################
+sub list_packages {
+
+ my $host = shift (@_);
+ my $cnt = 0;
+
+ if ($::pkgdelta eq 'rpm') {
+
+ my $cmd = "$::path{rpm} -q -a --queryformat '%{name}-%{version}-%{release}.%{arch}.rpm\\n'";
+
+ if ($host ne 'localhost') {
+ &log("| Identifying all RPM packages on host $host...");
+ $cmd = &maybe_remote_cmd($cmd, $host);
+ } else {
+ &log("| Identifying all RPM packages...");
+ }
+ if (defined($::debug)) {
+ &log("(debug) $cmd");
+ } else {
+ open(LIST,"$cmd |") || die;
+ while(<LIST>) {
+ if (m:^(.*)$:) {
+ $::package_list{$host}{$1} = 1;
+ if (&POSIX::isatty($::msg)) {
+ print $::msg &spinner(++$cnt) . "\r";
+ }
+ }
+ }
+ close(LIST);
+ }
+
+ } elsif ($::pkgdelta eq 'freebsd') {
+
+ my $cmd = "$::path{pkg_info}";
+
+ if ($host ne 'localhost') {
+ &log("| Identifying all FreeBSD packages on host $host...");
+ $cmd = &maybe_remote_cmd($cmd, $host);
+ } else {
+ &log("| Identifying all FreeBSD packages...");
+ }
+ if (defined($::debug)) {
+ &log("(debug) $cmd");
+ } else {
+ my (@junk, $pkg);
+ open(LIST,"$cmd |") || die;
+ while(<LIST>) {
+ if (&POSIX::isatty($::msg)) {
+ print $::msg &spinner(++$cnt) . "\r";
+ }
+ ($pkg, @junk) = split (/\s+/, $_);
+ $::package_list{$host}{$pkg} = 1;
+ }
+ close(LIST);
+ }
+
+ }
+
+}
+
+######################################################################
+# Fill %packaged with a list of files on host owned by packages
+######################################################################
+sub find_packaged_files {
+
+ my $host = shift (@_);
+ my $cnt = 0;
+
+ return if ($cfg::pkgdelta_archive_unowned eq 'false');
+
+ if ($::pkgdelta eq 'rpm') {
+
+ my $cmd = "$::path{rpm} -q -a -l";
+
+ if ($host ne 'localhost') {
+ &log("| Finding all files owned by RPM packages on host $host...");
+ $cmd = &maybe_remote_cmd($cmd, $host);
+ } else {
+ &log("| Finding all files owned by RPM packages...");
+ }
+ if (defined($::debug)) {
+ &log("(debug) $cmd");
+ } else {
+ open(LIST,"$cmd |") || die;
+ while(<LIST>) {
+ if (m:^(/.*)$:) {
+ $::packaged{$host}{$1} = 1;
+ if (&POSIX::isatty($::msg)) {
+ print $::msg &spinner(++$cnt) . "\r";
+ }
+ }
+ }
+ close(LIST);
+ }
+
+ } elsif ($::pkgdelta eq 'freebsd') {
+
+ my $cmd = "$::path{pkg_info} -f -q -a";
+ my ($fullpath, $localbase, $alt_localbase);
+ $localbase = '/usr/local';
+ $alt_localbase = '';
+ $fullpath = '';
+
+ if ($host ne 'localhost') {
+ &log("| Finding all files owned by FreeBSD packages on host $host...");
+ $cmd = &maybe_remote_cmd($cmd, $host);
+ } else {
+ &log("| Finding all files owned by FreeBSD packages...");
+ }
+ if (defined($::debug)) {
+ &log("(debug) $cmd");
+ } else {
+ open(LIST,"$cmd 2> /dev/null |") || die;
+ while(<LIST>) {
+ # If it starts with '@' then it's a pkg directive,
+ # else it's a (relative) path
+ #
+ if (/^\@/) {
+ if (/\@cwd\s+(\S+)/) {
+ my ($name, $path, $suffix);
+
+ $localbase = $1;
+ $alt_localbase = '';
+ ($name,$path,$suffix) = fileparse($localbase,'\.\S+');
+ $path =~ s/\/$//;
+ # In some (default) situations there are some packages which are
+ # installed relative to a PREFIX which is actually a link in the /
+ # filesystem. The following hack gets around that and creates an
+ # entry in $packaged twice--once for the full path that would be seen via
+ # pkg_info -L and one for the "unlinked" version. In this manner
+ # no matter which FS is being dumped, the code to filter out
+ # packaged files will always work.
+ #
+ if (-l $path) {
+ my $link;
+ $link = readlink ($path);
+ $link = '/' . $link . '/' . $name;
+ $alt_localbase = $link;
+ }
+ }
+ if (/\@dirrm\s+(\S+)/) {
+ $fullpath = $localbase . '/' . $1;
+ $::packaged{$host}{$fullpath} = 1;
+ if ($alt_localbase ne '') {
+ $fullpath = $alt_localbase . '/' . $1;
+ $::packaged{$host}{$fullpath} = 1;
+ }
+ if (&POSIX::isatty($::msg)) {
+ print $::msg &spinner(++$cnt) . "\r";
+ }
+ }
+ }
+ else {
+ $fullpath = $localbase . '/' . $_;
+ chomp ($fullpath);
+ $::packaged{$host}{$fullpath} = 1;
+ if ($alt_localbase ne '') {
+ $fullpath = $alt_localbase . '/' . $_;
+ chomp ($fullpath);
+ $::packaged{$host}{$fullpath} = 1;
+ }
+ if (&POSIX::isatty($::msg)) {
+ print $::msg &spinner(++$cnt) . "\r";
+ }
+ }
+ }
+ close(LIST);
+ }
+ }
+}
+
+
+######################################################################
+# Fill %changed with a list of packaged files on host that have been
+# modified
+######################################################################
+sub find_changed_files {
+
+ my $host = shift (@_);
+ my $cnt = 0;
+
+ return if ($cfg::pkgdelta_archive_changed eq 'false');
+
+ if ($::pkgdelta eq 'rpm') {
+
+ my $cmd = "$::path{rpm} -V -a";
+ my ($num);
+
+ if ($host ne 'localhost') {
+ &log("| Finding changed package files on host $host...");
+ $cmd = &maybe_remote_cmd($cmd, $host);
+ } else {
+ &log("| Finding changed package files...");
+ }
+
+ $num = scalar (keys %{$::package_list{$host}});
+
+ &log("| Analyzing $num packages may take quite a while, please be patient");
+ if (defined($::debug)) {
+ &log("(debug) $cmd");
+ } else {
+ open(LIST,"$cmd |") || die;
+ while(<LIST>) {
+ if (&POSIX::isatty($::msg)) {
+ print $::msg &spinner(++$cnt) . "\r";
+ }
+ # ex: if size, md5sum, and timestamp changed on a config file
+ # S.5....T c /etc/ntp.conf
+ if (m:^([\.S][\.M][\.5][\.D][\.L][\.U][\.G][\.T]) [dgc ] (.*)$:) {
+ $::changed{$host}{$2} = 1;
+ }
+ }
+ close(LIST);
+ }
+
+ } elsif ($::pkgdelta eq 'freebsd') {
+
+ my $cmd = "$::path{pkg_info} -g -a -q";
+ my ($num);
+
+ if ($host ne 'localhost') {
+ &log("| Finding changed package files on host $host...");
+ $cmd = &maybe_remote_cmd($cmd, $host);
+ } else {
+ &log("| Finding changed package files...");
+ }
+
+ $num = scalar (keys %{$::package_list{$host}});
+
+ &log("| Analyzing $num packages may take quite a while, please be patient");
+ if (defined($::debug)) {
+ &log("(debug) $cmd");
+ } else {
+ open(LIST,"$cmd 2> /dev/null |") || die;
+ while(<LIST>) {
+ if (&POSIX::isatty($::msg)) {
+ print $::msg &spinner(++$cnt) . "\r";
+ }
+ if (/^(\S+)\s+fails.*MD5.*checksum$/) {
+ $::changed{$host}{$1} = 1;
+ }
+ }
+ close(LIST);
+ }
+
+ }
+}
+
+#############################################################################
+# Actually test to see if we can run buffer. In situations where SysV shared
+# memory is low, or buffer can't run, buffer can fail
+#############################################################################
+sub test_bufferprog {
+
+ my $buffer_cmd = shift(@_);
+ my $host = shift(@_);
+ my $tmp_script = "$cfg::tmpdir/buftest.$host.$PROCESS_ID.sh";
+ my $retval = 0;
+ my $pipecmd;
+
+ $buffer_cmd =~ s:^\s*\|\s*::;
+ $buffer_cmd =~ s:\s*\|\s*$::;
+
+ # Create a script which tests the buffer program
+ open(SCR,"> $tmp_script") || die;
+ print SCR "#!/bin/sh\n";
+ print SCR "tmp_data=/tmp/bufftest\$\$.txt\n";
+ print SCR "tmp_err=/tmp/bufftest\$\$.err\n";
+ print SCR "echo testme > \$tmp_data\n";
+ print SCR "$buffer_cmd > /dev/null 2> \$tmp_err < \$tmp_data\n";
+ print SCR "res=\$?\n";
+ print SCR "out=\`cat \$tmp_err\`\n";
+ print SCR "if [ \$res -eq 0 ]; then\n";
+ print SCR " echo successful\n";
+ print SCR "else\n";
+ print SCR " echo \"unsuccessful: exit code \$res: \$out\" \n";
+ print SCR "fi\n";
+ print SCR "rm -f \$tmp_data \$tmp_err\n";
+ close(SCR);
+
+ if ($host eq 'localhost') {
+ print $::msg "| Checking '$cfg::buffer' on this machine... ";
+ $pipecmd = "sh $tmp_script ";
+ } else {
+ print $::msg "| Checking '$cfg::buffer' on host $host... ";
+ $pipecmd = "cat $tmp_script | ($::remoteshell $host 'cat > $tmp_script; sh $tmp_script; rm -f $tmp_script')";
+ }
+
+ if (!defined($::debug)) {
+
+ open(PIPE,"$pipecmd |") || die;
+ while (<PIPE>) {
+ if (/^unsuccessful: exit code (\d+): (.*)/) {
+ $retval = $1;
+ my $out = $2;
+ if ($retval != 0) {
+ push(@::errors, "Problems encountered testing '$cfg::buffer' on host '$host':");
+
+ if ($out ne '') {
+ push(@::errors, " --> " . $out);
+ }
+
+ if (($cfg::buffer eq 'buffer') and ($retval == 255)) {
+ push(@::errors, " You don't have enough shared memory to run '$cfg::buffer' on $host, or");
+ push(@::errors, " have exceeded buffering limits. Try lowering the amount specified in");
+ push(@::errors, " \$buffer_megs in your flexbackup.conf file, or reconfigure your");
+ push(@::errors, " kernel to include more SysV shared memory pages if using *BSD.");
+ } else {
+ push(@::errors, " Unknown problem trying to run '$cfg::buffer' (exit code $retval). Try disabling it");
+ push(@::errors, " or lowering \$buffer_megs.");
+ }
+ }
+ }
+ }
+ close (PIPE);
+
+ } else {
+ print $::msg "\n(debug) $pipecmd\n";
+ }
+
+ if ($retval == 0) {
+ print $::msg "Ok\n";
+ } else {
+ print $::msg "Failed!\n";
+ }
+ unlink("$tmp_script");
+
+ return($retval);
+}
+
+
+#############################################################################
+# Check that programs exist on remote systems
+# Check buffer execution on them too
+#############################################################################
+sub check_remote_progs {
+
+ my $remotehost_ref = shift(@_);
+ my $remoteprogs_ref = shift(@_);
+ my $err = 0;
+ my @progs;
+
+ foreach my $host (keys %$remotehost_ref) {
+ &check_shell($host);
+ }
+
+ foreach (@$remoteprogs_ref) {
+ # Could be '0' if original checkinpath failed on localhost
+ if ($_ ne '0') {
+ push(@progs,"type $_ 2>&1");
+ } else {
+ $err++;
+ }
+ }
+ my $string = join ('; ',@progs);
+ foreach my $host (keys %$remotehost_ref) {
+ print $::msg "| Checking for required programs on host $host... ";
+ my $cmd = "$::remoteshell $host \"sh -c '$string'\"";
+ if (defined($::debug)) {
+ print $::msg "\n(debug) $cmd\n";
+ next;
+ }
+ if (!(open(PIPE,"$cmd |"))) {
+ push (@::errors, "Could not open pipe to remote shell - $!");
+ $err++;
+ last;
+ }
+
+ while (<PIPE>) {
+ if (m/(\S+) not found/) {
+ push(@::errors, "Could not find program '$1' on remote machine '$host'");
+ $err++;
+ }
+ }
+ close (PIPE);
+
+ if ($err == 0) {
+ print $::msg "Ok\n";
+ } else {
+ print $::msg "Failed!\n";
+ }
+
+ }
+
+ if ($cfg::buffer ne 'false') {
+ foreach my $host (keys %$remotehost_ref) {
+ &test_bufferprog($::buffer_cmd, $host);
+ }
+ }
+
+}
+
+#############################################################################
+# Check shell on remote systems
+# (Mainly to see if we should use bash pipe exit trick at this point)
+#############################################################################
+sub check_shell {
+
+ my $host = shift(@_);
+ my $pipecmd;
+
+ $pipecmd = 'set x = 1 && test $x && echo csh:yes; echo tcsh:$tcsh; echo bash:$BASH_VERSION; echo zsh:$ZSH_VERSION; echo ksh:$KSH_VERSION';
+
+ if ($host eq 'localhost') {
+ print $::msg "| Checking /bin/sh on this machine... ";
+ } else {
+ print $::msg "| Checking shell on $host... ";
+ $pipecmd = "$::remoteshell $host '" . $pipecmd . "'";
+ }
+
+ $::shelltype{$host} = 'unknown';
+
+ if (defined($::debug)) {
+ print $::msg "\n(debug) $pipecmd\n";
+ }
+
+ if (!(open(PIPE,"$pipecmd 2>&1 |"))) {
+ return;
+ }
+
+ while (<PIPE>) {
+
+ if (m/^(\S+):(\S.+)$/) {
+ my $shell = $1;
+ my $ver = $2;
+ if ($shell eq 'bash') {
+ if ($ver =~ m/^2/) {
+ $::shelltype{$host} = 'bash2';
+ } else {
+ $::shelltype{$host} = 'bash1';
+ }
+ } else {
+ $::shelltype{$host} = $shell;
+ }
+ }
+ }
+ close (PIPE);
+
+ if (($::shelltype{$host} eq 'unknown') and ($::uname !~ m/Linux/)) {
+ print $::msg "$::shelltype{$host} (probably Bourne Shell)\n";
+ } else {
+ print $::msg "$::shelltype{$host}\n";
+ }
+}
+
+
+#############################################################################
+# Wipe a tape for use.
+#############################################################################
+sub newtape () {
+
+ my $retval;
+
+ if (defined($::tapedevice)) {
+ &log('| Rewinding & erasing tape...');
+ }
+ &mt('rewind');
+ &maybe_delete_old_index();
+ &mt('rewind');
+ &mt('generic-erase');
+ $retval = &new_tape_key();
+
+ return($retval);
+}
+
+
+#############################################################################
+# Test writing a couple files to tape, then read & diff. To help make
+# sure filemarks, blocks, padding, are working as we need.
+#############################################################################
+sub test_tape_drive {
+
+ my $cmd;
+ my $tmp1 = "$cfg::tmpdir/test1.$PROCESS_ID";
+ my $tmp2 = "$cfg::tmpdir/test2.$PROCESS_ID";
+ my $tmp3 = "$cfg::tmpdir/test3.$PROCESS_ID";
+ my $fail = 0;
+ my $configfile;
+
+ if (defined($::opt{'c'})) {
+ $configfile = $::opt{'c'};
+ } else {
+ $configfile = $::CONFFILE;
+ }
+
+ &mt("generic-blocksize $::mt_blksize");
+
+ &log("| Testing will *erase* the tape currently in the drive!");
+ &log("| Hit CTRL-C to abort within 10 seconds...");
+ &line();
+ sleep(10);
+ &log("| If for some reason this program does not exit within a few minutes,");
+ &log("| Hit CTRL-C, and try adjusting \$blksize, \$pad_blocks, or \$mt_blksize.");
+ &line();
+
+ &newtape();
+ &line();
+
+ &mt('generic-query');
+ &log('');
+ &log("Writing test file \#1");
+ $cmd = "$::path{cat} $0";
+ $cmd = &append_writer_cmd($cmd);
+ if (!defined($::debug)) {
+ system($cmd);
+ if ($CHILD_ERROR) {
+ $fail++;
+ }
+ } else {
+ &log($cmd);
+ }
+
+ &mt('generic-query');
+ &log("Writing test file \#2");
+ $cmd = "$::path{cat} $configfile";
+ $cmd = &append_writer_cmd($cmd);
+ if (!defined($::debug)) {
+ system($cmd);
+ if ($CHILD_ERROR) {
+ $fail++;
+ }
+ } else {
+ &log($cmd);
+ }
+
+ &mt('generic-query');
+ &log("Writing test file \#3");
+ $cmd = "$::path{cat} $0";
+ $cmd = &append_writer_cmd($cmd);
+ if (!defined($::debug)) {
+ system($cmd);
+ if ($CHILD_ERROR) {
+ $fail++;
+ }
+ } else {
+ &log($cmd);
+ }
+
+ &mt('generic-query');
+ &log('');
+ &log('Rewinding...');
+ &mt('rewind');
+ if ($cfg::indexes eq 'true') {
+ &log('Skipping index label...');
+ &mt('fsf 1');
+ }
+ &mt('generic-query');
+ &log('');
+
+ &log("Reading test file \#1");
+ $cmd = &read_function($::device);
+ if (defined($::remotetapehost)) {
+ $cmd = &maybe_remote_cmd($cmd, $::remotetapehost);
+ # Buffer both sides if remote
+ $cmd .= $::buffer_cmd;
+ }
+ # if pad blocks was true we have nulls at the end (won't be in this script otherwise)
+ if ($cfg::pad_blocks eq 'true') {
+ $cmd .= " | $::path{tr} -d '\\0' > $tmp1";
+ } else {
+ $cmd .= "> $tmp1";
+ }
+ if (!defined($::debug)) {
+ system($cmd);
+ if ($CHILD_ERROR) {
+ $fail++;
+ }
+ } else {
+ &log("(debug) $cmd");
+ }
+
+ &mt('generic-query');
+ &log("Reading test file \#2");
+ $cmd = &read_function($::device);
+ if (defined($::remotetapehost)) {
+ $cmd = &maybe_remote_cmd($cmd, $::remotetapehost);
+ # Buffer both sides if remote
+ $cmd .= $::buffer_cmd;
+ }
+ # if pad blocks was true we have nulls at the end (won't be in config file otherwise)
+ if ($cfg::pad_blocks eq 'true') {
+ $cmd .= " | $::path{tr} -d '\\0' > $tmp2";
+ } else {
+ $cmd .= "> $tmp2";
+ }
+ if (!defined($::debug)) {
+ system($cmd);
+ if ($CHILD_ERROR) {
+ $fail++;
+ }
+ } else {
+ &log("(debug) $cmd");
+ }
+
+ &mt('generic-query');
+ &log("Reading test file \#3");
+ $cmd = &read_function($::device);
+ if (defined($::remotetapehost)) {
+ $cmd = &maybe_remote_cmd($cmd, $::remotetapehost);
+ # Buffer both sides if remote
+ $cmd .= $::buffer_cmd;
+ }
+ # if pad blocks was true we have nulls at the end (won't be in this script otherwise)
+ if ($cfg::pad_blocks eq 'true') {
+ $cmd .= " | $::path{tr} -d '\\0' > $tmp3";
+ } else {
+ $cmd .= "> $tmp3";
+ }
+ if (!defined($::debug)) {
+ system($cmd);
+ if ($CHILD_ERROR) {
+ $fail++;
+ }
+ } else {
+ &log("(debug) $cmd");
+ }
+
+ &mt('generic-query');
+ &log('');
+ &mt('rewind');
+ &log("Comparing...");
+ if (!defined($::debug)) {
+ system("$::path{diff} -q $0 $tmp1");
+ if ($CHILD_ERROR) {
+ $fail++;
+ }
+ system("$::path{diff} -q $configfile $tmp2");
+ if ($CHILD_ERROR) {
+ $fail++;
+ }
+ system("$::path{diff} -q $0 $tmp3");
+ if ($CHILD_ERROR) {
+ $fail++;
+ }
+ } else {
+ &log("(debug) $::path{diff} -q $0 $tmp1");
+ &log("(debug) $::path{diff} -q $configfile $tmp2");
+ &log("(debug) $::path{diff} -q $0 $tmp3");
+ }
+
+ unlink $tmp1;
+ unlink $tmp2;
+ unlink $tmp3;
+
+ if ($fail != 0) {
+ print $::msg "\nFAILURE! Problem with tape driver or parameters. Please see the FAQ\n";
+ print $::msg "or try changing the \$blksize, \$pad_blocks, or \$mt_blksize settings.\n";
+ exit(1);
+ } else {
+ print $::msg "SUCCESS! Tape drive parameters seem to work just fine\n";
+ }
+
+}
+
+
+######################################################################
+# Check if the week day is as specified before backup (for complex cron setups)
+######################################################################
+sub check_wday {
+
+ if (defined($::opt{'wday'})) {
+ my @now = localtime;
+ my $wday_now = $now[6];
+
+ # Just silently hard-limit these to valid set
+ if ($::opt{'wday'} >= 7) {
+ $::opt{'wday'} = 0;
+ }
+ if ($::opt{'wday'} < 0) {
+ $::opt{'wday'} = 0;
+ }
+
+ if ($wday_now != $::opt{'wday'}) {
+ exit(0);
+ }
+ }
+}
+
+######################################################################
+# Split whitespace-separated list.
+# If it contains quotes, do a bit differently so we can have
+# items containing whitespace, as long as all elements are quoted.
+######################################################################
+sub split_list {
+
+ my $string = shift(@_);
+ my @array;
+
+ if ($string =~ m/\"/) {
+ $string =~ s/^\s*\"//;
+ $string =~ s/\"\s*$//;
+ @array = split(/\"\s+\"/,$string);
+ } elsif ($string =~ m/\'/) {
+ $string =~ s/^\s*\'//;
+ $string =~ s/\'\s*$//;
+ @array = split(/\'\s+\'/,$string);
+ } else {
+ @array = split(/\s+/,$string);
+ }
+
+ return(@array);
+}
+
+
+######################################################################
+# To show activity....
+######################################################################
+sub spinner {
+
+ my $index = shift(@_);
+ my (@spinner) = ('|','/','-','\\','|','/','-','\\');
+
+ $index = $index % $#spinner;
+
+ return($spinner[$index]);
+}
diff --git a/flexbackup.1 b/flexbackup.1
new file mode 100644
index 0000000..12e8028
--- /dev/null
+++ b/flexbackup.1
@@ -0,0 +1,161 @@
+.TH "FLEXBACKUP" "1" "Oct 2003" "Flexbackup"
+.SH "NAME"
+flexbackup \- a flexible backup/archiving tool
+.SH "SYNOPSIS"
+\fBflexbackup\fR [\fIOPTION\fR]
+.SH "DESCRIPTION"
+Flexbackup is a perl script front-end to various low-level archiving
+utilities such as \fBtar\fR, \fBdump\fR/\fBrestore\fR, \fBcpio\fR,
+and others.
+.SH "BACKUP OPTIONS"
+.TP
+\fBflexbackup\fR \fI-dir\fR <\fIdir\fR>
+Backup a directory tree starting from path \(dqpath\(dq using level 0 (full).
+.TP
+\fBflexbackup\fR \fI-set\fR \fIall\fR
+Backup all sets defined in \fBflexbackup.conf(5)\fR using level 0 (full).
+.TP
+\fBflexbackup\fR \fI-set\fR <\fItag\fR>
+Backup a set named \(dqtag\(dq using level 0 (full). The set name is defined
+in \fIflexbackup.conf(5)\fR.
+.TP
+\fBflexbackup\fR [...] \fI-level\fR <\fI0-9\fR | \fIfull\fR | \fIdifferential\fR | \fIincremental\fR>
+Change backup level to a number \(dq0-9\(dq, or one of the symbolic names:
+\(dqfull\(dq (level 0); \(dqdifferential\(dq (level 1); \(dqincremental\(dq
+(previous backup level + 1).
+.TP
+\fBflexbackup\fR [...] \fI-pkgdelta\fR <\fIrpm\fR | \fIfreebsd\fR>
+prune backup to files not part of a package or changed from distributed version.
+.TP
+\fBflexbackup\fR [...] \fI-wday\fR <\fI0-7\fR>
+Prune backup to files not part of a package or changed from distributed version.
+backup only if the week day matches the input number. Sunday is 0 or 7.
+.TP
+\fBflexbackup\fR [...] \fI-pipe\fR
+Write backup data to stdout rather than file/device.
+.TP
+\fBflexbackup\fR [...] \fI-ignore-errors\fR
+Continue backups even if commands return error status
+.SH "RESTORE OPTIONS"
+.TP
+\fBflexbackup\fR \fI-list\fR
+List files in archive.
+.TP
+\fBflexbackup\fR \fI-extract\fR
+Extract (restore) all files from archive into your current working directory.
+.TP
+\fBflexbackup\fR \fI-extract\fR \fI-flist\fR <\fIfilelist\fR>
+Extract (restore) the files listed in text file \(dqfilelist\(dq into your
+current working directory.
+.TP
+\fBflexbackup\fR \fI-extract\fR \fI-flist\fR <\fIfilename\fR>
+Extract (restore) the single file named \(dqfilename\(dq into your current
+working directory.
+.TP
+\fBflexbackup\fR \fI-compare\fR
+Compare the archive with the files in your current working directory.
+.TP
+\fBflexbackup\fR \fI-restore\fR
+Interactive restore (\fBdump\fR type only for now).
+.TP
+\fBflexbackup\fR [...] \fI-num\fR <\fIn\fR>
+Read file number \(dqn\(dq from tape. If not given uses current tape position.
+.TP
+\fBflexbackup\fR [...] <\fIarchive\fR>
+If archiving to files rather than a device, use file named \(dqarchive\(dq for
+the \fIlist\fR/\fIextract\fR/\fIcompare\fR/\fIrestore\fR operations.
+.TP
+\fBflexbackup\fR [...] \fI-pipe\fR
+Read backup data from stdin rather than file/device.
+.SH "INDEXING OPTIONS"
+.TP
+\fBflexbackup\fR \fI-toc\fR
+List current device's table of contents.
+.TP
+\fBflexbackup\fR \fI-toc\fR \fIall\fR
+List all known table of contents.
+.TP
+\fBflexbackup\fR \fI-toc\fR <\fIkey\fR>
+List table of contents for specific database index key named \(dqkey\(dq.
+.TP
+\fBflexbackup\fR \fI-rmindex\fR \fIall\fR
+Force delete all database index info.
+.TP
+\fBflexbackup\fR \fI-toc\fR <\fIkey\fR>
+Force delete specified database tape/dir index key named \(dqkey\(dq.
+.TP
+\fBflexbackup\fR \fI-toc\fR <\fIkey\fR:\fIfile\fR>
+Force delete specified database tape/dir index key named \(dqkey\(dq,
+file number \(dqfile\(dq.
+.SH "MISCELLENEOUS OPTIONS"
+.TP
+\fBflexbackup\fR \fI-newtape\fR
+Erase and create new index key (but don't do any backups).
+.TP
+\fBflexbackup\fR \fI-rmfile\fR \fIall\fR
+If archiving to files rather than device, remove all files and index
+information.
+.TP
+\fBflexbackup\fR \fI-rmfile\fR <\fIfile\fR>
+If archiving to files rather than device, remove the file named \(dqfile\(dq
+and its index information.
+.TP
+\fBflexbackup\fR [...] \fI-c\fR <\fIfile.conf\fR>
+Use file named \(dqfile.conf\(dq for configuration information instead of
+\fB/etc/flexbackup.conf\fR(5).
+.TP
+\fBflexbackup\fR [...] \fI-type\fR <\fItype\fR>
+Override \fB$type\fR setting from config file with \(dqtype\(dq.
+.TP
+\fBflexbackup\fR [...] \fI-compress\fR <\fItype\fR>
+Override \fB$compress\fR setting from config file with \(dqtype\(dq.
+.TP
+\fBflexbackup\fR [...] \fI-device\fR <\fIdevice\fR>
+Override \fB$device\fR setting from config file with \(dqdevice\(dq.
+.TP
+\fBflexbackup\fR [...] \fI-d\fR <\fI'setting=value'\fR>
+Override \fB$setting\fR setting from config file with \(dqvalue\(dq.
+.TP
+\fBflexbackup\fR \fI-dir\fR <\fIdir\fR> \fI-erase\fR
+Force a rewind/erase before backup of directory named \(dqdir\(dq.
+.TP
+\fBflexbackup\fR \fI-dir\fR <\fIdir\fR> \fI-norewind\fR
+Do not rewind tape after a single backup of directory named \(dqdir\(dq.
+.TP
+\fBflexbackup\fR \fI-set\fR <\fItag\fR> \fI-noreten\fR
+Do not retension tape for level 0 (full) backups of set named \(dqtag\(dq.
+.TP
+\fBflexbackup\fR \fI-set\fR <\fItag\fR> \fI-noerase\fR
+Do not rewind/erase tape for level 0 (full) backups of set named \(dqtag\(dq.
+.TP
+\fBflexbackup\fR [...] \fI-reten\fR
+Force a retension of tape before reading.
+.TP
+\fBflexbackup\fR [...] \fI-defaults\fR
+Do not use any default values for config variables.
+.TP
+\fBflexbackup\fR \fI-version\fR
+Display version and exit.
+.SH "TESTING/DEBUGGING OPTIONS"
+.TP
+\fBflexbackup\fR \fI-test-tape-drive\fR
+Tries writing/reading files to make sure you have tape driver and parameters
+set up correctly in \fBflexbackup.conf\fR(5).
+.TP
+\fBflexbackup\fR [...] \fI-n\fR
+Don't run actual dump or mt commands, just print the steps that would be taken
+(dry run).
+.TP
+\fBflexbackup\fR [...] \fI-type\fR \fIfilelist\fR
+Run a special backup type that just saves a list of files that would have
+been archived.
+.SH "FILES"
+/etc/flexbackup.conf \- configuration settings
+.SH "REPORTING BUGS"
+Report bugs to (flexbackup-help@lists.sourceforge.net)
+.SH "AUTHOR"
+Written by Edwin Huffstutler (edwinh@computer.org)
+.SH "SEE ALSO"
+\fBflexbackup.conf\fR(5)
+\fBafio\fR(1) \fBmt\fR(1) \fBtar\fR(1) \fBstar\fR(1) \fBcpio\fR(1)
+\fBdump\fR(1) \fBrestore\fR(1) \fBbuffer\fR(1) \fBmbuffer\fR(1)
diff --git a/flexbackup.conf b/flexbackup.conf
new file mode 100644
index 0000000..04f5010
--- /dev/null
+++ b/flexbackup.conf
@@ -0,0 +1,236 @@
+# -*-Mode: perl-*-
+# ----------------------------------------------------------------------
+# Flexbackup configuration file
+# check "flexbackup -help" for usage information
+# ----------------------------------------------------------------------
+# General configuration section
+
+# Archive type? afio, dump, tar, cpio, star, pax, zip, lha, ar, shar
+# 'copy' or 'rsync' are extra options if running in archive-to-disk mode.
+# 'filelist' dumps a list of files in your cwd - for debugging setup/exclusion
+$type = 'afio';
+
+# Configure backup "sets".
+# Not needed if you use "-dir <dir>" to backup one tree at a time.
+# Each set is a simple space-separated list of filesystems
+# Remote filesystems should denoted as 'host:dir'
+# You can use anything (other than 'all') as set names
+#
+# Example:
+# $set{'set1'} = "/home /usr";
+# $set{'set2'} = "/dir3 machine2:/dir4 machine3:/dir5";
+#
+# "-set all" will back up all defined sets. If you are doing a full backup
+# using tapes, each "set" will go onto a different tape and you will be
+# prompted for tape change in between.
+#
+$set{'backup'} = "/home";
+
+# Subtree pruning
+# A space-separated list of directories to prune from each backup.
+# Key is a filesystem or host:dir spec as outlined above
+# regular expressions allowed (not shell-type wildcards!)
+$prune{'/'} = "tmp proc";
+
+# Compression
+$compress = 'gzip'; # one of false/gzip/bzip2/lzop/zip/compress/hardware
+$compr_level = '4'; # compression level (1-9) (for gzip/bzip2/lzop/zip)
+
+# Buffering program - to help streaming
+$buffer = 'buffer'; # one of false/buffer/mbuffer
+$buffer_megs = '10'; # buffer memory size (in megabytes)
+$buffer_fill_pct = '75'; # start writing when buffer this percent full
+$buffer_pause_usec = '100'; # pause after write (tape devices only)
+
+# Device to backup to. -->> non-rewinding version, please! <<--
+#
+# Examples:
+# Linux SCSI: /dev/nst0 Linux IDE: /dev/nht0
+# Linux ftape: /dev/nqft0 FreeBSD SCSI: /dev/nrsa0 (4.x compat device node)
+# FreeBSD SCSI: /dev/nsa0 (5.x)
+#
+# If a directory, will archive to files in that directory rather than a device
+# If "host:/dev/tapedevice", will use remote tape drive via rsh/ssh
+#
+$device = '/dev/tape';
+
+# Block size (in kilobytes!) to use for archive programs and dd. Default is
+# 10 for most things. Some tape drives need 32 or 64. Set to '0' to
+# disable all blocking
+$blksize = '10';
+
+# Block size (in bytes!) to use for the tape device, with "mt setblk" or
+# equivalent. If set to 0, will use "variable" block size for the tape
+# device (which is recommended). Comment out or set to "$blksize * 1024" to
+# have it be the same as the archiver block size above.
+$mt_blksize = "0";
+
+# Padding. True to pad blocks to blocksize
+# (devices only, not used when archiving to files)
+$pad_blocks = 'true';
+
+# Other global flags
+$remoteshell = 'ssh'; # command for remote shell (rsh/ssh/ssh2)
+$remoteuser = ''; # if non-null, secondary username for remote shells
+$label = 'true'; # somehow store identifying label in archive?
+$verbose = 'true'; # echo each file?
+$sparse = 'true'; # handle sparse files?
+$indexes = 'true'; # false to turn off all table-of-contents support
+
+# If backing up to files, use static filenames - no date stamp
+# (same level backup of same directory will overwrite old backups)
+$staticfiles = 'false';
+
+# True to try and preserve file access times during backup, if the selected
+# archive program can do so. Note that if this is true, -cnewer checks (file
+# permission/status changes only, not content) are turned off when deciding
+# which files to archive on the local system.
+$atime_preserve = 'false';
+
+# Span across filesytems? ("dump" will ignore this option)
+# Set to "false" (don't) , "local" (all but nfs/smbfs), or "all" (everything)
+$traverse_fs = 'false';
+
+# Exclude files that match these *regular expressions* (not shell wildcards)
+# from the backups (no affect on 'dump' archives). You can list more than one,
+# just keep incrementing the index in the brackets for each. Also, strip off
+# leading directories (the filesystem specs above or the "-dir" flag).
+# Matches paths, not filenames, so put .* on the front/back as needed.
+# Comment these out to exclude nothing.
+$exclude_expr[0] = '.*/[Cc]ache/.*';
+$exclude_expr[1] = '.*~$';
+
+# If true (default), and using a tape device, level zero "set" backups
+# assume you want to erase and use a new tape for each set. If false, level
+# zero "set" backups append to tapes. To force an erase for any backup,
+# use "-erase" on the commandline.
+$erase_tape_set_level_zero = 'true';
+
+# Set this to "true" to make erase operations just call "mt rewind" - not
+# "mt rewind' followed by "mt erase". (For some tape drives, erase takes
+# hours rather than seconds or is otherwise undesirable)
+$erase_rewind_only = 'false';
+
+# ----------------------------------------------------------------------
+# Log/stamp files, path for temporary files
+
+$logdir = '/var/log/flexbackup'; # directory for log files
+$comp_log = 'gzip'; # compress log? false/gzip/bzip2/lzop/compress/zip
+$staticlogs = 'false'; # static log filenames w/ no date stamp
+$prefix = ''; # log files will start with this prefix
+$tmpdir = '/tmp'; # used for temporary refdate files, etc
+$stampdir = '/var/lib/flexbackup'; # directory for backup timestamps
+$index = '/var/lib/flexbackup/index'; # DB filename for tape indexes
+$keyfile = '00-index-key'; # filename for keyfile if archiving to dir
+$sprefix = ''; # stamp files will start with this prefix
+
+# ----------------------------------------------------------------------
+# Parameters for 'afio' only
+
+# File extensions that should not be compressed (seperate with spaces)
+$afio_nocompress_types = 'mp3 MP3 Z z gz gif zip ZIP lha jpeg jpg JPG taz tgz deb rpm bz2 lzo';
+
+# True to show block numbers
+$afio_echo_block = 'false';
+
+# Files less than this size (kilobytes) won't be compressed
+$afio_compress_threshold = '3';
+
+# Maximum amount of memory (megabytes) to use for temporary storage of
+# compression results. If a compressed file is bigger than this, compression
+# will have to run twice on the file (see manpage).
+$afio_compress_cache_size = '2';
+
+# ----------------------------------------------------------------------
+# Parameters for 'tar' only
+
+# True to show record numbers
+$tar_echo_record_num = 'false';
+
+# ----------------------------------------------------------------------
+# Parameters for 'cpio' only
+
+# Format of cpio archive
+$cpio_format = 'newc';
+
+# ----------------------------------------------------------------------
+# Parameters for 'dump' only
+
+# Estimated tape size (in kilobytes). This number doesn't really do much
+# but help 'dump' get size estimates if set to zero uses 'dump -a'
+$dump_length = '0';
+
+# True to use /etc/dumpdates (could mess things up if you dump subdirectories
+# of mount points). False to use flexbackup's timestamps.
+$dump_use_dumpdates = 'false';
+
+# ----------------------------------------------------------------------
+# Parameters for 'star' only
+
+# Use fifo (buffering)? (you probably want to set $buffer=false above if so)
+$star_fifo = 'true';
+
+# Handle ACLs?
+$star_acl = 'true';
+
+# Format of star archive
+$star_format = 'exustar';
+
+# True to show block numbers
+$star_echo_block_num = 'false';
+
+# ----------------------------------------------------------------------
+# Parameters for 'pax' only
+
+# Format of pax archive
+$pax_format = 'ustar';
+
+# ----------------------------------------------------------------------
+# Parameters for 'zip' only
+
+# File extensions that should not be compressed (seperate with spaces)
+$zip_nocompress_types = 'mp3 MP3 Z z gz gif zip ZIP lha jpeg jpg JPG taz tgz deb rpm bz2 lzo';
+
+# ----------------------------------------------------------------------
+# Parameters for 'package delta' backups
+
+# Archive a list of all installed packages in the top level of each backup?
+# Can be 'false', 'true' (save it for any backup), or
+# 'rootonly' (saves list only if the filesystem is '/')
+$pkgdelta_archive_list = 'rootonly';
+
+# Archive files not "owned" by packages?
+$pkgdelta_archive_unowned = 'true';
+
+# Archive any package-owned files which have been modified?
+$pkgdelta_archive_changed = 'true';
+
+# ----------------------------------------------------------------------
+# Paths to commands. Default will look for them in $PATH. Use this if
+# you want to set commands explicitly. You can use full paths or just
+# change command names.
+#
+# Example: If GNU tar is called "gtar" on your system:
+# $path{'tar'} = 'gtar';
+#
+# Or can be used to "sudo" certain commands. Examples:
+# $path{'find'} = 'sudo find';
+# $path{'dump'} = 'sudo dump';
+# $path{'afio'} = 'sudo -u nonrootuser afio';
+#
+
+# ----------------------------------------------------------------------
+# mt operation overrides. Set if flexbackup doesn't know the right mt
+# command(s) for your OS/device, or you want to override things.
+#
+# Example: use "mt status" instead of "mt tell"
+# $mt{'tell'} = 'status';
+
+
+######################################################################
+#
+# $Id: flexbackup.conf,v 1.74 2003/09/21 22:59:58 edwinh Exp $
+# $Name: v1_2_1 $
+#
+# Leave '1;' on the the next line - for perl 'require'
+1;
diff --git a/flexbackup.conf.5 b/flexbackup.conf.5
new file mode 100644
index 0000000..332ffdc
--- /dev/null
+++ b/flexbackup.conf.5
@@ -0,0 +1,304 @@
+..
+.de TQ
+.br
+.ns
+.TP \\$1
+..
+.TH "FLEXBACKUP.CONF" "5" "Oct 2003" "Flexbackup"
+.SH "NAME"
+/etc/flexbackup.conf \- a flexible backup/archiving tool configuration file
+.SH "DESCRIPTION"
+Flexbackup configuration file. Check \fBflexbackup\fR(1) or
+\(dq\fBflexbackup\fR \fI-help\fR\(dq for usage information.
+.SH "SETTINGS"
+.TP
+General Configuration Settings
+.RS
+.TP
+\fB$type\fR = \fI'afio'\fI;
+Which archive type to use. Supported types are: \fBafio\fR(1), \fBdump\fR(1),
+\fBtar\fR(1), \fBcpio\fR(1), \fBstar\fR(1), \fBpax\fR(1), \fBzip\fR(1),
+\fBlha\fR(1), \fBar\fR(1), \fBshar\fR(1). \fI'copy'\fR is another option if
+running in archive-to-disk mode. Use \fI'filelist'\fR to dump a list of files
+in your cwd - for debugging setup/exclusion.
+.TP
+\fB$set{\fI'tag'\fR}\fR = \fI'/dir'\fR;
+Configure backup \(dqsets\(dq. Not needed if \(dq-dir <dir>\(dq is used to
+backup one tree at a time. Each set is a simple space-separated list of
+filesystems/directories. Remote filesystems should be denoted as
+\(dqhost:directory\(dq. You can use anything (other than \fI'all'\fR) as set
+names. Using \(dq-set all\(dq will back up all defined sets. If you are doing
+a full backup using tapes, each \(dqset\(dq will go onto a different tape and
+you will be prompted for tape change in between. Examples:
+.RS
+.PP
+\fB$set{\fI'set1'\fI}\fR = \fI'/home /usr'\fR;
+.br
+\fB$set{\fI'set2'\fI}\fR = \fI'/dir3 machine2:/dir4 machine3:/dir5'\fR;
+.RE
+.TP
+\fB$prune{\fI'/'\fR}\fR = \fI'tmp proc'\fR;
+Configure subtree pruning. A space-separated list of directories to prune from
+each backup. Key is a filesystem/directory or \(dqhost:directory\(dq spec as
+outlined above regular expressions allowed (not shell-type wildcards!).
+.TP
+\fB$compress\fR = \fI'false|gzip|bzip2|lzop|zip|compress|hardware'\fR;
+.TQ
+\fB$compr_level\fR = \fI'4'\fR;
+Configure compression. Choose a type of compression to use and configure the
+level. The compression level only applies to \fIgzip/bzip2/lzop/zip\fR compression
+types.
+.TP
+\fB$buffer\fR = \fI'false|buffer|mbuffer'\fR;
+.TQ
+\fB$buffer_megs\fR = \fI'10'\fR;
+.TQ
+\fB$buffer_fill_pct\fR = \fI'75'\fR;
+.TQ
+\fB$buffer_pause_usec\fR = \fI'100'\fR;
+Configure buffering program to help streaming to tapes. Specify the buffer
+memory size in \fB$buffer_megs\fR, how full buffer needs to be to start writing
+in \fB$buffer_fill_pct\fR, and how long to sleep after every write (which helps
+sustain stream bursts) in \fB$buffer_pause_usec\fR.
+.TP
+\fB$device\fR = \fI'/dev/tape'\fR;
+Configure device to backup to. \fBNon-rewinding version, please!\fR. If the
+target is a directory, \fBflexbackup\fR will archive to files in that directory
+rather than to a device. If configured as \(dqhost:/dev/tapedevice\(dq, will
+use remote tape drive via rsh/ssha. Examples:
+.TS
+afCW s l.
+_
+Linux SCSI w/devfs: /dev/tapes/tape0/mtn
+Linux SCSI: /dev/nst0
+Linux IDE: /dev/nht0
+Linux ftape: /dev/nqft0
+FreeBSD SCSI: /dev/nrsa0
+_
+.TE
+.TP
+\fB$blksize\fR = \fI'10'\fR;
+Configure the block size (in kilobytes!) to use for archive programs and dd.
+Default is IfB10\fR for most things. Some tape drives need \fI32\fR or \fI64\fR.
+Set to \fI'0'\fR to disable all blocking.
+.TP
+\fB$mt_blksize\fR = \fI'0'\fR;
+Configure the block size (in bytes!) to use for the tape device, with
+\(dqmt setblk\(dq or equivalent. If set to \fI'0'\fR, will use \(dqvariable\(dq
+block size for the tape device (which is recommended). Comment out or set to
+\(dq\fB$blksize\fR * 1024\(dq to have it be the same as the archiver block size
+above.
+.TP
+\fB$pad_blocks\fR = \fI'true|false'\fR;
+Configure block padding. True to pad blocks to blocksize (devices only, not
+used when archiving to files).
+.TP
+\fB$mt{\fI'command'\fR}\fR = \fI'other'\fR;
+Configure \fBmt\fR operation overrides. Set these if \fBflexbackup\fR doesn't
+know the right \fBmt\fR command(s) for your OS/tape device, or you want to
+override things. Example:
+.RS
+.TP
+Use \fB\(dqmt \fIstatus\fR\(dq\fR instead of \fB\(dqmt \fItell\fR\(dq\fR
+\fB$mt{\fI'tell'\fR}\fR = \fI'status'\fR;
+.RE
+.RE
+.TP
+Other Global Settings
+.RS
+.TP
+\fB$remoteshell\fR = \fI'rsh|ssh|ssh2'\fR;
+Configure the command to use for remote shell.
+.TP
+\fB$remoteuser\fR = \fI''\fR;
+If not empty (or not set), the secondary username for remote shells.
+.TP
+\fB$label\fR = \fI'true|false'\fR;
+Somehow store identifying label in archive?
+.TP
+\fB$verbose\fR = \fI'true|false'\fR;
+Print each file?
+.TP
+\fB$sparse\fR = \fI'true|false'\fR;
+Handle sparse files?
+.TP
+\fB$indexes\fR = \fI'true|false'\fR;
+Set to \fI'false'\fR to turn off all table-of-contents support.
+.TP
+\fB$staticfiles\fR = \fI'true|false'\fR;
+If backing up to files, use static filenames \- no date stamp (same level
+backup of same directory will overwrite old backups).
+.TP
+\fB$atime_preserve\fR = \fI'true|false'\fR;
+Set to true to try and preserve file access times during backup, if the
+selected archive program can do so. Note that if this is true, -cnewer checks
+(file permission/status changes only, not content) are turned off when deciding
+which files to archive on the local system.
+.TP
+\fB$traverse_fs\fR = \fI'false|local|all'\fR;
+Span across filesytems? (backups of type \fB'dump'\fR will ignore this option).
+Set to \fI'false'\fR (don't), \fI'local'\fR (all but nfs/smbfs), or \fI'all'\fR
+(everything)
+.TP
+\fB$exclude_expr[\fI0\fR]\fR = \fI'.*'\fR;
+Exclude files that match these \fBregular expressions\fR (not shell wildcards)
+from the backups (backups of type \fB'dump'\fR will ignore this option). You
+can list more than one, just keep incrementing the index in the brackets for
+each. Also, strip off leading directories (the filesystem specs above or the
+\(dq-dir\(dq flag). Matches paths, not filenames, so put .* on the front/back
+as needed. Examples:
+.RS
+.PP
+\fB$exclude_expr[\fI0\fR]\fR = \fI'.*/[Cc]ache/.*'\fR;
+.br
+\fB$exclude_expr[\fI1\fR]\fR = \fI'.*~$'\fR;
+.RE
+.TP
+\fB$erase_tape_set_level_zero\fR = \fI'true|false'\fR;
+If set to true (default), and using a tape device, level 0 (full) \(dqset\(dq
+backup types assume you want to erase and use a new tape for each set. If
+set to false, level 0 (full) \(dqset\(dq backup types append to tapes. To force
+an erase for any backup, use \fI\(dq-erase\(dq\fR on the command-line.
+.TP
+\fB$erase_rewind_only\fR = \fI'true|false'\fR;
+Set this to \fB'true'\fR to make erase operations just call \fB\(dqmt
+\fIrewind\fR\(dq\fR - not \fB\(dqmt rewind\(dq\fR followed by \fB\(dqmt
+\fIerase\fR\(dq\fR.
+(For some tape drives, erase takes hours rather than seconds or is otherwise
+undesirable).
+.RE
+.TP
+Log, Stamps, and Binary Location Settings
+.RS
+.TP
+\fB$logdir\fR = \fI'/var/log/flexbackup'\fR;
+Directory for log files.
+.TP
+\fB$comp_log\fR = \fI'false|gzip|bzip2|lzop|compress|zip'\fR;
+Compress log?
+.TP
+\fB$staticlogs\fR = \fI'true|false'\fR;
+Use static log filenames with no date stamp?
+.TP
+\fB$prefix\fR = \fI''\fR;
+Log filenames will start with this prefix.
+.TP
+\fB$tmpdir\fR = \fI'/tmp'\fR;
+Used for temporary refdate files, etc.
+.TP
+\fB$stampdir\fR = \fI'/var/lib/flexbackup'\fR;
+Directory for backup timestamp files.
+.TP
+\fB$index\fR = \fI'/var/lib/flexbackup/index'\fR;
+Full path (without the .db extension) to the database filename for tape indexes.
+.TP
+\fB$keyfile\fR = \fI'00-index-key'\fR;
+Filename for keyfile if archiving to dir.
+.TP
+\fB$sprefix\fR = \fI''\fR;
+Stamp filenames will start with this prefix.
+.TP
+\fB$path{\fI'program'\fR}\fR = \fI'/path/to/program'\fR;
+Override paths to commands. By default \fBflexbackup\fR will look for them in
+\fI$PATH\fR. Use this if you want to set commands explicitly. You can use full
+paths or just change command names. Examples:
+.RS
+.TP
+If GNU \fBtar\fR is called \fB\(dqgtar\(dq\fR on your system:
+\fB$path{'tar'} = 'gtar';
+.TP
+Or it can be used to \fB\(dqsudo\(dq\fR certain commands:
+\fB$path{\fI'find'\fR}\fR = \fI'sudo find'\fR;
+.br
+\fB$path{\fI'dump'\fR}\fR = \fI'sudo dump'\fR;
+.br
+\fB$path{\fI'afio'\fR}\fR = \fI'sudo -u nonrootuser afio'\fR;
+.RE
+.RE
+.TP
+Specific Command Settings
+.RS
+.TP
+\fB$afio_nocompress_types\fR = \fI'ext1 ext2 ...'\fR;
+.TQ
+\fB$afio_echo_block\fR = \fI'true|false'\fR;
+.TQ
+\fB$afio_compress_threshold\fR = \fI'3'\fR;
+.TQ
+\fB$afio_compress_cache_size\fR = \fI'2'\fR;
+These settings apply to the \fB'afio'\fR backup types only. Files with
+extensions specified in \fB$afio_nocompress_types\fR will not be compressed.
+Define whether or not echo block numbers in \fB$afio_echo_block\fR. Configure
+the minimum file size (in kilobytes) required for compression in
+\fB$afio_compress_threshold\fR. \fB$afio_compress_cache_size\fR setting is
+used to specify the maximum amount of memory (megabytes) to use for temporary
+storage of compression results. If a compressed file is bigger than this,
+compression will have to run twice on the file. See the \fBafio\fR(1) manpage
+for more information.
+.TP
+\fB$tar_echo_record_num\fR = \fI'true|false'\fR;
+These settings apply to the \fB'tar'\fR backup types only. Define whether
+or not echo record numbers in \fB$tar_echo_record_num\fR.
+.TP
+\fB$cpio_format\fR = \fI'newc'\fR;
+These settings apply to the \fB'cpio'\fR backup types only. Configure the
+format of the archive in \fB$cpio_format\fR. See the \fBcpio\fR(1) manpage for
+allowed formats.
+.TP
+\fB$dump_length\fR = \fI'0'\fR;
+.TQ
+\fB$dump_use_dumpdates\fR = \fI'true|false'\fR;
+These settings apply to the \fB'dump'\fR backup types only. Configure the
+estimated tape size (in kilobytes) using the \fB$dump_length\fR setting. This
+number doesn't really do much but help \fBdump\fR get size estimates if set to
+0, \fBflexbackup\fR uses \fB'dump \fI-a\fR'\fR to determine this. Set
+\fB$dump_use_dumpdates\fR setting to \fI'true\fR to use \fB/etc/dumpdates\fR
+(could mess things up if you dump subdirectories of mount points). Set it to
+\fI'false'\fR to use \fBflexbackup\fR's internal timestamps.
+.TP
+\fB$star_fifo\fR = \fI'true|false'\fR;
+.TQ
+\fB$star_acl\fR = \fI'true|false'\fR;
+.TQ
+\fB$star_echo_block_num\fR = \fI'true|false'\fR;
+.TQ
+\fB$star_format\fR = \fI'exustar'\fR;
+These settings apply to the \fB'star'\fR backup types only. Define whether
+or not use fifo (buffering) in \fB$star_fifo\fR. If you set this to
+\fB'true'\fR you probably want to set \fB$buffer\fB = \fI'false'\fR (see above).
+Configure whether or not to handle ACLs in \fB$star_acl\fR. Define whether or
+not echo record numbers in \fB$star_echo_block_num\fR. Configure the format of
+the archive in \fB$star_format\fR. See the \fBstar\fR(1) manpage for allowed
+formats.
+.TP
+\fB$pax_format\fR = \fI'ustar'\fR;
+These settings apply to the \fB'pax'\fR backup types only. Configure the
+format of the archive in \fB$pax_format\fR. See the \fBpax\fR(1) manpage for
+allowed formats.
+.TP
+\fB$zip_nocompress_types\fR = \fI'ext1 ext2 ...'\fR;
+These settings apply to the \fB'zip'\fR backup types only. Files with
+extensions specified in \fB$zip_nocompress_types\fR will not be compressed.
+.TP
+\fB$pkgdelta_archive_list\fR = \fI'true|false|rootonly'\fR;
+.TQ
+\fB$pkgdelta_archive_unowned\fR = \fI'true|false'\fR;
+.TQ
+\fB$pkgdelta_archive_changed\fR = \fI'true|false'\fR;
+These settings apply to the \fB'pkgdelta'\fR backup types only.
+Configure whether to archive a list of all installed packages in the top level
+of each backup in \fB$pkgdelta_archive_list\fR. Can be \fI'false'\fR (don't),
+\fI'true'\fR (save it for any backup), or \fI'rootonly'\fR (saves list only if
+the filesystem is \fI'/'\fR). Define whether or not to archive files not
+\(dqowned\(dq by any package in \fB$pkgdelta_archive_unowned\fR. Specify whether
+or not to archive any package-owned files which have been modified in
+\fB$pkgdelta_archive_changed\fR.
+.RE
+.SH "FILES"
+/etc/flexbackup.conf \- configuration settings
+.SH "REPORTING BUGS"
+Report bugs to (flexbackup-help@lists.sourceforge.net)
+.SH "AUTHOR"
+Written by Edwin Huffstutler (edwinh@computer.org)
+.SH "SEE ALSO"
+\fBflexbackup\fR(1)
diff --git a/flexbackup.spec b/flexbackup.spec
new file mode 100644
index 0000000..a0f20a6
--- /dev/null
+++ b/flexbackup.spec
@@ -0,0 +1,96 @@
+# Hey emacs, use -*-rpm-spec-*- mode...
+
+%define version 1.2.1
+
+Summary: Flexible backup script
+Name: flexbackup
+Version: %{version}
+Release: 1
+Epoch: 0
+License: GPL
+Group: Applications/Archiving
+URL: http://www.flexbackup.org
+Source0: flexbackup-%{version}.tar.gz
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
+BuildArch: noarch
+Packager: Edwin Huffstutler <edwinh+flexbackup@edwinh.org>
+Requires: gzip
+Requires: fileutils
+Requires: findutils
+
+#---------------------------------------------------------------------
+%description
+A flexible backup tool
+
+Features:
+ o Easy to configure
+ o Uses dump, afio, GNU tar, cpio, star, pax, or zip archivers
+ o Full and numbered levels of incremental backup (acts like "dump")
+ o Compression and buffering options for all backup types
+ o Does remote filesystems (over rsh/ssh; no special service)
+ o Can backup only files not owned by rpm, or changed from rpm version
+ o Writes to tapes, on-disk archive files, or on-disk directory trees
+ o Keeps a table of contents so you know archives are on each tape
+ o Nice log files
+
+
+#---------------------------------------------------------------------
+%prep
+%setup -q
+
+#---------------------------------------------------------------------
+%build
+make all PREFIX=/usr CONFFILE=/etc/flexbackup.conf
+
+#---------------------------------------------------------------------
+%install
+rm -rf %{buildroot}
+mkdir -p $RPM_BUILD_ROOT/etc
+mkdir -p $RPM_BUILD_ROOT%{_bindir}
+mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1
+mkdir -p $RPM_BUILD_ROOT%{_mandir}/man5
+mkdir -p $RPM_BUILD_ROOT/var/lib/flexbackup
+mkdir -p $RPM_BUILD_ROOT/var/log/flexbackup
+make install PREFIX=$RPM_BUILD_ROOT/usr CONFFILE=$RPM_BUILD_ROOT/etc/flexbackup.conf
+
+#---------------------------------------------------------------------
+%clean
+rm -rf %{buildroot}
+
+#---------------------------------------------------------------------
+%files
+%defattr(-,root,root,-)
+%config(noreplace) /etc/flexbackup.conf
+%{_bindir}/*
+%{_mandir}/*/*
+%dir /var/lib/flexbackup
+%dir /var/log/flexbackup
+%doc CHANGES COPYING TODO README CREDITS INSTALL faq.html
+
+#---------------------------------------------------------------------
+%changelog
+* Tue Sep 23 2003 Edwin Huffstutler <edwinh@computer.org>
+- cleanup spec a bit
+
+* Tue Jul 29 2003 Edwin Huffstutler <edwinh@hercules.my-net>
+- add manpages, tweak a bit
+
+* Thu Jul 3 2003 Edwin Huffstutler <edwinh@hercules.my-net>
+- update description
+
+* Tue Feb 18 2003 Edwin Huffstutler <edwinh@hercules.my-net>
+- config file is noreplace
+
+* Wed Jan 15 2003 Edwin Huffstutler <edwinh+flexbackup@edwinh.org>
+- defattr in right spot
+
+* Sun Jan 12 2003 Edwin Huffstutler <edwinh+flexbackup@edwinh.org>
+- updated
+
+* Sat Sep 25 1999 Edwin Huffstutler <edwinh+flexbackup@edwinh.org>
+- add more requires, update description, email address.
+- really goes in /usr/bin since it needs perl anyway --
+ if you only have your root fs, run restore or tar by hand :)
+
+* Sat Sep 18 1999 Edwin Huffstutler <edwinh+flexbackup@edwinh.org>
+- initial rpm package