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 CD writer devices.
41
42 @sort: MediaDefinition, MediaCapacity, CdWriter,
43 MEDIA_CDRW_74, MEDIA_CDR_74, MEDIA_CDRW_80, MEDIA_CDR_80
44
45 @var MEDIA_CDRW_74: Constant representing 74-minute CD-RW media.
46 @var MEDIA_CDR_74: Constant representing 74-minute CD-R media.
47 @var MEDIA_CDRW_80: Constant representing 80-minute CD-RW media.
48 @var MEDIA_CDR_80: Constant representing 80-minute CD-R media.
49
50 @author: Kenneth J. Pronovici <pronovic@ieee.org>
51 """
52
53
54
55
56
57
58 import os
59 import re
60 import logging
61 import tempfile
62 import time
63
64
65 from CedarBackup2.util import resolveCommand, executeCommand
66 from CedarBackup2.util import convertSize, displayBytes, encodePath
67 from CedarBackup2.util import UNIT_SECTORS, UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES
68 from CedarBackup2.writers.util import validateDevice, validateScsiId, validateDriveSpeed
69 from CedarBackup2.writers.util import IsoImage
70
71
72
73
74
75
76 logger = logging.getLogger("CedarBackup2.log.writers.cdwriter")
77
78 MEDIA_CDRW_74 = 1
79 MEDIA_CDR_74 = 2
80 MEDIA_CDRW_80 = 3
81 MEDIA_CDR_80 = 4
82
83 CDRECORD_COMMAND = [ "cdrecord", ]
84 EJECT_COMMAND = [ "eject", ]
85 MKISOFS_COMMAND = [ "mkisofs", ]
182
263
270 """
271 Simple value object to hold image properties for C{DvdWriter}.
272 """
274 self.newDisc = False
275 self.tmpdir = None
276 self.mediaLabel = None
277 self.entries = None
278
279
280
281
282
283
284 -class CdWriter(object):
285
286
287
288
289
290 """
291 Class representing a device that knows how to write CD media.
292
293 Summary
294 =======
295
296 This is a class representing a device that knows how to write CD media. It
297 provides common operations for the device, such as ejecting the media,
298 writing an ISO image to the media, or checking for the current media
299 capacity. It also provides a place to store device attributes, such as
300 whether the device supports writing multisession discs, etc.
301
302 This class is implemented in terms of the C{eject} and C{cdrecord}
303 programs, both of which should be available on most UN*X platforms.
304
305 Image Writer Interface
306 ======================
307
308 The following methods make up the "image writer" interface shared
309 with other kinds of writers (such as DVD writers)::
310
311 __init__
312 initializeImage()
313 addImageEntry()
314 writeImage()
315 setImageNewDisc()
316 retrieveCapacity()
317 getEstimatedImageSize()
318
319 Only these methods will be used by other Cedar Backup functionality
320 that expects a compatible image writer.
321
322 The media attribute is also assumed to be available.
323
324 Media Types
325 ===========
326
327 This class knows how to write to two different kinds of media, represented
328 by the following constants:
329
330 - C{MEDIA_CDR_74}: 74-minute CD-R media (650 MB capacity)
331 - C{MEDIA_CDRW_74}: 74-minute CD-RW media (650 MB capacity)
332 - C{MEDIA_CDR_80}: 80-minute CD-R media (700 MB capacity)
333 - C{MEDIA_CDRW_80}: 80-minute CD-RW media (700 MB capacity)
334
335 Most hardware can read and write both 74-minute and 80-minute CD-R and
336 CD-RW media. Some older drives may only be able to write CD-R media.
337 The difference between the two is that CD-RW media can be rewritten
338 (erased), while CD-R media cannot be.
339
340 I do not support any other configurations for a couple of reasons. The
341 first is that I've never tested any other kind of media. The second is
342 that anything other than 74 or 80 minute is apparently non-standard.
343
344 Device Attributes vs. Media Attributes
345 ======================================
346
347 A given writer instance has two different kinds of attributes associated
348 with it, which I call device attributes and media attributes. Device
349 attributes are things which can be determined without looking at the
350 media, such as whether the drive supports writing multisession disks or
351 has a tray. Media attributes are attributes which vary depending on the
352 state of the media, such as the remaining capacity on a disc. In
353 general, device attributes are available via instance variables and are
354 constant over the life of an object, while media attributes can be
355 retrieved through method calls.
356
357 Talking to Hardware
358 ===================
359
360 This class needs to talk to CD writer hardware in two different ways:
361 through cdrecord to actually write to the media, and through the
362 filesystem to do things like open and close the tray.
363
364 Historically, CdWriter has interacted with cdrecord using the scsiId
365 attribute, and with most other utilities using the device attribute.
366 This changed somewhat in Cedar Backup 2.9.0.
367
368 When Cedar Backup was first written, the only way to interact with
369 cdrecord was by using a SCSI device id. IDE devices were mapped to
370 pseudo-SCSI devices through the kernel. Later, extended SCSI "methods"
371 arrived, and it became common to see C{ATA:1,0,0} or C{ATAPI:0,0,0} as a
372 way to address IDE hardware. By late 2006, C{ATA} and C{ATAPI} had
373 apparently been deprecated in favor of just addressing the IDE device
374 directly by name, i.e. C{/dev/cdrw}.
375
376 Because of this latest development, it no longer makes sense to require a
377 CdWriter to be created with a SCSI id -- there might not be one. So, the
378 passed-in SCSI id is now optional. Also, there is now a hardwareId
379 attribute. This attribute is filled in with either the SCSI id (if
380 provided) or the device (otherwise). The hardware id is the value that
381 will be passed to cdrecord in the C{dev=} argument.
382
383 Testing
384 =======
385
386 It's rather difficult to test this code in an automated fashion, even if
387 you have access to a physical CD writer drive. It's even more difficult
388 to test it if you are running on some build daemon (think of a Debian
389 autobuilder) which can't be expected to have any hardware or any media
390 that you could write to.
391
392 Because of this, much of the implementation below is in terms of static
393 methods that are supposed to take defined actions based on their
394 arguments. Public methods are then implemented in terms of a series of
395 calls to simplistic static methods. This way, we can test as much as
396 possible of the functionality via testing the static methods, while
397 hoping that if the static methods are called appropriately, things will
398 work properly. It's not perfect, but it's much better than no testing at
399 all.
400
401 @sort: __init__, isRewritable, _retrieveProperties, retrieveCapacity, _getBoundaries,
402 _calculateCapacity, openTray, closeTray, refreshMedia, writeImage,
403 _blankMedia, _parsePropertiesOutput, _parseBoundariesOutput,
404 _buildOpenTrayArgs, _buildCloseTrayArgs, _buildPropertiesArgs,
405 _buildBoundariesArgs, _buildBlankArgs, _buildWriteArgs,
406 device, scsiId, hardwareId, driveSpeed, media, deviceType, deviceVendor,
407 deviceId, deviceBufferSize, deviceSupportsMulti, deviceHasTray, deviceCanEject,
408 initializeImage, addImageEntry, writeImage, setImageNewDisc, getEstimatedImageSize
409 """
410
411
412
413
414
415 - def __init__(self, device, scsiId=None, driveSpeed=None,
416 mediaType=MEDIA_CDRW_74, noEject=False,
417 refreshMediaDelay=0, unittest=False):
418 """
419 Initializes a CD writer object.
420
421 The current user must have write access to the device at the time the
422 object is instantiated, or an exception will be thrown. However, no
423 media-related validation is done, and in fact there is no need for any
424 media to be in the drive until one of the other media attribute-related
425 methods is called.
426
427 The various instance variables such as C{deviceType}, C{deviceVendor},
428 etc. might be C{None}, if we're unable to parse this specific information
429 from the C{cdrecord} output. This information is just for reference.
430
431 The SCSI id is optional, but the device path is required. If the SCSI id
432 is passed in, then the hardware id attribute will be taken from the SCSI
433 id. Otherwise, the hardware id will be taken from the device.
434
435 If cdrecord improperly detects whether your writer device has a tray and
436 can be safely opened and closed, then pass in C{noEject=False}. This
437 will override the properties and the device will never be ejected.
438
439 @note: The C{unittest} parameter should never be set to C{True}
440 outside of Cedar Backup code. It is intended for use in unit testing
441 Cedar Backup internals and has no other sensible purpose.
442
443 @param device: Filesystem device associated with this writer.
444 @type device: Absolute path to a filesystem device, i.e. C{/dev/cdrw}
445
446 @param scsiId: SCSI id for the device (optional).
447 @type scsiId: If provided, SCSI id in the form C{[<method>:]scsibus,target,lun}
448
449 @param driveSpeed: Speed at which the drive writes.
450 @type driveSpeed: Use C{2} for 2x device, etc. or C{None} to use device default.
451
452 @param mediaType: Type of the media that is assumed to be in the drive.
453 @type mediaType: One of the valid media type as discussed above.
454
455 @param noEject: Overrides properties to indicate that the device does not support eject.
456 @type noEject: Boolean true/false
457
458 @param refreshMediaDelay: Refresh media delay to use, if any
459 @type refreshMediaDelay: Number of seconds, an integer >= 0
460
461 @param unittest: Turns off certain validations, for use in unit testing.
462 @type unittest: Boolean true/false
463
464 @raise ValueError: If the device is not valid for some reason.
465 @raise ValueError: If the SCSI id is not in a valid form.
466 @raise ValueError: If the drive speed is not an integer >= 1.
467 @raise IOError: If device properties could not be read for some reason.
468 """
469 self._image = None
470 self._device = validateDevice(device, unittest)
471 self._scsiId = validateScsiId(scsiId)
472 self._driveSpeed = validateDriveSpeed(driveSpeed)
473 self._media = MediaDefinition(mediaType)
474 self._noEject = noEject
475 self._refreshMediaDelay = refreshMediaDelay
476 if not unittest:
477 (self._deviceType,
478 self._deviceVendor,
479 self._deviceId,
480 self._deviceBufferSize,
481 self._deviceSupportsMulti,
482 self._deviceHasTray,
483 self._deviceCanEject) = self._retrieveProperties()
484
485
486
487
488
489
491 """
492 Property target used to get the device value.
493 """
494 return self._device
495
497 """
498 Property target used to get the SCSI id value.
499 """
500 return self._scsiId
501
503 """
504 Property target used to get the hardware id value.
505 """
506 if self._scsiId is None:
507 return self._device
508 return self._scsiId
509
511 """
512 Property target used to get the drive speed.
513 """
514 return self._driveSpeed
515
521
523 """
524 Property target used to get the device type.
525 """
526 return self._deviceType
527
529 """
530 Property target used to get the device vendor.
531 """
532 return self._deviceVendor
533
535 """
536 Property target used to get the device id.
537 """
538 return self._deviceId
539
541 """
542 Property target used to get the device buffer size.
543 """
544 return self._deviceBufferSize
545
547 """
548 Property target used to get the device-support-multi flag.
549 """
550 return self._deviceSupportsMulti
551
553 """
554 Property target used to get the device-has-tray flag.
555 """
556 return self._deviceHasTray
557
559 """
560 Property target used to get the device-can-eject flag.
561 """
562 return self._deviceCanEject
563
569
570 device = property(_getDevice, None, None, doc="Filesystem device name for this writer.")
571 scsiId = property(_getScsiId, None, None, doc="SCSI id for the device, in the form C{[<method>:]scsibus,target,lun}.")
572 hardwareId = property(_getHardwareId, None, None, doc="Hardware id for this writer, either SCSI id or device path.")
573 driveSpeed = property(_getDriveSpeed, None, None, doc="Speed at which the drive writes.")
574 media = property(_getMedia, None, None, doc="Definition of media that is expected to be in the device.")
575 deviceType = property(_getDeviceType, None, None, doc="Type of the device, as returned from C{cdrecord -prcap}.")
576 deviceVendor = property(_getDeviceVendor, None, None, doc="Vendor of the device, as returned from C{cdrecord -prcap}.")
577 deviceId = property(_getDeviceId, None, None, doc="Device identification, as returned from C{cdrecord -prcap}.")
578 deviceBufferSize = property(_getDeviceBufferSize, None, None, doc="Size of the device's write buffer, in bytes.")
579 deviceSupportsMulti = property(_getDeviceSupportsMulti, None, None, doc="Indicates whether device supports multisession discs.")
580 deviceHasTray = property(_getDeviceHasTray, None, None, doc="Indicates whether the device has a media tray.")
581 deviceCanEject = property(_getDeviceCanEject, None, None, doc="Indicates whether the device supports ejecting its media.")
582 refreshMediaDelay = property(_getRefreshMediaDelay, None, None, doc="Refresh media delay, in seconds.")
583
584
585
586
587
588
590 """Indicates whether the media is rewritable per configuration."""
591 return self._media.rewritable
592
594 """
595 Retrieves properties for a device from C{cdrecord}.
596
597 The results are returned as a tuple of the object device attributes as
598 returned from L{_parsePropertiesOutput}: C{(deviceType, deviceVendor,
599 deviceId, deviceBufferSize, deviceSupportsMulti, deviceHasTray,
600 deviceCanEject)}.
601
602 @return: Results tuple as described above.
603 @raise IOError: If there is a problem talking to the device.
604 """
605 args = CdWriter._buildPropertiesArgs(self.hardwareId)
606 command = resolveCommand(CDRECORD_COMMAND)
607 (result, output) = executeCommand(command, args, returnOutput=True, ignoreStderr=True)
608 if result != 0:
609 raise IOError("Error (%d) executing cdrecord command to get properties." % result)
610 return CdWriter._parsePropertiesOutput(output)
611
613 """
614 Retrieves capacity for the current media in terms of a C{MediaCapacity}
615 object.
616
617 If C{entireDisc} is passed in as C{True} the capacity will be for the
618 entire disc, as if it were to be rewritten from scratch. If the drive
619 does not support writing multisession discs or if C{useMulti} is passed
620 in as C{False}, the capacity will also be as if the disc were to be
621 rewritten from scratch, but the indicated boundaries value will be
622 C{None}. The same will happen if the disc cannot be read for some
623 reason. Otherwise, the capacity (including the boundaries) will
624 represent whatever space remains on the disc to be filled by future
625 sessions.
626
627 @param entireDisc: Indicates whether to return capacity for entire disc.
628 @type entireDisc: Boolean true/false
629
630 @param useMulti: Indicates whether a multisession disc should be assumed, if possible.
631 @type useMulti: Boolean true/false
632
633 @return: C{MediaCapacity} object describing the capacity of the media.
634 @raise IOError: If the media could not be read for some reason.
635 """
636 boundaries = self._getBoundaries(entireDisc, useMulti)
637 return CdWriter._calculateCapacity(self._media, boundaries)
638
640 """
641 Gets the ISO boundaries for the media.
642
643 If C{entireDisc} is passed in as C{True} the boundaries will be C{None},
644 as if the disc were to be rewritten from scratch. If the drive does not
645 support writing multisession discs, the returned value will be C{None}.
646 The same will happen if the disc can't be read for some reason.
647 Otherwise, the returned value will be represent the boundaries of the
648 disc's current contents.
649
650 The results are returned as a tuple of (lower, upper) as needed by the
651 C{IsoImage} class. Note that these values are in terms of ISO sectors,
652 not bytes. Clients should generally consider the boundaries value
653 opaque, however.
654
655 @param entireDisc: Indicates whether to return capacity for entire disc.
656 @type entireDisc: Boolean true/false
657
658 @param useMulti: Indicates whether a multisession disc should be assumed, if possible.
659 @type useMulti: Boolean true/false
660
661 @return: Boundaries tuple or C{None}, as described above.
662 @raise IOError: If the media could not be read for some reason.
663 """
664 if not self._deviceSupportsMulti:
665 logger.debug("Device does not support multisession discs; returning boundaries None.")
666 return None
667 elif not useMulti:
668 logger.debug("Use multisession flag is False; returning boundaries None.")
669 return None
670 elif entireDisc:
671 logger.debug("Entire disc flag is True; returning boundaries None.")
672 return None
673 else:
674 args = CdWriter._buildBoundariesArgs(self.hardwareId)
675 command = resolveCommand(CDRECORD_COMMAND)
676 (result, output) = executeCommand(command, args, returnOutput=True, ignoreStderr=True)
677 if result != 0:
678 logger.debug("Error (%d) executing cdrecord command to get capacity." % result)
679 logger.warn("Unable to read disc (might not be initialized); returning boundaries of None.")
680 return None
681 boundaries = CdWriter._parseBoundariesOutput(output)
682 if boundaries is None:
683 logger.debug("Returning disc boundaries: None")
684 else:
685 logger.debug("Returning disc boundaries: (%d, %d)" % (boundaries[0], boundaries[1]))
686 return boundaries
687
688 @staticmethod
690 """
691 Calculates capacity for the media in terms of boundaries.
692
693 If C{boundaries} is C{None} or the lower bound is 0 (zero), then the
694 capacity will be for the entire disc minus the initial lead in.
695 Otherwise, capacity will be as if the caller wanted to add an additional
696 session to the end of the existing data on the disc.
697
698 @param media: MediaDescription object describing the media capacity.
699 @param boundaries: Session boundaries as returned from L{_getBoundaries}.
700
701 @return: C{MediaCapacity} object describing the capacity of the media.
702 """
703 if boundaries is None or boundaries[1] == 0:
704 logger.debug("Capacity calculations are based on a complete disc rewrite.")
705 sectorsAvailable = media.capacity - media.initialLeadIn
706 if sectorsAvailable < 0: sectorsAvailable = 0
707 bytesUsed = 0
708 bytesAvailable = convertSize(sectorsAvailable, UNIT_SECTORS, UNIT_BYTES)
709 else:
710 logger.debug("Capacity calculations are based on a new ISO session.")
711 sectorsAvailable = media.capacity - boundaries[1] - media.leadIn
712 if sectorsAvailable < 0: sectorsAvailable = 0
713 bytesUsed = convertSize(boundaries[1], UNIT_SECTORS, UNIT_BYTES)
714 bytesAvailable = convertSize(sectorsAvailable, UNIT_SECTORS, UNIT_BYTES)
715 logger.debug("Used [%s], available [%s]." % (displayBytes(bytesUsed), displayBytes(bytesAvailable)))
716 return MediaCapacity(bytesUsed, bytesAvailable, boundaries)
717
718
719
720
721
722
724 """
725 Initializes the writer's associated ISO image.
726
727 This method initializes the C{image} instance variable so that the caller
728 can use the C{addImageEntry} method. Once entries have been added, the
729 C{writeImage} method can be called with no arguments.
730
731 @param newDisc: Indicates whether the disc should be re-initialized
732 @type newDisc: Boolean true/false.
733
734 @param tmpdir: Temporary directory to use if needed
735 @type tmpdir: String representing a directory path on disk
736
737 @param mediaLabel: Media label to be applied to the image, if any
738 @type mediaLabel: String, no more than 25 characters long
739 """
740 self._image = _ImageProperties()
741 self._image.newDisc = newDisc
742 self._image.tmpdir = encodePath(tmpdir)
743 self._image.mediaLabel = mediaLabel
744 self._image.entries = {}
745
746 - def addImageEntry(self, path, graftPoint):
747 """
748 Adds a filepath entry to the writer's associated ISO image.
749
750 The contents of the filepath -- but not the path itself -- will be added
751 to the image at the indicated graft point. If you don't want to use a
752 graft point, just pass C{None}.
753
754 @note: Before calling this method, you must call L{initializeImage}.
755
756 @param path: File or directory to be added to the image
757 @type path: String representing a path on disk
758
759 @param graftPoint: Graft point to be used when adding this entry
760 @type graftPoint: String representing a graft point path, as described above
761
762 @raise ValueError: If initializeImage() was not previously called
763 """
764 if self._image is None:
765 raise ValueError("Must call initializeImage() before using this method.")
766 if not os.path.exists(path):
767 raise ValueError("Path [%s] does not exist." % path)
768 self._image.entries[path] = graftPoint
769
771 """
772 Resets (overrides) the newDisc flag on the internal image.
773 @param newDisc: New disc flag to set
774 @raise ValueError: If initializeImage() was not previously called
775 """
776 if self._image is None:
777 raise ValueError("Must call initializeImage() before using this method.")
778 self._image.newDisc = newDisc
779
781 """
782 Gets the estimated size of the image associated with the writer.
783 @return: Estimated size of the image, in bytes.
784 @raise IOError: If there is a problem calling C{mkisofs}.
785 @raise ValueError: If initializeImage() was not previously called
786 """
787 if self._image is None:
788 raise ValueError("Must call initializeImage() before using this method.")
789 image = IsoImage()
790 for path in self._image.entries.keys():
791 image.addEntry(path, self._image.entries[path], override=False, contentsOnly=True)
792 return image.getEstimatedSize()
793
794
795
796
797
798
800 """
801 Opens the device's tray and leaves it open.
802
803 This only works if the device has a tray and supports ejecting its media.
804 We have no way to know if the tray is currently open or closed, so we
805 just send the appropriate command and hope for the best. If the device
806 does not have a tray or does not support ejecting its media, then we do
807 nothing.
808
809 If the writer was constructed with C{noEject=True}, then this is a no-op.
810
811 @raise IOError: If there is an error talking to the device.
812 """
813 if not self._noEject:
814 if self._deviceHasTray and self._deviceCanEject:
815 args = CdWriter._buildOpenTrayArgs(self._device)
816 command = resolveCommand(EJECT_COMMAND)
817 result = executeCommand(command, args)[0]
818 if result != 0:
819 raise IOError("Error (%d) executing eject command to open tray." % result)
820
822 """
823 Closes the device's tray.
824
825 This only works if the device has a tray and supports ejecting its media.
826 We have no way to know if the tray is currently open or closed, so we
827 just send the appropriate command and hope for the best. If the device
828 does not have a tray or does not support ejecting its media, then we do
829 nothing.
830
831 If the writer was constructed with C{noEject=True}, then this is a no-op.
832
833 @raise IOError: If there is an error talking to the device.
834 """
835 if not self._noEject:
836 if self._deviceHasTray and self._deviceCanEject:
837 args = CdWriter._buildCloseTrayArgs(self._device)
838 command = resolveCommand(EJECT_COMMAND)
839 result = executeCommand(command, args)[0]
840 if result != 0:
841 raise IOError("Error (%d) executing eject command to close tray." % result)
842
868
869 - def writeImage(self, imagePath=None, newDisc=False, writeMulti=True):
870 """
871 Writes an ISO image to the media in the device.
872
873 If C{newDisc} is passed in as C{True}, we assume that the entire disc
874 will be overwritten, and the media will be blanked before writing it if
875 possible (i.e. if the media is rewritable).
876
877 If C{writeMulti} is passed in as C{True}, then a multisession disc will
878 be written if possible (i.e. if the drive supports writing multisession
879 discs).
880
881 if C{imagePath} is passed in as C{None}, then the existing image
882 configured with C{initializeImage} will be used. Under these
883 circumstances, the passed-in C{newDisc} flag will be ignored.
884
885 By default, we assume that the disc can be written multisession and that
886 we should append to the current contents of the disc. In any case, the
887 ISO image must be generated appropriately (i.e. must take into account
888 any existing session boundaries, etc.)
889
890 @param imagePath: Path to an ISO image on disk, or C{None} to use writer's image
891 @type imagePath: String representing a path on disk
892
893 @param newDisc: Indicates whether the entire disc will overwritten.
894 @type newDisc: Boolean true/false.
895
896 @param writeMulti: Indicates whether a multisession disc should be written, if possible.
897 @type writeMulti: Boolean true/false
898
899 @raise ValueError: If the image path is not absolute.
900 @raise ValueError: If some path cannot be encoded properly.
901 @raise IOError: If the media could not be written to for some reason.
902 @raise ValueError: If no image is passed in and initializeImage() was not previously called
903 """
904 if imagePath is None:
905 if self._image is None:
906 raise ValueError("Must call initializeImage() before using this method with no image path.")
907 try:
908 imagePath = self._createImage()
909 self._writeImage(imagePath, writeMulti, self._image.newDisc)
910 finally:
911 if imagePath is not None and os.path.exists(imagePath):
912 try: os.unlink(imagePath)
913 except: pass
914 else:
915 imagePath = encodePath(imagePath)
916 if not os.path.isabs(imagePath):
917 raise ValueError("Image path must be absolute.")
918 self._writeImage(imagePath, writeMulti, newDisc)
919
921 """
922 Creates an ISO image based on configuration in self._image.
923 @return: Path to the newly-created ISO image on disk.
924 @raise IOError: If there is an error writing the image to disk.
925 @raise ValueError: If there are no filesystem entries in the image
926 @raise ValueError: If a path cannot be encoded properly.
927 """
928 path = None
929 capacity = self.retrieveCapacity(entireDisc=self._image.newDisc)
930 image = IsoImage(self.device, capacity.boundaries)
931 image.volumeId = self._image.mediaLabel
932 for key in self._image.entries.keys():
933 image.addEntry(key, self._image.entries[key], override=False, contentsOnly=True)
934 size = image.getEstimatedSize()
935 logger.info("Image size will be %s." % displayBytes(size))
936 available = capacity.bytesAvailable
937 logger.debug("Media capacity: %s" % displayBytes(available))
938 if size > available:
939 logger.error("Image [%s] does not fit in available capacity [%s]." % (displayBytes(size), displayBytes(available)))
940 raise IOError("Media does not contain enough capacity to store image.")
941 try:
942 (handle, path) = tempfile.mkstemp(dir=self._image.tmpdir)
943 try: os.close(handle)
944 except: pass
945 image.writeImage(path)
946 logger.debug("Completed creating image [%s]." % path)
947 return path
948 except Exception, e:
949 if path is not None and os.path.exists(path):
950 try: os.unlink(path)
951 except: pass
952 raise e
953
954 - def _writeImage(self, imagePath, writeMulti, newDisc):
955 """
956 Write an ISO image to disc using cdrecord.
957 The disc is blanked first if C{newDisc} is C{True}.
958 @param imagePath: Path to an ISO image on disk
959 @param writeMulti: Indicates whether a multisession disc should be written, if possible.
960 @param newDisc: Indicates whether the entire disc will overwritten.
961 """
962 if newDisc:
963 self._blankMedia()
964 args = CdWriter._buildWriteArgs(self.hardwareId, imagePath, self._driveSpeed, writeMulti and self._deviceSupportsMulti)
965 command = resolveCommand(CDRECORD_COMMAND)
966 result = executeCommand(command, args)[0]
967 if result != 0:
968 raise IOError("Error (%d) executing command to write disc." % result)
969 self.refreshMedia()
970
983
984
985
986
987
988
989 @staticmethod
991 """
992 Parses the output from a C{cdrecord} properties command.
993
994 The C{output} parameter should be a list of strings as returned from
995 C{executeCommand} for a C{cdrecord} command with arguments as from
996 C{_buildPropertiesArgs}. The list of strings will be parsed to yield
997 information about the properties of the device.
998
999 The output is expected to be a huge long list of strings. Unfortunately,
1000 the strings aren't in a completely regular format. However, the format
1001 of individual lines seems to be regular enough that we can look for
1002 specific values. Two kinds of parsing take place: one kind of parsing
1003 picks out out specific values like the device id, device vendor, etc.
1004 The other kind of parsing just sets a boolean flag C{True} if a matching
1005 line is found. All of the parsing is done with regular expressions.
1006
1007 Right now, pretty much nothing in the output is required and we should
1008 parse an empty document successfully (albeit resulting in a device that
1009 can't eject, doesn't have a tray and doesnt't support multisession
1010 discs). I had briefly considered erroring out if certain lines weren't
1011 found or couldn't be parsed, but that seems like a bad idea given that
1012 most of the information is just for reference.
1013
1014 The results are returned as a tuple of the object device attributes:
1015 C{(deviceType, deviceVendor, deviceId, deviceBufferSize,
1016 deviceSupportsMulti, deviceHasTray, deviceCanEject)}.
1017
1018 @param output: Output from a C{cdrecord -prcap} command.
1019
1020 @return: Results tuple as described above.
1021 @raise IOError: If there is problem parsing the output.
1022 """
1023 deviceType = None
1024 deviceVendor = None
1025 deviceId = None
1026 deviceBufferSize = None
1027 deviceSupportsMulti = False
1028 deviceHasTray = False
1029 deviceCanEject = False
1030 typePattern = re.compile(r"(^Device type\s*:\s*)(.*)(\s*)(.*$)")
1031 vendorPattern = re.compile(r"(^Vendor_info\s*:\s*'\s*)(.*?)(\s*')(.*$)")
1032 idPattern = re.compile(r"(^Identifikation\s*:\s*'\s*)(.*?)(\s*')(.*$)")
1033 bufferPattern = re.compile(r"(^\s*Buffer size in KB:\s*)(.*?)(\s*$)")
1034 multiPattern = re.compile(r"^\s*Does read multi-session.*$")
1035 trayPattern = re.compile(r"^\s*Loading mechanism type: tray.*$")
1036 ejectPattern = re.compile(r"^\s*Does support ejection.*$")
1037 for line in output:
1038 if typePattern.search(line):
1039 deviceType = typePattern.search(line).group(2)
1040 logger.info("Device type is [%s]." % deviceType)
1041 elif vendorPattern.search(line):
1042 deviceVendor = vendorPattern.search(line).group(2)
1043 logger.info("Device vendor is [%s]." % deviceVendor)
1044 elif idPattern.search(line):
1045 deviceId = idPattern.search(line).group(2)
1046 logger.info("Device id is [%s]." % deviceId)
1047 elif bufferPattern.search(line):
1048 try:
1049 sectors = int(bufferPattern.search(line).group(2))
1050 deviceBufferSize = convertSize(sectors, UNIT_KBYTES, UNIT_BYTES)
1051 logger.info("Device buffer size is [%d] bytes." % deviceBufferSize)
1052 except TypeError: pass
1053 elif multiPattern.search(line):
1054 deviceSupportsMulti = True
1055 logger.info("Device does support multisession discs.")
1056 elif trayPattern.search(line):
1057 deviceHasTray = True
1058 logger.info("Device has a tray.")
1059 elif ejectPattern.search(line):
1060 deviceCanEject = True
1061 logger.info("Device can eject its media.")
1062 return (deviceType, deviceVendor, deviceId, deviceBufferSize, deviceSupportsMulti, deviceHasTray, deviceCanEject)
1063
1064 @staticmethod
1066 """
1067 Parses the output from a C{cdrecord} capacity command.
1068
1069 The C{output} parameter should be a list of strings as returned from
1070 C{executeCommand} for a C{cdrecord} command with arguments as from
1071 C{_buildBoundaryArgs}. The list of strings will be parsed to yield
1072 information about the capacity of the media in the device.
1073
1074 Basically, we expect the list of strings to include just one line, a pair
1075 of values. There isn't supposed to be whitespace, but we allow it anyway
1076 in the regular expression. Any lines below the one line we parse are
1077 completely ignored. It would be a good idea to ignore C{stderr} when
1078 executing the C{cdrecord} command that generates output for this method,
1079 because sometimes C{cdrecord} spits out kernel warnings about the actual
1080 output.
1081
1082 The results are returned as a tuple of (lower, upper) as needed by the
1083 C{IsoImage} class. Note that these values are in terms of ISO sectors,
1084 not bytes. Clients should generally consider the boundaries value
1085 opaque, however.
1086
1087 @note: If the boundaries output can't be parsed, we return C{None}.
1088
1089 @param output: Output from a C{cdrecord -msinfo} command.
1090
1091 @return: Boundaries tuple as described above.
1092 @raise IOError: If there is problem parsing the output.
1093 """
1094 if len(output) < 1:
1095 logger.warn("Unable to read disc (might not be initialized); returning full capacity.")
1096 return None
1097 boundaryPattern = re.compile(r"(^\s*)([0-9]*)(\s*,\s*)([0-9]*)(\s*$)")
1098 parsed = boundaryPattern.search(output[0])
1099 if not parsed:
1100 raise IOError("Unable to parse output of boundaries command.")
1101 try:
1102 boundaries = ( int(parsed.group(2)), int(parsed.group(4)) )
1103 except TypeError:
1104 raise IOError("Unable to parse output of boundaries command.")
1105 return boundaries
1106
1107
1108
1109
1110
1111
1112 @staticmethod
1114 """
1115 Builds a list of arguments to be passed to a C{eject} command.
1116
1117 The arguments will cause the C{eject} command to open the tray and
1118 eject the media. No validation is done by this method as to whether
1119 this action actually makes sense.
1120
1121 @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}.
1122
1123 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1124 """
1125 args = []
1126 args.append(device)
1127 return args
1128
1129 @staticmethod
1131 """
1132 Builds a list of arguments to be passed to a C{eject} command.
1133
1134 The arguments will cause the C{eject} command to close the tray and reload
1135 the media. No validation is done by this method as to whether this
1136 action actually makes sense.
1137
1138 @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}.
1139
1140 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1141 """
1142 args = []
1143 args.append("-t")
1144 args.append(device)
1145 return args
1146
1147 @staticmethod
1149 """
1150 Builds a list of arguments to be passed to a C{cdrecord} command.
1151
1152 The arguments will cause the C{cdrecord} command to ask the device
1153 for a list of its capacities via the C{-prcap} switch.
1154
1155 @param hardwareId: Hardware id for the device (either SCSI id or device path)
1156
1157 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1158 """
1159 args = []
1160 args.append("-prcap")
1161 args.append("dev=%s" % hardwareId)
1162 return args
1163
1164 @staticmethod
1166 """
1167 Builds a list of arguments to be passed to a C{cdrecord} command.
1168
1169 The arguments will cause the C{cdrecord} command to ask the device for
1170 the current multisession boundaries of the media using the C{-msinfo}
1171 switch.
1172
1173 @param hardwareId: Hardware id for the device (either SCSI id or device path)
1174
1175 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1176 """
1177 args = []
1178 args.append("-msinfo")
1179 args.append("dev=%s" % hardwareId)
1180 return args
1181
1182 @staticmethod
1184 """
1185 Builds a list of arguments to be passed to a C{cdrecord} command.
1186
1187 The arguments will cause the C{cdrecord} command to blank the media in
1188 the device identified by C{hardwareId}. No validation is done by this method
1189 as to whether the action makes sense (i.e. to whether the media even can
1190 be blanked).
1191
1192 @param hardwareId: Hardware id for the device (either SCSI id or device path)
1193 @param driveSpeed: Speed at which the drive writes.
1194
1195 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1196 """
1197 args = []
1198 args.append("-v")
1199 args.append("blank=fast")
1200 if driveSpeed is not None:
1201 args.append("speed=%d" % driveSpeed)
1202 args.append("dev=%s" % hardwareId)
1203 return args
1204
1205 @staticmethod
1206 - def _buildWriteArgs(hardwareId, imagePath, driveSpeed=None, writeMulti=True):
1207 """
1208 Builds a list of arguments to be passed to a C{cdrecord} command.
1209
1210 The arguments will cause the C{cdrecord} command to write the indicated
1211 ISO image (C{imagePath}) to the media in the device identified by
1212 C{hardwareId}. The C{writeMulti} argument controls whether to write a
1213 multisession disc. No validation is done by this method as to whether
1214 the action makes sense (i.e. to whether the device even can write
1215 multisession discs, for instance).
1216
1217 @param hardwareId: Hardware id for the device (either SCSI id or device path)
1218 @param imagePath: Path to an ISO image on disk.
1219 @param driveSpeed: Speed at which the drive writes.
1220 @param writeMulti: Indicates whether to write a multisession disc.
1221
1222 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1223 """
1224 args = []
1225 args.append("-v")
1226 if driveSpeed is not None:
1227 args.append("speed=%d" % driveSpeed)
1228 args.append("dev=%s" % hardwareId)
1229 if writeMulti:
1230 args.append("-multi")
1231 args.append("-data")
1232 args.append(imagePath)
1233 return args
1234