CedarBackup2.writers.util

Package CedarBackup2 :: Package writers :: Module util |
[hide private]
[frames] | no frames]

# Source Code for Module CedarBackup2.writers.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) 2004-2007,2010 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 (>= 2.5)
     29  # Project  : Cedar Backup, release 2
     30  # Revision : $Id: util.py 1006 2010-07-07 21:03:57Z pronovic $
     31  # Purpose  : Provides utilities related to image writers.
     32  #
     33  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
     34
     35  ########################################################################
     36  # Module documentation
     37  ########################################################################
     38
     39  """
     40  Provides utilities related to image writers.
     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 re
     52  import logging
     53
     54  # Cedar Backup modules
     55  from CedarBackup2.util import resolveCommand, executeCommand
     56  from CedarBackup2.util import convertSize, UNIT_BYTES, UNIT_SECTORS, encodePath
     57
     58
     59  ########################################################################
     60  # Module-wide constants and variables
     61  ########################################################################
     62
     63  logger = logging.getLogger("CedarBackup2.log.writers.util")
     64
     65  MKISOFS_COMMAND      = [ "mkisofs", ]
     66  VOLNAME_COMMAND      = [ "volname", ]
     67
     68
     69  ########################################################################
     70  # Functions used to portably validate certain kinds of values
     71  ########################################################################
     72
     73  ############################
     74  # validateDevice() function
     75  ############################
     76
     77 -def validateDevice(device, unittest=False):
     78     """
     79     Validates a configured device.
     80     The device must be an absolute path, must exist, and must be writable.
     81     The unittest flag turns off validation of the device on disk.
     82     @param device: Filesystem device path.
     83     @param unittest: Indicates whether we're unit testing.
     84     @return: Device as a string, for instance C{"/dev/cdrw"}
     85     @raise ValueError: If the device value is invalid.
     86     @raise ValueError: If some path cannot be encoded properly.
     87     """
     88     if device is None:
     89        raise ValueError("Device must be filled in.")
     90     device = encodePath(device)
     91     if not os.path.isabs(device):
     92        raise ValueError("Backup device must be an absolute path.")
     93     if not unittest and not os.path.exists(device):
     94        raise ValueError("Backup device must exist on disk.")
     95     if not unittest and not os.access(device, os.W_OK):
     96        raise ValueError("Backup device is not writable by the current user.")
     97     return device
     98
     99
    100  ############################
    101  # validateScsiId() function
    102  ############################
    103
    104 -def validateScsiId(scsiId):
    105     """
    106     Validates a SCSI id string.
    107     SCSI id must be a string in the form C{[<method>:]scsibus,target,lun}.
    108     For Mac OS X (Darwin), we also accept the form C{IO.*Services[/N]}.
    109     @note: For consistency, if C{None} is passed in, C{None} will be returned.
    110     @param scsiId: SCSI id for the device.
    111     @return: SCSI id as a string, for instance C{"ATA:1,0,0"}
    112     @raise ValueError: If the SCSI id string is invalid.
    113     """
    114     if scsiId is not None:
    115        pattern = re.compile(r"^\s*(.*:)?\s*[0-9][0-9]*\s*,\s*[0-9][0-9]*\s*,\s*[0-9][0-9]*\s*$")
    116        if not pattern.search(scsiId):
    117           pattern = re.compile(r"^\s*IO.*Services(\/[0-9][0-9]*)?\s*$")
    118           if not pattern.search(scsiId):
    119              raise ValueError("SCSI id is not in a valid form.")
    120     return scsiId
    121
    122
    123  ################################
    124  # validateDriveSpeed() function
    125  ################################
    126
    127 -def validateDriveSpeed(driveSpeed):
    128     """
    129     Validates a drive speed value.
    130     Drive speed must be an integer which is >= 1.
    131     @note: For consistency, if C{None} is passed in, C{None} will be returned.
    132     @param driveSpeed: Speed at which the drive writes.
    133     @return: Drive speed as an integer
    134     @raise ValueError: If the drive speed value is invalid.
    135     """
    136     if driveSpeed is None:
    137        return None
    138     try:
    139        intSpeed = int(driveSpeed)
    140     except TypeError:
    141        raise ValueError("Drive speed must be an integer >= 1.")
    142     if intSpeed < 1:
    143        raise ValueError("Drive speed must an integer >= 1.")
    144     return intSpeed
    145
    146
    147  ########################################################################
    148  # General writer-related utility functions
    149  ########################################################################
    150
    151  ############################
    152  # readMediaLabel() function
    153  ############################
    154
    155 -def readMediaLabel(devicePath):
    156     """
    157     Reads the media label (volume name) from the indicated device.
    158     The volume name is read using the C{volname} command.
    159     @param devicePath: Device path to read from
    160     @return: Media label as a string, or None if there is no name or it could not be read.
    161     """
    162     args = [ devicePath, ]
    163     command = resolveCommand(VOLNAME_COMMAND)
    164     (result, output) = executeCommand(command, args, returnOutput=True, ignoreStderr=True)
    165     if result != 0:
    166        return None
    167     if output is None or len(output) < 1:
    168        return None
    169     return output[0].rstrip()
    170
    171
    172  ########################################################################
    173  # IsoImage class definition
    174  ########################################################################
    175
    176 -class IsoImage(object):
    177
    178     ######################
    179     # Class documentation
    180     ######################
    181
    182     """
    183     Represents an ISO filesystem image.
    184
    185     Summary
    186     =======
    187
    188        This object represents an ISO 9660 filesystem image.  It is implemented
    189        in terms of the C{mkisofs} program, which has been ported to many
    190        operating systems and platforms.  A "sensible subset" of the C{mkisofs}
    191        functionality is made available through the public interface, allowing
    192        callers to set a variety of basic options such as publisher id,
    193        application id, etc. as well as specify exactly which files and
    194        directories they want included in their image.
    195
    196        By default, the image is created using the Rock Ridge protocol (using the
    197        C{-r} option to C{mkisofs}) because Rock Ridge discs are generally more
    198        useful on UN*X filesystems than standard ISO 9660 images.  However,
    199        callers can fall back to the default C{mkisofs} functionality by setting
    200        the C{useRockRidge} instance variable to C{False}.  Note, however, that
    201        this option is not well-tested.
    202
    203     Where Files and Directories are Placed in the Image
    204     ===================================================
    205
    206        Although this class is implemented in terms of the C{mkisofs} program,
    207        its standard "image contents" semantics are slightly different than the original
    208        C{mkisofs} semantics.  The difference is that files and directories are
    209        added to the image with some additional information about their source
    210        directory kept intact.
    211
    212        As an example, suppose you add the file C{/etc/profile} to your image and
    213        you do not configure a graft point.  The file C{/profile} will be created
    214        in the image.  The behavior for directories is similar.  For instance,
    215        suppose that you add C{/etc/X11} to the image and do not configure a
    216        graft point.  In this case, the directory C{/X11} will be created in the
    217        image, even if the original C{/etc/X11} directory is empty.  I{This
    218        behavior differs from the standard C{mkisofs} behavior!}
    219
    220        If a graft point is configured, it will be used to modify the point at
    221        which a file or directory is added into an image.  Using the examples
    222        from above, let's assume you set a graft point of C{base} when adding
    223        C{/etc/profile} and C{/etc/X11} to your image.  In this case, the file
    224        C{/base/profile} and the directory C{/base/X11} would be added to the
    225        image.
    226
    227        I feel that this behavior is more consistent than the original C{mkisofs}
    228        behavior.  However, to be fair, it is not quite as flexible, and some
    229        users might not like it.  For this reason, the C{contentsOnly} parameter
    230        to the L{addEntry} method can be used to revert to the original behavior
    231        if desired.
    232
    233     @sort: __init__, addEntry, getEstimatedSize, _getEstimatedSize, writeImage,
    234            _buildDirEntries _buildGeneralArgs, _buildSizeArgs, _buildWriteArgs,
    235            device, boundaries, graftPoint, useRockRidge, applicationId,
    236            biblioFile, publisherId, preparerId, volumeId
    237     """
    238
    239     ##############
    240     # Constructor
    241     ##############
    242
    243 -   def __init__(self, device=None, boundaries=None, graftPoint=None):
    244        """
    245        Initializes an empty ISO image object.
    246
    247        Only the most commonly-used configuration items can be set using this
    248        constructor.  If you have a need to change the others, do so immediately
    249        after creating your object.
    250
    251        The device and boundaries values are both required in order to write
    252        multisession discs.  If either is missing or C{None}, a multisession disc
    253        will not be written.  The boundaries tuple is in terms of ISO sectors, as
    254        built by an image writer class and returned in a L{writer.MediaCapacity}
    255        object.
    256
    257        @param device: Name of the device that the image will be written to
    258        @type device: Either be a filesystem path or a SCSI address
    259
    260        @param boundaries: Session boundaries as required by C{mkisofs}
    261        @type boundaries: Tuple C{(last_sess_start,next_sess_start)} as returned from C{cdrecord -msinfo}, or C{None}
    262
    263        @param graftPoint: Default graft point for this page.
    264        @type graftPoint: String representing a graft point path (see L{addEntry}).
    265        """
    266        self._device = None
    267        self._boundaries = None
    268        self._graftPoint = None
    269        self._useRockRidge = True
    270        self._applicationId = None
    271        self._biblioFile = None
    272        self._publisherId = None
    273        self._preparerId = None
    274        self._volumeId = None
    275        self.entries = { }
    276        self.device = device
    277        self.boundaries = boundaries
    278        self.graftPoint = graftPoint
    279        self.useRockRidge = True
    280        self.applicationId = None
    281        self.biblioFile = None
    282        self.publisherId = None
    283        self.preparerId = None
    284        self.volumeId = None
    285        logger.debug("Created new ISO image object.")
    286
    287
    288     #############
    289     # Properties
    290     #############
    291
    292 -   def _setDevice(self, value):
    293        """
    294        Property target used to set the device value.
    295        If not C{None}, the value can be either an absolute path or a SCSI id.
    296        @raise ValueError: If the value is not valid
    297        """
    298        try:
    299           if value is None:
    300              self._device = None
    301           else:
    302              if os.path.isabs(value):
    303                 self._device = value
    304              else:
    305                 self._device = validateScsiId(value)
    306        except ValueError:
    307           raise ValueError("Device must either be an absolute path or a valid SCSI id.")
    308
    309 -   def _getDevice(self):
    310        """
    311        Property target used to get the device value.
    312        """
    313        return self._device
    314
    315 -   def _setBoundaries(self, value):
    316        """
    317        Property target used to set the boundaries tuple.
    318        If not C{None}, the value must be a tuple of two integers.
    319        @raise ValueError: If the tuple values are not integers.
    320        @raise IndexError: If the tuple does not contain enough elements.
    321        """
    322        if value is None:
    323           self._boundaries = None
    324        else:
    325           self._boundaries = (int(value[0]), int(value[1]))
    326
    327 -   def _getBoundaries(self):
    328        """
    329        Property target used to get the boundaries value.
    330        """
    331        return self._boundaries
    332
    333 -   def _setGraftPoint(self, value):
    334        """
    335        Property target used to set the graft point.
    336        The value must be a non-empty string if it is not C{None}.
    337        @raise ValueError: If the value is an empty string.
    338        """
    339        if value is not None:
    340           if len(value) < 1:
    341              raise ValueError("The graft point must be a non-empty string.")
    342        self._graftPoint = value
    343
    344 -   def _getGraftPoint(self):
    345        """
    346        Property target used to get the graft point.
    347        """
    348        return self._graftPoint
    349
    350 -   def _setUseRockRidge(self, value):
    351        """
    352        Property target used to set the use RockRidge flag.
    353        No validations, but we normalize the value to C{True} or C{False}.
    354        """
    355        if value:
    356           self._useRockRidge = True
    357        else:
    358           self._useRockRidge = False
    359
    360 -   def _getUseRockRidge(self):
    361        """
    362        Property target used to get the use RockRidge flag.
    363        """
    364        return self._useRockRidge
    365
    366 -   def _setApplicationId(self, value):
    367        """
    368        Property target used to set the application id.
    369        The value must be a non-empty string if it is not C{None}.
    370        @raise ValueError: If the value is an empty string.
    371        """
    372        if value is not None:
    373           if len(value) < 1:
    374              raise ValueError("The application id must be a non-empty string.")
    375        self._applicationId = value
    376
    377 -   def _getApplicationId(self):
    378        """
    379        Property target used to get the application id.
    380        """
    381        return self._applicationId
    382
    383 -   def _setBiblioFile(self, value):
    384        """
    385        Property target used to set the biblio file.
    386        The value must be a non-empty string if it is not C{None}.
    387        @raise ValueError: If the value is an empty string.
    388        """
    389        if value is not None:
    390           if len(value) < 1:
    391              raise ValueError("The biblio file must be a non-empty string.")
    392        self._biblioFile = value
    393
    394 -   def _getBiblioFile(self):
    395        """
    396        Property target used to get the biblio file.
    397        """
    398        return self._biblioFile
    399
    400 -   def _setPublisherId(self, value):
    401        """
    402        Property target used to set the publisher id.
    403        The value must be a non-empty string if it is not C{None}.
    404        @raise ValueError: If the value is an empty string.
    405        """
    406        if value is not None:
    407           if len(value) < 1:
    408              raise ValueError("The publisher id must be a non-empty string.")
    409        self._publisherId = value
    410
    411 -   def _getPublisherId(self):
    412        """
    413        Property target used to get the publisher id.
    414        """
    415        return self._publisherId
    416
    417 -   def _setPreparerId(self, value):
    418        """
    419        Property target used to set the preparer id.
    420        The value must be a non-empty string if it is not C{None}.
    421        @raise ValueError: If the value is an empty string.
    422        """
    423        if value is not None:
    424           if len(value) < 1:
    425              raise ValueError("The preparer id must be a non-empty string.")
    426        self._preparerId = value
    427
    428 -   def _getPreparerId(self):
    429        """
    430        Property target used to get the preparer id.
    431        """
    432        return self._preparerId
    433
    434 -   def _setVolumeId(self, value):
    435        """
    436        Property target used to set the volume id.
    437        The value must be a non-empty string if it is not C{None}.
    438        @raise ValueError: If the value is an empty string.
    439        """
    440        if value is not None:
    441           if len(value) < 1:
    442              raise ValueError("The volume id must be a non-empty string.")
    443        self._volumeId = value
    444
    445 -   def _getVolumeId(self):
    446        """
    447        Property target used to get the volume id.
    448        """
    449        return self._volumeId
    450
    451     device = property(_getDevice, _setDevice, None, "Device that image will be written to (device path or SCSI id).")
    452     boundaries = property(_getBoundaries, _setBoundaries, None, "Session boundaries as required by C{mkisofs}.")
    453     graftPoint = property(_getGraftPoint, _setGraftPoint, None, "Default image-wide graft point (see L{addEntry} for details).")
    454     useRockRidge = property(_getUseRockRidge, _setUseRockRidge, None, "Indicates whether to use RockRidge (default is C{True}).")
    455     applicationId = property(_getApplicationId, _setApplicationId, None, "Optionally specifies the ISO header application id value.")
    456     biblioFile = property(_getBiblioFile, _setBiblioFile, None, "Optionally specifies the ISO bibliographic file name.")
    457     publisherId = property(_getPublisherId, _setPublisherId, None, "Optionally specifies the ISO header publisher id value.")
    458     preparerId = property(_getPreparerId, _setPreparerId, None, "Optionally specifies the ISO header preparer id value.")
    459     volumeId = property(_getVolumeId, _setVolumeId, None, "Optionally specifies the ISO header volume id value.")
    460
    461
    462     #########################
    463     # General public methods
    464     #########################
    465
    466 -   def addEntry(self, path, graftPoint=None, override=False, contentsOnly=False):
    467        """
    468        Adds an individual file or directory into the ISO image.
    469
    470        The path must exist and must be a file or a directory.  By default, the
    471        entry will be placed into the image at the root directory, but this
    472        behavior can be overridden using the C{graftPoint} parameter or instance
    473        variable.
    474
    475        You can use the C{contentsOnly} behavior to revert to the "original"
    476        C{mkisofs} behavior for adding directories, which is to add only the
    477        items within the directory, and not the directory itself.
    478
    479        @note: Things get I{odd} if you try to add a directory to an image that
    480        will be written to a multisession disc, and the same directory already
    481        exists in an earlier session on that disc.  Not all of the data gets
    482        written.  You really wouldn't want to do this anyway, I guess.
    483
    484        @note: An exception will be thrown if the path has already been added to
    485        the image, unless the C{override} parameter is set to C{True}.
    486
    487        @note: The method C{graftPoints} parameter overrides the object-wide
    488        instance variable.  If neither the method parameter or object-wide value
    489        is set, the path will be written at the image root.  The graft point
    490        behavior is determined by the value which is in effect I{at the time this
    491        method is called}, so you I{must} set the object-wide value before
    492        calling this method for the first time, or your image may not be
    493        consistent.
    494
    495        @note: You I{cannot} use the local C{graftPoint} parameter to "turn off"
    496        an object-wide instance variable by setting it to C{None}.  Python's
    497        default argument functionality buys us a lot, but it can't make this
    498        method psychic. :)
    499
    500        @param path: File or directory to be added to the image
    501        @type path: String representing a path on disk
    502
    503        @param graftPoint: Graft point to be used when adding this entry
    504        @type graftPoint: String representing a graft point path, as described above
    505
    506        @param override: Override an existing entry with the same path.
    507        @type override: Boolean true/false
    508
    509        @param contentsOnly: Add directory contents only (standard C{mkisofs} behavior).
    510        @type contentsOnly: Boolean true/false
    511
    512        @raise ValueError: If path is not a file or directory, or does not exist.
    513        @raise ValueError: If the path has already been added, and override is not set.
    514        @raise ValueError: If a path cannot be encoded properly.
    515        """
    516        path = encodePath(path)
    517        if not override:
    518           if path in self.entries.keys():
    519              raise ValueError("Path has already been added to the image.")
    520        if os.path.islink(path):
    521           raise ValueError("Path must not be a link.")
    522        if os.path.isdir(path):
    523           if graftPoint is not None:
    524              if contentsOnly:
    525                 self.entries[path] = graftPoint
    526              else:
    527                 self.entries[path] = os.path.join(graftPoint, os.path.basename(path))
    528           elif self.graftPoint is not None:
    529              if contentsOnly:
    530                 self.entries[path] = self.graftPoint
    531              else:
    532                 self.entries[path] = os.path.join(self.graftPoint, os.path.basename(path))
    533           else:
    534              if contentsOnly:
    535                 self.entries[path] = None
    536              else:
    537                 self.entries[path] = os.path.basename(path)
    538        elif os.path.isfile(path):
    539           if graftPoint is not None:
    540              self.entries[path] = graftPoint
    541           elif self.graftPoint is not None:
    542              self.entries[path] = self.graftPoint
    543           else:
    544              self.entries[path] = None
    545        else:
    546           raise ValueError("Path must be a file or a directory.")
    547
    548 -   def getEstimatedSize(self):
    549        """
    550        Returns the estimated size (in bytes) of the ISO image.
    551
    552        This is implemented via the C{-print-size} option to C{mkisofs}, so it
    553        might take a bit of time to execute.  However, the result is as accurate
    554        as we can get, since it takes into account all of the ISO overhead, the
    555        true cost of directories in the structure, etc, etc.
    556
    557        @return: Estimated size of the image, in bytes.
    558
    559        @raise IOError: If there is a problem calling C{mkisofs}.
    560        @raise ValueError: If there are no filesystem entries in the image
    561        """
    562        if len(self.entries.keys()) == 0:
    563           raise ValueError("Image does not contain any entries.")
    564        return self._getEstimatedSize(self.entries)
    565
    566 -   def _getEstimatedSize(self, entries):
    567        """
    568        Returns the estimated size (in bytes) for the passed-in entries dictionary.
    569        @return: Estimated size of the image, in bytes.
    570        @raise IOError: If there is a problem calling C{mkisofs}.
    571        """
    572        args = self._buildSizeArgs(entries)
    573        command = resolveCommand(MKISOFS_COMMAND)
    574        (result, output) = executeCommand(command, args, returnOutput=True, ignoreStderr=True)
    575        if result != 0:
    576           raise IOError("Error (%d) executing mkisofs command to estimate size." % result)
    577        if len(output) != 1:
    578           raise IOError("Unable to parse mkisofs output.")
    579        try:
    580           sectors = float(output[0])
    581           size = convertSize(sectors, UNIT_SECTORS, UNIT_BYTES)
    582           return size
    583        except:
    584           raise IOError("Unable to parse mkisofs output.")
    585
    586 -   def writeImage(self, imagePath):
    587        """
    588        Writes this image to disk using the image path.
    589
    590        @param imagePath: Path to write image out as
    591        @type imagePath: String representing a path on disk
    592
    593        @raise IOError: If there is an error writing the image to disk.
    594        @raise ValueError: If there are no filesystem entries in the image
    595        @raise ValueError: If a path cannot be encoded properly.
    596        """
    597        imagePath = encodePath(imagePath)
    598        if len(self.entries.keys()) == 0:
    599           raise ValueError("Image does not contain any entries.")
    600        args = self._buildWriteArgs(self.entries, imagePath)
    601        command = resolveCommand(MKISOFS_COMMAND)
    602        (result, output) = executeCommand(command, args, returnOutput=False)
    603        if result != 0:
    604           raise IOError("Error (%d) executing mkisofs command to build image." % result)
    605
    606
    607     #########################################
    608     # Methods used to build mkisofs commands
    609     #########################################
    610
    611     @staticmethod
    612 -   def _buildDirEntries(entries):
    613        """
    614        Uses an entries dictionary to build a list of directory locations for use
    615        by C{mkisofs}.
    616
    617        We build a list of entries that can be passed to C{mkisofs}.  Each entry is
    618        either raw (if no graft point was configured) or in graft-point form as
    619        described above (if a graft point was configured).  The dictionary keys
    620        are the path names, and the values are the graft points, if any.
    621
    622        @param entries: Dictionary of image entries (i.e. self.entries)
    623
    624        @return: List of directory locations for use by C{mkisofs}
    625        """
    626        dirEntries = []
    627        for key in entries.keys():
    628           if entries[key] is None:
    629              dirEntries.append(key)
    630           else:
    631              dirEntries.append("%s/=%s" % (entries[key].strip("/"), key))
    632        return dirEntries
    633
    634 -   def _buildGeneralArgs(self):
    635        """
    636        Builds a list of general arguments to be passed to a C{mkisofs} command.
    637
    638        The various instance variables (C{applicationId}, etc.) are filled into
    639        the list of arguments if they are set.
    640        By default, we will build a RockRidge disc.  If you decide to change
    641        this, think hard about whether you know what you're doing.  This option
    642        is not well-tested.
    643
    644        @return: List suitable for passing to L{util.executeCommand} as C{args}.
    645        """
    646        args = []
    647        if self.applicationId is not None:
    648           args.append("-A")
    649           args.append(self.applicationId)
    650        if self.biblioFile is not None:
    651           args.append("-biblio")
    652           args.append(self.biblioFile)
    653        if self.publisherId is not None:
    654           args.append("-publisher")
    655           args.append(self.publisherId)
    656        if self.preparerId is not None:
    657           args.append("-p")
    658           args.append(self.preparerId)
    659        if self.volumeId is not None:
    660           args.append("-V")
    661           args.append(self.volumeId)
    662        return args
    663
    664 -   def _buildSizeArgs(self, entries):
    665        """
    666        Builds a list of arguments to be passed to a C{mkisofs} command.
    667
    668        The various instance variables (C{applicationId}, etc.) are filled into
    669        the list of arguments if they are set.  The command will be built to just
    670        return size output (a simple count of sectors via the C{-print-size} option),
    671        rather than an image file on disk.
    672
    673        By default, we will build a RockRidge disc.  If you decide to change
    674        this, think hard about whether you know what you're doing.  This option
    675        is not well-tested.
    676
    677        @param entries: Dictionary of image entries (i.e. self.entries)
    678
    679        @return: List suitable for passing to L{util.executeCommand} as C{args}.
    680        """
    681        args = self._buildGeneralArgs()
    682        args.append("-print-size")
    683        args.append("-graft-points")
    684        if self.useRockRidge:
    685           args.append("-r")
    686        if self.device is not None and self.boundaries is not None:
    687           args.append("-C")
    688           args.append("%d,%d" % (self.boundaries[0], self.boundaries[1]))
    689           args.append("-M")
    690           args.append(self.device)
    691        args.extend(self._buildDirEntries(entries))
    692        return args
    693
    694 -   def _buildWriteArgs(self, entries, imagePath):
    695        """
    696        Builds a list of arguments to be passed to a C{mkisofs} command.
    697
    698        The various instance variables (C{applicationId}, etc.) are filled into
    699        the list of arguments if they are set.  The command will be built to write
    700        an image to disk.
    701
    702        By default, we will build a RockRidge disc.  If you decide to change
    703        this, think hard about whether you know what you're doing.  This option
    704        is not well-tested.
    705
    706        @param entries: Dictionary of image entries (i.e. self.entries)
    707
    708        @param imagePath: Path to write image out as
    709        @type imagePath: String representing a path on disk
    710
    711        @return: List suitable for passing to L{util.executeCommand} as C{args}.
    712        """
    713        args = self._buildGeneralArgs()
    714        args.append("-graft-points")
    715        if self.useRockRidge:
    716           args.append("-r")
    717        args.append("-o")
    718        args.append(imagePath)
    719        if self.device is not None and self.boundaries is not None:
    720           args.append("-C")
    721           args.append("%d,%d" % (self.boundaries[0], self.boundaries[1]))
    722           args.append("-M")
    723           args.append(self.device)
    724        args.extend(self._buildDirEntries(entries))
    725        return args
    726