diff options
author | Michael Gilbert <mgilbert@debian.org> | 2012-11-02 17:46:44 +0000 |
---|---|---|
committer | Michael Gilbert <mgilbert@debian.org> | 2012-11-02 17:46:44 +0000 |
commit | ed8cee1b61f957d05dcd223e6c954821ea1294e4 (patch) | |
tree | 3c499a9186dde0212ddb425de501864878c183fd |
flexbackup (1.2.1-6.2) unstable; urgency=medium
* Non-maintainer upload.
* Fix placement of afio alternative (closes: #691860).
# imported from the archive
36 files changed, 9708 insertions, 0 deletions
@@ -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: @@ -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. @@ -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> + @@ -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 @@ -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: @@ -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 <dir>" backs up one directory tree (old "-fs <dir>") +<LI>"-set <tag>" 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 <topdir>; find . -regex "<expression>"</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 & 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 +<n>' 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 & 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 = ¤t_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 = ¤t_time('ctime'); + $::stamp_at_start = ¤t_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 = ¤t_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." . ¤t_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 = ¤t_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 |