1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 """
40 Provides functionality related to DVD writer devices.
41
42 @sort: MediaDefinition, DvdWriter, MEDIA_DVDPLUSR, MEDIA_DVDPLUSRW
43
44 @var MEDIA_DVDPLUSR: Constant representing DVD+R media.
45 @var MEDIA_DVDPLUSRW: Constant representing DVD+RW media.
46
47 @author: Kenneth J. Pronovici <pronovic@ieee.org>
48 @author: Dmitry Rutsky <rutsky@inbox.ru>
49 """
50
51
52
53
54
55
56 import os
57 import re
58 import logging
59 import tempfile
60 import time
61
62
63 from CedarBackup2.writers.util import IsoImage
64 from CedarBackup2.util import resolveCommand, executeCommand
65 from CedarBackup2.util import convertSize, displayBytes, encodePath
66 from CedarBackup2.util import UNIT_SECTORS, UNIT_BYTES, UNIT_GBYTES
67 from CedarBackup2.writers.util import validateDevice, validateDriveSpeed
68
69
70
71
72
73
74 logger = logging.getLogger("CedarBackup2.log.writers.dvdwriter")
75
76 MEDIA_DVDPLUSR = 1
77 MEDIA_DVDPLUSRW = 2
78
79 GROWISOFS_COMMAND = [ "growisofs", ]
80 EJECT_COMMAND = [ "eject", ]
155
218
225 """
226 Simple value object to hold image properties for C{DvdWriter}.
227 """
229 self.newDisc = False
230 self.tmpdir = None
231 self.mediaLabel = None
232 self.entries = None
233
240
241
242
243
244
245 """
246 Class representing a device that knows how to write some kinds of DVD media.
247
248 Summary
249 =======
250
251 This is a class representing a device that knows how to write some kinds
252 of DVD media. It provides common operations for the device, such as
253 ejecting the media and writing data to the media.
254
255 This class is implemented in terms of the C{eject} and C{growisofs}
256 utilities, all of which should be available on most UN*X platforms.
257
258 Image Writer Interface
259 ======================
260
261 The following methods make up the "image writer" interface shared
262 with other kinds of writers::
263
264 __init__
265 initializeImage()
266 addImageEntry()
267 writeImage()
268 setImageNewDisc()
269 retrieveCapacity()
270 getEstimatedImageSize()
271
272 Only these methods will be used by other Cedar Backup functionality
273 that expects a compatible image writer.
274
275 The media attribute is also assumed to be available.
276
277 Unlike the C{CdWriter}, the C{DvdWriter} can only operate in terms of
278 filesystem devices, not SCSI devices. So, although the constructor
279 interface accepts a SCSI device parameter for the sake of compatibility,
280 it's not used.
281
282 Media Types
283 ===========
284
285 This class knows how to write to DVD+R and DVD+RW media, represented
286 by the following constants:
287
288 - C{MEDIA_DVDPLUSR}: DVD+R media (4.4 GB capacity)
289 - C{MEDIA_DVDPLUSRW}: DVD+RW media (4.4 GB capacity)
290
291 The difference is that DVD+RW media can be rewritten, while DVD+R media
292 cannot be (although at present, C{DvdWriter} does not really
293 differentiate between rewritable and non-rewritable media).
294
295 The capacities are 4.4 GB because Cedar Backup deals in "true" gigabytes
296 of 1024*1024*1024 bytes per gigabyte.
297
298 The underlying C{growisofs} utility does support other kinds of media
299 (including DVD-R, DVD-RW and BlueRay) which work somewhat differently
300 than standard DVD+R and DVD+RW media. I don't support these other kinds
301 of media because I haven't had any opportunity to work with them. The
302 same goes for dual-layer media of any type.
303
304 Device Attributes vs. Media Attributes
305 ======================================
306
307 As with the cdwriter functionality, a given dvdwriter instance has two
308 different kinds of attributes associated with it. I call these device
309 attributes and media attributes.
310
311 Device attributes are things which can be determined without looking at
312 the media. Media attributes are attributes which vary depending on the
313 state of the media. In general, device attributes are available via
314 instance variables and are constant over the life of an object, while
315 media attributes can be retrieved through method calls.
316
317 Compared to cdwriters, dvdwriters have very few attributes. This is due
318 to differences between the way C{growisofs} works relative to
319 C{cdrecord}.
320
321 Media Capacity
322 ==============
323
324 One major difference between the C{cdrecord}/C{mkisofs} utilities used by
325 the cdwriter class and the C{growisofs} utility used here is that the
326 process of estimating remaining capacity and image size is more
327 straightforward with C{cdrecord}/C{mkisofs} than with C{growisofs}.
328
329 In this class, remaining capacity is calculated by asking doing a dry run
330 of C{growisofs} and grabbing some information from the output of that
331 command. Image size is estimated by asking the C{IsoImage} class for an
332 estimate and then adding on a "fudge factor" determined through
333 experimentation.
334
335 Testing
336 =======
337
338 It's rather difficult to test this code in an automated fashion, even if
339 you have access to a physical DVD writer drive. It's even more difficult
340 to test it if you are running on some build daemon (think of a Debian
341 autobuilder) which can't be expected to have any hardware or any media
342 that you could write to.
343
344 Because of this, some of the implementation below is in terms of static
345 methods that are supposed to take defined actions based on their
346 arguments. Public methods are then implemented in terms of a series of
347 calls to simplistic static methods. This way, we can test as much as
348 possible of the "difficult" functionality via testing the static methods,
349 while hoping that if the static methods are called appropriately, things
350 will work properly. It's not perfect, but it's much better than no
351 testing at all.
352
353 @sort: __init__, isRewritable, retrieveCapacity, openTray, closeTray, refreshMedia,
354 initializeImage, addImageEntry, writeImage, setImageNewDisc, getEstimatedImageSize,
355 _writeImage, _getEstimatedImageSize, _searchForOverburn, _buildWriteArgs,
356 device, scsiId, hardwareId, driveSpeed, media, deviceHasTray, deviceCanEject
357 """
358
359
360
361
362
363 - def __init__(self, device, scsiId=None, driveSpeed=None,
364 mediaType=MEDIA_DVDPLUSRW, noEject=False,
365 refreshMediaDelay=0, ejectDelay=0, unittest=False):
366 """
367 Initializes a DVD writer object.
368
369 Since C{growisofs} can only address devices using the device path (i.e.
370 C{/dev/dvd}), the hardware id will always be set based on the device. If
371 passed in, it will be saved for reference purposes only.
372
373 We have no way to query the device to ask whether it has a tray or can be
374 safely opened and closed. So, the C{noEject} flag is used to set these
375 values. If C{noEject=False}, then we assume a tray exists and open/close
376 is safe. If C{noEject=True}, then we assume that there is no tray and
377 open/close is not safe.
378
379 @note: The C{unittest} parameter should never be set to C{True}
380 outside of Cedar Backup code. It is intended for use in unit testing
381 Cedar Backup internals and has no other sensible purpose.
382
383 @param device: Filesystem device associated with this writer.
384 @type device: Absolute path to a filesystem device, i.e. C{/dev/dvd}
385
386 @param scsiId: SCSI id for the device (optional, for reference only).
387 @type scsiId: If provided, SCSI id in the form C{[<method>:]scsibus,target,lun}
388
389 @param driveSpeed: Speed at which the drive writes.
390 @type driveSpeed: Use C{2} for 2x device, etc. or C{None} to use device default.
391
392 @param mediaType: Type of the media that is assumed to be in the drive.
393 @type mediaType: One of the valid media type as discussed above.
394
395 @param noEject: Tells Cedar Backup that the device cannot safely be ejected
396 @type noEject: Boolean true/false
397
398 @param refreshMediaDelay: Refresh media delay to use, if any
399 @type refreshMediaDelay: Number of seconds, an integer >= 0
400
401 @param ejectDelay: Eject delay to use, if any
402 @type ejectDelay: Number of seconds, an integer >= 0
403
404 @param unittest: Turns off certain validations, for use in unit testing.
405 @type unittest: Boolean true/false
406
407 @raise ValueError: If the device is not valid for some reason.
408 @raise ValueError: If the SCSI id is not in a valid form.
409 @raise ValueError: If the drive speed is not an integer >= 1.
410 """
411 if scsiId is not None:
412 logger.warn("SCSI id [%s] will be ignored by DvdWriter." % scsiId)
413 self._image = None
414 self._device = validateDevice(device, unittest)
415 self._scsiId = scsiId
416 self._driveSpeed = validateDriveSpeed(driveSpeed)
417 self._media = MediaDefinition(mediaType)
418 self._refreshMediaDelay = refreshMediaDelay
419 self._ejectDelay = ejectDelay
420 if noEject:
421 self._deviceHasTray = False
422 self._deviceCanEject = False
423 else:
424 self._deviceHasTray = True
425 self._deviceCanEject = True
426
427
428
429
430
431
433 """
434 Property target used to get the device value.
435 """
436 return self._device
437
439 """
440 Property target used to get the SCSI id value.
441 """
442 return self._scsiId
443
445 """
446 Property target used to get the hardware id value.
447 """
448 return self._device
449
451 """
452 Property target used to get the drive speed.
453 """
454 return self._driveSpeed
455
461
463 """
464 Property target used to get the device-has-tray flag.
465 """
466 return self._deviceHasTray
467
469 """
470 Property target used to get the device-can-eject flag.
471 """
472 return self._deviceCanEject
473
479
481 """
482 Property target used to get the configured eject delay, in seconds.
483 """
484 return self._ejectDelay
485
486 device = property(_getDevice, None, None, doc="Filesystem device name for this writer.")
487 scsiId = property(_getScsiId, None, None, doc="SCSI id for the device (saved for reference only).")
488 hardwareId = property(_getHardwareId, None, None, doc="Hardware id for this writer (always the device path).")
489 driveSpeed = property(_getDriveSpeed, None, None, doc="Speed at which the drive writes.")
490 media = property(_getMedia, None, None, doc="Definition of media that is expected to be in the device.")
491 deviceHasTray = property(_getDeviceHasTray, None, None, doc="Indicates whether the device has a media tray.")
492 deviceCanEject = property(_getDeviceCanEject, None, None, doc="Indicates whether the device supports ejecting its media.")
493 refreshMediaDelay = property(_getRefreshMediaDelay, None, None, doc="Refresh media delay, in seconds.")
494 ejectDelay = property(_getEjectDelay, None, None, doc="Eject delay, in seconds.")
495
496
497
498
499
500
502 """Indicates whether the media is rewritable per configuration."""
503 return self._media.rewritable
504
506 """
507 Retrieves capacity for the current media in terms of a C{MediaCapacity}
508 object.
509
510 If C{entireDisc} is passed in as C{True}, the capacity will be for the
511 entire disc, as if it were to be rewritten from scratch. The same will
512 happen if the disc can't be read for some reason. Otherwise, the capacity
513 will be calculated by subtracting the sectors currently used on the disc,
514 as reported by C{growisofs} itself.
515
516 @param entireDisc: Indicates whether to return capacity for entire disc.
517 @type entireDisc: Boolean true/false
518
519 @return: C{MediaCapacity} object describing the capacity of the media.
520
521 @raise ValueError: If there is a problem parsing the C{growisofs} output
522 @raise IOError: If the media could not be read for some reason.
523 """
524 sectorsUsed = 0
525 if not entireDisc:
526 sectorsUsed = self._retrieveSectorsUsed()
527 sectorsAvailable = self._media.capacity - sectorsUsed
528 bytesUsed = convertSize(sectorsUsed, UNIT_SECTORS, UNIT_BYTES)
529 bytesAvailable = convertSize(sectorsAvailable, UNIT_SECTORS, UNIT_BYTES)
530 return MediaCapacity(bytesUsed, bytesAvailable)
531
532
533
534
535
536
538 """
539 Initializes the writer's associated ISO image.
540
541 This method initializes the C{image} instance variable so that the caller
542 can use the C{addImageEntry} method. Once entries have been added, the
543 C{writeImage} method can be called with no arguments.
544
545 @param newDisc: Indicates whether the disc should be re-initialized
546 @type newDisc: Boolean true/false
547
548 @param tmpdir: Temporary directory to use if needed
549 @type tmpdir: String representing a directory path on disk
550
551 @param mediaLabel: Media label to be applied to the image, if any
552 @type mediaLabel: String, no more than 25 characters long
553 """
554 self._image = _ImageProperties()
555 self._image.newDisc = newDisc
556 self._image.tmpdir = encodePath(tmpdir)
557 self._image.mediaLabel = mediaLabel
558 self._image.entries = {}
559
560 - def addImageEntry(self, path, graftPoint):
561 """
562 Adds a filepath entry to the writer's associated ISO image.
563
564 The contents of the filepath -- but not the path itself -- will be added
565 to the image at the indicated graft point. If you don't want to use a
566 graft point, just pass C{None}.
567
568 @note: Before calling this method, you must call L{initializeImage}.
569
570 @param path: File or directory to be added to the image
571 @type path: String representing a path on disk
572
573 @param graftPoint: Graft point to be used when adding this entry
574 @type graftPoint: String representing a graft point path, as described above
575
576 @raise ValueError: If initializeImage() was not previously called
577 @raise ValueError: If the path is not a valid file or directory
578 """
579 if self._image is None:
580 raise ValueError("Must call initializeImage() before using this method.")
581 if not os.path.exists(path):
582 raise ValueError("Path [%s] does not exist." % path)
583 self._image.entries[path] = graftPoint
584
586 """
587 Resets (overrides) the newDisc flag on the internal image.
588 @param newDisc: New disc flag to set
589 @raise ValueError: If initializeImage() was not previously called
590 """
591 if self._image is None:
592 raise ValueError("Must call initializeImage() before using this method.")
593 self._image.newDisc = newDisc
594
596 """
597 Gets the estimated size of the image associated with the writer.
598
599 This is an estimate and is conservative. The actual image could be as
600 much as 450 blocks (sectors) smaller under some circmstances.
601
602 @return: Estimated size of the image, in bytes.
603
604 @raise IOError: If there is a problem calling C{mkisofs}.
605 @raise ValueError: If initializeImage() was not previously called
606 """
607 if self._image is None:
608 raise ValueError("Must call initializeImage() before using this method.")
609 return DvdWriter._getEstimatedImageSize(self._image.entries)
610
611
612
613
614
615
617 """
618 Opens the device's tray and leaves it open.
619
620 This only works if the device has a tray and supports ejecting its media.
621 We have no way to know if the tray is currently open or closed, so we
622 just send the appropriate command and hope for the best. If the device
623 does not have a tray or does not support ejecting its media, then we do
624 nothing.
625
626 Starting with Debian wheezy on my backup hardware, I started seeing
627 consistent problems with the eject command. I couldn't tell whether
628 these problems were due to the device management system or to the new
629 kernel (3.2.0). Initially, I saw simple eject failures, possibly because
630 I was opening and closing the tray too quickly. I worked around that
631 behavior with the new ejectDelay flag.
632
633 Later, I sometimes ran into issues after writing an image to a disc:
634 eject would give errors like "unable to eject, last error: Inappropriate
635 ioctl for device". Various sources online (like Ubuntu bug #875543)
636 suggested that the drive was being locked somehow, and that the
637 workaround was to run 'eject -i off' to unlock it. Sure enough, that
638 fixed the problem for me, so now it's a normal error-handling strategy.
639
640 @raise IOError: If there is an error talking to the device.
641 """
642 if self._deviceHasTray and self._deviceCanEject:
643 command = resolveCommand(EJECT_COMMAND)
644 args = [ self.device, ]
645 result = executeCommand(command, args)[0]
646 if result != 0:
647 logger.debug("Eject failed; attempting kludge of unlocking the tray before retrying.")
648 self.unlockTray()
649 result = executeCommand(command, args)[0]
650 if result != 0:
651 raise IOError("Error (%d) executing eject command to open tray (failed even after unlocking tray)." % result)
652 logger.debug("Kludge was apparently successful.")
653 if self.ejectDelay is not None:
654 logger.debug("Per configuration, sleeping %d seconds after opening tray." % self.ejectDelay)
655 time.sleep(self.ejectDelay)
656
658 """
659 Unlocks the device's tray via 'eject -i off'.
660 @raise IOError: If there is an error talking to the device.
661 """
662 command = resolveCommand(EJECT_COMMAND)
663 args = [ "-i", "off", self.device, ]
664 result = executeCommand(command, args)[0]
665 if result != 0:
666 raise IOError("Error (%d) executing eject command to unlock tray." % result)
667
669 """
670 Closes the device's tray.
671
672 This only works if the device has a tray and supports ejecting its media.
673 We have no way to know if the tray is currently open or closed, so we
674 just send the appropriate command and hope for the best. If the device
675 does not have a tray or does not support ejecting its media, then we do
676 nothing.
677
678 @raise IOError: If there is an error talking to the device.
679 """
680 if self._deviceHasTray and self._deviceCanEject:
681 command = resolveCommand(EJECT_COMMAND)
682 args = [ "-t", self.device, ]
683 result = executeCommand(command, args)[0]
684 if result != 0:
685 raise IOError("Error (%d) executing eject command to close tray." % result)
686
713
714 - def writeImage(self, imagePath=None, newDisc=False, writeMulti=True):
715 """
716 Writes an ISO image to the media in the device.
717
718 If C{newDisc} is passed in as C{True}, we assume that the entire disc
719 will be re-created from scratch. Note that unlike C{CdWriter},
720 C{DvdWriter} does not blank rewritable media before reusing it; however,
721 C{growisofs} is called such that the media will be re-initialized as
722 needed.
723
724 If C{imagePath} is passed in as C{None}, then the existing image
725 configured with C{initializeImage()} will be used. Under these
726 circumstances, the passed-in C{newDisc} flag will be ignored and the
727 value passed in to C{initializeImage()} will apply instead.
728
729 The C{writeMulti} argument is ignored. It exists for compatibility with
730 the Cedar Backup image writer interface.
731
732 @note: The image size indicated in the log ("Image size will be...") is
733 an estimate. The estimate is conservative and is probably larger than
734 the actual space that C{dvdwriter} will use.
735
736 @param imagePath: Path to an ISO image on disk, or C{None} to use writer's image
737 @type imagePath: String representing a path on disk
738
739 @param newDisc: Indicates whether the disc should be re-initialized
740 @type newDisc: Boolean true/false.
741
742 @param writeMulti: Unused
743 @type writeMulti: Boolean true/false
744
745 @raise ValueError: If the image path is not absolute.
746 @raise ValueError: If some path cannot be encoded properly.
747 @raise IOError: If the media could not be written to for some reason.
748 @raise ValueError: If no image is passed in and initializeImage() was not previously called
749 """
750 if not writeMulti:
751 logger.warn("writeMulti value of [%s] ignored." % writeMulti)
752 if imagePath is None:
753 if self._image is None:
754 raise ValueError("Must call initializeImage() before using this method with no image path.")
755 size = self.getEstimatedImageSize()
756 logger.info("Image size will be %s (estimated)." % displayBytes(size))
757 available = self.retrieveCapacity(entireDisc=self._image.newDisc).bytesAvailable
758 if size > available:
759 logger.error("Image [%s] does not fit in available capacity [%s]." % (displayBytes(size), displayBytes(available)))
760 raise IOError("Media does not contain enough capacity to store image.")
761 self._writeImage(self._image.newDisc, None, self._image.entries, self._image.mediaLabel)
762 else:
763 if not os.path.isabs(imagePath):
764 raise ValueError("Image path must be absolute.")
765 imagePath = encodePath(imagePath)
766 self._writeImage(newDisc, imagePath, None)
767
768
769
770
771
772
773 - def _writeImage(self, newDisc, imagePath, entries, mediaLabel=None):
774 """
775 Writes an image to disc using either an entries list or an ISO image on
776 disk.
777
778 Callers are assumed to have done validation on paths, etc. before calling
779 this method.
780
781 @param newDisc: Indicates whether the disc should be re-initialized
782 @param imagePath: Path to an ISO image on disk, or c{None} to use C{entries}
783 @param entries: Mapping from path to graft point, or C{None} to use C{imagePath}
784
785 @raise IOError: If the media could not be written to for some reason.
786 """
787 command = resolveCommand(GROWISOFS_COMMAND)
788 args = DvdWriter._buildWriteArgs(newDisc, self.hardwareId, self._driveSpeed, imagePath, entries, mediaLabel, dryRun=False)
789 (result, output) = executeCommand(command, args, returnOutput=True)
790 if result != 0:
791 DvdWriter._searchForOverburn(output)
792 raise IOError("Error (%d) executing command to write disc." % result)
793 self.refreshMedia()
794
795 @staticmethod
797 """
798 Gets the estimated size of a set of image entries.
799
800 This is implemented in terms of the C{IsoImage} class. The returned
801 value is calculated by adding a "fudge factor" to the value from
802 C{IsoImage}. This fudge factor was determined by experimentation and is
803 conservative -- the actual image could be as much as 450 blocks smaller
804 under some circumstances.
805
806 @param entries: Dictionary mapping path to graft point.
807
808 @return: Total estimated size of image, in bytes.
809
810 @raise ValueError: If there are no entries in the dictionary
811 @raise ValueError: If any path in the dictionary does not exist
812 @raise IOError: If there is a problem calling C{mkisofs}.
813 """
814 fudgeFactor = convertSize(2500.0, UNIT_SECTORS, UNIT_BYTES)
815 if len(entries.keys()) == 0:
816 raise ValueError("Must add at least one entry with addImageEntry().")
817 image = IsoImage()
818 for path in entries.keys():
819 image.addEntry(path, entries[path], override=False, contentsOnly=True)
820 estimatedSize = image.getEstimatedSize() + fudgeFactor
821 return estimatedSize
822
824 """
825 Retrieves the number of sectors used on the current media.
826
827 This is a little ugly. We need to call growisofs in "dry-run" mode and
828 parse some information from its output. However, to do that, we need to
829 create a dummy file that we can pass to the command -- and we have to
830 make sure to remove it later.
831
832 Once growisofs has been run, then we call C{_parseSectorsUsed} to parse
833 the output and calculate the number of sectors used on the media.
834
835 @return: Number of sectors used on the media
836 """
837 tempdir = tempfile.mkdtemp()
838 try:
839 entries = { tempdir: None }
840 args = DvdWriter._buildWriteArgs(False, self.hardwareId, self.driveSpeed, None, entries, None, dryRun=True)
841 command = resolveCommand(GROWISOFS_COMMAND)
842 (result, output) = executeCommand(command, args, returnOutput=True)
843 if result != 0:
844 logger.debug("Error (%d) calling growisofs to read sectors used." % result)
845 logger.warn("Unable to read disc (might not be initialized); returning zero sectors used.")
846 return 0.0
847 sectorsUsed = DvdWriter._parseSectorsUsed(output)
848 logger.debug("Determined sectors used as %s" % sectorsUsed)
849 return sectorsUsed
850 finally:
851 if os.path.exists(tempdir):
852 try:
853 os.rmdir(tempdir)
854 except: pass
855
856 @staticmethod
858 """
859 Parse sectors used information out of C{growisofs} output.
860
861 The first line of a growisofs run looks something like this::
862
863 Executing 'mkisofs -C 973744,1401056 -M /dev/fd/3 -r -graft-points music4/=music | builtin_dd of=/dev/cdrom obs=32k seek=87566'
864
865 Dmitry has determined that the seek value in this line gives us
866 information about how much data has previously been written to the media.
867 That value multiplied by 16 yields the number of sectors used.
868
869 If the seek line cannot be found in the output, then sectors used of zero
870 is assumed.
871
872 @return: Sectors used on the media, as a floating point number.
873
874 @raise ValueError: If the output cannot be parsed properly.
875 """
876 if output is not None:
877 pattern = re.compile(r"(^)(.*)(seek=)(.*)('$)")
878 for line in output:
879 match = pattern.search(line)
880 if match is not None:
881 try:
882 return float(match.group(4).strip()) * 16.0
883 except ValueError:
884 raise ValueError("Unable to parse sectors used out of growisofs output.")
885 logger.warn("Unable to read disc (might not be initialized); returning zero sectors used.")
886 return 0.0
887
888 @staticmethod
890 """
891 Search for an "overburn" error message in C{growisofs} output.
892
893 The C{growisofs} command returns a non-zero exit code and puts a message
894 into the output -- even on a dry run -- if there is not enough space on
895 the media. This is called an "overburn" condition.
896
897 The error message looks like this::
898
899 :-( /dev/cdrom: 894048 blocks are free, 2033746 to be written!
900
901 This method looks for the overburn error message anywhere in the output.
902 If a matching error message is found, an C{IOError} exception is raised
903 containing relevant information about the problem. Otherwise, the method
904 call returns normally.
905
906 @param output: List of output lines to search, as from C{executeCommand}
907
908 @raise IOError: If an overburn condition is found.
909 """
910 if output is None:
911 return
912 pattern = re.compile(r"(^)(:-[(])(\s*.*:\s*)(.* )(blocks are free, )(.* )(to be written!)")
913 for line in output:
914 match = pattern.search(line)
915 if match is not None:
916 try:
917 available = convertSize(float(match.group(4).strip()), UNIT_SECTORS, UNIT_BYTES)
918 size = convertSize(float(match.group(6).strip()), UNIT_SECTORS, UNIT_BYTES)
919 logger.error("Image [%s] does not fit in available capacity [%s]." % (displayBytes(size), displayBytes(available)))
920 except ValueError:
921 logger.error("Image does not fit in available capacity (no useful capacity info available).")
922 raise IOError("Media does not contain enough capacity to store image.")
923
924 @staticmethod
925 - def _buildWriteArgs(newDisc, hardwareId, driveSpeed, imagePath, entries, mediaLabel=None, dryRun=False):
926 """
927 Builds a list of arguments to be passed to a C{growisofs} command.
928
929 The arguments will either cause C{growisofs} to write the indicated image
930 file to disc, or will pass C{growisofs} a list of directories or files
931 that should be written to disc.
932
933 If a new image is created, it will always be created with Rock Ridge
934 extensions (-r). A volume name will be applied (-V) if C{mediaLabel} is
935 not C{None}.
936
937 @param newDisc: Indicates whether the disc should be re-initialized
938 @param hardwareId: Hardware id for the device
939 @param driveSpeed: Speed at which the drive writes.
940 @param imagePath: Path to an ISO image on disk, or c{None} to use C{entries}
941 @param entries: Mapping from path to graft point, or C{None} to use C{imagePath}
942 @param mediaLabel: Media label to set on the image, if any
943 @param dryRun: Says whether to make this a dry run (for checking capacity)
944
945 @note: If we write an existing image to disc, then the mediaLabel is
946 ignored. The media label is an attribute of the image, and should be set
947 on the image when it is created.
948
949 @note: We always pass the undocumented option C{-use-the-force-like=tty}
950 to growisofs. Without this option, growisofs will refuse to execute
951 certain actions when running from cron. A good example is -Z, which
952 happily overwrites an existing DVD from the command-line, but fails when
953 run from cron. It took a while to figure that out, since it worked every
954 time I tested it by hand. :(
955
956 @return: List suitable for passing to L{util.executeCommand} as C{args}.
957
958 @raise ValueError: If caller does not pass one or the other of imagePath or entries.
959 """
960 args = []
961 if (imagePath is None and entries is None) or (imagePath is not None and entries is not None):
962 raise ValueError("Must use either imagePath or entries.")
963 args.append("-use-the-force-luke=tty")
964 if dryRun:
965 args.append("-dry-run")
966 if driveSpeed is not None:
967 args.append("-speed=%d" % driveSpeed)
968 if newDisc:
969 args.append("-Z")
970 else:
971 args.append("-M")
972 if imagePath is not None:
973 args.append("%s=%s" % (hardwareId, imagePath))
974 else:
975 args.append(hardwareId)
976 if mediaLabel is not None:
977 args.append("-V")
978 args.append(mediaLabel)
979 args.append("-r")
980 args.append("-graft-points")
981 keys = entries.keys()
982 keys.sort()
983 for key in keys:
984
985 if entries[key] is None:
986 args.append(key)
987 else:
988 args.append("%s/=%s" % (entries[key].strip("/"), key))
989 return args
990