CedarBackup3.actions.util

Package CedarBackup3 :: Package actions :: Module util |
[hide private]
[frames] | no frames]

# Source Code for Module CedarBackup3.actions.util

      1  # -*- coding: iso-8859-1 -*-
      2  # vim: set ft=python ts=3 sw=3 expandtab:
      3  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      4  #
      5  #              C E D A R
      6  #          S O L U T I O N S       "Software done right."
      7  #           S O F T W A R E
      8  #
      9  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
     10  #
     11  # Copyright (c) 2007,2010,2015 Kenneth J. Pronovici.
     12  # All rights reserved.
     13  #
     14  # This program is free software; you can redistribute it and/or
     15  # modify it under the terms of the GNU General Public License,
     16  # Version 2, as published by the Free Software Foundation.
     17  #
     18  # This program is distributed in the hope that it will be useful,
     19  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     20  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     21  #
     22  # Copies of the GNU General Public License are available from
     23  # the Free Software Foundation website, http://www.gnu.org/.
     24  #
     25  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
     26  #
     27  # Author   : Kenneth J. Pronovici <pronovic@ieee.org>
     28  # Language : Python 3 (>= 3.4)
     29  # Project  : Cedar Backup, release 3
     30  # Purpose  : Implements action-related utilities
     31  #
     32  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
     33
     34  ########################################################################
     35  # Module documentation
     36  ########################################################################
     37
     38  """
     39  Implements action-related utilities
     40  @sort: findDailyDirs
     41  @author: Kenneth J. Pronovici <pronovic@ieee.org>
     42  """
     43
     44
     45  ########################################################################
     46  # Imported modules
     47  ########################################################################
     48
     49  # System modules
     50  import os
     51  import time
     52  import tempfile
     53  import logging
     54
     55  # Cedar Backup modules
     56  from CedarBackup3.filesystem import FilesystemList
     57  from CedarBackup3.util import changeOwnership
     58  from CedarBackup3.util import deviceMounted
     59  from CedarBackup3.writers.util import readMediaLabel
     60  from CedarBackup3.writers.cdwriter import CdWriter
     61  from CedarBackup3.writers.dvdwriter import DvdWriter
     62  from CedarBackup3.writers.cdwriter import MEDIA_CDR_74, MEDIA_CDR_80, MEDIA_CDRW_74, MEDIA_CDRW_80
     63  from CedarBackup3.writers.dvdwriter import MEDIA_DVDPLUSR, MEDIA_DVDPLUSRW
     64  from CedarBackup3.config import DEFAULT_MEDIA_TYPE, DEFAULT_DEVICE_TYPE, REWRITABLE_MEDIA_TYPES
     65  from CedarBackup3.actions.constants import INDICATOR_PATTERN
     66
     67
     68  ########################################################################
     69  # Module-wide constants and variables
     70  ########################################################################
     71
     72  logger = logging.getLogger("CedarBackup3.log.actions.util")
     73  MEDIA_LABEL_PREFIX   = "CEDAR BACKUP"
     74
     75
     76  ########################################################################
     77  # Public utility functions
     78  ########################################################################
     79
     80  ###########################
     81  # findDailyDirs() function
     82  ###########################
     83
     84 -def findDailyDirs(stagingDir, indicatorFile):
     85     """
     86     Returns a list of all daily staging directories that do not contain
     87     the indicated indicator file.
     88
     89     @param stagingDir: Configured staging directory (config.targetDir)
     90
     91     @return: List of absolute paths to daily staging directories.
     92     """
     93     results = FilesystemList()
     94     yearDirs = FilesystemList()
     95     yearDirs.excludeFiles = True
     96     yearDirs.excludeLinks = True
     97     yearDirs.addDirContents(path=stagingDir, recursive=False, addSelf=False)
     98     for yearDir in yearDirs:
     99        monthDirs = FilesystemList()
    100        monthDirs.excludeFiles = True
    101        monthDirs.excludeLinks = True
    102        monthDirs.addDirContents(path=yearDir, recursive=False, addSelf=False)
    103        for monthDir in monthDirs:
    104           dailyDirs = FilesystemList()
    105           dailyDirs.excludeFiles = True
    106           dailyDirs.excludeLinks = True
    107           dailyDirs.addDirContents(path=monthDir, recursive=False, addSelf=False)
    108           for dailyDir in dailyDirs:
    109              if os.path.exists(os.path.join(dailyDir, indicatorFile)):
    110                 logger.debug("Skipping directory [%s]; contains %s.", dailyDir, indicatorFile)
    111              else:
    112                 logger.debug("Adding [%s] to list of daily directories.", dailyDir)
    113                 results.append(dailyDir) # just put it in the list, no fancy operations
    114     return results
    115
    116
    117  ###########################
    118  # createWriter() function
    119  ###########################
    120
    121 -def createWriter(config):
    122     """
    123     Creates a writer object based on current configuration.
    124
    125     This function creates and returns a writer based on configuration.  This is
    126     done to abstract action functionality from knowing what kind of writer is in
    127     use.  Since all writers implement the same interface, there's no need for
    128     actions to care which one they're working with.
    129
    130     Currently, the C{cdwriter} and C{dvdwriter} device types are allowed.  An
    131     exception will be raised if any other device type is used.
    132
    133     This function also checks to make sure that the device isn't mounted before
    134     creating a writer object for it.  Experience shows that sometimes if the
    135     device is mounted, we have problems with the backup.  We may as well do the
    136     check here first, before instantiating the writer.
    137
    138     @param config: Config object.
    139
    140     @return: Writer that can be used to write a directory to some media.
    141
    142     @raise ValueError: If there is a problem getting the writer.
    143     @raise IOError: If there is a problem creating the writer object.
    144     """
    145     devicePath = config.store.devicePath
    146     deviceScsiId = config.store.deviceScsiId
    147     driveSpeed = config.store.driveSpeed
    148     noEject = config.store.noEject
    149     refreshMediaDelay = config.store.refreshMediaDelay
    150     ejectDelay = config.store.ejectDelay
    151     deviceType = _getDeviceType(config)
    152     mediaType = _getMediaType(config)
    153     if deviceMounted(devicePath):
    154        raise IOError("Device [%s] is currently mounted." % (devicePath))
    155     if deviceType == "cdwriter":
    156        return CdWriter(devicePath, deviceScsiId, driveSpeed, mediaType, noEject, refreshMediaDelay, ejectDelay)
    157     elif deviceType == "dvdwriter":
    158        return DvdWriter(devicePath, deviceScsiId, driveSpeed, mediaType, noEject, refreshMediaDelay, ejectDelay)
    159     else:
    160        raise ValueError("Device type [%s] is invalid." % deviceType)
    161
    162
    163  ################################
    164  # writeIndicatorFile() function
    165  ################################
    166
    167 -def writeIndicatorFile(targetDir, indicatorFile, backupUser, backupGroup):
    168     """
    169     Writes an indicator file into a target directory.
    170     @param targetDir: Target directory in which to write indicator
    171     @param indicatorFile: Name of the indicator file
    172     @param backupUser: User that indicator file should be owned by
    173     @param backupGroup: Group that indicator file should be owned by
    174     @raise IOException: If there is a problem writing the indicator file
    175     """
    176     filename = os.path.join(targetDir, indicatorFile)
    177     logger.debug("Writing indicator file [%s].", filename)
    178     try:
    179        with open(filename, "w") as f:
    180           f.write("")
    181        changeOwnership(filename, backupUser, backupGroup)
    182     except Exception as e:
    183        logger.error("Error writing [%s]: %s", filename, e)
    184        raise e
    185
    186
    187  ############################
    188  # getBackupFiles() function
    189  ############################
    190
    191 -def getBackupFiles(targetDir):
    192     """
    193     Gets a list of backup files in a target directory.
    194
    195     Files that match INDICATOR_PATTERN (i.e. C{"cback.store"}, C{"cback.stage"},
    196     etc.) are assumed to be indicator files and are ignored.
    197
    198     @param targetDir: Directory to look in
    199
    200     @return: List of backup files in the directory
    201
    202     @raise ValueError: If the target directory does not exist
    203     """
    204     if not os.path.isdir(targetDir):
    205        raise ValueError("Target directory [%s] is not a directory or does not exist." % targetDir)
    206     fileList = FilesystemList()
    207     fileList.excludeDirs = True
    208     fileList.excludeLinks = True
    209     fileList.excludeBasenamePatterns = INDICATOR_PATTERN
    210     fileList.addDirContents(targetDir)
    211     return fileList
    212
    213
    214  ####################
    215  # checkMediaState()
    216  ####################
    217
    218 -def checkMediaState(storeConfig):
    219     """
    220     Checks state of the media in the backup device to confirm whether it has
    221     been initialized for use with Cedar Backup.
    222
    223     We can tell whether the media has been initialized by looking at its media
    224     label.  If the media label starts with MEDIA_LABEL_PREFIX, then it has been
    225     initialized.
    226
    227     The check varies depending on whether the media is rewritable or not.  For
    228     non-rewritable media, we also accept a C{None} media label, since this kind
    229     of media cannot safely be initialized.
    230
    231     @param storeConfig: Store configuration
    232
    233     @raise ValueError: If media is not initialized.
    234     """
    235     mediaLabel = readMediaLabel(storeConfig.devicePath)
    236     if storeConfig.mediaType in REWRITABLE_MEDIA_TYPES:
    237        if mediaLabel is None:
    238           raise ValueError("Media has not been initialized: no media label available")
    239        elif not mediaLabel.startswith(MEDIA_LABEL_PREFIX):
    240           raise ValueError("Media has not been initialized: unrecognized media label [%s]" % mediaLabel)
    241     else:
    242        if mediaLabel is None:
    243           logger.info("Media has no media label; assuming OK since media is not rewritable.")
    244        elif not mediaLabel.startswith(MEDIA_LABEL_PREFIX):
    245           raise ValueError("Media has not been initialized: unrecognized media label [%s]" % mediaLabel)
    246
    247
    248  #########################
    249  # initializeMediaState()
    250  #########################
    251
    252 -def initializeMediaState(config):
    253     """
    254     Initializes state of the media in the backup device so Cedar Backup can
    255     recognize it.
    256
    257     This is done by writing an mostly-empty image (it contains a "Cedar Backup"
    258     directory) to the media with a known media label.
    259
    260     @note: Only rewritable media (CD-RW, DVD+RW) can be initialized.  It
    261     doesn't make any sense to initialize media that cannot be rewritten (CD-R,
    262     DVD+R), since Cedar Backup would then not be able to use that media for a
    263     backup.
    264
    265     @param config: Cedar Backup configuration
    266
    267     @raise ValueError: If media could not be initialized.
    268     @raise ValueError: If the configured media type is not rewritable
    269     """
    270     if not config.store.mediaType in REWRITABLE_MEDIA_TYPES:
    271        raise ValueError("Only rewritable media types can be initialized.")
    272     mediaLabel = buildMediaLabel()
    273     writer = createWriter(config)
    274     writer.refreshMedia()
    275     writer.initializeImage(True, config.options.workingDir, mediaLabel) # always create a new disc
    276     tempdir = tempfile.mkdtemp(dir=config.options.workingDir)
    277     try:
    278        writer.addImageEntry(tempdir, "CedarBackup")
    279        writer.writeImage()
    280     finally:
    281        if os.path.exists(tempdir):
    282           try:
    283              os.rmdir(tempdir)
    284           except: pass
    285
    286
    287  ####################
    288  # buildMediaLabel()
    289  ####################
    290
    291 -def buildMediaLabel():
    292     """
    293     Builds a media label to be used on Cedar Backup media.
    294     @return: Media label as a string.
    295     """
    296     currentDate = time.strftime("%d-%b-%Y").upper()
    297     return "%s %s" % (MEDIA_LABEL_PREFIX, currentDate)
    298
    299
    300  ########################################################################
    301  # Private attribute "getter" functions
    302  ########################################################################
    303
    304  ############################
    305  # _getDeviceType() function
    306  ############################
    307
    308 -def _getDeviceType(config):
    309     """
    310     Gets the device type that should be used for storing.
    311
    312     Use the configured device type if not C{None}, otherwise use
    313     L{config.DEFAULT_DEVICE_TYPE}.
    314
    315     @param config: Config object.
    316     @return: Device type to be used.
    317     """
    318     if config.store.deviceType is None:
    319        deviceType = DEFAULT_DEVICE_TYPE
    320     else:
    321        deviceType = config.store.deviceType
    322     logger.debug("Device type is [%s]", deviceType)
    323     return deviceType
    324
    325
    326  ###########################
    327  # _getMediaType() function
    328  ###########################
    329
    330 -def _getMediaType(config):
    331     """
    332     Gets the media type that should be used for storing.
    333
    334     Use the configured media type if not C{None}, otherwise use
    335     C{DEFAULT_MEDIA_TYPE}.
    336
    337     Once we figure out what configuration value to use, we return a media type
    338     value that is valid in one of the supported writers::
    339
    340        MEDIA_CDR_74
    341        MEDIA_CDRW_74
    342        MEDIA_CDR_80
    343        MEDIA_CDRW_80
    344        MEDIA_DVDPLUSR
    345        MEDIA_DVDPLUSRW
    346
    347     @param config: Config object.
    348
    349     @return: Media type to be used as a writer media type value.
    350     @raise ValueError: If the media type is not valid.
    351     """
    352     if config.store.mediaType is None:
    353        mediaType = DEFAULT_MEDIA_TYPE
    354     else:
    355        mediaType = config.store.mediaType
    356     if mediaType == "cdr-74":
    357        logger.debug("Media type is MEDIA_CDR_74.")
    358        return MEDIA_CDR_74
    359     elif mediaType == "cdrw-74":
    360        logger.debug("Media type is MEDIA_CDRW_74.")
    361        return MEDIA_CDRW_74
    362     elif mediaType == "cdr-80":
    363        logger.debug("Media type is MEDIA_CDR_80.")
    364        return MEDIA_CDR_80
    365     elif mediaType == "cdrw-80":
    366        logger.debug("Media type is MEDIA_CDRW_80.")
    367        return MEDIA_CDRW_80
    368     elif mediaType == "dvd+r":
    369        logger.debug("Media type is MEDIA_DVDPLUSR.")
    370        return MEDIA_DVDPLUSR
    371     elif mediaType == "dvd+rw":
    372        logger.debug("Media type is MEDIA_DVDPLUSRW.")
    373        return MEDIA_DVDPLUSRW
    374     else:
    375        raise ValueError("Media type [%s] is not valid." % mediaType)
    376