simplebackup perl script [TIPP]

Post there if you plan to contribute to this SystemCd
vanepp
Posts: 85
Joined: 22 Jun 2012, 02:24

simplebackup perl script [TIPP]

Postby vanepp » 24 Mar 2016, 02:20

I expect that other folks here have the same problem I do: family or
friends that could use the rescueCD to do backups but have no hope of running
it as it stands. Enter simplebackup which is a perl script designed to autorun
in a customized copy of the rescueCD. It uses dialog and pre configuration
(which you will need to do!) to make a backup as simple as boot the CD and
wait for dialog to come up then select backup and press enter. Assuming no
errors, the Windows system will be backed up and then the system will shut down.
If something does go wrong the script will tell the user to contact their
support person (who is likely you!) and has (assuming it found a backupstore)
logged everything that it did to a log file for you. Under limited conditions
(mostly the same disk that the backup was done from) it will also do a restore
of a previous version (it by default backs up the current system first to avoid
data loss) so the user can recover from downloading something they shouldn't
have (or a problem from Windows update).

Design principles are:

EKISS: (Externally, Keep It Simple Stupid), internally it isn't so simple but
as noted externally it is menu driven and requires the user to know nothing
other than they want to backup (or hopefully rarely, restore) their system.

Be CD friendly: that means configuration can't be in the script as the file
system is read only. Thus configuration is done via zero length text files
inserted in to the Windows file systems. The script mounts the file
systems and searches for the files to identify what disks / partitions it
needs.

Have an External backupstore: In my case (and probably yours too) that is a
USB connected 500gig to 4 terabyte USB drive that holds the backup files
and can be moved off site if desired for fire safety. It needs to be
formatted ntfs (not fat) because of the 2 gig fat file system limitation
(backups are 15 gigabytes or larger!). It too is identified to the system
by having a zero length text file in its root file system.

Do as little as possible on the rescueCD: Things like splitting the backups in
to less than 4 gig chunks and writing them to dvd for archiving can and
should be done in Windows, the backup script only needs to backup, restore
and do simple backupstore maintance while booted from Linux, so thats all
it does.

So to try this out the first thing you will need to do is recreate the
simplebackup.pl script into a single file by cutting and pasting the 3 or so
posts (to obey the 60k character post limit :-)) that the script has been
divided into (as the script is currently 140,000+ characters) then:

1) Prepare a backupstore. Typically an external USB disk drive. I use a 500Gig
Seagate. The drive needs to be reformatted to ntfs (for larger than 2 gig
files) and needs a file systemresccd_backupstore.txt (which can be empty,
its existance is what's important) in the root directory to mark this as
the backupstore drive.

2) Install a file systemresccd_windows.txt (which can also be empty) in the
root of the Windows c: drive to mark the windows drive/partition (note
that by default the script will find and mark other partitions on the
system disk for itself such as a boot and reserved / recovery partitions).

3) Plug in the backupstore drive on the windows system then boot from the
rescue CD (or if you like from the backupstore drive). From a terminal
window run simplebackup.pl and follow the prompts (and/or deal with the
error messages :-)).

4) The windows system will be backed up (assuming everything is configured
correctly) and will then shut down (changing $BACKUP_END = "shutdown";
to $BACKUP_END = "prompt"; in the perl script will bring you back to the
command prompt for testing).

The backup will appear in a directory on the backupstore that looks
like this:

SB,2016-03-23-11-10,demo

which contains:

#SB,b,2016-03-23-11-10-56,fat32,part1,ST3500320AS_9QM50QS1,.pimg
#SB,m,2016-03-23-11-10-56,msftres,part2,ST3500320AS_9QM50QS1,.msft
#SB,p,2016-03-23-11-10-56,gpt,MBR,ST3500320AS_9QM50QS1,.gpt
#SB,p,2016-03-23-11-10-56,gpt,MBR,ST3500320AS_9QM50QS1,.gpt.md5
#SB,s,2016-03-23-11-10-56,ntfs,part3,ST3500320AS_9QM50QS1,.img
SB,2016-03-23-11-10,demo,var_log_messages
SB,2016-03-23-11-10,demo,var_log_perl.log
SB,2016-03-23-11-10,demo.log

which are the various files from the backup. The odd looking filename scheme
is in fact a primitive database encoding SB for simple backup, the date and
time of the backup, and an optional user comment. The backup files (the ones
with a leading #) include the partition type, partition number (except for
the MBR which doesn't have a number), the disk serial number of the disk they
came from and an appropriate file extension to tell you what tool to use to
manually restore them if needed. At the end are the 3 log files:

/var/log/messages

from linux (to find hardware errors or boot problems mostly)

/var/log/perl.log

the console output from perl for perl errors

demo.log

the simplebackup log file which contains a listing of all the commands the perl
script executed and what the user requested so you can see after the fact
what went on if something goes wrong. Grepping for "error" and "warning"
in this file is a good bet to see if problems occurred.

By default the script will only restore a file to the same disk serial
number it came from. In the case of a disk failure you would need to manually
restore the backup files to a new disk. The purpose of this is that a single
backupstore can be used to backup and more importantly restore safely multiple
machines (such as a desktop and a laptop) and will present the user with only
the restore options for the correct machine preventing restoring an OS that
won't work on to a machine.
When things are working correctly you can then create a custom CD to
automate the backup. I use my backupstore as my development and boot device.
To do so I have the following layout:

/dev/sdx1 ext4 4 gigabytes boot # os images grub config bootable
/dev/sdx2 ext4 8 gigabytes # /mnt/custom for rescue CD extraction
/dev/sdx3 ntfs rest of disk (450Gigs) # backupstore with backupstore file in
# root partition.

the grub.cfg (needs to be grub2!) on /dev/sdx1 looks like this:

set default=300
set timeout=300
set root=(hd0,1)

menuentry "SystemRescueCd (isoloop) test.iso docache autorun simplebackup" {
loopback loop /backup.iso
linux (loop)/isolinux/rescue64 docache nomodeset vga=769 setkmap=us isoloop=backup.iso
initrd (loop)/isolinux/initram.igz
}
...

where backup.iso is a custom rescueCD iso generated by isogen

docache releases /dev/sdx1 so you can make changes to the boot files and the
nomodeset and vga=769 set the screen res to 640*480 for dialog formatting
(and old eyes!).

To create a custom CD you need to extract the current rescue CD, make the
following changes and create a new iso and burn it (this is documented on
the web page so I won't repeat it).

1) Create /mnt/custom/customcd/files/root/autorun containing:

/usr/local/bin/simplebackup.pl 2>/var/log/perl.log

2) Move the simplebackup.pl file you created (and hopefully tested!) to

/usr/local/bin/simplebackup.pl

3) chmod +x /usr/local/bin/simplebackup.pl

4) vi /mnt/custom/customcd/isoroot/isolinux/isolinux.cfg and replace the

MENU LABEL 1) SystemRescueCd: default boot options append line with

APPEND rescue64 docache nomodeset vga=769 nodhcp nonet nonm setkmap=us scandelay=1 -- rescue32 docache nomodeset vga=769 nodhcp nonet nonm setkmap=us scandelay=1

(at present the nodhcp etc. options don't work but they should and are
desirable for security. Simplebackup doesn't need and shouldn't have
network access as most folks don't have firewalls I don't think).

5) vi /mnt/custom/customcd/isoroot/boot/grub/grub-471.cfg and replace the
default boot entry with

menuentry "SystemRescueCd (64bit, default boot options)" {
linux /isolinux/rescue64 docache nonet nonm nodhcp setkmap=us nomodeset vga=769
initrd /isolinux/initram.igz
}

(again this doesn't actually appear to work, the nomodeset and vga=796 appear
to be ignored. The script still works, just the screen is small and looks odd
because the resolution is incorrect, thats life!). This appears to be for
some UEFI boots. It looks like it will only work for 64 bit machines as well.

6) Create a new iso using isogen and burn it (or copy it to partition 1 of
the backupstore drive) and away you should go.

If you have questions, post them here and as I remember I'll try and
answer. Since the script works for me I'm unlikely to do much else with it but
you should feel free to both thank the folks that make the rescueCD (as I
certainly do!) for the excellent product that makes this script possible and
post any fixes or improvements to the script you make here.

Peter Van Epp

vanepp
Posts: 85
Joined: 22 Jun 2012, 02:24

Re: simplebackup perl script

Postby vanepp » 24 Mar 2016, 02:23

Code: Select all


#!/usr/bin/perl -w

# A simple perl backup script for the System Rescue CD tool kit.

# Copyright (c) 2016 Peter Van Epp

# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:

# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.

# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# Aimed at:

# The casual Windows user (family, friends) that isn't Linux or even
# very  computer literate but want to be able to backup and perhaps restore
# their system for themselves. That won't always be possible, when things go
# wrong this script will tell the user to "Seek support" which is probably you,
# or someone else that can run the tools on the Rescue CD. This script
# will hopefully have saved enough information about whats going on to let
# you recover somehow. For instance before allowing the user to restore, it
# will take a current backup of the system partitions and MBR which should
# prevent accidental data loss during restore. This is intended for a custom
# CD or USB key installation that will boot directly in to this script to make
# it easy to use. It needs to do docache so the user can remove the USB key or
# the CD during the backup (by default it will shutdown, but it can be set to
# either reboot or stop at a dialog prompt when it finishes) so a reboot will
# bring windows back up normally. The script keeps a log of all actions
# performed (and return codes) and saves a copy of /var/log/messages from
# Linux on the backupstore so you can see hardware type issues (disk errors
# etc.) from the backup.

# Assumptions:

# Backups stored on an external USB hard drive (the backupstore). I have 500gig
# Seagate and Western Digital units reformatted from FAT (which won't store
# a bigger than 2 gig file) to NTFS (which will take the 50+ gigs of
# Windows 7 Home premium (blotted pig that it is :-)), XP is around 10 to 15
# gigs) neither will fit in a fat file system without being split. Nothing
# stopping you using a second internal SATA drive (it is in fact faster) but
# the external can be taken off site for backup and thus is better. An internal
# SATA or IDE disk with the backup then split and written to CD for offsite
# storage would do as well.

# Disks (backup and backupstore) identified by a file in the root file system
# $BACKUPSTORE_FILE (defined below) for the backupstore drive, $WINDOWS_FILE
# for the system disk, $BOOT_FILE for the boot partition and some others. This
# allows user configuration without changes to the bootable CD image (in fact
# more configuration than is currently implemented is easily possible, just
# read the contents of one or more of those files from the file system and
# change configuation as desired. Again I currently don't have the need and
# thus haven't done it). These files end in ".txt" so a Windows only user can
# create a $WINDOWS_FILE on the Windows C: drive using notepad and a
# $BACKUPSTORE_FILE on a suitably formatted USB drive for the backupstore and
# the system will find and tag the boot partition and any reserved partitions
# on the system disk for itself.
#       Because backup image names contain the model and serial number
# of the disk they came from, and restore will only offer restore images from
# the current disk, it is possible to back up more than one machine on a single
# backupstore (a desktop and a laptop for instance).

# Restore requires that the system disk MBR/VBR (and thus the partition data)
# be identical to the backup and the disk model number / serial number be the
# same for the user to be able to restore. If either are different the restore
# will abort and tell the user to seek support (i.e. you or someone else that
# can run the System Rescue CD tools :-)).
#       The obvious case this doesn't cover is system disk failure, but this
# is a simple backup aimed at the non technical user and restoring to a new
# disk that needs partitioning and formatting is best left to someone familiar
# with the tools. It would be fairly easy to restore the saved MBR/VBR to
# a new disk of the same model number (but different serial number), run parted
# to format it, then restore from the backup but I haven't done that. I think
# disk failures are rare enough for this to be acceptable (YMMV). Note that
# Windows can be picky about the geometry of a new drive if it is a different
# layout than the original, and thus may not boot even with a successful
# restore. Windows also requires the VBR (the 63 reserved sectors after the
# MBR) to boot so the script saves that as well (but you get to restore it
# with dd :-)). Even then you should be able to mount (if not boot) the disk
# to at least get the files off of it.

# Start of actual scipt

# User setable parameters:

# The name of this script, so that if the user were to type that at the shell
# prompt this script will restart.

$SCRIPT_NAME = "simplebackup.pl";

# If $DESCRIPTION is set to "y", then backup will prompt the user for a
# text description of the backup. If it is set to "n" the backup will proceed
# without any further user intervention as soon as backup is selected in the
# menu.

$DESCRIPTION = "y";

# You can keep a copy of the MD5 checksum of the backup image file and check
# it during restore, but with this implementation it about doubles the time
# that backup and restore take. The proper answer is to modify ntfsclone to
# calculate and output the MD5 of the image files that it processes (but I
# so far haven't done so). This code is here but disabled by default for
# taking too much time. Uncommenting the next line will enable it (and about
# double the time the backup takes!).

$DO_MD5 = 0;                    # enable MD5 checks (1).     Default: disabled

# By default the script will backup the system before doing a restore
# (to insure that you have a copy to recover from if the restore was a mistake!)
# This of course adds time to the restore so if you comment out this line that
# step will be skipped (but you have no insurance copy either!).

$BACKUP_BEFORE_RESTORE = 1;     # Do backup before restore. Default: enabled

# Chose what will happen at the end of backup. By default the system will
# put up a success screen and wait for user acknowledgement. You can also
# set it to shutdown or reboot (presumably to windows by removing the rescueCD
# media) after the backup or restore.

$BACKUP_END = "prompt";         # options "prompt" to come back to a prompt
                                # to allow the user to select what happens next.
                                # "shutdown" to power off after a successfull
                                # backup (errors will cause a prompt until there
                                # is user acknowlegement of the error).
                                # "reboot" to reboot to Windows after the
                                # backup (presuming the rescueCD media was
                                # removed.) On error the system will wait at
                                # a prompt til the user acknowledges the error.

$RESTORE_END = "prompt";        # options "prompt" to come back to a prompt
                                # to allow the user to select what happens next.# "shutdown" to power off after a successfull
                                # restore (errors will cause a prompt until
                                # there is user acknowlegement of the error).
                                # "reboot" to reboot to Windows after the
                                # restore (presumably to Windows if the rescue
                                # CD media is removed.) On error the system
                                # will wait at a prompt til the user
                                # acknowledges the error.

# Typically the swap file is a) large, and b) useless in the backup so by
# default we will truncate the file /pagefile.sys in the Windows partition
# (Windows will happily restore it to its huge size as part of boot) to save
# the useless backup time (my pagefile.sys is around 5 gigabytes which saves
# a significant amount of backup time). In the case where this is undesirable
# for some reason commenting out the line below will stop backup from
# truncating the file.

$TRUNCATE_PAGEFILE = 1; # Truncate pagefile.sys.    Default: enabled 0 disables

# Auto detect and tag Windows reserved and boot partitions without the text
# files below in the filesystem. There may be some cases when this interferes,
# so commenting out the line below will disable this feature (but the system
# eserved and boot partitions (except msftres partitions) won't be backed up or
# restored unless you make sure the appropriate text file is in the file
# system ...).

$AUTO_DETECT = 1;       # Autodetect reserved.      Default: enabled 0 disables

# Keep /var/log/messages only during the backup (deletes the boot messages!)
# Originally this was the default, but the file size of the complete syslog
# file is negligable compared to the backup files so the current default is
# to keep the entire syslog. However since the code was there, keeping only
# the syslog data during the backup or restore can be set by setting the value
# to 1 from 0

$SYSLOG_DURING_OP_ONLY = 0;     # Don't keep entire syslog  Default: disabled

# Label of ntfs volume where images will be stored (must be ntfs or other
# filesystem capable of storing > 2 gig files i.e. not FAT!).
# Create this as a file (0 length is fine) in the root of the ntfs file system
# so we can find it and identify the backup store disk. If more than one such
# disk is found the one with the most free space will be used. Doing this means
# the CD doesn't need to change, the user creates files in their file systems
# they want backed up to do the necessary configuration. We also can (but don't
# currently) set other configuration options from this file as it resides on
# a user editable hard disk. The .txt ending makes creation on Windows via
# notepad easy for user configuration. Given the encryption viruses running
# around these days making this file system ext3 or ext4 may be a good bet
# but it isn't currently done, only ntfs amd ext would need script code changes.

$BACKUPSTORE_FILE = "systemresccd_backupstore.txt";

# disk label (as above) of the Windows system volume to be backed up.
# There should be only one system volume, defining two will cause an error
# response. The .txt ending makes creation on Windows via notepad easy for user
# configuration.

$WINDOWS_FILE  = "systemresccd_windows.txt";

# Data volume (other than the system disk) to be backed up.

$BACKUP_FILE  = "systemresccd_back_me_up.txt";

# As of Windows 7 there is a 100 meg boot partition (its possible that the VBR
# is no longer used). It isn't accessable from Windows and thus the script will
# auto detect it. For cases when auto detect is undesirable, the following file
# should be placed in the partition's / file system from the rescuecd
# (autodetect writes this file if it isn't present and autodetect is running).
# For systems with an msftres file system (containing the keys), it will be
# automatically marked and backed up as you can't mount it to add or read files.

$BOOT_FILE = "systemresccd_boot_part.txt";

# Various OEM copies of windows have reserved partitions (usually to restore
# a corrupted system) also not accessable from Windows and autodetected.
# For cases when auto detect is undesirable, the following file should be
# placed in the partition's / file system from the rescuecd (autodetect writes
# this file if it isn't present and autodetect is running).

$RESERVED_FILE = "systemresccd_reserved_part.txt";

# Directories and files to identify a Windows system disk to verify the
# backup file is on the correct partition. This may vary by windows version
# and thus may need changes ...

$WINDOWS_DIR = "WINDOWS";       # Windows XP
$WINDOWS_DIR1 = "Windows";      # Windows 7 windows 10
$PROG_DIR = "Program Files";

# Page file name so it can be zeroed before backup if desired.

$PAGE_FILE = "pagefile.sys";

# Number of blocks to add to the size of the used block count on the CLONE
# partition to decide if the BACKUPSTORE has enough space to do the backup
# and store the log files generated.  Err on the side of too much space
# available for safety, you really don't want to run out of disk space after
# a half hour of backing up ...

$OVERHEAD     = 1000;

# dialog formatting settings.

# Backtitle message displayed on dialog screens (top line can only be 1 line).

$BACKTITLE_MSG = "\'\t\tSystem Rescue CD Simple Backup    (http://www.sysresccd.org)\'";

# The options for the dialog command

$DIALOG_OPTIONS = "--no-shadow --stdout --colors --begin 3 2 --backtitle $BACKTITLE_MSG";

# The height and width of the dialog boxes.

$DIALOG_HEIGHT = 20;

$DIALOG_WIDTH  = 76;

$DIALOG_RADIO_SELECT  = 6;      # Height of radio box select window for commands

$DIALOG_RADIO_RESTORE = 12;     # Height radio box select window for restore
                                # (will scroll if more than 12 are present)

#$DEBUG = 1;                    # enable debug printouts for testing.

# End of user configuration section.

# absolute path to the various programs (may change with new software versions
# and/or different base OSes) as this script is running as root and we want
# to avoid path based attacks, specify the full path to the programs.

$CAT       = "/bin/cat";
$CP        = "/bin/cp";
$DATE      = "/bin/date";
$DD        = "/bin/dd";
$DF        = "/bin/df";
$DIALOG    = "/usr/bin/dialog";
$GREP      = "/bin/grep";
$HDPARM    = "/sbin/hdparm";
$LS        = "/bin/ls";
$MD5SUM    = "/usr/bin/md5sum";
$MKDIR     = "/bin/mkdir";
$MOUNT     = "/bin/mount";
$NTFS_3G   = "/usr/bin/ntfs-3g";
$NTFSCLONE = "/usr/sbin/ntfsclone";
$PARTCLONE_FAT = "/usr/sbin/partclone.fat";
$PARTCLONE_RESTORE = "/usr/sbin/partclone.restore";
$PARTED    = "/usr/sbin/parted";
$RM        = "/bin/rm";
$SGDISK    = "/usr/sbin/sgdisk";
$SHUTDOWN  = "/sbin/shutdown";
$SORT      = "/bin/sort";
$TOUCH     = "/usr/bin/touch";
$UDEVADM   = "/bin/udevadm";
$UMOUNT    = "/bin/umount";

# Start of code.

local ($prog, $err_msg, $have_disks, $warnings_issued, $msg, $status, $res);
local (%disks_parted, %disks_df, %backups);

$prog = "main";

# Arrange to trap the int and quit signals to dismount mounted file systems
# first before exiting.

$SIG{'INT'}  = 'interrupt_handler';
$SIG{'QUIT'} = 'interrupt_handler';

# Check for the existance as a file for all the programs from the rescueCD in
# use. This should flag changes in the location of binaries which has happened
# with different rescueCD versions in the past.

if (!((-f "$CAT") ||
      (-f "$CP") ||
      (-f "$DATE") ||
      (-f "$DD") ||
      (-f "$DF") ||
      (-f "$DIALOG") ||
      (-f "$GREP") ||
      (-f "$HDPARM") ||
      (-f "$LS") ||
      (-f "$MD5SUM") ||
      (-f "$MKDIR") ||
      (-f "$MOUNT") ||
      (-f "$NTFS_3G") ||
      (-f "$NTFSCLONE") ||
      (-f "$PARTCLONE_FAT") ||
      (-f "$PARTCLONE_RESTORE") ||
      (-f "$PARTED") ||
      (-f "$RM") ||
      (-f "$SGDISK") ||
      (-f "$SHUTDOWN") ||
      (-f "$SORT") ||
      (-f "$TOUCH") ||
      (-f "$UDEVADM") ||
      (-f "$UMOUNT"))) {

        $err_msg = "$prog: Error: One or more required programs not found, RescueCD version change?\n";

        &unrecoverable_error($err_msg);
}

# Set the initial values of the global variables.

# Current position in the /var/log/messages file.

$log_cur = "";

# Storage (in memory) for error messages before the log file is opened.

$prelog_msgs = "";

# Indicate the /mnt/backup file system isn't mounted (used during disk probing)

$mnt_backup_mounted = "n";

# Filled in from the backupstore with the most free space available (if there
# is more than one backupstore present, which will cause a warning).

$backupstore_drive_part = "";   # backupstore drive_partition

$backupstore_space = "";        # Available space in 1K blocks

$backupstore_percent = "";      # %full from a df

# Indicate the backupstore isn't yet mounted on /mnt/backup. Once it is we need
# to unmount it before exiting.

$backupstore_drive_mounted = "n";

# Amount of space in 1K blocks needed to backup the selected partitions.

$needed_space = 0;

# Count of system tag files found (should be 1).

$system_tag_files_found= 0;

# Count of boot tag files found (should be 1).

$boot_tag_files_found = 0;

# Count of backupstore tag files found (should be 1)

$backupstore_tag_files_found = 0;

# Flag indicating warning messages have been written to the log file.

$warn = "n";

# The drive/partition of the system partition.

$sys_drive_part = "";

# A flag indicating that the system partition is also the boot partition.

$sys_bootable_no_tag = "n";

# The type (msdos or gpt) of the system drive partition table.

$sys_mbr_type = "";

# Flag indicating the /mnt/windows file system is mounted.

$mnt_windows_mounted = "n";

# The current backup directory name.

$dir_name = "";

# flag indicating the log file is open.

$log_open = "n";

# end of global variables.

open(SYSLOG, "/var/log/messages");

if ($? != 0) {

        $err_msg = "$prog: Error: Can't open /var/log/messages $! ($?)\n";

        &unrecoverable_error($err_msg);
}

if ($SYSLOG_DURING_OP_ONLY == 1) {

        # Remember the starting position of /var/log/messages to copy what
        # happens during backup to the backup log later. At this point there
        # is a ream of OS dependent stuff from boot in the file that we
        # typically don't care about. We mostly care about things that happen
        # (such as I/O errors) while our backup is running (or at least I do).
        # The various exit subroutines use this to copy from this point to
        # EOF in /var/log/messages to a log file on the BACKUPSTORE drive so
        # you can see it after the rescuecd system is rebooted and the in
        # memory copy goes away. It basically seeks to EOF on /var/log/messages
        # right now and remembers that position as a starting point later.

        $res = seek(SYSLOG, 0, 2);

} else {

        # set the start position to start of file so we save the entire syslog
        # file to the backupstore.

        $res = seek(SYSLOG, 0, 0);
}

# Set the syslog file current position so we only save the log data once in the
# case of multiple operations that cause a log file save.

$log_cur = tell(SYSLOG);

close (SYSLOG);

# Make sure /mnt/backup and /mnt/windows are not mounted (as they shouldn't be)
# and unmount them if they are mounted. This is mostly for development when
# I have /mnt/backup mounted before starting the script on a real boot it
# shouldn't ever occur.

if (&is_mounted("/mnt/backup")) {

        # Set up to log the partition information that we dismounted.
        # on the assumption it will succeed.

        $prelog_msgs .= "$prog: Warning: unmounted /mnt/backup\n";

        # This won't return if the unmount isn't successful!

        &unmount("/mnt/backup");
}

if (&is_mounted("/mnt/windows")) {

        # Set up to log the partition information that we dismounted.
        # on the assumption it will succeed.

        $prelog_msgs .= "$prog: Warning: unmounted /mnt/windows\n";

        # This won't return if the unmount isn't successful!

        &unmount("/mnt/windows");
}

# Main command loop. The user will need to select an exit to get out of here
# (that can be reboot, shutdown or escape to a Linux shell to run tools).
# We need to close the log file, copy /var/log/messages and dismount the
# backupstore file system before exiting. As a result interrupts are trapped
# so a cntrl-c will close any open files before exiting.

# indicate that we don't yet have any disks detected (setting this back to "n"
# later will cause a rescan of the disks which can be usefull when a USB
# drive is connected after the fact).

$have_disks = "n";

# Have we issued the warning messages yet? ("y" prevents multiple warnings
# about the same problem.

$warnings_issued = "n";

while (1) {

        if ($have_disks eq "n") {

                # Tell the user we are looking for disks via dialog ...

                $msg = "\'\n\n\n\n                     Welcome to Simple Backup\n\n                Examining the disks, please wait ...\n\n                (Should complete in a minute or so)\n\'";

                ($status, $res ) = &output_dialog_screen ("", "--infobox", $msg, "");

                # Use subroutine get_disks() to find all the disks linux has
                # discovered. It won't return if a fatal error occurs.

                %disks_parted = &get_disks();

                # If we got here we have a list of disks / partitions so
                # mount the partitions (only ntfs and fat for now) one at a time
                # and use df to gather usage data and look for our tag files
                # indicating a backupstore, a system partition, data partition
                # or a boot partition. Then dismount them again. It won't return
                # if a fatal error occurs.

                %disks_df = &get_partition_df(%disks_parted);

        }

        # if we got here we have successfully found the disks so see if we
        # found a backupstore (as it is the minimum we need to do anything at
        # all!)

        if ($backupstore_drive_part eq "") {

                # No, so give the user the bad news and see what they want to
                # do.

                &no_backupstore();

                # if we return the user elected to try connecting their USB
                # connected backupstore again so fall through to rescan the
                # disks. If the user chooses to reboot or shutdown
                # no_backupstore won't return.

        } else {

                if ($have_disks eq "n") {

                        # We have found a backupstore at least so mark the
                        # disks as scanned and proceed.

                        $have_disks = "y";

                        if ($backupstore_drive_mounted eq "n") {

                                # Try and mount the backupstore filesystem an
                                # error will be fatal so no need to check the
                                # return code.

                                &mount_backupstore();
                        }

                        # Now try and create a backup directory and open a log
                        # file in it. If this fails it will give the user the
                        # option of exiting or retrying again after a minute
                        # when the file name will change.

                        while ($log_open eq "n") {

                                &open_log(%disks_parted, %disks_df);
                        }

                        # We now have all the info about the disks so check it
                        # over and flag any errors found and form a list of
                        # partitions to back up. If a fatal error occurs this
                        # won't return.

                        %backups = &process_disk_info(%disks_df);

                        # Issue warnings about no system disk, too many system
                        # disks or backupstores etc. If there is no system disk
                        # this will divert in to maintance mode and not return.

                        if ($warnings_issued eq "n") {

                                &issue_warnings();

                                $warnings_issued = "y";

                        }

                        while (($needed_space) > ($backupstore_space))  {

                                # If there isn't enough space on the
                                # backupstore, then go to backupstore_maint()
                                # for the user to delete a backup or backups
                                # to free enough space.

                                &backupstore_maint();
                        }
                }

                # If the warnings have been dealt with  successfully
                # then put up the main menu and process the user's
                # requests. Once all the state variables are set above
                # this loop will only run this menu.

                &main_menu(%backups);
        }
}

# Shouldn't ever get here, but if we do remember to unmount things ...

$err_msg = "$prog: Error: Sofrware error: exited main loop (should not occur)\n";

&unrecoverable_error($err_msg);

# end of main commmand loop, start of subroutines.

Last edited by vanepp on 24 Mar 2016, 18:21, edited 1 time in total.

vanepp
Posts: 85
Joined: 22 Jun 2012, 02:24

Re: simplebackup perl script

Postby vanepp » 24 Mar 2016, 02:49

Code: Select all


sub get_disks {

        # Figure out what disks we have to work with using parted and hdparm
        # or udadmin (for the disk serial number). If we can't find a serial
        # number set it to unknown which will block user restores to this
        # disk (because we can't uniquely identify it). Populate global
        # associative array %disk_parted indexed by device/partition
        # (i.e. /dev/sda1)  with partition start, partition end, partition size
        # partition filesystem type, partition flags (i.e. boot) and the
        # disk model number serial number (which may contain blanks which will
        # be stripped before storage) all seperated by the ":". The subroutine
        # will return 0, (with the data in %disk_parted) for success, or exit
        # with an error message via &unrecoverable_error on error.
        #       This is a simple state machine parser, variable $state is
        # updated as each line is parsed. It may need changes for
        # internationalization if the format of parted changes by language it
        # currently assumes english! In such a case just replace the current
        # text being searched for with a variable in backup.base and add the
        # current text to backupmsgs.us file and the appropriate text for
        # your language to your language file. Same applies to df or any of
        # the other system tools if output changes by language.

        local ($prog, $err_msg, $parted_data, $now, $line, $state, $dev, $size);
        local ($type, $ssize, $csize, $mbr_type, $name, $sep, $manf, $model);
        local ($res, $serial, $partno, $start, $end, $psize, $ptype, $junk);
        local ($flags, %disks_parted);

        $prog= "get_disks";

        $parted_data = `$PARTED -m -s -l 2>&1`;

        if ($? != 0) {

                $err_msg = "$prog: Error $PARTED -m -s -l rc $! ($?) res $parted_data\n";

                &unrecoverable_error($err_msg);
        }

        # Log the parted output before parsing it.

        $now = `$DATE`;

        chop $now;

        $prelog_msgs .= "$now $prog: Output from parted\n$parted_data\n";

        ($line, $parted_data) = split('\n',$parted_data,2);

        $state = 0;

        # look for the first line containing "BYT;" to skip any header lines
        # (there currently aren't any so this is just in case).

        while (!($line eq "BYT;")) {

                ($line, $parted_data) = split('\n',$parted_data,2);
        }

        while ($parted_data) {

                if ($line eq "BYT;") {

                        # If this is a "BYT" line, just eat it and change to
                        # the next state to expect a device line.

                        $state = 1;

                } elsif ($state == 1) {

                        # A device line like this should be next:
                        # /dev/sda:500GB:scsi:512:512:msdos:ATA ST3500320AS:;
                        # So parse the data out of it in to local variables.

                        ($dev, $size, $type, $ssize, $csize, $mbr_type, $name, $sep) = split(':',$line,8);

                        ($manf, $model) = split (' ',$name,2);

                        # See if hdparm knows about this disk's serial number

                        $res = `$HDPARM -i $dev 2>&1 | $GREP Model 2>&1`;

                        if ($? != 0) {

                                # Not recognized by hdparm so try USB!
                                #
                                # (this is a US?B thumb drive that I'm booted
                                # from):
                                #
                                # BYT;
                                # /dev/sdc:501MB:scsi:512:512:msdos:Kingston DataTraveler 2.0:;
                                # 1:512B:501MB:501MB:fat32::boot, lba;
                                #
                                # hdparm says this (with a non zero return code):
                                # hdparm -i /dev/sdc
                                #
                                # /dev/sdc:
                                # SG_IO: bad/missing sense data, sb[]:  70 00 05 00 00 00 00 0a 00 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
                                #  HDIO_GET_IDENTITY failed: Invalid argument
                                #
                                # udevadm says this:
                                #
                                # udevadm info --query=property --name /dev/sdc
                                # DEVLINKS=/dev/disk/by-id/usb-Kingston_DataTraveler_2.0_5B76059CC8F2-0:0 /dev/disk/by-path/pci-0000:00:02.1-usb-0:4:1.0-scsi-0:0:0:0
                                # DEVNAME=/dev/sdc
                                # DEVPATH=/devices/pci0000:00/0000:00:02.1/usb1/1-4/1-4:1.0/host8/target8:0:0/8:0:0:0/block/sdc
                                # DEVTYPE=disk
                                # ID_BUS=usb
                                # ID_INSTANCE=0:0
                                # ID_MODEL=DataTraveler_2.0
                                # ID_MODEL_ENC=DataTraveler\x202.0
                                # ID_MODEL_ID=1a00
                                # ID_PART_TABLE_TYPE=dos
                                # ID_PATH=pci-0000:00:02.1-usb-0:4:1.0-scsi-0:0:0:0
                                # ID_PATH_TAG=pci-0000_00_02_1-usb-0_4_1_0-scsi-0_0_0_0
                                # ID_REVISION=PMAP
                                # ID_SERIAL=Kingston_DataTraveler_2.0_5B76059CC8F2-0:0
                                # ID_SERIAL_SHORT=5B76059CC8F2
                                # ID_TYPE=disk
                                # ID_USB_DRIVER=usb-storage
                                # ID_USB_INTERFACES=:080650:
                                # ID_USB_INTERFACE_NUM=00
                                # ID_VENDOR=Kingston
                                # ID_VENDOR_ENC=Kingston
                                # ID_VENDOR_ID=13fe
                                # MAJOR=8
                                # MINOR=32
                                # SUBSYSTEM=block
                                # USEC_INITIALIZED=856700
                                #

                                $prelog_msgs .= "$now $prog: $HDPARM -i $dev rc $! ($?) res $res trying $UDEVADM\n";

                                $serial = `$UDEVADM info --query=property --name $dev | $GREP ID_SERIAL_SHORT 2>&1`;

                                if ($? != 0) {

                                        # not USB so give up and set serial
                                        # number to unknown to block restore.

                                        $prelog_msgs .= "$now $prog: $UDEVADM rc $! ($?) res $res setting name unknown\n";

                                        $serial ="unknown";

                                } else {

                                        $prelog_msgs .= "$now $prog: Output from $UDEVADM info --query=property --name $dev | $GREP ID_SERIAL_SHORT serial $serial res $res\n";

                                        # leave only the serial number (remove
                                        # the cr first though!)

                                        chop $serial;

                                        $serial =~ s/ID_SERIAL_SHORT=//;

                                        if ($serial eq "") {

                                                # set unknown if there isn't a
                                                # serial number there!

                                                $serial = "unknown";
                                        }
                                }

                                # then form the name

                                $name = "${model}_${serial}";

                                # then remove all the white space from the
                                # model and serial number to make later parsing
                                # easier.

                                $name =~ s/\s//g;

                        } else {

                                # device recognized by hdparm, so parse its
                                # model and serial number from the output of
                                # hdparm to form the name:
                                #
                                # /dev/sda:
                                #
                                #  Model=ST3500320AS, FwRev=SD15, SerialNo=9QM50QS1
                                #

                                $prelog_msgs .= "$now $prog: Output from $HDPARM -i $dev | $GREP Model rc $res\n";

                                # Extract the model number and serial number
                                # (if any) from the data with a regex.

                                $res =~ /Model=(.+)\, .+SerialNo=(.+)$/;

                                $serial = $2;

                                if ($serial eq "") {

                                        # didn't find a serial number!

                                        $name = "$1_unknown";

                                } else {

                                        $name = "$1_$serial";
                                }

                                # then remove all the white space from the model
                                # and serial number to make later parsing
                                # easier.

                                $name =~ s/\s//g;
                        }

                        $prelog_msgs .= "$now $prog: set name = $name\n";

                        # then set state to expect a partition line as the
                        # next thing to parse.

                        $state = 2;

                } elsif (($state == 2) && ($line ne "")) {

                        ($partno, $start, $end, $psize, $ptype, $junk, $flags) = split(':', $line, 7);

                        # Now we have all the information, populate the disk
                        # array by  partition for later.

                        $disks_parted{"${dev}${partno}"} = "$start:$end:$psize:$ptype:$flags:$mbr_type:$name";

                } elsif ($line eq "") {

                        # when we hit a blank line reset the state to looking
                        # for BVT again for the next drive.

                        $state = 0;

                } else {

                        # If the format has changed somehow report an error
                        # and exit as we can't proceed til its fixed.

                        $err_msg = "$prog: Error: software error, line $line not processed.\n";
                        &unrecoverable_error($err_msg);
                }

                ($line, $parted_data) = split('\n',$parted_data,2);
        }

        return (%disks_parted);
}

sub get_partition_df {

        # Cycle through all the partitions found by get_disks mounting the
        # fat and ntfs ones and looking for the various files (system, boot,
        # backup and backupstore) that we set and for the Windows directory
        # files to identify the system disk (in case the backup file is
        # misplaced by the user). Count the number of files found to complain
        # if there are errors later, and use df to obtain the amount of
        # space available and in use and the boot flags to identify what we
        # should be doing later.

        local (%disks_parted) = @_;
        local ($prog, $drive_part, $start, $end, $psize, $ptype, $flags);
        local ($mbr_type, $name, $size, $err_msg, %disks_df, $res, $data);
        local ($title, $dev, $total, $used, $avail, $percent, $mntpnt, $drive);
        local ($system, $backup, $boot, $reserved, $backupstore, $windows);

        $prog = "get_partition_df";

        # First check that /mnt/backup is not currently mounted and
        # unmounted if it is.

        if (&is_mounted("/mnt/backup")) {

                # Set up to log the partition information that we dismounted.
                # on the assumption it will succeed.

                $prelog_msgs .= "$prog: Error: unmounted /mnt/backup\n";

                # This won't return if the unmount isn't successful!

                &unmount("/mnt/backup");
        }

        foreach $drive_part (sort keys (%disks_parted)) {

                ($start, $end, $psize, $ptype, $flags, $mbr_type, $name) = split(/:/,$disks_parted{$drive_part});

                # Since the tools don't (so far AFAIK) deal with msftdata
                # partitions, use dd to back up the entire partition and thus
                # set the size to the whole thing rather than the amount used.
                # Also set the backup file found flag (even though it wasn't!).

                if (($ptype eq "") && ($flags =~ /msftres/)) {

                        # Fake a data entry for this unmountable file system.
                        # Convert the size from MB/GB to 1K blocks to match
                        # df.

                        if ($psize =~ /(\d+)MB/) {

                                $size = $1 * 1000;

                        } elsif ($psize =~ /(\d+)GB/) {

                                $size = $1 * 1000000;

                        } else {

                                $err_msg = "$prog: Error: $psize neither MB nor GB. Can't convert partition size\n";

                                &unrecoverable_error($err_msg);
                        }

                        # Set the backup flag (even though there isn't a tag
                        # file) so the partition gets backed up.

                        $disks_df{$drive_part} = "n:y:n:n:n:n:msftres:$flags:$size:$size:0:%100:$mbr_type:$name";

                }

                # Only fat and ntfs partitions are of interest so only select
                # those.

                if (($ptype eq "ntfs") || ($ptype =~ /^fat/)) {

                        # First check the partition isn't already mounted
                        # since multiple mounts are now allowed (but in this
                        # case not desired).

                        if (&is_mounted($drive_part)) {

                                # Set up to log the partition information that
                                # we dismounted in case it fails.

                                $prelog_msgs .= "$prog: Error unmounted $drive_part\n";

                                # If the partition is mounted already unmount
                                # it first. This won't return if it doesn't
                                # succeed.

                                &unmount($drive_part);
                        }

                        $res = `$MOUNT $drive_part /mnt/backup 2>&1`;

                        if ($? != 0) {

                                $err_msg = "$prog: Error $MOUNT $drive_part /mnt/backup rc $! ($?) res $res\n";

                                &unrecoverable_error($err_msg);
                        }

                        # Mark /mnt/backup as mounted so we will unmount it on
                        # error!

                        $mnt_backup_mounted = "y";

                        $data = `$DF -P $drive_part 2>&1`;

                        if ($? != 0) {

                                $err_msg = "$prog: Error: $DF -P $drive_part rc $! ($?) ret $data\n";

                                &unrecoverable_error($err_msg);
                        }

                        # parse the data returned by df (skip the title line
                        # which comes first!)

                        ($title, $data) = split ('\n', $data, 2);

                        ($dev, $total, $used, $avail, $percent, $mntpnt) = split(' ',$data);

                        if ($dev ne $drive_part) {

                                # Returned dev doesn't match the partition we
                                # think we mounted so toss an error!

                                $err_msg = "$prog: Error: $DF -P $drive_part wrong drive $data\n";

                                &unrecoverable_error($err_msg);
                        }

                        # Extract the drive from the drive/partition.

                        $drive = $drive_part;

                        chop $drive;

                        if (! defined($backupstores{"$drive"})) {

                                # If this is the first time we have seen this
                                # drive, mark it as not a backupstore.

                                $backupstores{"$drive"} = "n";
                        }

                        # Check if the drive is marked as the Windows system
                        # partition

                        if (-r "/mnt/backup/$WINDOWS_FILE") {

                                # if we found the Windows system disk file
                                # mark that for later and count it.

                                $system = "y";

                                $system_tag_files_found += 1;

                        } else {

                                # Otherwise this isn't the Windows partition.

                                $system = "n";
                        }

                        if (-r "/mnt/backup/$BACKUP_FILE") {

                                # if we found the backme up file mark that in
                                # the data for this partition.

                                $backup = "y";

                        } else {

                                # Otherwise this isn't (yet) a backup partition.

                                $backup = "n";
                        }

                        if (-r "/mnt/backup/$BOOT_FILE") {

                                # This partition is marked as a boot
                                # partition for windows so mark it as such
                                # and count it.

                                $boot = "y";

                                $boot_tag_files_found += 1;

                        } else {

                                $boot = "n";
                        }

                        if (-r "/mnt/backup/$RESERVED_FILE") {

                                # This partition is marked as a reserved
                                # partition for windows so mark it as such.

                                $reserved = "y";

                        } else {

                                $reserved = "n";
                        }

                        if (-r "/mnt/backup/$BACKUPSTORE_FILE") {

                                # This is marked as a backupstore (valid or
                                # invalid) mark the disk its on as a backupstore
                                # which isn't eligable to have any partitions
                                # backed up and count the tag file. Note
                                # associative array %backupstores is global
                                # as its needed later in process_disk_info.

                                $backupstore_tag_files_found += 1;

                                $backupstores{"$drive"} = "y";

                                if ($ptype eq "ntfs") {

                                        # The backstore must be ntfs to support
                                        # greater than 2 gig files, if it isn't
                                        # then mark an error as this can't be
                                        # a backstore. As it is ntfs make this
                                        # the backstore. Set up the backupstore
                                        # files so we can mount it first thing
                                        # and store the log file we have been
                                        # accumulating in memory. Note that the
                                        # backupstore with the most free space
                                        # (if there is more than one) will be
                                        # used.

                                        $backupstore = "y";

                                        if ($backupstore_drive_part eq "") {

                                                # This is the first so mark it.

                                                $backupstore_drive_part = $drive_part;
                                                $backupstore_space = $avail;

                                                $backupstore_percent = $percent;

                                                $prelog_msgs .= "$prog: set $drive_part ($name) as backupstore\n";

                                        } elsif ($avail > $backupstore_space) {

                                                # The current candidate has
                                                # more free space than the
                                                # current choice so replace it.

                                                $backupstore_drive_part = $drive_part;
                                                $backupstore_space = $avail;

                                                $backupstore_percent = $percent;

                                                $prelog_msgs .= "$prog: Warning: set $drive_part ($name) as new backupstore\n";

                                                $warn = "y";
                                        }

                                } else {

                                        # This isn't an ntfs filesystem and
                                        # thus can't be a backstore so mark
                                        # it as a nonfatal error. If there
                                        # isn't another valid backupstore that
                                        # will be a fatal error.

                                        $warn = "y";

                                        $prelog_msgs .= "$prog: Warning: $drive_part marked as backstore but not ntfs. Ignored\n";

                                        $backupstore = "n";
                                }

                        } else {

                                # not a backupstore so flag that.

                                $backupstore = "n";
                        }

                        # Check for the typical Windows directories to see if
                        # this is likely the Windows system disk (so we can
                        # tell if the back me up file is likely on the right
                        # partition).

                        if ((-d "/mnt/backup/$WINDOWS_DIR") ||
                            (-d "/mnt/backup/$WINDOWS_DIR1")&&
                            (-d "/mnt/backup/$PROG_DIR")) {

                                $windows = "y";

                        } else {

                                $windows = "n";
                        }

                        # Put the backup, backupstore, boot, probably windows,
                        # reserved, MBR type, flags, df sizes and name in to
                        # global associative array %disks_df (again indexed by
                        # partition such as /dev/sda1) for later use.

                        $disks_df{$drive_part} = "$system:$backup:$backupstore:$boot:$windows:$reserved:$ptype:$flags:$total:$used:$avail:$percent:$mbr_type:$name";

                        # done with this partition so unmount it.

                        $res = `$UMOUNT /mnt/backup 2>&1`;

                        if ($? != 0) {

                                $err_msg = "$prog: Error $UMOUNT /mnt/backup rc $! ($?) res $res\n";

                                &unrecoverable_error($err_msg);
                        }

                        # Mark /mnt/backup as dismounted again.

                        $mnt_backup_mounted = "n";

                }
        }

        # Last, check that /mnt/backup is not currently mounted and
        # unmount it if it is.

        if (&is_mounted("/mnt/backup")) {

                # Set up to log the partition information that we dismounted.
                # on the assumption it will succeed.

                $prelog_msgs .= "$prog: Error at exit, unmountng /mnt/backup\n";

                # This won't return if the unmount isn't successful!

                &unmount("/mnt/backup");
        }

        # Successful finish so return %disks_df!

        return (%disks_df);
}

sub no_backupstore {

        # No backupstore was detected. In case the backupstore is a USB
        # connected device, prompt the user to check it is connected and try
        # unplugging it and plugging it back in again.

        local ($prog, $msg, $status, $res, $now);

        $prog = "no_backupstore";

        $msg = "\"\t             Error: No backupstore device was detected.\n\nWe can not proceed without a backupstore to store backups on.\nIf your backupstore is USB connected, check that it is powered on\nand the USB cable is connected. If that appears to be so, try disconnecting and then reconnecting the USB cable to the computer.\nWait a minute or so after doing that for the drive to be recognized\nand then press < yes > (or just press enter) to try again.\n\nIf you select < no > below, a screen with shutdown options will come up\n      (or you can just power the machine down from here).\n\"";

        # Send the error message to the user and see what they want to do.

        ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

        if ($status == 1) {

                # User pressed cancel so go shutdown.

                &shutdown();

        }

        $now = `$DATE`;

        chop $now;

        # User opted to try again so return to let them

        $prelog_msgs .= "$prog: $now user elected to retry, returning\n";

        return(0);
}

sub process_disk_info {

        # At this point we have identified and mounted a backupstore device
        # (or we wouldn't have gotten this far!). We have also flagged all
        # disks flagged as backupstores in array %backupstores (as no partition
        # on any of those disks are eligable for backup!) and scanned all the
        # drives and partitions for tag files, partition table types and boot
        # flage. So we cycle through all the partitions looking for a single
        # Windows system disk (preferably with both a tag file and containing
        # the Windows directories). If the tag file partition doesn't contain
        # the Windows directories flag a warning (as the tag file may be in
        # the wrong partition) but proceed with backup. If no system tag file
        # was found but AUTO_DETECT is enabled and Windows directories were
        # found set that as the system partition (if it isn't on a backupstore
        # drive!) and write a tag file, otherwise declare an error because we
        # don't have a system directory to backup. If we found a system
        # partition shedule it and the partition table of its disk for backup.
        # If the Windows directories are found on a partition other than the
        # system partition, issue a warning message. If more than one system
        # tag file is found issue a fatal error message.
        #       Now that we have identified the system partition we rescan all
        # the partitions with the aim of scheduling any partition flagged for
        # backup to be backed up and checking that there is a bootable partition
        # on the same drive as the system partition (assuming the system
        # partition itself isn't bootable), declaring a warning if no bootable
        # partition is found.

        local (%disks_df) = @_;
        local ($prog, $drive_part, $drive, $system, $backup, $backupstore);
        local ($boot, $windows, $reserved, $fstype, $flags, $total, $used);
        local ($avail, $percent, $mbr_type, $name, $sys_drive, %backups);
        local (%part_tables, $type);

        $prog = "process_disk_info";

        # Cycle through all the partitions found scheduling those tagged to
        # be backed up for backup (assuming they aren't on a backstore marked
        # drive).

        foreach $drive_part (sort keys (%disks_df)) {

            # Get the current drive

            $drive = $drive_part;

            chop $drive;

            # get the partition information for this partition

            ($system, $backup, $backupstore, $boot, $windows, $reserved, $fstype, $flags, $total, $used, $avail, $percent, $mbr_type, $name) = split (/:/,$disks_df{$drive_part});

            if (($system eq "y") || ($windows eq "y") || ($backup eq "y") ||
                ($boot eq "y") || ($reserved eq "y")) {

                # This partition is tagged to be backed up so check
                # further and issue warnings if required.

                if (($system eq "y") && ($windows eq "n"))  {

                    # Tagged as system partition but no Windows
                    # files, warning.

                    print LOG "$prog: Warning: $drive_part is tagged as the system disk but doesn't have Windows files\n";

                    $warn = "y";

                }

                if (($system eq "n") && ($windows eq "y"))  {

                    # Not tagged as system partition but has Windows files,
                    # warning.

                    print LOG "$prog: Warning: $drive_part isn't tagged as the system disk but has Windows files\n";

                    $warn = "y";

                }

                if ($backupstores{"$drive"} eq "y") {

                    # This is a backupstore drive and not eligable for backup,
                    # so flag this as a warning in the log file.

                    print LOG "$prog: Warning: $drive_part is tagged for backup but on a backupstore drive, ignored\n";

                    $warn = "y";

                } else {

                    # Schedule this partition to be backed up.

                    if ($system eq "y") {

                        # This is flagged as a system partition so mark it as
                        # such

                        $backups{$drive_part} = "s:$fstype:$used:$name";

                        if ($system_tag_files_found == 1) {

                            # Remember the drive/partition for truncating the
                            # pagefile (if enabled) later and for restore as
                            # $sys_drive_part is a global variable.

                            $sys_drive_part = $drive_part;

                            # remember the system drive for later in this
                            # routine in a local variable.

                            $sys_drive = $drive;

                        } else {

                            # There is a problem so clear this to indicate no
                            # valid system partition was found.

                            $sys_drive_part = "";

                            $sys_drive = "";

                        }

                        if (($boot eq "y") || ($flags =~ /boot/)) {

                            # This is also the boot partition so note that.

                            print LOG "$prog: system disk $drive_part is also the boot partition\n";

                            if ($boot ne "y") {

                                # Note that the system is bootable but not
                                # tagged (in case another partition is tagged
                                # as the boot partition in error).

                                $sys_bootable_no_tag = "y";
                            }
                        }

                    } elsif ($boot eq "y") {

                        # This is flagged as a boot partition so mark it as
                        # such.

                        $backups{$drive_part} = "b:$fstype:$used:$name";

                        if ($flags !~ /boot/) {

                            # Tagged but not bootable so flag a warning!

                            print LOG "$prog: Warning: $drive_part tagged boot but not bootable in mbr\n";

                            $warn = "y";

                        }

                    } elsif ($reserved eq "y") {

                        # This is a reserved partition so schedule it for
                        # backup.

                        $backups{$drive_part} = "r:$fstype:$used:$name";

                    } elsif ($fstype eq "msftres") {

                        # This is a msft file system that the tools don't
                        # handle (and thus we can't tag! So schedule it for
                        # backup without a tag file.

                        $backups{$drive_part} = "m:$fstype:$used:$name";

                    } else {

                        # Mark every thing else as a data partition

                        $backups{$drive_part} = "d:$fstype:$used:$name";
                    }

                    print LOG "$prog: partition $drive_part $backups{$drive_part} scheduled for backup\n";

                    if (!defined($part_tables{$drive})) {

                        # Back up this drives partition table so we know the
                        # partition info on restore. Either the msdos mbr or a
                        # gpt MBR use 32 K of disk space or less so use that
                        # as the size.

                        $backups{$drive} = "p:$mbr_type:32:$name";

                        print LOG "$prog: partition table of $drive $backups{$drive} scheduled for backup\n";

                        # And note we have scheduled this mbr for backup already

                        $part_tables{$drive} = "$mbr_type";
                    }
                }
            }
        }

        # If AUTO_DETECT is set and we have a single marked system partition,
        # check for, tag and backup the boot partition and any reserved
        # partitions on the system drive (as Windows won't show any of those
        # partitions making it difficult for a Windows only user to tag them).

        if (($AUTO_DETECT == 1) && ($sys_drive_part ne "")) {

            foreach $drive_part (sort keys (%disks_df)) {

                # Get the current drive

                $drive = $drive_part;

                chop $drive;

                if ($drive eq $sys_drive) {

                    # This is the system drive so get the partition information
                    # for this partition.

                    ($system, $backup, $backupstore, $boot, $windows, $fstype, $flags, $total, $used, $avail, $percent, $mbr_type, $name) = split (/:/,$disks_df{$drive_part});

                    if (($system ne "y") && ($backup ne "y") &&
                        ($boot ne "y") && ($reserved ne "y")) {

                        # Not tagged yet so check further.

                        if (($fstype eq "ntfs") || ($fstype =~/^fat/)) {

                            # Its either ntfs or fat so check boot status.

                            if ($flags =~ /boot/) {

                                # Bootable, but not tagged so if there isn't
                                # already a  tagged boot partition set this as
                                # it and tag it.

                                if ($boot_tag_file_found == 0) {

                                    # Write the tag file to the partition for
                                    # next time.

                                    &write_tag_file($drive_part, $BOOT_FILE);

                                    $boot_tag_files_found += 1;

                                    # and schedule it for backup.

                                    $backups{$drive_part} = "b:$fstype:$used:$name";

                                    print LOG "$prog: warning: untagged partition $drive_part $backups{$drive_part} scheduled for backup and tagged\n";

                                    # Set the warn flag to alert the support
                                    # person this was done automatically.

                                    $warn = "y";

                                } else {

                                   # Otherwise issue a warning.

                                   print LOG "$prog: warning: $drive_part bootable but not tagged, another partition is however\n";

                                   $warn = "y";

                                }

                            } else {

                                # Not bootable so must be a reserved partition
                                # so tag it for next time and schedule it for
                                # backup.

                                &write_tag_file($drive_part, $RESERVED_FILE);

                                $backups{$drive_part} = "r:$fstype:$used:$name";

                                print LOG "$prog: warning: untagged reserved partition $drive_part scheduled for backup and tagged\n";

                                $warn = "y";

                           }
                        }
                    }
                }
            }
        }

        # Now we have seen all the partitions, make sure there is only one
        # system partition and one boot partition (either tagged or untagged
        # and the system partition) anything else is a fatal error!

        if (($system_tag_files_found != 1) && ($boot_tag_file_found > 1) &&
            (($sys_bootable_no_tag eq "y") && ($boot_tag_file_found != 0))) {

            print LOG "$prog: Fatal Error:  system_tag_files_found $system_tag_files_found\nboot_tag_file_found $boot_tag_file_found\nsys_bootable_no_tag $sys_bootable_no_tag\nare an invalid combination\n";

            $sys_drive_part = "";

            $warn = "y";

        } else {

            # Delete the pagefile if that option is enabled and recalculate
            # the space needed for the system partition without the page file
            # before calculating how much backup space is needed.

            if ($TRUNCATE_PAGEFILE) {

                # This is the system partition and TRUNCATE_PAGEFILE is set so
                # Mount the windows volume and truncate the pagefile to 0
                # length to save some backup space. Windows will recreate the
                # page file at full length on the next boot and we save backing
                # up a potentially large, useless file.

                %backups = &truncate_pagefile(%backups);

            }

            # Now set the system partition table type for restore in a global
            # variable.

            $sys_mbr_type = $part_tables{$sys_drive};

        }

        if ($backupstore_tag_files_found > 1) {

            # If more than one backupstore was found issue a non fatal warning.

            print LOG "$prog: Warning: $backupstore_tag_files_found backupstores found, only 1 is preferable\n";

            $warn = "y";
        }

        # We have identified all the partitions to be backed up, so now
        # calculate how much space on the backstore we need for all the
        # partitions to be backed up. As a side effect log the partitions
        # scheduled for backup.

        print LOG "$prog: Partitions scheduled for backup\n";

        foreach $drive_part (keys %backups) {

            ($type, $fstype, $used, $name) = split (/:/,$backups{$drive_part});

            $needed_space += $used;

            print LOG "$prog: $type $fstype $drive_part $used $name\n";
        }

        $needed_space += $OVERHEAD;

        print LOG "$prog: backupstore space needed $needed_space blocks\n";

        return (%backups);
}

sub mount_backupstore {

        local ($prog, $res, $err_msg);

        # Mount the backupstore on /mnt/backup to access the image files (and
        # potentially a configuration file to modify this script although
        # that isn't currently implemented). Use ntfs3g to mount because this
        # can't be a fat file system as there is a 2 gig file limit in fat.
        # As well use option "windows_names" to only allow filenames acceptable
        # to Windows (i.e. not including any of " * / : < > ? \ | )

        $prog = "mount_backupstore";

        # Use ntfs-3g rather than mount to make sure this is an ntfs file
        # system, use -o windows_names to only allow Windows legal characters
        # (not posix!) in file names.

        $res = `$NTFS_3G -o windows_names $backupstore_drive_part /mnt/backup 2>&1`;

        if ($? != 0) {

                # if the mount fails, complain and exit.

                $err_msg = "$prog: Error: $NTFS_3G -o windows_names $backupstore_drive_part /mnt/backup rc $? ($?) res $res\n";

                &unrecoverable_error($err_msg);
        }

        $backupstore_drive_mounted = "y";

        # Now log the fact that we have mounted the backupstore.

        $prelog_msgs .= "$prog: mounted $backupstore_drive_part /mnt/backup\n";

        # From now on we need to unmount the /mnt/backup filesystem before
        # exiting.

        return (0);
}

sub truncate_pagefile {

        local (%backups) = @_;
        local ($prog, $res, $err_msg, $data, $title, $dev, $total, $used);
        local ($avail, $percent, $mntpnt, $type, $fstype, $cur_used, $name );
        local ($saved);

        # Mount the windows partition on /mnt/windows and then zero out the
        # file pagefile.sys. After doing that update the required size of the
        # partitions to be backed up (it will be considerably smaller after
        # this!).

        $prog = "truncate_pagefile";

        $res = `$MOUNT $sys_drive_part /mnt/windows 2>&1`;

        if ($? != 0) {

                # if the mount fails, complain and exit.

                $err_msg = "$prog: Error: $MOUNT $sys_drive_part /mnt/windows rc $! ($?) res $res\n";

                &unrecoverable_error($err_msg);
        }

        # Note that /mnt/windows is mounted in case of exit.

        $mnt_windows_mounted = "y";

        # Now check if pagefile.sys exists, we don't want to create one if it
        # isn't there now!

        if (-r "/mnt/windows/$PAGE_FILE") {

                # It exists so truncate it to 0 length in preparation for the
                # backup.

                $res = `$CP /dev/null /mnt/windows/$PAGE_FILE 2>&1`;

                # If the copy fails, complain and exit.

                if ($? != 0) {

                        $err_msg = "$prog: Error: $CP /dev/null /mnt/windows/$PAGE_FILE rc $! ($?) res $res\n";

                        &unrecoverable_error($err_msg);
                }

                # Now update the backup space needed as it should be quite a
                # bit smaller without the page file ...

                $data = `$DF -P $sys_drive_part 2>&1`;

                if ($? != 0) {

                        $err_msg = "$prog: Error: $DF -P $sys_drive_part rc $! ($?) res $data\n";

                        &unrecoverable_error($err_msg);
                }

                # parse the data returned by df (skip the title line
                # which comes first!)

                ($title, $data) = split ('\n', $data, 2);

                ($dev, $total, $used, $avail, $percent, $mntpnt) = split(' ', $data);

                if ($dev ne $sys_drive_part) {

                        $err_msg = "$prog: Error: drive mismatch in df data\nwant $sys_drive_part got: $data\n";

                        &unrecoverable_error($err_msg);
                }

                ($type, $fstype, $cur_used, $name) = split(/:/,$backups{$sys_drive_part});

                # Figure out how much space we saved.

                $saved = $cur_used - $used;

                # Then write back the new used value (this is before the
                # needed_space calculation so we don't need to update that
                # here.)

                $backups{$sys_drive_part} = "s:$fstype:$used:$name";

                # Log the amount of space saved.

                print LOG "$prog: truncating the pagefile saved $saved blocks in backup space needed\n";

        }

        &unmount("/mnt/windows");

        # We won't get here if the above unmount fails, so mark /mnt/windows
        # as unmounted again and return.

        $mnt_windows_mounted = "n";

        return (%backups);
}

sub open_log {

        # So create a directory name that encodes application, date and time.
        # Thus a ls listing of the backup volume sorted in reverse order will
        # give you a list of directories that may contain backup files from
        # newest to oldest (as you are most likely to want the newest) to
        # select from during a restore operation. Each directory will contain
        # log files, image files, mbr/vbr files and md5 files of image and mbr
        # files from a backup / restore operation as well as the log file and
        # /var/log/messages file from that time period for your later enjoyment.

        local (%disks_parted, %disks_df) = @_;
        local ($prog, $timestamp, $description, $description_valid, $msg);
        local ($status, $res, $err_msg, $now);

        $prog = "open_log";

        $timestamp = `$DATE +%Y-%m-%d-%H-%M`;

        chop $timestamp;

        # If the description option is enabled, prompt the user for a
        # description of the backup.

        if ($DESCRIPTION eq "y") {

            # Get a text description of the backup from the user to include
            # in the file names.

            $description = "";

            $description_valid = "n";

            while ($description_valid eq "n") {

                $msg = "\'\t             Enter a description of the backup:\'";

                ($status, $res) = &output_dialog_screen ("", "--inputbox", $msg, "");

                if ($status == 0) {

                    # get and process the description (if any was entered).
                    # Replace " ", "/", "\", "'", "`", ":", "<", ">",
                    # "?", "*", "$", "|", ",", "#", "\n", "\r", ";", and '"'
                    # with "~" to create a legal file name (and avoid
                    # parsing problems when the filename is used in code
                    # later!). The comma and "#" are used as field
                    # separators and thats why they are being translated.
                    # The ";" causes a shell error from dialog. As well a
                    # trailing "." at the end of the name is illegal in Windows,
                    # so translate them to a "~" if present.

                    $description = $res;

                    if ($description =~ /\s|\/|\\|\'|\`|\:|\<|\>|\?|\*|\$|\||\,|\"|#|\n|\r|\;|\.+$/) {

                        $description_valid = "n";

                        # Then substitute the invalid character with a "." and
                        # set up to ask the user if this is OK by setting
                        # $description_valid to "n".

                        $description =~ s/\s|\/|\\|\'|\`|\:|\<|\>|\?|\*|\$|\||\,|\"|#|\n|\r|\;/\./g;

                        # Since a trailing "." is also illegal, translate
                        # "."s at the end of the name to "~" to avoid an error
                        # on directory creation.

                        $description =~ s/\.+$/\~/g;

                    } else {

                        # If no illegal charaters set the input as the
                        # description.

                        $description_valid = "y";

                    }

                    if ($description_valid ne "y") {

                        # Print the substituted string and ask the user
                        # if it is OK. If not go back to get new input.

                                $msg = "\"\n\n\t             Error: invalid characters in the input\n\nCharacters invalid in a file name were found in the description and translated to:\n\n$description\n\n       If this name is acceptable click < yes > (or press enter)\n\n             otherwise click < no > to enter a new string.\n\"";

                        ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                        if ($status == 0 ) {

                            # The user accepted the name so set the valid
                            # flag to use it as is.

                            $description_valid = "y";

                            $prelog_msgs .= "$prog: get description, user accepted \'$description\'\n";
                        }
                    }
                }
            }
        }

        # Now set global $dir_name to the directory name just created for
        # later use by other subroutines.

        $dir_name = "SB,${timestamp},${description}";

        # If it doesn't already exist (which it shouldn't unless we had a
        # failure and are rerunning within the same minute) create a directory
        # to store the various files in.

        stat("/mnt/backup/$dir_name");

        if (-e _) {

                # The filename already exists, so check if it is a directory
                # and complain if not!

                if (!(-d _)) {

                        $prelog_msgs .= "$prog: non fatal error: $dir_name already exists and is not a directory\n";

                        # So tell the user there is a problem and see what they
                        # want to do.

                        $msg = "\"The name\n\n$dir_name\n\nis already in use on the backupstore. While this shouldn't have occurred since the name is partly based on time, waiting for a minute should fix the problem by creating a new name.\n\nIf you would like to try that click \< Yes \> or press enter.\n\n      (after waiting for a minute) To return and try again.\n\nIf you click \< No \> (or use tab to move to it and press enter)\n\n      The exit options will come up.\n\"";

                        ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                        if ($status == 0 ) {

                                # return without setting the success flag to
                                # try again.

                                return (0);

                        } else {

                                # The user elected to exit so do so.

                                $err_msg = "$prog: Error: log directory $dir_name already exists, aborting at user request\n";

                                &unrecoverable_error($err_msg);

                        }
                }

        } else {

                # filename doesn't exist, so create a new directory for the
                # files

                if ($DEBUG) {

                        # Do a precreate df to see that the backupstore is
                        # in fact mounted as it should be.

                        $err_msg = `$DF`;

                        $prelog_msgs .= "$prog: pre mkdir df: $err_msg\n";
                }

                $res = `$MKDIR /mnt/backup/$dir_name 2>&1`;

                if ($? != 0) {

                        # Still no log file so this is all you will get. Disk
                        # full is a likely candidate here. It is also likely
                        # unrecoverable. Note mkdir puts `s in its error
                        # message so the response must be edited to keep
                        # dialog happy.

                        $res =~ s/\`//g;

                        $res =~ s/\'//g;

                        $res =~ s/\"//g;

                        $err_msg = "$prog: Error: $MKDIR /mnt/backup/$dir_name rc $? res $res\n";

                        &unrecoverable_error($err_msg);
                }
        }

        # open the log file append (in the unlikely case that we have a dup
        # log file no data will be lost this way, and it will create the file
        # if it doesn't exist as it usually won't).

        $res = open (LOG, ">> /mnt/backup/${dir_name}/${dir_name}.log");

        if ($? != 0) {

                $err_msg = "prog: Error: couldn't open /mnt/backup/${dir_name}/${dir_name}.log $! ($?) res $res\n";

                &unrecoverable_error($err_msg);
        }

        # Write the time we started the log as the first entry.

        $now = `$DATE`;

        chop $now;

        # Signal we have successfully opened (and thus now need to close before
        # exiting!) the log file in global $log_open.

        $log_open = "y";

        print LOG "$prog: Log started $now\n";

        # Then print any prelog error messages we have collected.

        print LOG "$prelog_msgs";

        $prelog_msgs = "";

        # We have successfully mounted the back store and started the log file
        # so return success.

        return (0);
}
Last edited by vanepp on 24 Mar 2016, 18:25, edited 1 time in total.

vanepp
Posts: 85
Joined: 22 Jun 2012, 02:24

Re: simplebackup perl script

Postby vanepp » 24 Mar 2016, 03:12

Code: Select all

sub issue_warnings {

        local ($prog, $msg, $status, $res);

        # set the subroutine name for log messages.

        $prog = "issue_warnings";

        # Start with the system partition, possible errors are: there isn't one,
        # there are more than one tag files present, the system tag file is on
        # a disk marked as a backupstore.

        if (($sys_drive_part eq "") || ( $system_tag_files_found != 1)) {

                # There isn't a valid system partition present so figure out
                # why.

                if ($system_tag_files_found == 0) {

                        # There isn't a system partition with a tag file
                        # present so log the problem.

                        print LOG "$prog: Error: no system partition tag file present, aborting\n";

                        # Then tell the user there is a problem and suggest
                        # possible solutions. Then exit as this is fatal.

                        $msg = "\'Error:\n\n      No system disk detected, backups or restore is not possible\n\nIf Windows will boot, check that the file:\n\nC:\\$WINDOWS_FILE\n\nexists. If it does not, use notepad to create that file and try again.\n\nOtherwise you will need to seek support.\n\nClick \< Yes \> or press enter to get the exit options to reboot to\n\nWindows or shutdown.\n\'";

                } elsif ($system_tag_files_found > 1) {

                        # Log the problem.

                        print LOG "$prog: Error: Too many ($system_tag_files_found) system partition tag files found, aborting\n";

                        # Then tell the user there is a problem and suggest
                        # possible solutions.

                        $msg = "\'Error:\n\n      More than one $WINDOWS_FILE file found.\n\nIf Windows will boot, check that the file $WINDOWS_FILE exists only on the C: drive.\nIf it exists on other drive(s) then replace them with the $BACKUP_FILE file and try again\nIf you can't find a second $WINDOWS_FILE you will need to seek support\n\nClick \< Yes \> or press enter to get the exit options.\n\'";

                } elsif (($sys_drive_part eq "") && ($system_tag_files_found == 1)) {

                        # System partition is on a tagged backupstore drive.
                        # Log the problem.

                        print LOG "$prog: Error: system partition tag file on a backupstore tagged drive, aborting\n";

                        # Then tell the user there is a problem and suggest
                        # possible solutions.

                        $msg = "\'Error: The Windows partition (drive C:) has both a $WINDOWS_FILE file and a $BACKUPSTORE_FILE file on it.\n\nIf Windows will boot, you need to move the $BACKUPSTORE_FILE file to a different disk and try again.\nIf you can't do that you will need to seek support\n\nClick OK or press enter to get the exit options and reboot to Windows\n\'";

                } elsif (($boot_tag_files_found == 0) &&
                         ($sys_bootable_no_tag eq "n")) {

                        # Log the problem

                        print LOG "$prog: Error: no boot partition tag files found, and system partition not bootable, aborting\n";

                        # Then tell the user there is a problem and suggest
                        # possible solutions.

                        $msg = "\'Error: No boot tag file found.\n\nYou will likely need to get your support person to set the correct flag on the boot partition\n\nClick OK or press enter to get the exit options and reboot to Windows\n\'";

                } elsif ($boot_tag_file_found > 1) {

                        # Log the problem

                        print LOG "$prog: too many boot partition tag files found, aborting\n";

                        # Then tell the user there is a problem and suggest
                        # possible solutions.

                        $msg = "\"\n\n\t             Error: Too many boot tag files found.\n\n     You will likely need to get your support person to set the\n\n               $BOOT_FILE\n\n         file on only the correct boot partition\n\n     Click < Yes > (or press enter) to get the exit options.\n\n     (or just power off right from here as we have shutdown already.)\n\"";

                }

                # Close any open files in case the user just powers off.

                &close_files();

                # Display the error message (which is fatal)

                ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                # and then exit.

                &shutdown();
        }

        # If we have logged warnings during earlier processing, advise the
        # user to bring them to the attention of their support person before
        # proceeding.

        if ($warn eq "y") {

                print LOG "$prog: Warning messages being brought to the attention of the user\n";

                # Format a dialog message for the user and wait for
                # confirmation.

                $msg = "\"\n\n\t         Warning message(s) have been written to the log file.\n\n\n\n\t        Please alert your support person to check the log file.\n\n   (to make sure the operation was successfull before it is needed!)\n\n\n\n\t            Click \< Yes \> or press enter to proceed.\n\"";

                ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

        }

        return (0);
}

sub main_menu {

        # Display the menu and process user requests.

        local (%backups) = @_;
        local ($prog, $msg, $items, $status, $res);

        $prog = "main_menu";

        # No message this time.

        $msg = "\' \'";

        $items = " $DIALOG_RADIO_SELECT \\
                1 'Help' on \\
                2 'Backup'  off \\
                3 'Exit options when done'  off \\
                4 'Restore' off \\
                5 'Remove backups' off \\
                6 'Shell (for your support person)' off ";

        ($status, $res) = &output_dialog_screen ("", "--radiolist", $msg, $items);

        print LOG "$prog: select user selected $res $status\n";

        if ($status != 0) {

                print LOG "$prog: user pressed esc, cancel or an error occurred in dialog res  $res status $status\n";

                # User pressed cancel or esc, so fall through to go back to
                # the menu (or a dialog error occurred but we can't tell!)

        } elsif ($res == 1) {

                # Display a help screen (and incidentally recover gracefully
                # from forgetting to select an action which I usually do ...)
                # Note the use of --no-collapse to control dialog formatting.

                $msg =  "\'                     Welcome to SimpleBackup\n\n\t           On the previous screen you can select:\n\nBackup         - to back up the data on your hard disk.\n\nExit options   - exit when finished by rebooting or shutting down.\n\nRestore        - to restore the hard drive from a previous backup.\n\nRemove backups - to delete a backup to free space on the backupstore.\n\nShell          - to get a command prompt (for your support person).\n\n\nNow any of \"enter\" < Yes > or < No> will get you back to the main screen\'";

                ($status, $res) = &output_dialog_screen ("--no-collapse", "--yesno", $msg, "");

                print LOG "$prog: help screen res $res status $status\n";

                # Fall through to go back to the select screen no matter the
                # return code.

        } elsif ($res == 2) {

                print LOG "$prog: user selected backup\n";

                # Mark this as a user requested backup "SB" in the file name
                # to differentiate from a safety backup during a restore.
                # Then call the backup routine to do the backup.

                &backup ("SB", %backups);

                # go and do what is specified by $BACKUP_END (shutdown, reboot,
                # do more).

                &finish_operation("backup", $BACKUP_END);

                # If this returns the user wants to do more so fall through
                # to go back to the menu as requested.

                print LOG "$prog: backup completed successfully continuing\n";

        } elsif ($res == 3) {

                print LOG "$prog: user selected exit options\n";

                # and call shutdown to display shutdown options (this won't
                # return.)

                &shutdown();

        } elsif ($res == 4) {

                print LOG "$prog: user selected restore\n";

                # Do a restore as requested

                $res = &restore(%backups);

                if ($res == 0) {

                        # restore completed successfully go and do what is
                        # specified by $RESTORE_END (shutdown, reboot, do more).
                        $res = &finish_operation("restore", $RESTORE_END );

                        # If this returns the user wants to do more so fall
                        # through to go back to the menu as requested.

                        print LOG "$prog: restore completed successfully, continuing\n";

                } else {

                        # The user clicked No or pressed escape to cancel.
                        # So put up a panel saying so til they acknowledge.

                        $msg = "\"\n\n\n\n\t                Restore cancelled as requested.\n\n\n\                   Press Enter or click < Yes >\n\n\n               to return to the main menu and proceed\n\"";

                        ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                        print LOG "$prog: restore canceled at user request, continuing\n";
                }

        } elsif ($res == 5) {

                # Remove one or more backups to free space on the back store.

                print LOG "$prog: user selected remove backup\n";

                &backupstore_maint();

                print LOG "$prog: proceeding after successful maintance\n";

        } else {

                # Assume a shell was requested for all other values!

                print LOG "$prog: user selected shell\n";

                # quietly unmount before exiting.

                &close_files();

                # Launch a shell (this won't return!)

                &shell();

        }
}

sub is_mounted {

        local($mount_point) = @_;
        local ($prog, $res, $err_msg, $header, $line);

        # Discover if the supplied mount point is in fact mounted or not.
        # returns 0 for not mounted, 1 for mounted, doesn't return on
        # error.

        $prog = "is_mounted";

        $res = `$DF -P $mount_point 2>&1`;

        if ($? != 0) {

                $err_msg = "$prog: Error: $DF -P $mount_point rc $! ($?) res $res\n";

                &unrecoverable_error($err_msg);
        }

        ($header, $line) = split (/\n/, $res);

        if ($header !~ /^Filesystem/) {

                $err_msg = "$prog: Error: $DF -P $mount_point invalid res $res\n";

                &unrecoverable_error($err_msg);
        }

        if ($DEBUG) {

                if ($log_open eq "n") {

                        $prelog_msgs .= "$prog: $DF -P $mount_point\n$line\n";

                } else {

                        print LOG "$prog: $DF -P $mount_point\n$line\n";
                }
        }

        if ($line !~ /$mount_point/) {

                # Not mounted so return 0

                return (0);

        } else {

                # It is mounted so return 1

                return (1);

        }
}

sub unmount {

        local($mount_point) = @_;
        local ($prog, $res, $err_msg);

        $prog = "unmount";

        # Try and unmount the given partition.

        $res = `$UMOUNT $mount_point 2>&1`;

        if ($? != 0) {

                # Error out if the umount fails.

                $err_msg = "$prog: Error: $UMOUNT $mount_point rc $! ($?) res $res\n";

                &unrecoverable_error($err_msg);
        }

        if ($DEBUG) {

                if ($log_open eq "n") {

                        $prelog_msgs .= "$prog: $UMOUNT $mount_point\n";

                } else {

                        print LOG "$prog: $UMOUNT $mount_point\n";

                }
        }

        if (&is_mounted($mount_point)) {

                # Error out if the device is still mounted as well. .

                $err_msg = "$prog: Error: $mount_point still mounted after umount\n";

                &unrecoverable_error($err_msg);
        }

        # Otherwise return success.

        return (0);
}

sub unrecoverable_error {

        local ($err_msg) = @_;
        local ($prog, $msg, $dlg, $status, $res);

        $prog = "unrecoverable_error";

        if ($err_msg eq "") {

                # If we don't have an error message create one to keep dialog
                # happy!

                $err_msg =  "\"$prog: Error: software error, error message missing\n\"";
        }

        # Log the error message.

        if ($log_open eq "n") {

                # The log file isn't open so the user is the only logging hope.

                $msg = "\"\t            An unrecoverable error has occurred\n\n    Please write this message down and give it to your support person:\n    (or leave the machine here for your support person if possible)\n\n$err_msg\n\n\n    Then press enter or < Yes > and the shutdown menu will come up.\n\n\t        (or just power down now as we are shutdown already.)\n\"";

        } else {

                # Send the error message to the log file.

                print LOG "$prog: $err_msg";

                # And a generic message to the user (without the details).

                $msg = "\"\n\n\t          An unrecoverable error has occurred and been logged.\n\n        You will need to ask your support person to check the log\n\n                      and correct the error.\n\n\n\n\n\n  Then press enter or click < Yes > and the shutdown menu will come up.\n\n         (or just power down now as we are shutdown already.)\n\"";

        }

        # Close the files in case the user just powers down ...

        &close_files();

        # Then display it to the user.

        ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

        # When the user acknowledges, call the shutdown menu to shutdown.

        &shutdown();
}

sub shutdown {

        # Give the user their choice of exit options (shutdown, reboot, shell)

        local ($prog, $msg, $items, $status, $res);

        $prog = "shutdown";

        # First close the log file, copy syslog and dismount the filesystems.

        &close_files();

        $msg = "\"\t          Please select a shutdown option.\n\"";

        $items = "$DIALOG_RADIO_SELECT \\
                1 'Shutdown (will power off the machine)' on \\
                2 'reboot   (remove the SimpleBackup CD or USB drive first!)' off \\
                3 'Shell    (Linux shell for your support person)' off ";

        ($status, $res) = &output_dialog_screen ("", "--radiolist", $msg, $items);

        if (($status != 0) || ($res == 1)) {

                # If esc or cancel or default choice, shut down

                `$SHUTDOWN -P now`;

                # since this will fork to do the shutdown, exit perl to
                # prevent reopening the files and restarting the menu before
                # the shutdown takes place.

                exit(0);

        } elsif ($res == 2) {

                # Reboot if requested

                `$SHUTDOWN -r now`;

                # since this will fork to do the shutdown, exit perl to
                # prevent reopening the files and restarting the menu before
                # the reboot takes place.

                exit(0);

        } else {

                # For all other responses, drop to a Linux shell (this won't
                # return!)

                &shell();
        }
}

# Subroutine to back up the Windows system, the MBR/VBR, the Windows partition
# and a separate boot partition if there is one. Also called by restore to do
# a backup before the restore (enabled by default but can be disabled, although
# that is a bad idea ...).

sub backup {

        # make the input arguments local and the local variables as well.

        local($backup_type, %backups) = @_;
        local($prog, $now, $local_timestamp, $backup_part, $type, $fstype);
        local($used, $name, $win_filename, $part, $err_msg);

        $prog = "backup";

        # Create a new local timestamp so each backup is unique in case the
        # same backup gets run more than once for some reason. All the files
        # in a backup will share the same timestamp.

        $local_timestamp = `$DATE +%Y-%m-%d-%H-%M-%S`;

        chop $local_timestamp;

        # Cycle through the partitions selected for backup, backing them up
        # as we go.

        foreach $backup_part (sort keys (%backups)) {

                # Cycle through the partitions to back up, create an appropriate
                # file name and then back them up.

                ($type, $fstype, $used, $name) = split(/:/,$backups{$backup_part});

                # Create a filename from the backup_type: SB for a user
                # requested backup, SR for a backup done before a restore
                # operation, filesystem type (to select the appropriate restore
                # program either manually or automatically later), partition
                # number on the drive (this will be MBR for the mbr(s)),
                # timestamp, and disk serial number (or unknown if we don't
                # have a serial number). Thus the file name is a simple
                # database entry that describes what the file is and what
                # drive (and thus partition table, to determine the partition
                # size if necessary) it came from.

                if (($fstype eq "msdos") || ($fstype eq "gpt")) {

                        # This is the MBR so no partition number.

                        $win_filename = "${dir_name}/#${backup_type},${type},${local_timestamp},${fstype},MBR,${name},";

                } else {

                        # Get only the partition number of the partition for
                        # the filename creation (later the device may well be
                        # different depending on how Linux probes it!).

                        $part = $backup_part;

                        $part = chop $part;

                        $win_filename = "${dir_name}/#${backup_type},${type},${local_timestamp},${fstype},part${part},${name},";

                }

                # Now back up the partitions by partition type.

                if ($fstype eq "ntfs") {

                        &backup_ntfs_part ($win_filename, $backup_part);

                } elsif ($fstype =~/^fat/) {

                        &backup_fat_part ($win_filename, $backup_part);

                } elsif ($fstype eq "msftres") {

                        &backup_msft_part ($win_filename, $backup_part);

                } elsif (($fstype eq "msdos") || ($fstype eq "gpt")) {

                        &backup_parttable ($win_filename, $backup_part, $fstype);

                } else {

                        $err_msg = "$prog: Software Error: Unknown file system type $fstype\n";

                        &unrecoverable_error($err_msg);
                }
        }

        return (0);

}       # end of backup subroutine.

sub backup_ntfs_part {

        local ($win_filename, $drive_part) = @_;
        local ($prog, $msg, $status, $res, $now, $rc, $err_msg, $md5);

        $prog = "backup_ntfs_part";

        # Enter with the backup filename and drive/partition to back up.
        # The selected partition will be backed up using ntfsclone to the
        # directory and filename provided.

        $msg = "\'\n\n\n\n\t                  Backing up NTFS partition $drive_part\n\n\t                      (This may take some time).\n\'";

        ($status, $res) = &output_dialog_screen ("", "--infobox", $msg, "");

        # Grab and log the current time so you can see how much time it is
        # taking later (as always with the substituted values for
        # troubleshooting as well).

        $now = `$DATE`;

        chop $now;

        print LOG "$now $prog: Starting ntfsclone\n$NTFSCLONE --save-image $drive_part -O /mnt/backup/${win_filename}.img\n";

        $res = `$NTFSCLONE --save-image $drive_part -O /mnt/backup/${win_filename}.img 2>&1`;

        $rc = $?;

        # log the end time of ntfsclone so we have it even if there are errors.

        $now = `$DATE`;

        chop $now;

        if ($rc != 0) {

                print LOG  "$prog: $now: Error: $NTFSCLONE $?\n$res\n";

                &unrecoverable_error($err_msg);
        }

        # Put the ntfsclone output in the log file.

        print LOG "$now $NTFSCLONE --save-image $drive_part -O /mnt/backup/${win_filename}.img completed successfully\n$res\n";

        if ($DO_MD5) {

                # If we are doing MD5 processing (which takes a long time),
                # reassure the user that something really is happening still ...

                $msg = "\'\n\n\t              Calculating the MD5 of image file:\n\n${win_filename}.img\n\n\t                  (This may take some time.)\n\'";

                ($status, $res) = &output_dialog_screen ("", "--infobox", $msg, "");

                $now = `$DATE`;

                chop $now;

                print LOG "$now $prog: Starting MD5 of image\n$MD5SUM /mnt/backup/${win_filename}.img 2>&1 > /mnt/backup/${win_filename}.img.md5\n";

                # take the md5 hash of the saved image file

                $res = `$MD5SUM /mnt/backup/${win_filename}.img 2>&1 > /mnt/backup/${win_filename}.img.md5`;

                $rc = $?;

                $now = `$DATE`;

                chop $now;

                print LOG "$now $prog: end of md5\n";

                # and again bitch and exit if the return code isn't 0

                if ($rc != 0) {

                        $err_msg =  "$prog: Error: $MD5SUM rc $rc res $res\n";

                        &unrecoverable_error($err_msg);
                }

                # Save the image MD5 in the log file as additional insurance.

                $md5 = `$CAT /mnt/backup/${win_filename}.img.md5`;

                if ($? != 0) {

                        $err_msg = "$prog: Error: $CAT /mnt/backup/${win_filename}.msft.md5 rc $! ($?) res $md5\n";

                        &unrecoverable_error($err_msg);
                }

                chop $md5;

                print LOG "$prog: image MD5 $md5\n";
        }

        # Then return to the caller to continue.

        return (0);

}

sub backup_fat_part {

        local ($win_filename, $drive_part) = @_;
        local ($prog, $msg, $status, $res, $now, $rc, $err_msg, $md5);

        $prog = "backup_fat_part";

        # Enter with the backup filename and drive/partition to back up.
        # The selected partition will be backed up using partclone to the
        # directory and filename provided.

        $msg = "\'\n\n\n\n\t                  Backing up FAT partition $drive_part\n\n\t                      (This may take some time).\n\'";

        ($status, $res) = &output_dialog_screen ("", "--infobox", $msg, "");

        # Grab and log the current time so you can see how much time it is
        # taking later (as always with the substituted values for
        # troubleshooting as well).

        $now = `$DATE`;

        chop $now;

        print LOG "$now $prog: Starting partclone\n$PARTCLONE_FAT -c -s $drive_part -o /mnt/backup/${win_filename}.pimg\n";

        # Then execute the partclone command.

        $res = `$PARTCLONE_FAT -c -s $drive_part -O /mnt/backup/${win_filename}.pimg 2>&1`;

        # Save the partclone return code.

        $rc = $?;

        # log the end time of partclone so we have it even if there are errors.

        $now = `$DATE`;

        chop $now;

        if ($rc != 0) {

                $err_msg = "$prog: $now: Error: $PARTCLONE_FAT $rc\n$res\n";

                &unrecoverable_error($err_msg);
        }

        print LOG "$now $prog: $PARTCLONE_FAT completed\n$res\n";

        if ($DO_MD5) {

                # If we are doing MD5 processing (which takes a long time),
                # reassure the user that something really is happening still ...

                $msg = "\'\n\n\t              Calculating the MD5 of image file:\n\n${win_filename}.pimg\n\n\t                  (This may take some time.)\n\'";

                ($status, $res) = &output_dialog_screen ("", "--infobox", $msg, "");

                $now = `$DATE`;

                chop $now;

                print LOG "$now $prog: Starting MD5 of image\n$MD5SUM /mnt/backup/${win_filename}.pimg 2>&1 > /mnt/backup/${win_filename}.pimg.md5\n";

                # take the md5 hash of the saved image file

                $res = `$MD5SUM /mnt/backup/${win_filename}.pimg 2>&1 > /mnt/backup/${win_filename}.pimg.md5`;

                $rc = $?;

                $now = `$DATE`;

                chop $now;

                print LOG "$now $prog: End of md5\n";

                # and again bitch and exit if the return code isn't 0

                if ($rc != 0) {

                        print LOG "$prog: Error: $MD5SUM return code $rc non zero\n$res\n";
                        &unrecoverable_error($err_msg);
                }

                # Save the image MD5 in the log file as additional insurance.

                $md5 = `$CAT /mnt/backup/${win_filename}.pimg.md5`;

                if ($? != 0) {

                        $err_msg = "$prog: Error: $CAT /mnt/backup/${win_filename}.msft.md5 rc $! ($?) res $md5\n";

                        &unrecoverable_error($err_msg);
                }

                chop $md5;

                print LOG "$prog: image MD5 $md5\n";
        }

        # Then return to the caller to continue.

        return (0);
}

sub backup_msft_part {

        local ($win_filename, $drive_part) = @_;
        local ($prog, $msg, $status, $res, $now, $rc, $err_msg, $md5);

        $prog = "backup_msft_part";

        # Enter with the backup filename and drive/partition to back up.
        # The selected partition will be backed up using dd to the
        # directory and filename provided.

        $msg = "\"\n\n\n\n\t                  Backing up MSFT partition $drive_part\n\n\t                      (This may take some time).\n\"";

        ($status, $res) = &output_dialog_screen ("", "--infobox", $msg, "");

        # Grab and log the current time so you can see how much time it is
        # taking later (as always with the substituted values for
        # troubleshooting as well).

        $now = `$DATE`;

        chop $now;

        print LOG "$now $prog: Starting dd\n$DD if=$drive_part of=/mnt/backup/${win_filename}.msft\n";

        $res = `$DD if=$drive_part of=/mnt/backup/${win_filename}.msft 2>&1`;

        $rc = $?;

        # log the end time of dd so we have it even if there are errors.

        $now = `$DATE`;

        chop $now;

        print LOG "$now $prog: $DD if=$drive_part of=/mnt/backup/${win_filename}.msft completed\n";

        # Check for a zero return code and bitch and exit if it isn't.

        if ($rc != 0) {

                $err_msg = "$prog: Error: $DD if=$drive_part of=/mnt/backup/${win_filename}.msft rc $rc res $res\n";

                &unrecoverable_error($err_msg);
        }

        if ($DO_MD5) {

                # If we are doing MD5 processing (which takes a long time),
                # reassure the user that something really is happening still.

                $msg = "\"\n\n\t              Calculating the MD5 of image file:\n\n${win_filename}.msft\n\n\t                  (This may take some time.)\n\"";

                ($status, $res) = &output_dialog_screen ("", "--infobox", $msg, "");

                $now = `$DATE`;

                chop $now;

                print LOG "$now $prog: Starting MD5 of image\n$MD5SUM /mnt/backup/${win_filename}.msft 2>&1 > /mnt/backup/${win_filename}.msft.md5\n";

                # take the md5 hash of the saved image file

                $res = `$MD5SUM /mnt/backup/${win_filename}.msft 2>&1 > /mnt/backup/${win_filename}.msft.md5`;

                $rc = $?;

                $now = `$DATE`;

                chop $now;

                print LOG "$now $prog: End of md5\n";

                # and again bitch and exit if the return code isn't 0

                if ($rc != 0) {

                        $err_msg = "$prog: Error: $MD5SUM /mnt/backup/${win_filename}.msft > /mnt/backup/${win_filename}.msft.md5 rc i$rc res $res\n";

                        &unrecoverable_error($err_msg);
                }

                # Save the image MD5 in the log file as additional insurance.

                $md5 = `$CAT /mnt/backup/${win_filename}.msft.md5 2>&1`;

                if ($? != 0) {

                        $err_msg = "$prog: Error: $CAT /mnt/backup/${win_filename}.msft.md5 rc $! ($?) res $md5\n";

                        &unrecoverable_error($err_msg);
                }

                chop $md5;

                print LOG "$prog: image MD5 $md5\n";
        }

        # Then return to the caller to continue.

        return (0);
}

sub backup_parttable {

        local ($win_filename, $drive_part, $fstype) = @_;
        local ($prog, $now, $res, $err_msg, $filename, $rc, $md5);

        $prog = "backup_parttable";

        # Grab and log the current time so you can see how much time it is
        # taking later (as always with the substituted values for
        # troubleshooting as well).

        $now = `$DATE`;

        chop $now;

        if ($fstype eq "msdos") {

                # This is an old style DOS partition table so use dd to backup
                # the first 32K of the disk

                print LOG "$now $prog: $DD if=$drive_part of=/mnt/backup/${win_filename}.dd bs=512 count=64 2>&1\n";

                $res = `$DD if=$drive_part of=/mnt/backup/${win_filename}.dd bs=512 count=64 2>&1`;

                if ($? != 0) {

                        $err_msg = "$prog: Error: $DD if=$drive_part of=/mnt/backup/${win_filename}.dd bs=512 count=64 rc = $! ($?) res $res\n";

                        &unrecoverable_error($err_msg);
                }

                # remember the filename in case MD5 is requested

                $filename = "/mnt/backup/${win_filename}.dd";

        } elsif ($fstype eq "gpt") {

                # This is a gpt type part table so use sgdisk to back it up

                print LOG "$prog: $SGDISK -b /mnt/backup/${win_filename}.gpt $drive_part\n";

                $res = `$SGDISK -b /mnt/backup/${win_filename}.gpt $drive_part 2>&1`;

                if ($? != 0) {

                        # At present this never seems to execute ...

                        $err_msg = "$prog: Error: $SGDISK -b /mnt/backup/${win_filename}.gpt $drive_part rc $! ($?) res $res\n";

                        &unrecoverable_error($err_msg);
                }

                # As sgdisk currently doesn't seem to set a non zero return
                # code on error, log the response to document fatal errors.

                print LOG "$prog: $SGDISK -b /mnt/backup/${win_filename}.gpt $drive_part rc $! res\n$res\n";

                # remember the filename in case MD5 is requested

                $filename = "/mnt/backup/${win_filename}.gpt";

        } else {

                # Shouldn't ever get here but just in case ...

                $err_msg = "$prog: Software Error: $fstype unknown partition table type\n";

                &unrecoverable_error($err_msg);
        }

        $now = `$DATE`;

        chop $now;

        print LOG "$prog: $now Starting MD5 of mbr\n$MD5SUM $filename > ${filename}.md5\n";

        # take the md5 hash of the saved mbr file

        $res = `$MD5SUM $filename 2>&1 > ${filename}.md5`;

        # and again bitch and exit if the return code isn't 0

        if ($? != 0) {

                $err_msg = "$prog: $MD5SUM $filename > ${filename}.md5 rc $! ($?) res $res\n";

                &unrecoverable_error($err_msg);
        }

        # Save the image MD5 in the log file as additional insurance.

        $md5 = `$CAT ${filename}.md5 2>&1`;

        if ($? != 0) {

                $err_msg = "$prog: Error: $CAT ${filename}.md5 rc $! ($?) res $md5\n";
                &unrecoverable_error($err_msg);
        }

        chop $md5;

        $res = print LOG "$prog: mbr/gpt MD5 $md5\n";

        # Then return to the caller to continue.

        return (0);
}
Last edited by vanepp on 24 Mar 2016, 18:28, edited 1 time in total.

vanepp
Posts: 85
Joined: 22 Jun 2012, 02:24

Re: simplebackup perl script

Postby vanepp » 24 Mar 2016, 03:22

Code: Select all

sub restore {

        # Subroutine to restore a partition. For safety we will first do a
        # backup of the partition to be restored (so you can recover it if
        # this was a mistake!). The user will then be given a choice of the
        # backup files for this disk stored on the backupstore to restore from.

        local (%backups) = @_;
        local ($prog, $type, $fstype, $used, $name, $err_msg, $msg, $status);
        local ($res, @res_disp, $filenames, $sel, $index, $backup_name, $items);
        local ($sel_backup_type, $sel_timestamp, $sel_desc, $system_found);
        local ($part_table_found, $dir_filename, $dir, $filename, $backup_type);
        local ($backup_timestamp, $part, $ext, $md5, $rest, %md5s);
        local (@restore_ntfs, @restore_fat, @restore_msftres);
        local ($file_part_table_type, $restore_mbr, $sys_drive, $rest);
        local ($part_tables_different, $cur_md5, $file_md5, $restore_file);
        local ($drive_part);

        $prog = "restore";

        # list the current backups available to restore and let the user pick
        # one. Then check that it is reasonable to try and restore the selected
        # backup set to the target disk.

        # In order to do that first find the partition and disk name of the
        # system disk in global $sys_drive_part to select suitable backups.


        ($type, $fstype, $used, $name) = split(/:/,$backups{$sys_drive_part});

        if ($type ne "s") {

                # Didn't find the system disk (shouldn't occur, but just in
                # case ...)

                $err_msg = "$prog: Error didn't find system disk $sys_drive_part\n";

                &unrecoverable_error($err_msg);
        }

        if ($name =~ /unknown/) {

                # If we didn't find a serial number on the disk then backup
                # isn't simple and the restore needs to be done by the
                # support person not ths script. So give the user the bad
                # news.

                $msg = "\"\n\n\n\n\n\t        Unfortunatly this disk does not have a serial number.\n\n\t            Thus this program can not safely restore it.\n\n\t You will need to get your support person to do the restore manually.\n\"";

                ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                return (0);

        }

        # Get the backup dirctory names for the disk serial number identified
        # above from the backupstore and sort them in reverse time order so
        # the latest is first on the assumption that the user will want the
        # newest backup to work on.

        ($filenames, @res_disp) = &list_restore_imgs($name);

        if ($#res_disp == -1) {

                # There are no files to restore, so give the user the bad news.

                $msg = "\"\n\n\n\n\t        Error: no suitable backup image for this disk could be\n\n\t        found on this backupstore. If you have another backupstore\n\n\t        try connecting that in place of the current one and try again.\n\n\t        Otherwise contact your support person for assistance\n\"";

                ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                # Then return to the main menu.

                return (0);
        }

        # Then present the list to the user and ask them to select the file
        # to restore. Set $sel to "on" so the first entry will be preselected

        $sel = "on";

        for ($index = 0; $index <= $#res_disp; $index++ ) {


                $backup_name = $res_disp[$index];

                $dialog_data .= " \\ $index $backup_name $sel";

                # Set $sel off to unselect the rest of the entries.

                $sel = "off";
        }

        $msg = "\"              Select which backup to restore from:\"";

        $items = "$DIALOG_RADIO_RESTORE $dialog_data";

        ($status, $res) = &output_dialog_screen ("", "--radiolist", $msg, $items);

        if ($status == 1) {

                # User elected to cancel so return 1

                return (1);
        }

        # Set the backup file to restore from and check that we have all the
        # required files to do the restore so if there is an error we won't
        # waste time doing the pre restore backup (if selected).
        # Split out the timestamp from the selected restore as that will be
        # used to select the needed files from $filenames.

        ($sel_backup_type, $sel_timestamp, $sel_desc) = split(/_/,$res_disp[$res],3);

        # Cycle through the filenanes returned and select only those with a
        # matching backup type and timestamp and store them for restore by
        # file type. Save any .md5 files found in an associative array indexed
        # by file name to make checking easier.

        # Mark the system and partition tables both not yet found.

        $system_found = 0;

        $part_table_found = 0;

        # split one file name from the list.

        ($dir_filename, $filenames) = split (/\n/,$filenames,2);

        while ($dir_filename) {

                # Split the directory and the filename apart.

                ($dir, $filename) = split (/#/,$dir_filename,2);

                # Split out the filename fields.

                ($backup_type, $type, $backup_timestamp, $fstype, $part, $name, $ext) = split(/,/,$filename,7);

                if (($sel_backup_type eq $backup_type) &&
                    ($sel_timestamp eq $backup_timestamp)) {

                        # If the backup type and timestamps match then save
                        # this file for restore.

                        if ($ext =~ /\.md5$/) {

                                # If this is an md5 of one of the other files
                                # read the md5 value from file and save it in
                                # an associative array indexed by filename for
                                # later reference.

                                $md5 = `$CAT ${dir_filename} 2>&1`;

                                if ($? != 0) {

                                        $err_msg = "$prog: Error: $CAT ${dir_filename} ret $! ($?) ret $md5\n";

                                        &unrecoverable_error($err_msg);
                                }

                                ($md5, $rest) = split (/ /,$md5,2);

                                # log the found md5 value

                                print LOG "$prog: $CAT ${dir_filename} ret $md5\n";

                                $md5s{"$dir_filename"} = "$md5";

                        } elsif ($fstype eq "ntfs") {

                                if ($type eq "s") {

                                        # If this is a system partition note
                                        # that.

                                        if ($DEBUG) {

                                                print LOG "$prog: found system image $dir_filename\n";
                                        }

                                        $system_found += 1;
                                }

                                # otherwise save the file by filesystem type
                                # for restore

                                if ($DEBUG) {

                                        print LOG "$prog: found ntfs image $dir_filename\n";
                                }


                                push (@restore_ntfs, "$dir_filename");

                        } elsif ($fstype =~ /^fat/) {

                                if ($DEBUG) {

                                        print LOG "$prog: found fat image $dir_filename\n";
                                }

                                push (@restore_fat, "$dir_filename");

                        } elsif ($fstype eq "msftres") {

                                if ($DEBUG) {

                                        print LOG "$prog: found mstf image $dir_filename\n";
                                }

                                push (@restore_msftres, "$dir_filename");

                        } elsif (($fstype eq "gpt") || ($fstype eq "msdos")) {

                                # Note that this is the MBR

                                $part_table_found += 1;

                                if ($DEBUG) {

                                        print LOG "$prog: found part table image $dir_filename\n";
                                }

                                # Save the type (msdos or gpt) of the part
                                # table for later.

                                $file_part_table_type = $fstype;

                                $restore_mbr = "$dir_filename";

                        } else {

                                $err_msg = "$prog: Error: $fstype is an unknown filesystem type in $dir_filename, aborting\n";

                                &unrecoverable_error($err_msg);

                        }
                }

                # Get the next file name (if any).

                ($dir_filename, $filenames) = split (/\n/,$filenames,2);
        }

        # Now do some error checking, first make sure there is only 1 system
        # partition and one partition table present.

        if (($system_found != 1) || ($part_table_found != 1)) {

                # We need 1 system partition and 1 partition table, anything
                # else is a fatal error. This could still fail if someone
                # deleted the boot partition, but it can also be part of the
                # system partition so one not being present isn't necessarily
                # an error.

                $err_msg = "$prog: Error: Too many or too few partition ($part_table_found) or system ($system_found) files found. Aborting\n";

                &unrecoverable_error($err_msg);

        }

        # Get the sys_drive by deleting the partition number.

        $sys_drive = $sys_drive_part;

        chop $sys_drive;

        # Now check if the current partition table matches the one from the
        # backup, if not, ask the user if they want to proceed.

        ($type, $fstype, $rest) = split(/:/,$backups{"$sys_drive"},3);

        if (($type ne "p") && (($fstype ne "msdos") || ($fstype ne "gpt"))) {

                # while this shouldn't occur, just in case ...

                $err_msg = "$prog: Error: partition table $backups{$sys_drive} for drive $sys_drive invalid: aborting\n";

                &unrecoverable_error($err_msg);

        }

        $part_tables_different = "n";

        if ($file_part_table_type ne $fstype) {

                # Mark that the two part tables are different types

                $part_tables_different = "Y";

                # and log that.

                print LOG "$prog: current part table: $fstype, restore part table $file_part_table_type\n";

        }

        if ($part_tables_different eq "n") {

                # They are the same type, so see if md5 values match.
                # First get the md5 of the system drive partition table.
                # By figuring which type it is from global $sys_mbr_type.

                if ($sys_mbr_type eq "msdos") {

                        $cur_md5 = `$DD if=$sys_drive bs=512 count=64 2>&1 | $MD5SUM 2>&1`;

                        if ($? != 0) {

                                $err_msg = "$prog: Error: $DD if=$sys_drive bs=512 count=64 2>&1 | $MD5SUM 2>&1 failed, rc = $? res = $cur_md5\n";

                                &unrecoverable_error($err_msg);

                        }

                } elsif ($sys_mbr_type eq "gpt") {

                        $res = `$SGDISK -b /tmp/cur_part_table $sys_drive 2>&1`;

                        if ($? != 0) {

                                # At present this never executes ...

                                $err_msg = "$prog: Error: $SGDISK -b /tmp/cur_part_table $sys_drive  2>&1 failed, rc = $? res = $res\n";

                                &unrecoverable_error($err_msg);

                        }

                        # So log the output in case of error

                        print LOG "$prog: $SGDISK -b /tmp/cur_part_table $sys_drive 2>&1 rc $? res $res\n";

                        $cur_md5 = `$MD5SUM /tmp/cur_part_table 2>&1`;

                        if ($? != 0) {

                                $err_msg = "$prog: Error: $MD5SUM /tmp/cur_part_table 2>&1 failed, rc = $? cur_md5 = $cur_md5\n";

                                &unrecoverable_error($err_msg);

                        }

                } else {

                        # Shouldn't occur but ...

                        $err_msg =  "$prog: Error: Invalid partition table type $sys_mbr_type, aborting\n";

                        &unrecoverable_error($err_msg);
                }

                # Then compare it to the one in the restore file. Note this
                # will need work if we decide to restore data partitions from
                # other than the system disk (as there will be multiple part
                # tables and we would have to find the correct one here).

                $file_md5 = $md5s{"${restore_mbr}.md5"};

                # remove the file names leaving only the md5 value to compare.

                ($cur_md5, $rest) = split(/ /,$cur_md5,2);

                # Make sure both md5s have a value

                if (($file_md5 eq "") || ($cur_md5 eq "")) {

                        $err_msg = "$prog: Error: file ($file_md5) or current ($cur_md5) missing, aborting\n";

                        &unrecoverable_error($err_msg);

                }

                # If they are both present but different then we need to
                # replace the part table (with the user's permission because
                # it is dangerous!)

                if ($file_md5 ne $cur_md5) {

                        $part_tables_different = "y";

                }
        }

        if ($part_tables_different eq "y") {

                print LOG "$prog: Warning: current and restore file partition tables are different\n current md5 $cur_md5 file md5 $file_md5\nUser asked for configmation of restore\n";

                # The partition tables are different, so ask the user if that
                # is OK since data loss may occur if we replace the partition
                # table.

                $msg = "\"\n\t       Important warning:\tThe partition tables are different!\n\n\t     If you are changing from one Windows version to another\n\t             (such as Win7 to Win10) this may be normal.\n\n\t      However if you proceed with the restore it is possible\n\t   that important data will be lost, so be very sure this is OK.\n\n\t     The safest course of action is to select the default \< Yes \> answer to cancel the restore and then check with your support person to make sure this is expected. If you are sure it is OK, select \< No \>  below to not cancel the restore.\n\n\tDo you want to cancel the restore as data loss may occur?\n\"";


                ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                if ($status == 0 ) {

                        # User selected yes, so abort the restore. (Smart user,
                        # consider yourself blessed :-)).

                        print LOG "$prog: User elected to cancel the restore\n";

                        return (1);
                }

                print LOG "$prog: Warning: User elected to replace the partition table and proceed with the restore which may cause data loss.\n";

        }

        if ($DO_MD5) {

                # OK you asked for it :-). Cycle through all the files (other
                # than the mbr which we did above) to be restored and compare
                # their current md5 check sum with the one on file from the
                # backup. This will take a long time.

                # Note we haven't reported a missing md5 yet (so we will only
                # do so once per restore.)

                $no_md5_reported = "n";

                for ($index = 0; $index <= $#restore_ntfs; $index++ ) {

                        $dir_filename = $restore_ntfs[$index];

                        # check_md5 won't return if there is an error.

                        ($status, $no_md5_reported) = &check_md5($no_md5_reported, $dir_filename, %md5s);

                        if ($status == 1) {

                                # The user requested the restore be cancelled
                                # so do so.

                                return (1);
                        }

                }

                for ($index = 0; $index <= $#restore_fat; $index++ ) {

                        $dir_filename = $restore_fat[$index];

                        # check_md5 won't return if there is an error.

                        ($status, $no_md5_reported) = &check_md5($no_md5_reported, $dir_filename, %md5s);

                        if ($status == 1) {

                                # The user requested the restore be cancelled
                                # so do so.

                                return (1);
                        }
                }

                for ($index = 0; $index <= $#restore_msftres; $index++ ) {

                        $dir_filename = $restore_msftres[$index];

                        # check_md5 won't return if there is an error.

                        ($status, $no_md5_reported) = &check_md5($no_md5_reported, $dir_filename, %md5s);

                        if ($status == 1) {

                                # The user requested the restore be cancelled
                                # so do so.

                                return (1);
                        }
                }
        }

        # Things look ok to do a restore, so now do the safety backup if that
        # is enabled.


        if ($BACKUP_BEFORE_RESTORE) {

                print LOG "$prog: starting backup of current disk\n";

                # Backup the current volume first set the backup type to "SR"
                # so we know it was a pre restore backup rather than a user
                # requested backup during later restores (as this probably
                # isn't a good choice to restore from normally!) and for
                # backupstore maintance operations. Backup won't return if
                # there is an error.

                $res = &backup ("SR", %backups);
        }

        # If we get here we are now ready to do the actual restore having
        # backed up the current disk.

        if ($part_tables_different eq "y") {

                # The partition tables are different so restore the old
                # partiiton table first (as that is required before we can
                # restore the previous partitions!)

                if ($file_part_table_type eq "msdos") {

                        # Original part table so restore it with dd.

                        $res = `$DD if=$restore_mbr of=$sys_drive 2>&1`;

                        if ($? != 0) {

                                $err_msg =  "$prog: Error: $DD if=$restore_mbr of=$sys_drive 2>&1 return $? response $res, aborting\n";

                                &unrecoverable_error($err_msg);

                        }

                        print LOG "$prog: $DD if=$restore_mbr of=$sys_drive 2>&1 restored part table\n";

                } elsif ($file_part_table_type eq "gpt") {

                        $res = `$SGDISK -l $restore_mbr $sys_drive 2>&1`;

                        if ($? != 0) {

                                # At present this never executes ...

                                $err_msg = "$prog: Error: $SGDISK -l $restore_mbr $sys_drive 2>&1 failed, rc = $? res = $res\n";

                                &unrecoverable_error($err_msg);

                        }

                        # So log the output in case of error

                        print LOG "$prog: $SGDISK -l $restore_mbr $sys_drive 2>&1 rc $? res $res\n"

                } else {

                        $err_msg = "$prog: Error: $file_part_table_type is an invalid partition table type, aborting\n";

                        &unrecoverable_error($err_msg);
                }
        }

        # Now restore the file systems one at a time starting with FAT.

        for ($index = 0; $index <= $#restore_fat; $index++ ) {

                # First extract the partition number from the file name.

                $restore_file = "$restore_fat[$index]";

                # Clear any previous file first.

                $dir = "";

                $filename = "";

                $fstype = "";

                ($dir, $filename) = split(/#/,$restore_file,2);

                ($backup_type, $type, $backup_timestamp, $fstype, $part, $name, $ext) = split(/,/,$filename,7);

                if (($fstype !~ /^fat/) || ($dir eq "") ||
                    ($filename eq "") || ($ext ne ".pimg")) {

                        $err_msg = "$prog: Error: $restore_file fstype $fstype isn't fat or dir $dir or filename $filename is blank or $ext isn't .pimg\n";

                        &unrecoverable_error($err_msg);
                }

                # remove the "part" before the part number

                $part = chop $part;

                # Then add on the current system drive value to form the
                # current drive_part value.

                $drive_part = "${sys_drive}${part}";

                # Then restore the partition (this won't return if there is an
                # error).

                &restore_fat_part($restore_file, $drive_part);
        }

        # Then msftres

        for ($index = 0; $index <= $#restore_msftres; $index++ ) {

                # Clear any previous file first.

                $dir = "";

                $filename = "";

                $fstype = "";

                # First extract the partition number from the file name.

                $restore_file = "$restore_msftres[$index]";

                ($dir, $filename) = split(/#/,$restore_file,2);

                ($backup_type, $type, $backup_timestamp, $fstype, $part, $name, $ext) = split(/,/,$filename,7);

                if (($fstype ne "msft") || ($dir eq "") ||
                    ($filename eq "") || ($ext ne ".msft")) {

                        $err_msg = "$prog: Error: $restore_file fstype $fstype isn't msft or dir $dir or filename $filename is blank\n";

                        &unrecoverable_error($err_msg);
                }

                # remove the "part" before the part number

                $part = chop $part;

                # Then add on the current system drive value to form the
                # current drive_part value.

                $drive_part = "${sys_drive}${part}";

                # Then restore the partition (this won't return if there is an
                # error).

                &restore_msftres_part($restore_file, $drive_part);

        }

        # And finally ntfs

        for ($index = 0; $index <= $#restore_ntfs; $index++ ) {

                # Clear any previous file first.

                $dir = "";

                $filename = "";

                $fstype = "";

                # First extract the partition number from the file name.

                $restore_file = "$restore_ntfs[$index]";

                ($dir, $filename) = split(/#/,$restore_file,2);

                ($backup_type, $type, $backup_timestamp, $fstype, $part, $name, $ext) = split(/,/,$filename,7);

                if (($fstype ne "ntfs") || ($dir eq "") ||
                    ($filename eq "") || ($ext ne ".img")) {

                        $err_msg = "$prog: Error: $restore_file fstype $fstype isn't ntfs or dir $dir or filename $filename is blank or $ext isn't .img\n";

                        &unrecoverable_error($err_msg);
                }

                # remove the "part" before the part number

                $part = chop $part;

                # Then add on the current system drive value to form the
                # current drive_part value.

                $drive_part = "${sys_drive}${part}";

                # Then restore the partition (this won't return if there is an
                # error).

                &restore_ntfs_part($restore_file, $drive_part);

        }

        return (0);
}

sub restore_ntfs_part {

        local ($win_filename, $drive_part) = @_;
        local ($prog, $msg, $status, $res, $now, $rc, $err_msg);

        $prog = "restore_ntfs_part";

        # Enter with the backup filename and drive/partition to restore.
        # The selected partition will be restored using ntfsclone to the
        # drive and partition provided.

        $msg = "\"\n\n\n\n\t                  Restoring NTFS partition $drive_part\n\n\t                      (This may take some time).\n\"";

        ($status, $res) = &output_dialog_screen ("", "--infobox", $msg, "");

        # Grab and log the current time so you can see how much time it is
        # taking later (as always with the substituted values for
        # troubleshooting as well).

        $now = `$DATE`;

        chop $now;

        print LOG "$now $prog: Starting ntfsclone\n$NTFSCLONE --restore-image ${win_filename} -O $drive_part\n";

        # run ntfsclone.

        $res = `$NTFSCLONE --restore-image ${win_filename} -O $drive_part 2>&1`;

        $rc = $?;

        # log the end time of ntfsclone so we have it even if there are errors.

        $now = `$DATE`;

        chop $now;

        if ($rc != 0) {

                $err_msg = "$prog: $now: Error: $NTFSCLONE $rc\n$res\n";

                &unrecoverable_error($err_msg);
        }

        # Copy the output to the log file.

        print LOG "$NTFSCLONE completed successfully\$res\n";

        # Then return to the caller to continue.

        return (0);

}

sub restore_fat_part {

        local ($win_filename, $drive_part) = @_;
        local ($prog, $msg, $status, $res, $now, $rc, $err_msg);

        $prog = "restore_fat_part";

        # Enter with the backup filename and drive/partition to restore.
        # The selected file will be restored using partclone to the
        # drive/partition provided.

        $msg = "\"\n\n\n\n\t                  Restoring FAT partition $drive_part\n\n\t                      (This may take some time).\n\"";

        ($status, $res) = &output_dialog_screen ("", "--infobox", $msg, "");

        # Grab and log the current time so you can see how much time it is
        # taking later (as always with the substituted values for
        # troubleshooting as well).

        $now = `$DATE`;

        chop $now;

        print LOG "$now $prog: Starting partclone\n$PARTCLONE_RESTORE -s ${win_filename} -O $drive_part\n";

        # run partclone

        $res = `$PARTCLONE_RESTORE -s ${win_filename} -O $drive_part 2>&1`;

        $rc = $?;

        # log the end time of partclone so we have it even if there are errors.

        $now = `$DATE`;

        chop $now;

        if ($rc != 0) {

                $err_msg = "$prog: $now: Error: $PARTCLONE_RESTORE $rc\n$res\n";

                &unrecoverable_error($err_msg);
        }

        print LOG "$now $prog: $PARTCLONE_RESTORE completed\n$res\n";

        # Then return to the caller to continue.

        return (0);
}

sub restore_msft_part {

        local ($win_filename, $drive_part) = @_;
        local ($prog, $msg, $status, $res, $now, $rc, $err_msg);

        $prog = "restore_msft_part";

        # Enter with the backup filename and drive/partition to restore.
        # The selected file will be restored using dd to the disk/partition
        # provided.

        $msg = "\"\n\n\n\n\t                  Restoring MSFT partition $drive_part\n\n\t                      (This may take some time).\n\"";

        ($status, $res) = &output_dialog_screen ("", "--infobox", $msg, "");

        # Grab and log the current time so you can see how much time it is
        # taking later (as always with the substituted values for
        # troubleshooting as well).

        $now = `$DATE`;

        chop $now;

        print LOG "$now $prog: Starting dd\n$DD if=${win_filename} of=$drive_part\n";

        $res = `$DD if=${win_filename} of=$drive_part 2>&1`;

        $rc = $?;

        # log the end time of dd so we have it even if there are errors.

        $now = `$DATE`;

        chop $now;

        print LOG "$now $prog: $DD completed\n";

        # Check for a zero return code and bitch and exit if it isn't.

        if ($rc != 0) {

                $err_msg = "$prog: Error: $DD return code $rc non zero\n$res\n";

                &unrecoverable_error($err_msg);
        }

        # Then return to the caller to continue.

        return (0);

}

sub backupstore_maint {

        # Display the backupstore statistics and ask the user what they want
        # to do.

        local ($prog, $print_backupstore_space, $print_needed_space);
        local ($free_space, $print_free_space, $msg, $items, $status, $res);

        $prog = "backupstore_maint";

        # Make printable (with commas every 3 digits) versions of the global
        # space variables $backupstore_space and $needed_space.

        $print_backupstore_space = &commas($backupstore_space);

        $print_needed_space = &commas($needed_space);

        if ($needed_space > $backupstore_space) {

                # Then calculate and display how much space needs to be freed.

                $free_space = $needed_space - $backupstore_space;

                $print_free_space = &commas($free_space);

                $msg = sprintf "\" The backupstore has %16s K available (%s) space used\n                     %16s K is needed for backup.\n you need to free    %16s K space on the backupstore\n\n\t\t(by deleting older backups)\n\"", $print_backupstore_space, $backupstore_percent, $print_needed_space, $print_free_space;

        } else {

                # If there is enough space indicate that nothing needs to be
                # deleted.

                $msg = sprintf "\"   The backupstore has %16s K availabe (%s space used)\n                       %16s K are needed for backup.\n\nSufficient space is available for the backup without deleting anything\n\"", $print_backupstore_space, $backupstore_percent, $print_needed_space;

        }

        # Note that in this case dialog has an extra option  --no-collapse
        # to control the formatting of the numbers in $msg.

        $items = "$DIALOG_RADIO_SELECT \\
                1 'Delete images from the backupstore' on \\
                2 'return to last menu'  off \\
                3 'Choose exit options' off ";

        ($status, $res) = &output_dialog_screen ("--no-collapse", "--radiolist", $msg, $items);

        if (($status != 0) || ($res == 2)) {

                # If cancel, escape or item 2 selected, Go back up one menu.

                return (1);
        }

        if ($res == 1) {

                # Go delete an image or images.

                &delete_images();

                return (0);

        } else {

                # For all other responses, shut down.

                &shutdown();
        }
}
Last edited by vanepp on 24 Mar 2016, 18:30, edited 1 time in total.

vanepp
Posts: 85
Joined: 22 Jun 2012, 02:24

Re: simplebackup perl script

Postby vanepp » 24 Mar 2016, 03:24

Code: Select all

sub delete_images {

        # List the backup images on the backupstore that could be deleted to
        # free up space on the backupstore. Present them oldest first on the
        # assumption that older backups are less important. The SR (restore
        # safety) backups are presented last as they may be the more valuable
        # ones.

        local ($prog, $filenames, @del_disp, $msg, $status, $res, $sel);
        local ($index, $backup_name, $dialog_data, $items, $del_index);
        local ($sel_backup_type, $sel_backup_timestamp, $sel_desc);
        local ($dir_file, $dir, $filename, $backup_type, $type);
        local ($backup_timestamp, $fstype, $part, $name, $err_msg);

        $prog = "delete_images";

        # First get a list of all the images available to delete (sorted by
        # largest number of generations, then oldest first.)

        ($filenames, @del_disp) = &list_delete_imgs();

        if ($#del_disp == -1) {

                print LOG "$prog: backupstore $backupstore_percent full, but no files to delete. Aborting\n";

                # Close the files in case the user just powers down.

                &close_files();

                # No image files to delete! So tell the user.

                $msg = "\"\t                 The Backupstore is $backupstore_percent full\n\n       However none of our image files were found to delete.\n\n   You can either reboot in to Windows and move or delete files from\n   the current backupstore drive, replace the current backupstore\n   with another, or get your support person to free up space on the\n   current backupstore.\n\n\n       There isn't anything further that this program can do.\n\n\n       Press Enter or click \< Yes \> to bring up exit options\n\"";

                ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                &shutdown();

        } else {

                # Then present the list to the user and ask them to select the
                # file to delete.

                $sel = "on";

                for ($index = 0; $index <= $#del_disp; $index++ ) {

                        $backup_name = $del_disp[$index];

                        $dialog_data .= " \\ $index $backup_name $sel";

                        $sel = "off";
                }

                $msg = "\'\t                   Select which backup to delete:\n                   (choose the least valuable one!)\n\'";

                $items = "$DIALOG_RADIO_RESTORE $dialog_data";

                ($status, $res) = &output_dialog_screen ("", "--radiolist", $msg, $items);

                if ($status != 0) {

                        # The user pressed cancel so abort and go back to the
                        # initial selection screen!

                        return ($status);
                }

                # Save the index value that the user chose.

                $del_index = $res;

                # Get the selected name.

                $backup_name = $del_disp[$del_index];

                # Confirm the user really wants to delete this backup!
                # Note the user must choose the "no" response to actually
                # do the delete, just pressing enter will cancel the deletion.

                $msg = "\"\n       You have requested backup:\n\n       $backup_name\n\n       Be deleted.\n\n       You must select \'no\' below to complete the deletion.\n\n       The default answer of \'yes\' will cancel the deletion\n       leaving the backupstore unchanged!\n\n\n                 Do you want to cancel this deletion?\n\"";

                ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                if ($status == 0) {

                        # The user chose yes, so cancel this deletion and
                        # return to the main menu.

                        $msg = "\"\n\n\n               Deletion cancelled as requested.\n\n\n\n\n\n\n\n\n\n\n       Press enter or click < Yes > to return to the main menu,\n\"";

                        # Tell the user what happened and wait til they
                        # acknowledge before returning.

                        ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");
                }

                # User chose no so delete the backup image files.
                # Split out the selected file information to match.

                ($sel_backup_type, $sel_backup_timestamp, $sel_desc) = split (/_/,$backup_name,3);

                # Then select the files that match from $filenames and delete
                # them one at a time til there aren't any more matches. The
                # combination of $backup_type and $backup_timestamp will insure
                # all the image files from the selected backup are deleted.
                # Note the log files (which are small) are left for the
                # support person so they know what went on and should be
                # manually deleted later. Now get the first filename:

                ($dir_file, $filenames) = split(/\n/,$filenames,2);

                while ($dir_file) {

                        # Split out the directory and the filename

                        ($dir, $filename) = split (/#/,$dir_file,2);

                        # and then the fields in the filename.

                        ($backup_type, $type, $backup_timestamp, $fstype, $part, $name) = split(/,/,$filename,6);

                        if (($backup_type eq $sel_backup_type) &&
                            ($backup_timestamp eq $sel_backup_timestamp)) {

                                # Delete this file using the fully qualified
                                # path name.

                                $res = `$RM $dir_file 2>&1`;

                                if ($? != 0) {

                                        # Log an error from rm.

                                        $err_msg = "$prog: Error: $RM $dir_file failed $! ($?) res $res\n";

                                        &unrecoverable_error($err_msg);
                                }
                        }

                        # Then get the next file and repeat til done.

                        ($dir_file, $filenames) = split(/\n/,$filenames,2);
                }
        }

        # Update the space on the backupstore.

        &update_backupstore_space();

        # Then return success.

        return (0);
}

sub list_restore_imgs {

        # List the available img files for restore.

        local ($name) = @_;
        local ($prog, $files, $err_msg, $dir_file, $dir, $filename);
        local ($filenames, $sb, $dir_timestamp, $desc, $backup_type, $type);
        local ($backup_timestamp, $fstype, $part, $backup_name, $ext);
        local (@res_disp, @rres_disp);

        $prog = "list_restore_imgs";

        #  First get all the directories with potential backup files in them.

        $files = `$LS /mnt/backup/SB,*/*${name}* 2>&1`;

        if ($? != 0) {

                # Report the unlikely event of an error in ls.

                $err_msg = "$prog: Error: $LS /mnt/backup/SB,*/*${name}* 2>&1 rc $! ($?)\n$files\n";

                &unrecoverable_error($err_msg);
        }

        # Now go through the directories one at a time and select the files
        # representing system partition backups as there will be only one of
        # them per backup. Put the file names followed by the directory they
        # are in, in to an array sorted in time order newest first. Thus we
        # can easily select the file name based on the index value returned
        # by dialog. First put the "SB" files (user requested backups)
        # in time order followed by the "SR" files (restore safety backup)
        # again in time order as being the less likely restore target. There
        # is an unlikely but possible case where a directory may contain more
        # than one backup (they would likely be identical, as there won't be
        # an exit from the script between them to cause changes, but it is
        # possible so lets allow for it). In that case the backup file
        # timestamps will be different but the directory will be the same.

        # Split out the first file name.

        ($dir_file, $files) = split(/\n/,$files,2);


        while ($dir_file) {

                # If this is a log file or syslog file, it won't have the
                # "#" in the name (and -w objects to the undefined value
                # in $filename) so just skip it if there isn't a #. Because
                # we searched on a disk serial number we shouldn't hit any
                # log files but just in case. As well skip the md5 files so
                # that there are only image files present.

                if ($dir_file =~ /#/) {

                        # then split the directory name and the file name

                        ($dir, $filename) = split (/#/,$dir_file,2);

                        # Save a copy of the fully qualified file name to
                        # return to the caller in $filenames delimited by
                        # a newline.

                        $filenames .= "$dir_file\n";

                        # Get the description (if there is one) from the
                        # directory.

                        ($sb, $dir_timestamp, $desc) = split(/,/,$dir,3);

                        # Remove the trailing "/" from the description

                        chop ($desc);

                        # Then get the date and time of the actual backup file

                        ($backup_type, $type, $backup_timestamp, $fstype, $part, $backup_name, $ext) = split(/,/,$filename,7);

                        # Save only the system files (of which there will be
                        # only one per backup) so each backup will only appear
                        # in the list once. We need to ignore the md5 files
                        # however as they will provide a dup file name.

                        if (($backup_type eq "SB") && ($type eq "s") &&
                            (!($ext =~ /\.md5$/))) {

                                # put the SB files in to the array in reverse
                                # order (because ls sorted them in descending
                                # time order and we want ascending time order)
                                # with the type, filename (not directory!)
                                # timestamp, and description (to display to the
                                # user for selection to restore).

                                unshift (@res_disp, "${backup_type}_${backup_timestamp}_${desc}");

                        } elsif (($backup_type eq "SR") && ($type eq "s") &&
                                 (!($ext =~ /\.md5$/))) {

                                # Do the same as above to the SR (recovery
                                # backup) files in to a temp array. After this
                                # we will append these files to the end of
                                # the SB file array above on the assumption
                                # the safety backup files will be the least
                                # likely to be restored.

                                unshift (@rres_disp, "${backup_type}_${backup_timestamp}_${desc}");
                        } else {

                                if ($DEBUG) {

                                        # If debug is enabled, log but ignore,
                                        # unselected image files.

                                        print LOG "$prog: unselected image file $dir_file, ignored\n";
                                }
                        }

                } else {

                        if ($DEBUG) {

                                # Log but ignore log and syslog files

                                print LOG "$prog: unselected log file $dir_file, ignored\n";
                        }
                }

                ($dir_file, $files) = split(/\n/,$files,2);
        }

        while (@rres_disp) {

                # Then append the SR files to the end of the SB display
                # list.

                $file = shift(@rres_disp);

                push (@res_disp, $file);
        }

        # Then return the selected file names to the caller.

        return ($filenames, @res_disp);
}

sub list_delete_imgs {

        local ($prog, $files, $err_msg, $dir_file, $dir, $filename, $sb);
        local ($dir_timestamp, $desc, $backup_type, $type, $backup_timestamp);
        local ($fstype, $part, $name, $ext, %sb_count, %sb_desc, %sr_count);
        local (%sr_desc, %sb_by_count, %sr_by_count, $count, $line, $entries);
        local (@del_disp);

        $prog = "list_delete_imgs";

        #  First get all the directories with potential backup files in them.

        $files = `$LS /mnt/backup/SB,*/* 2>&1`;

        if ($? != 0) {

                # Report the unlikely event of an error in ls.

                $err_msg = "$prog: Error: $LS /mnt/backup/SB,*/* 2>&1 rc $! ($?)\n$files\n";

                &unrecoverable_error($err_msg);
        }

        # Now go through the directories one at a time and select the files
        # representing system partition backups as there will be only one of
        # them per backup. While doing this maintain a count of backups for
        # the disk in question that have been found (we will favor deleting
        # the oldest of the most numerous backup as being least valuable first!)
        # in an associative array.

        ($dir_file, $files) = split(/\n/,$files,2);

        while ($dir_file) {

                # If this is a log file or syslog file, it won't have the
                # "#" in the name (and -w objects to the undefined value
                # in $filename) so just skip it if there isn't a #. As well
                # skip the .md5 files to leave only image files (of which
                # there should only be one).

                if ($dir_file =~ /#/) {

                        ($dir, $filename) = split (/#/,$dir_file);

                        # Get just the description (if there is one) from the
                        # directory.

                        ($sb, $dir_timestamp, $desc) = split(/,/,$dir,3);

                        # Remove the trailing "/" from the description

                        chop ($desc);

                        # Then get the date and time of the actual backup file
                        # and the disk name.

                        ($backup_type, $type, $backup_timestamp, $fstype, $part, $name, $ext) = split(/,/,$filename,7);

                        # Count only the system files (of which there will be
                        # only one per backup) so we know how many generations
                        # there are for this backup type and disk later.

                        if (($backup_type eq "SB") && ($type eq "s") &&
                            (!($ext =~ /\.md5$/))) {

                                $sb_count{$name} += 1;

                                # Keep the description for the user to select
                                # as well.

                                $sb_desc{$name} .= "${backup_type}_${backup_timestamp}_${desc}\n";

                        } elsif (($backup_type eq "SR") && ($type eq "s") &&
                                 (!($ext =~ /\.md5$/))) {

                                $sr_count{$name} += 1;

                                # Keep the description for the user to select
                                # as well.

                                $sr_desc{$name} .= "${backup_type}_${backup_timestamp}_${desc}\n";

                        }

                        # Then save a copy of the fully qualified file name
                        # which will be one of the image files (not the log
                        # file or syslog files both of which are small and
                        # will be ignored in this) for later potential
                        # deletion. These will be selected based on the
                        # ${backup_timestamp} field in the desc associative
                        # arrays above.

                        $filenames .= "$dir_file\n";

                }

                ($dir_file, $files) = split(/\n/,$files,2);
        }

        # We now have a picture of all the image files on the backupstore
        # and how many generations of backup are available for any particular
        # disk. So arrange that information in to a list to present to the
        # user to select backups to delete. Favor (by listing first) the oldest
        # of the backup with the most generations (as being least valuable).
        # First set up to index the associative arrays by count rather than
        # disk name and fill it with newline delimited descriptions to create
        # a select array for the user.

        foreach $name (keys %sb_count) {

                $sb_by_count{"$sb_count{$name}"} .= "$sb_desc{$name}";

        }

        # Then do the same for the SR backups.

        foreach $name (keys %sr_count) {

                $sr_by_count{"$sr_count{$name}"} .= "$sr_desc{$name}";

        }

        foreach $count (sort numerically (keys %sb_by_count)) {

                # Now form a display array with the largest number of backup
                # generations and oldest times first.

                $entries = $sb_by_count{$count};

                # Since a given count may have more than one entry we need
                # to split them out by the \n that delimits them.

                ($line, $entries) = split(/\n/,$entries,2);

                while ($line) {

                        # Then insert the entry in to the array to display
                        # to the user.

                        push (@del_disp, $line);

                        ($line, $entries) = split(/\n/,$entries,2);

                }
        }

        # Then do the same for the SR entries.

        foreach $count (sort numerically (keys %sr_by_count)) {

                # Now form a display array with the largest number of backup
                # generations and oldest times first.

                $entries = $sr_by_count{$count};

                # Since a given count may have more than one entry we need
                # to split them out by the \n that delimits them.

                ($line, $entries) = split(/\n/,$entries,2);

                while ($line) {

                        # Then insert the entry in to the array to display
                        # to the user (after all the SB entries).

                        push (@del_disp, $line);

                        ($line, $entries) = split(/\n/,$entries,2);
                }
        }

        # Now return the complete set of filenames and the selection array
        # to the caller.

        return ($filenames, @del_disp)
}

sub update_backupstore_space {

        # Update the space available on the backupstore so the space tally is
        # current. Make everything except $backupstore_space and
        # $backupstore_percent (which are global) local to avoid naming
        # conflicts.

        local ($prog, $data, $err_msg, $junk, $dev, $total, $used);
        local ($backupstore_space, $backupstore_percent, $mntpnt);

        $prog = "update_backupstore_space";

        $data = `$DF -P $backupstore_drive_part`;

        if ($? != 0) {

                $err_msg = "$prog: Error: $DF -P $backupstore_drive_part code $? non zero\n$data\n";

                &unrecoverable_error($err_msg);
        }

        ($junk, $data) = split ('\n', $data, 2);

        ($dev, $total, $used, $backupstore_space, $backupstore_percent, $mntpnt) = split(' ', $data);

        if ($dev ne $backupstore_drive_part) {

                $err_msg = "$prog: Error: $DF of $backupstore_drive_part incorrect\n$data\n";

                &unrecoverable_error($err_msg);
        }

        return (0);
}

sub close_files {

        # Normal exit so copy the /var/log/messages file to the backupstore
        # then unmount the file systems in preparation for departing, then
        # close the log file and return to the caller to let them do what ever
        # action (shutdown, reboot, exit to shell) was requested with all
        # the files closed (so a power down won't hurt). Because this may have
        # been called from &unrecoverable_error do all error checking here
        # without calling any other routine to avoid a potential recursive
        # error loop (specifically don't call &is_mounted()).

        local ($prog, $data);

        $prog = "close_files";

        if ($DEBUG) {

            if ($log_open eq "y") {

                print LOG "debug: entered $prog\n";

            } else {

                $prelog_msgs .= "debug: entered $prog\n";
            }
        }

        # open /var/log/messages and seek to the start point determined when
        # this script started. Then copy from there to end of file to the
        # log drive so we have the system log messages on the backup drive.
        # However check if the backupstore is mounted first!

        if ($backupstore_drive_mounted eq "y") {

            $res = open (MSGS, "/var/log/messages");

            if ($? != 0) {

                if ($log_open eq "y") {

                    print LOG "$prog: Error: open /var/log/messages rc failed $! ($?) res $res skipping copy of /var/log/messages\n";

                } else {

                    $prelog_msgs .= "$prog: Error: open /var/log/messages failed rc $! ($?) res $res skipping copy of /var/log/messages\n";

                }

            } else {

                $res = seek(MSGS, $log_cur, 0);

                if ($? != 0) {

                   if ($log_open eq "y") {

                        print LOG "$prog: Error: seek $log_cur in /var/log/messages failed rc $! ($?) res $res skipping copy of /var/log/messages\n";

                   } else {

                        $prelog_msgs .= "$prog: Error: seek in /var/log/messages rc $! ($?) res $res skipping copy of /var/log/messages\n";

                   }

                } else {

                    $res = open (SYSLOG, ">> /mnt/backup/${dir_name}/${dir_name},var_log_messages");

                   if ($? != 0) {

                        if ($log_open eq "y") {

                            print LOG "$prog: Error: open /mnt/backup/${dir_name}/${dir_name},var_log_messages failed rc $! ($?) res $res skipping copy of /var/log/messages\n";

                        } else {

                            $prelog_msgs .= "$prog: Error: open /mnt/backup/${dir_name}/${dir_name},var_log_messages failed rc $! ($?) res $res skipping copy of /var/log/messages\n";

                        }

                   } else {

                        # Finally copy (or at least try) the syslog to the
                        # backupstore.

                        while(<MSGS>) {

                            print SYSLOG $_;
                        }
                   }

                   # Update $log_cur to be at the end of the data just written.

                   $log_cur = seek(MSGS, 0, 2);

                   if ($? != 0) {

                        # Note a failure of the seek but otherwise ignore it.

                        if ($log_open eq "y") {

                            print LOG "$prog: Error: seek end of /var/log/messages failed rc $! ($?) res $res ignored.\n";

                        } else {

                            $prelog_msgs .= "$prog: Error: seek end of /var/log/messages failed rc $! ($?) res $res ignored.\n";

                        }

                   }

                   # close both files ignoring errors.

                   close (MSGS);

                   close (SYSLOG);
                }
            }

            # Now repeat this for /var/log/perl.log (the error stream from this
            # script) in case of perl errors.

            $res = open (MSGS, "/var/log/perl.log");

            if ($? != 0) {

                if ($log_open eq "y") {

                    print LOG "$prog: Error: open /var/log/perl.log failed rc $! ($?) res $res skipping copy of /var/log/perl.log\n";

                } else {

                    $prelog_msgs .= "$prog: Error: open /var/log/perl.log failed rc $! ($?) res $res skipping copy of /var/log/perl.log\n";

                }

            } else {

                    $res = open (SYSLOG, ">> /mnt/backup/${dir_name}/${dir_name},var_log_perl.log");

                   if ($? != 0) {

                        if ($log_open eq "y") {

                            print LOG "$prog: Error: open /mnt/backup/${dir_name}/${dir_name},perl.log failed rc $! ($?) res $res skipping copy of /var/log/perl.log\n";

                        } else {

                            $prelog_msgs .= "$prog: Error: open /mnt/backup/${dir_name}/${dir_name},var_log_perl.log failed rc $! ($?) res $res skipping copy of /var/log/perl.log\n";

                        }

                   } else {

                        # Finally copy (or at least try) the perl.log to the
                        # backupstore.

                        while(<MSGS>) {

                            print SYSLOG $_;
                        }
                   }

                   # close both files ignoring errors.

                   close (MSGS);

                   close (SYSLOG);

            }

        } else {

            $prelog_msgs .= "$prog: Error: Backupstore not mounted, so /var/log/messages not copied anywhere\n";

        }

        if ($DEBUG) {

                # Get the df status before unmounts.

                $data = `$DF 2>&1`;

                $prelog_msgs .= "$prog: before unmounts mnt_windows_mounted = $mnt_windows_mounted\n\tmnt_backup_mounted = $mnt_backup_mounted\n\tbackupstore_drive_mounted $backupstore_drive_mounted\ndf\n$data";

        }

        # Unmount /mnt/windows whether we think it was mounted or not. If it
        # isn't mounted we will get an error (which we will ignore) and if is
        # mounted we will unmount it which is safest.

        $res = `$UMOUNT /mnt/windows 2>&1`;

        $mnt_windows_mounted = "n";

        if ($log_open eq "y") {

                # if there is a log file open, then log a shutdown message.

                $now = `$DATE`;

                chop $now;

                print LOG "$now $prog: closed files and now log\n";

                # and close the log file.

                close (LOG);

                $log_open = "n";
        }

        # Then unmount the /mnt/backup file system. If it isn't mounted this
        # will cause an error (which we will ignore) and if it is open it will
        # be dismounted as desired.

        $res = `$UMOUNT /mnt/backup 2>&1`;

        $mnt_backup_mounted = "n";

        $backupstore_drive_mounted = "n";

        if ($DEBUG) {

                # If debug is enabled do a final df so we know everything was
                # dismounted. The log file is closed and the backstore
                # dismounted so there is only memory.

                $data = `$DF 2>&1`;

                $prelog_msgs .= "$prog: end mnt_windows_mounted = $mnt_windows_mounted\n\tmnt_backup_mounted = $mnt_backup_mounted\n\tbackupstore_drive_mounted $backupstore_drive_mounted\ndf\n$data";

        }

        return (0);
}

sub shell {

        # Exit to a user shell after telling the user how to restart the backup
        # script and writing a file /root/drives.zsh with the current
        # drive/partition of the backupstore and system drive in it.

        # First Tell the user how to get back here if they didn't mean to do
        # this.

        print "\n\n/root/drives.zsh has the drive definitions in it ready to source\n\nTyping $SCRIPT_NAME at the prompt will restart backup if you didn't\nmean to do this\n\n";

        # write a file that details the drive names for the backupstore
        # and backup drive in shell variables.

        $res = open (DATA, "> /root/drives.zsh");

        if ($? != 0) {

                # Since we are heading for a shell, just give the user this
                # error and proceed.

                print "Error: unable to write /root/drives.zsh $! ($?)\n";

                print "export BACKUPSTORE=$backupstore_drive_part\n";

                print "export SYSTEM=$sys_drive_part\n";

        } else {

                print DATA "export BACKUPSTORE=$backupstore_drive_part\n";

                print DATA "export SYSTEM=$sys_drive_part\n";

                close (DATA);
        }

        # Then exit to a shell.

        exit(0);
}

sub write_tag_file {

        local ($drive_part, $tag_file) = @_;
        local ($prog, $res, $err_msg);

        $prog = "write_tag_file";

        # Now mount the file system and write a tag file to the file system
        # for next time.

        if (&is_mounted("/mnt/windows")) {

                print LOG "$prog: Error unmounting /mnt/windows (shouldn't be mounted!)\n";

                # This won't return if the unmount isn't
                # successful!

                &unmount("/mnt/windows");
        }

        $res = `$MOUNT $drive_part /mnt/windows 2>&1`;

        if ($? != 0) {

                $err_msg ="$prog: Error $MOUNT $drive_part /mnt/windows rc $! ($?) res $res\n";

                &unrecoverable_error($err_msg);
        }

        # mark /mnt/windows mounted for shutdown.

        $mnt_windows_mounted = "y";

        $res = `$TOUCH /mnt/windows/$tag_file 2>&1`;

        if ($? != 0) {

                $err_msg ="$prog: Error: $TOUCH /mnt/windows/$tag_file rc $! ($?) res $res\n";

                &unrecoverable_error($err_msg);
        }

        # This won't return if it isn't successfull.

        &unmount("/mnt/windows");

        $mnt_windows_mounted = "n";

        return (0);
}

sub check_md5 {

        local ($no_md5_reported, $filename, %md5s) = @_;
        local ($prog, $status, $res, $cur_md5, $rest, $err_msg);

        $prog = "check_md5";

        if (! defined($md5s{"${filename}.md5"})) {

                if ($no_md5_reported eq "n") {

                        # first file with no md5, so ask the user what to do
                        # as this may be a backup from before md5s were enabled.

                        $msg = "\"\n\n                 There isn't an MD5 file from the backup.\n\n This isn't necessarily a problem, as MD5 checking may have been off when the original backup occurred. While we can't verify that the file to restore is OK, we can still do the restore if you wish. You should bring this to the attention of your support person if you haven't already.\n\n           Press enter or click < Yes > to cancel the restore.\n\n                       Click or select < No >\n\n     to proceed with the restore without checking the backup files\n\"";

                        ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                        if ($status == 0) {

                                # User requested the restore be cancelled so
                                # return 1.

                                $err_msg = "$prog: Error $filename has no md5 in the backup, aborting at user request\n";

                                return (1, $no_md5_reported);
                        }
                }

                # User has elected to proceed. So log the failure and proceed
                # after noting we have asked already so we don't repeat the
                # warning.

                $no_md5_reported = "y";

                print LOG "$prog: Warning: filename $filename has no md5 in the backup ignoring at user request\n";

                # The user has elected to proceed without checking the MD5s
                # so return success even though we haven't checked the MD5.

                return (0, $no_md5_reported);
        }

        $msg = "\"\n\n                 Checking the MD5 of image file:\n\n$filename\n\n                    This may take some time\n\"";

        ($status, $res) = &output_dialog_screen ("", "--msgbox", $msg, "");

        $cur_md5 = `$MD5SUM $filename 2>&1`;

        if ($? != 0) {

                $err_msg = "$prog: Error: md5 of $filename: $cur_md5 rc $res\n";

                &unrecoverable_error($err_msg);
        }

        ($cur_md5, $rest) = split (/ /,$cur_md5,2);

        if ($cur_md5 ne $md5s{"$filename.md5"}) {

                $err_msg = "$prog: Error $filename md5 mismatch $cur_md5 $md5s{$filename}.md5, aborting\n";

                &unrecoverable_error($err_msg);

        }

        # The MD5 check was successfull so return 0.

        return (0, $no_md5_reported);
}

sub finish_operation {

        local ($operation, $prompt) = @_;
        local ($prog, $msg, $res, $status, $err_msg, $now);

        $prog = "finish_operation";

        # the operation (backup or restore) has ended successfully so first
        # close all the files.

        &close_files();

        # then proceed according to the $prompt setting

        if ($prompt eq "shutdown") {

                # Then shut down

                `$SHUTDOWN  -P now`;

                # since this forks, now exit perl so we don't reopen the files

                exit (0);

        } elsif ($prompt eq "reboot") {

                # then reboot

                `$SHUTDOWN  -r now`;

                # since this forks, now exit perl so we don't reopen the files

                exit (0);

        } else {

                # then put up a dialog box til the user acknowledges that
                # operation completed OK (or as noted just powers off).

                $msg = "\"\n\n         The requested $operation operation completed successfully\n\n\n               Remove the CD or USB key and either\n\n\n\n                   Press enter or click \< Yes \>\n\n         to return to the exit options if you want to reboot or\n\n               Just power down if you are finished.\"";

                ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

                # Remount the backupstore on /mnt/backup to access the image
                # files as the user has chosen to do more.

                &mount_backupstore();

                # Reopen the log file but log a message with the current time
                # as well.

                $res = open (LOG, ">> /mnt/backup/${dir_name}/${dir_name}.log");

                if ($? != 0) {

                        $err_msg =  "$prog: Error: Couldn't reopen log file /mnt/backup/${dir_name}/${dir_name}.log $! ($?) res $res\n";

                        &unrecoverable_error($err_msg);
                }

                # note that the log file is open again.

                $log_open = "y";

                $now = `$DATE`;

                chop $now;

                print LOG "$now $prog: restarted after successful $operation at user request\n";

        }

        return (0);
}

sub output_dialog_screen {

        local ($extra_options, $type, $msg, $item_list) = @_;
        local ($res, $dlg, $status);

        # For everything except a radiolist $item_list should be "" as
        # should $extra_options unless you want to change an option.

        $dlg = "$DIALOG $DIALOG_OPTIONS $extra_options $type $msg $DIALOG_HEIGHT $DIALOG_WIDTH $item_list";

        $res    = `$dlg`;

        $status = $? >> 8;

        # Unfortunatly the cancel key counts as an error along with real
        # errors, so we just return the status and assume there isn't a
        # real error (which absent a coding error, there shouldn't be).

        return ($status, $res);
}

sub commas {

        local ($_) = @_;

        1 while s/(.*\d)(\d\d\d)/$1,$2/;
        $_;
}

sub numerically {$b <=> $a;}

sub interrupt_handler {

        local ($sig) = @_;

        # First log that we caught a signal.

        if ($log_open eq "y") {

                print LOG "Caught a SIG$sig shutting down\n";

        } else {

                $prelog_msgs .= "Caught a SIG$sig shutting down\n";

        }

        # Then close any open files and dismount the file systems if possible.

        &close_files();


        # then put up a dialog box til the user acknowledges that something
        # bad has happened (and what to do to restart) before exiting.

        $msg = "\"\n             An unexpected interrupt has occurred.\n              (perhaps pressing the cntrl-C key?)\n\n     If you don't think that you pressed the cntrl-c key, please bring this to the attention of your support person to see what the error was.\n\n  Any operation in progress has been terminated and the files closed,\n               and should be considered to have failed.\n\n     If this wasn't intentional and you wish to continue, press enter or click \< Yes \> and then type autorun at the command prompt to restart the backup. You can also just reboot or power cycle the machine as the files have been closed ready for shutdown.\n\"";

                ($status, $res) = &output_dialog_screen ("", "--yesno", $msg, "");

        # drop to a shell so the user can restart if desired.

        exit(0);

}
Last edited by vanepp on 24 Mar 2016, 18:32, edited 1 time in total.

vanepp
Posts: 85
Joined: 22 Jun 2012, 02:24

Re: simplebackup perl script

Postby vanepp » 24 Mar 2016, 03:46

Well its better than it first appeared, while the post software seems to have stripped the formatting if you copy and past the 5 posts in to a single file perl doesn't seem to care about the formatting and it appears on a quick test to still run OK. Either a pretty printer or a manual format should restore the formatting
to human readable though.

Peter Van Epp

gernot
Posts: 1127
Joined: 07 Apr 2010, 16:19

Re: simplebackup perl script

Postby gernot » 24 Mar 2016, 05:28

thanks. looks good.
You can use the code statement to make your posting handy and readable. Use [ instead of (
(code]
sample
sample
sample
[/code]

Code: Select all

sample
    sample
        sample

vanepp
Posts: 85
Joined: 22 Jun 2012, 02:24

Re: simplebackup perl script

Postby vanepp » 24 Mar 2016, 18:36

Thank you! The addition of the code flags has made it back to human readable as well. I've gone back and edited the posts and learned something new at the same time.

Peter Van Epp


Return to “Contributions”

Who is online

Users browsing this forum: No registered users and 2 guests